diff -Nru geary-0.12.4/bindings/metadata/Soup-2.4.metadata geary-3.32.0/bindings/metadata/Soup-2.4.metadata --- geary-0.12.4/bindings/metadata/Soup-2.4.metadata 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/bindings/metadata/Soup-2.4.metadata 1970-01-01 00:00:00.000000000 +0000 @@ -1,3 +0,0 @@ -AuthDomain.accepts skip -AuthDomain.challenge skip - diff -Nru geary-0.12.4/bindings/metadata/WebKit2-4.0.metadata geary-3.32.0/bindings/metadata/WebKit2-4.0.metadata --- geary-0.12.4/bindings/metadata/WebKit2-4.0.metadata 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/bindings/metadata/WebKit2-4.0.metadata 1970-01-01 00:00:00.000000000 +0000 @@ -1,15 +0,0 @@ - -JavascriptResult - .get_global_context nullable=false unowned=true - .get_value nullable=false unowned=true - -//Forward upstream -Download - .failed#signal.error type="WebKit.DownloadError" -PrintOperation - .failed#signal.error type="WebKit.PrintError" -WebResource - .failed#signal.error type="GLib.Error" -WebView - .load_failed#signal.error type="GLib.Error" - .show_option_menu#signal skip diff -Nru geary-0.12.4/bindings/metadata/WebKit2WebExtension-4.0-custom.vala geary-3.32.0/bindings/metadata/WebKit2WebExtension-4.0-custom.vala --- geary-0.12.4/bindings/metadata/WebKit2WebExtension-4.0-custom.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/bindings/metadata/WebKit2WebExtension-4.0-custom.vala 1970-01-01 00:00:00.000000000 +0000 @@ -1,5 +0,0 @@ -namespace WebKit { - namespace DOM { - public delegate void EventTargetFunc (WebKit.DOM.EventTarget target, WebKit.DOM.Event event); - } -} diff -Nru geary-0.12.4/bindings/metadata/WebKit2WebExtension-4.0.metadata geary-3.32.0/bindings/metadata/WebKit2WebExtension-4.0.metadata --- geary-0.12.4/bindings/metadata/WebKit2WebExtension-4.0.metadata 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/bindings/metadata/WebKit2WebExtension-4.0.metadata 1970-01-01 00:00:00.000000000 +0000 @@ -1,9 +0,0 @@ -DOM* parent="WebKit.DOM" name="DOM(.+)" - -DOMEventTarget.add_event_listener skip -_ContextMenu skip -_ContextMenuItem skip - -Frame.get_javascript_* nullable=false unowned=true - -DOMEventTarget.add_event_listener_with_closure.handler type="owned WebKit.DOM.EventTargetFunc" diff -Nru geary-0.12.4/bindings/vapi/enchant-2.vapi geary-3.32.0/bindings/vapi/enchant-2.vapi --- geary-0.12.4/bindings/vapi/enchant-2.vapi 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/bindings/vapi/enchant-2.vapi 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,34 @@ +[CCode (cheader_filename = "enchant.h")] +namespace Enchant { + public delegate void BrokerDescribeFn (string provider_name, string provider_desc, string provider_dll_file); + public delegate void DictDescribeFn (string lang_tag, string provider_name, string provider_desc, string provider_file); + + [Compact] + [CCode (free_function = "enchant_broker_free")] + public class Broker { + [CCode (cname = "enchant_broker_init")] + public Broker (); + + public unowned Dict request_dict (string tag); + public unowned Dict request_pwl_dict (string pwl); + public void free_dict (Dict dict); + public int dict_exists (string tag); + public void set_ordering (string tag, string ordering); + public void describe (BrokerDescribeFn fn); + public void list_dicts (DictDescribeFn fn); + public unowned string get_error (); + } + + [Compact] + public class Dict { + public int check (string word, long len = -1); + public unowned string[] suggest (string word, long len = -1); + public void free_string_list ([CCode (array_length = false)] string[] string_list); + public void add_to_session (string word, long len = -1); + public int is_in_session (string word, long len = -1); + public void store_replacement ( string mis, long mis_len, string cor, long cor_len); + public void add_to_pwl ( string word, long len = -1); + public void describe (DictDescribeFn fn); + public unowned string get_error (); + } +} diff -Nru geary-0.12.4/bindings/vapi/gmime-2.6.vapi geary-3.32.0/bindings/vapi/gmime-2.6.vapi --- geary-0.12.4/bindings/vapi/gmime-2.6.vapi 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/bindings/vapi/gmime-2.6.vapi 2019-03-17 13:39:29.000000000 +0000 @@ -1376,7 +1376,7 @@ [CCode (cheader_filename = "gmime/gmime.h", cname = "g_mime_utils_structured_header_fold")] public static string utils_structured_header_fold (string header); [CCode (cheader_filename = "gmime/gmime.h", cname = "g_mime_utils_text_is_8bit")] - public static bool utils_text_is_8bit (uint text, size_t len); + public static bool utils_text_is_8bit (string text, size_t len); [CCode (cheader_filename = "gmime/gmime.h", cname = "g_mime_utils_unquote_string")] public static void utils_unquote_string (string str); [CCode (cheader_filename = "gmime/gmime.h", cname = "g_mime_utils_unstructured_header_fold")] diff -Nru geary-0.12.4/bindings/vapi/javascriptcore-4.0.vapi geary-3.32.0/bindings/vapi/javascriptcore-4.0.vapi --- geary-0.12.4/bindings/vapi/javascriptcore-4.0.vapi 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/bindings/vapi/javascriptcore-4.0.vapi 1970-01-01 00:00:00.000000000 +0000 @@ -1,155 +0,0 @@ -/* - * Copyright 2017 Michael Gratton - * - * This software is licensed under the GNU Lesser General Public License - * (version 2.1 or later). See the COPYING file in this distribution. - */ - -[CCode (cprefix = "JS", - gir_namespace = "JavaScriptCore", - gir_version = "4.0", - lower_case_cprefix = "JS_", - cheader_filename = "JavaScriptCore/JavaScript.h")] -namespace JS { - - [CCode (cname = "JSContextRef")] - [SimpleType] - public struct Context { - - [CCode (cname = "JSEvaluateScript")] - public Value evaluate_script(String script, - Object? thisObject, - String? sourceURL, - int startingLineNumber, - out Value? exception); - - [CCode (cname = "JSCheckScriptSyntax")] - public Value check_script_syntax(String script, - String? sourceURL, - int startingLineNumber, - out Value? exception); - - } - - [CCode (cname = "JSGlobalContextRef")] - [SimpleType] - public struct GlobalContext : Context { - - [CCode (cname = "JSGlobalContextRetain")] - public bool retain(); - - [CCode (cname = "JSGlobalContextRelease")] - public bool release(); - - } - - [CCode (cname = "JSType", has_type_id = false)] - public enum Type { - - [CCode (cname = "kJSTypeUndefined")] - UNDEFINED, - - [CCode (cname = "kJSTypeNull")] - NULL, - - [CCode (cname = "kJSTypeBoolean")] - BOOLEAN, - - [CCode (cname = "kJSTypeNumber")] - NUMBER, - - [CCode (cname = "kJSTypeString")] - STRING, - - [CCode (cname = "kJSTypeObject")] - OBJECT - } - - [CCode (cname = "JSObjectRef")] - [SimpleType] - public struct Object { - - [CCode (cname = "JSObjectMakeFunction")] - public Object.make_function(String? name, - [CCode (array_length_pos=1.5)] - String[]? parameterNames, - String body, - String? sourceURL, - int startingLineNumber, - out Value? exception); - - [CCode (cname = "JSObjectCallAsFunction", instance_pos = 1.1)] - public Value call_as_function(Context ctx, - Object? thisObject, - [CCode (array_length_pos=2.5)] - Value[]? arguments, - out Value? exception); - - [CCode (cname = "JSObjectHasProperty", instance_pos = 1.1)] - public bool has_property(Context ctx, String property_name); - - [CCode (cname = "JSObjectGetProperty", instance_pos = 1.1)] - public Value get_property(Context ctx, - String property_name, - out Value? exception); - - } - - [CCode (cname = "JSValueRef")] - [SimpleType] - public struct Value { - - [CCode (cname = "JSValueGetType", instance_pos = 1.1)] - public Type get_type(Context context); - - [CCode (cname = "JSValueIsBoolean", instance_pos = 1.1)] - public bool is_boolean(Context ctx); - - [CCode (cname = "JSValueIsNumber", instance_pos = 1.1)] - public bool is_number(Context ctx); - - [CCode (cname = "JSValueIsObject", instance_pos = 1.1)] - public bool is_object(Context ctx); - - [CCode (cname = "JSValueIsString", instance_pos = 1.1)] - public bool is_string(Context ctx); - - [CCode (cname = "JSValueToBoolean", instance_pos = 1.1)] - public bool to_boolean(Context ctx); - - [CCode (cname = "JSValueToNumber", instance_pos = 1.1)] - public double to_number(Context ctx, out Value exception); - - [CCode (cname = "JSValueToObject", instance_pos = 1.1)] - public Object to_object(Context ctx, out Value exception); - - [CCode (cname = "JSValueToStringCopy", instance_pos = 1.1)] - public String to_string_copy(Context ctx, out Value exception); - - } - - [CCode (cname = "JSStringRef")] - [SimpleType] - public struct String { - - [CCode (cname = "JSStringCreateWithUTF8CString")] - public String.create_with_utf8_cstring(string str); - - [CCode (cname = "JSStringGetLength")] - public int String.get_length(); - - [CCode (cname = "JSStringGetMaximumUTF8CStringSize")] - public int String.get_maximum_utf8_cstring_size(); - - [CCode (cname = "JSStringGetUTF8CString")] - public void String.get_utf8_cstring(string* buffer, int bufferSize); - - [CCode (cname = "JSStringRetain")] - public void String.retain(); - - [CCode (cname = "JSStringRelease")] - public void String.release(); - - } - -} diff -Nru geary-0.12.4/bindings/vapi/libunwind.vapi geary-3.32.0/bindings/vapi/libunwind.vapi --- geary-0.12.4/bindings/vapi/libunwind.vapi 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/bindings/vapi/libunwind.vapi 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,91 @@ +/* + * Based on version from Sentry-GLib: https://github.com/arteymix/sentry-glib + * Courtesy of Guillaume Poirier-Morency + * + * Copyright (C) 1996 X Consortium + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE X CONSORTIUM BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * Except as contained in this notice, the name of the X Consortium + * shall not be used in advertising or otherwise to promote the sale, + * use or other dealings in this Software without prior written + * authorization from the X Consortium. + * + * X Window System is a trademark of X Consortium, Inc. + */ + +[CCode (cprefix = "UNW_", lower_case_cprefix = "unw_", cheader_filename = "libunwind.h")] +namespace Unwind +{ + + [CCode (cname = "unw_context_t")] + public struct Context + { + [CCode (cname = "unw_getcontext")] + public Context (); + } + + [CCode (cname = "unw_proc_info_t")] + public struct ProcInfo + { + void* start_ip; + void* end_ip; + void* lsda; + void* handler; + void* gp; + long flags; + int format; + } + + [CCode (cname = "unw_frame_regnum_t")] + public enum Reg + { + IP, + SP, + EH + } + + [CCode (cname = "unw_cursor_t", cprefix = "unw_")] + public struct Cursor + { + public Cursor.local (Context ctx); + public int get_proc_info (out ProcInfo pip); + public int get_proc_name (uint8[] bufp, out long offp = null); + public int get_reg (Reg reg, out void* valp); + public int step (); + } + + [CCode (cname = "unw_error_t", cprefix = "UNW_E", has_type_id = false)] + public enum Error + { + SUCCESS, + UNSPEC, + NOMEM, + BADREG, + READONLYREG, + STOPUNWIND, + INVALIDIP, + BADFRAME, + INVAL, + BADVERSION, + NOINFO + } + +} diff -Nru geary-0.12.4/build-aux/git_version.py geary-3.32.0/build-aux/git_version.py --- geary-0.12.4/build-aux/git_version.py 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/build-aux/git_version.py 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 + +import re +import sys +import subprocess + +try: + version = subprocess.check_output( + ['git', 'describe', '--all', '--long', '--dirty'], + stderr=subprocess.DEVNULL + ) +except: + sys.exit(1) + +version = str(version, encoding='UTF-8').strip() +print(re.sub(r'^heads\/(.*)-0-(g.*)$', r'\1~\2', version)) diff -Nru geary-0.12.4/build-aux/post_install.py geary-3.32.0/build-aux/post_install.py --- geary-0.12.4/build-aux/post_install.py 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/build-aux/post_install.py 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,15 @@ +#!/usr/bin/env python3 + +import os +import subprocess + +install_prefix = os.environ['MESON_INSTALL_PREFIX'] +icondir = os.path.join(install_prefix, 'share', 'icons', 'hicolor') +schemadir = os.path.join(install_prefix, 'share', 'glib-2.0', 'schemas') + +if not os.environ.get('DESTDIR'): + print('Update icon cache...') + subprocess.call(['gtk-update-icon-cache', '-f', '-t', icondir]) + + print('Compiling gsettings schemas...') + subprocess.call(['glib-compile-schemas', schemadir]) diff -Nru geary-0.12.4/cmake/FindDesktopFileValidate.cmake geary-3.32.0/cmake/FindDesktopFileValidate.cmake --- geary-0.12.4/cmake/FindDesktopFileValidate.cmake 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/cmake/FindDesktopFileValidate.cmake 1970-01-01 00:00:00.000000000 +0000 @@ -1,20 +0,0 @@ -# FindDesktopFileValidate.cmake -# -# Charles Lindsay -# Copyright 2016 Software Freedom Conservancy Inc. - -find_program (DESKTOP_FILE_VALIDATE_EXECUTABLE desktop-file-validate) - -if (DESKTOP_FILE_VALIDATE_EXECUTABLE) - set (DESKTOP_FILE_VALIDATE_FOUND TRUE) -else (DESKTOP_FILE_VALIDATE_EXECUTABLE) - set (DESKTOP_FILE_VALIDATE_FOUND FALSE) -endif (DESKTOP_FILE_VALIDATE_EXECUTABLE) - -if (DESKTOP_FILE_VALIDATE_FOUND) - macro (VALIDATE_DESKTOP_FILE desktop_id) - add_custom_command (TARGET ${desktop_id} POST_BUILD - COMMAND ${DESKTOP_FILE_VALIDATE_EXECUTABLE} ${desktop_id} - ) - endmacro (VALIDATE_DESKTOP_FILE desktop_id) -endif (DESKTOP_FILE_VALIDATE_FOUND) diff -Nru geary-0.12.4/cmake/FindIntltool.cmake geary-3.32.0/cmake/FindIntltool.cmake --- geary-0.12.4/cmake/FindIntltool.cmake 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/cmake/FindIntltool.cmake 1970-01-01 00:00:00.000000000 +0000 @@ -1,44 +0,0 @@ -# FindIntltool.cmake -# -# Jim Nelson -# Copyright 2016 Software Freedom Conservancy Inc. - -find_program (INTLTOOL_MERGE_EXECUTABLE intltool-merge) - -if (INTLTOOL_MERGE_EXECUTABLE) - set (INTLTOOL_MERGE_FOUND TRUE) -else (INTLTOOL_MERGE_EXECUTABLE) - set (INTLTOOL_MERGE_FOUND FALSE) -endif (INTLTOOL_MERGE_EXECUTABLE) - -if (INTLTOOL_MERGE_FOUND) - macro (INTLTOOL_MERGE_APPDATA appstream_name po_dir) - add_custom_target (${appstream_name}.in ALL - ${INTLTOOL_MERGE_EXECUTABLE} --xml-style ${CMAKE_SOURCE_DIR}/${po_dir} - ${CMAKE_CURRENT_SOURCE_DIR}/${appstream_name}.in ${appstream_name} - ) - install (FILES ${CMAKE_CURRENT_BINARY_DIR}/${appstream_name} DESTINATION ${CMAKE_INSTALL_PREFIX}/share/appdata) - endmacro (INTLTOOL_MERGE_APPDATA appstream_name po_dir) - macro (INTLTOOL_MERGE_DESKTOP desktop_id po_dir) - add_custom_target (org.gnome.Geary.desktop ALL - ${INTLTOOL_MERGE_EXECUTABLE} --desktop-style ${CMAKE_SOURCE_DIR}/${po_dir} - ${CMAKE_CURRENT_SOURCE_DIR}/${desktop_id}.in ${desktop_id} - ) - install (FILES ${CMAKE_CURRENT_BINARY_DIR}/org.gnome.Geary.desktop DESTINATION ${CMAKE_INSTALL_PREFIX}/share/applications) - endmacro (INTLTOOL_MERGE_DESKTOP desktop_id po_dir) - macro (INTLTOOL_MERGE_AUTOSTART_DESKTOP desktop_id po_dir) - add_custom_target (geary-autostart.desktop ALL - ${INTLTOOL_MERGE_EXECUTABLE} --desktop-style ${CMAKE_SOURCE_DIR}/${po_dir} - ${CMAKE_CURRENT_SOURCE_DIR}/${desktop_id}.in ${desktop_id} - ) - install (FILES ${CMAKE_CURRENT_BINARY_DIR}/geary-autostart.desktop DESTINATION ${CMAKE_INSTALL_PREFIX}/share/applications) - endmacro (INTLTOOL_MERGE_AUTOSTART_DESKTOP desktop_id po_dir) - macro (INTLTOOL_MERGE_CONTRACT desktop_id po_dir) - add_custom_target (geary-attach.contract ALL - ${INTLTOOL_MERGE_EXECUTABLE} --desktop-style ${CMAKE_SOURCE_DIR}/${po_dir} - ${CMAKE_CURRENT_SOURCE_DIR}/${desktop_id}.in ${desktop_id} - ) - install (FILES ${CMAKE_CURRENT_BINARY_DIR}/geary-attach.contract DESTINATION ${CMAKE_INSTALL_PREFIX}/share/contractor) - endmacro (INTLTOOL_MERGE_CONTRACT desktop_id po_dir) -endif (INTLTOOL_MERGE_FOUND) - diff -Nru geary-0.12.4/cmake/FindVala.cmake geary-3.32.0/cmake/FindVala.cmake --- geary-0.12.4/cmake/FindVala.cmake 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/cmake/FindVala.cmake 1970-01-01 00:00:00.000000000 +0000 @@ -1,65 +0,0 @@ -## -# Copyright 2009-2010 Jakob Westhoff. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# 1. Redistributions of source code must retain the above copyright notice, -# this list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# THIS SOFTWARE IS PROVIDED BY JAKOB WESTHOFF ``AS IS'' AND ANY EXPRESS OR -# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO -# EVENT SHALL JAKOB WESTHOFF OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE -# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# -# The views and conclusions contained in the software and documentation are those -# of the authors and should not be interpreted as representing official policies, -# either expressed or implied, of Jakob Westhoff -## - -## -# Find module for the Vala compiler (valac) -# -# This module determines wheter a Vala compiler is installed on the current -# system and where its executable is. -# -# Call the module using "find_package(Vala) from within your CMakeLists.txt. -# -# The following variables will be set after an invocation: -# -# VALA_FOUND Whether the vala compiler has been found or not -# VALA_EXECUTABLE Full path to the valac executable if it has been found -# VALA_VERSION Version number of the available valac -## - - -# Search for the valac executable in the usual system paths. -find_program(VALA_EXECUTABLE - NAMES valac) - -# Handle the QUIETLY and REQUIRED arguments, which may be given to the find call. -# Furthermore set VALA_FOUND to TRUE if Vala has been found (aka. -# VALA_EXECUTABLE is set) - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(Vala DEFAULT_MSG VALA_EXECUTABLE) - -mark_as_advanced(VALA_EXECUTABLE) - -# Determine the valac version -if(VALA_FOUND) - execute_process(COMMAND ${VALA_EXECUTABLE} "--version" - OUTPUT_VARIABLE "VALA_VERSION") - string(REPLACE "Vala" "" "VALA_VERSION" ${VALA_VERSION}) - string(STRIP ${VALA_VERSION} "VALA_VERSION") -endif(VALA_FOUND) diff -Nru geary-0.12.4/cmake/FindValadoc.cmake geary-3.32.0/cmake/FindValadoc.cmake --- geary-0.12.4/cmake/FindValadoc.cmake 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/cmake/FindValadoc.cmake 1970-01-01 00:00:00.000000000 +0000 @@ -1,20 +0,0 @@ - -# Search for the valadocc executable in the usual system paths. -find_program(VALADOC_EXECUTABLE NAMES valadoc) - -# Handle the QUIETLY and REQUIRED arguments, which may be given to the find call. -# Furthermore set VALA_FOUND to TRUE if Vala has been found (aka. -# VALA_EXECUTABLE is set) - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(Valadoc DEFAULT_MSG VALADOC_EXECUTABLE) - -mark_as_advanced(VALADOC_EXECUTABLE) - -# Determine the valac version -if(VALA_FOUND) - execute_process(COMMAND ${VALA_EXECUTABLE} "--version" - OUTPUT_VARIABLE "VALA_VERSION") - string(REPLACE "Vala" "" "VALA_VERSION" ${VALA_VERSION}) - string(STRIP ${VALA_VERSION} "VALA_VERSION") -endif(VALA_FOUND) diff -Nru geary-0.12.4/cmake/GCR_CMake/LICENSE geary-3.32.0/cmake/GCR_CMake/LICENSE --- geary-0.12.4/cmake/GCR_CMake/LICENSE 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/cmake/GCR_CMake/LICENSE 1970-01-01 00:00:00.000000000 +0000 @@ -1,675 +0,0 @@ -GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. 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 -them 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 prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. 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. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey 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; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If 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 convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU 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 that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - 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. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -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. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - 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 -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - {one line to give the program's name and a brief idea of what it does.} - Copyright (C) {year} {name of author} - - 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 3 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, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - {project} Copyright (C) {year} {fullname} - This program 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, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU 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. But first, please read -. - diff -Nru geary-0.12.4/cmake/GCR_CMake/macros/BuildTargetScript.cmake geary-3.32.0/cmake/GCR_CMake/macros/BuildTargetScript.cmake --- geary-0.12.4/cmake/GCR_CMake/macros/BuildTargetScript.cmake 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/cmake/GCR_CMake/macros/BuildTargetScript.cmake 1970-01-01 00:00:00.000000000 +0000 @@ -1,57 +0,0 @@ -# This file is used to be invoked at build time. It generates the needed -# resource XML file. - -# Input variables that need to provided when invoking this script: -# GXML_OUTPUT The output file path where to save the XML file. -# GXML_COMPRESS_ALL Sets all COMPRESS flags in all resources in resource -# list. -# GXML_NO_COMPRESS_ALL Removes all COMPRESS flags in all resources in -# resource list. -# GXML_STRIPBLANKS_ALL Sets all STRIPBLANKS flags in all resources in -# resource list. -# GXML_NO_STRIPBLANKS_ALL Removes all STRIPBLANKS flags in all resources in -# resource list. -# GXML_TOPIXDATA_ALL Sets all TOPIXDATA flags i nall resources in resource -# list. -# GXML_NO_TOPIXDATA_ALL Removes all TOPIXDATA flags in all resources in -# resource list. -# GXML_PREFIX Overrides the resource prefix that is prepended to -# each relative name in registered resources. -# GXML_RESOURCES The list of resource files. Whether absolute or -# relative path is equal. - -# Include the GENERATE_GXML() function. -include(${CMAKE_CURRENT_LIST_DIR}/GenerateGXML.cmake) - -# Set flags to actual invocation flags. -if(GXML_COMPRESS_ALL) - set(GXML_COMPRESS_ALL COMPRESS_ALL) -endif() -if(GXML_NO_COMPRESS_ALL) - set(GXML_NO_COMPRESS_ALL NO_COMPRESS_ALL) -endif() -if(GXML_STRIPBLANKS_ALL) - set(GXML_STRIPBLANKS_ALL STRIPBLANKS_ALL) -endif() -if(GXML_NO_STRIPBLANKS_ALL) - set(GXML_NO_STRIPBLANKS_ALL NO_STRIPBLANKS_ALL) -endif() -if(GXML_TOPIXDATA_ALL) - set(GXML_TOPIXDATA_ALL TOPIXDATA_ALL) -endif() -if(GXML_NO_TOPIXDATA_ALL) - set(GXML_NO_TOPIXDATA_ALL NO_TOPIXDATA_ALL) -endif() - -# Replace " " with ";" to import the list over the command line. Otherwise -# CMake would interprete the passed resources as a whole string. -string(REPLACE " " ";" GXML_RESOURCES ${GXML_RESOURCES}) - -# Invoke the gresource XML generation function. -generate_gxml(${GXML_OUTPUT} - ${GXML_COMPRESS_ALL} ${GXML_NO_COMPRESS_ALL} - ${GXML_STRIPBLANKS_ALL} ${GXML_NO_STRIPBLANKS_ALL} - ${GXML_TOPIXDATA_ALL} ${GXML_NO_TOPIXDATA_ALL} - PREFIX ${GXML_PREFIX} - RESOURCES ${GXML_RESOURCES}) - diff -Nru geary-0.12.4/cmake/GCR_CMake/macros/CompileGResources.cmake geary-3.32.0/cmake/GCR_CMake/macros/CompileGResources.cmake --- geary-0.12.4/cmake/GCR_CMake/macros/CompileGResources.cmake 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/cmake/GCR_CMake/macros/CompileGResources.cmake 1970-01-01 00:00:00.000000000 +0000 @@ -1,216 +0,0 @@ -include(CMakeParseArguments) - -# Path to this file. -set(GCR_CMAKE_MACRO_DIR ${CMAKE_CURRENT_LIST_DIR}) - -# Compiles a gresource resource file from given resource files. Automatically -# creates the XML controlling file. -# The type of resource to generate (header, c-file or bundle) is automatically -# determined from TARGET file ending, if no TYPE is explicitly specified. -# The output file is stored in the provided variable "output". -# "xml_out" contains the variable where to output the XML path. Can be used to -# create custom targets or doing postprocessing. -# If you want to use preprocessing, you need to manually check the existence -# of the tools you use. This function doesn't check this for you, it just -# generates the XML file. glib-compile-resources will then throw a -# warning/error. -function(COMPILE_GRESOURCES output xml_out) - # Available options: - # COMPRESS_ALL, NO_COMPRESS_ALL Overrides the COMPRESS flag in all - # registered resources. - # STRIPBLANKS_ALL, NO_STRIPBLANKS_ALL Overrides the STRIPBLANKS flag in all - # registered resources. - # TOPIXDATA_ALL, NO_TOPIXDATA_ALL Overrides the TOPIXDATA flag in all - # registered resources. - set(CG_OPTIONS COMPRESS_ALL NO_COMPRESS_ALL - STRIPBLANKS_ALL NO_STRIPBLANKS_ALL - TOPIXDATA_ALL NO_TOPIXDATA_ALL) - - # Available one value options: - # TYPE Type of resource to create. Valid options are: - # EMBED_C: A C-file that can be compiled with your project. - # EMBED_H: A header that can be included into your project. - # BUNDLE: Generates a resource bundle file that can be loaded - # at runtime. - # AUTO: Determine from target file ending. Need to specify - # target argument. - # PREFIX Overrides the resource prefix that is prepended to each - # relative file name in registered resources. - # SOURCE_DIR Overrides the resources base directory to search for resources. - # Normally this is set to the source directory with that CMake - # was invoked (CMAKE_SOURCE_DIR). - # TARGET Overrides the name of the output file/-s. Normally the output - # names from glib-compile-resources tool is taken. - set(CG_ONEVALUEARGS TYPE PREFIX SOURCE_DIR TARGET) - - # Available multi-value options: - # RESOURCES The list of resource files. Whether absolute or relative path is - # equal, absolute paths are stripped down to relative ones. If the - # absolute path is not inside the given base directory SOURCE_DIR - # or CMAKE_SOURCE_DIR (if SOURCE_DIR is not overriden), this - # function aborts. - # OPTIONS Extra command line options passed to glib-compile-resources. - set(CG_MULTIVALUEARGS RESOURCES OPTIONS) - - # Parse the arguments. - cmake_parse_arguments(CG_ARG - "${CG_OPTIONS}" - "${CG_ONEVALUEARGS}" - "${CG_MULTIVALUEARGS}" - "${ARGN}") - - # Variable to store the double-quote (") string. Since escaping - # double-quotes in strings is not possible we need a helper variable that - # does this job for us. - set(Q \") - - # Check invocation validity with the _UNPARSED_ARGUMENTS variable. - # If other not recognized parameters were passed, throw error. - if (CG_ARG_UNPARSED_ARGUMENTS) - set(CG_WARNMSG "Invocation of COMPILE_GRESOURCES with unrecognized") - set(CG_WARNMSG "${CG_WARNMSG} parameters. Parameters are:") - set(CG_WARNMSG "${CG_WARNMSG} ${CG_ARG_UNPARSED_ARGUMENTS}.") - message(WARNING ${CG_WARNMSG}) - endif() - - # Check invocation validity depending on generation mode (EMBED_C, EMBED_H - # or BUNDLE). - if ("${CG_ARG_TYPE}" STREQUAL "EMBED_C") - # EMBED_C mode, output compilable C-file. - set(CG_GENERATE_COMMAND_LINE "--generate-source") - set(CG_TARGET_FILE_ENDING "c") - elseif ("${CG_ARG_TYPE}" STREQUAL "EMBED_H") - # EMBED_H mode, output includable header file. - set(CG_GENERATE_COMMAND_LINE "--generate-header") - set(CG_TARGET_FILE_ENDING "h") - elseif ("${CG_ARG_TYPE}" STREQUAL "BUNDLE") - # BUNDLE mode, output resource bundle. Don't do anything since - # glib-compile-resources outputs a bundle when not specifying - # something else. - set(CG_TARGET_FILE_ENDING "gresource") - else() - # Everything else is AUTO mode, determine from target file ending. - if (CG_ARG_TARGET) - set(CG_GENERATE_COMMAND_LINE "--generate") - else() - set(CG_ERRMSG "AUTO mode given, but no target specified. Can't") - set(CG_ERRMSG "${CG_ERRMSG} determine output type. In function") - set(CG_ERRMSG "${CG_ERRMSG} COMPILE_GRESOURCES.") - message(FATAL_ERROR ${CG_ERRMSG}) - endif() - endif() - - # Check flag validity. - if (CG_ARG_COMPRESS_ALL AND CG_ARG_NO_COMPRESS_ALL) - set(CG_ERRMSG "COMPRESS_ALL and NO_COMPRESS_ALL simultaneously set. In") - set(CG_ERRMSG "${CG_ERRMSG} function COMPILE_GRESOURCES.") - message(FATAL_ERROR ${CG_ERRMSG}) - endif() - if (CG_ARG_STRIPBLANKS_ALL AND CG_ARG_NO_STRIPBLANKS_ALL) - set(CG_ERRMSG "STRIPBLANKS_ALL and NO_STRIPBLANKS_ALL simultaneously") - set(CG_ERRMSG "${CG_ERRMSG} set. In function COMPILE_GRESOURCES.") - message(FATAL_ERROR ${CG_ERRMSG}) - endif() - if (CG_ARG_TOPIXDATA_ALL AND CG_ARG_NO_TOPIXDATA_ALL) - set(CG_ERRMSG "TOPIXDATA_ALL and NO_TOPIXDATA_ALL simultaneously set.") - set(CG_ERRMSG "${CG_ERRMSG} In function COMPILE_GRESOURCES.") - message(FATAL_ERROR ${CG_ERRMSG}) - endif() - - # Check if there are any resources. - if (NOT CG_ARG_RESOURCES) - set(CG_ERRMSG "No resource files to process. In function") - set(CG_ERRMSG "${CG_ERRMSG} COMPILE_GRESOURCES.") - message(FATAL_ERROR ${CG_ERRMSG}) - endif() - - # Extract all dependencies for targets from resource list. - foreach(res ${CG_ARG_RESOURCES}) - if (NOT(("${res}" STREQUAL "COMPRESS") OR - ("${res}" STREQUAL "STRIPBLANKS") OR - ("${res}" STREQUAL "TOPIXDATA"))) - - list(APPEND CG_RESOURCES_DEPENDENCIES "${res}") - endif() - endforeach() - - # Construct .gresource.xml path. - set(CG_XML_FILE_PATH "${CMAKE_BINARY_DIR}/.gresource.xml") - - # Generate gresources XML target. - list(APPEND CG_CMAKE_SCRIPT_ARGS "-D") - list(APPEND CG_CMAKE_SCRIPT_ARGS "GXML_OUTPUT=${Q}${CG_XML_FILE_PATH}${Q}") - if(CG_ARG_COMPRESS_ALL) - list(APPEND CG_CMAKE_SCRIPT_ARGS "-D") - list(APPEND CG_CMAKE_SCRIPT_ARGS "GXML_COMPRESS_ALL") - endif() - if(CG_ARG_NO_COMPRESS_ALL) - list(APPEND CG_CMAKE_SCRIPT_ARGS "-D") - list(APPEND CG_CMAKE_SCRIPT_ARGS "GXML_NO_COMPRESS_ALL") - endif() - if(CG_ARG_STRPIBLANKS_ALL) - list(APPEND CG_CMAKE_SCRIPT_ARGS "-D") - list(APPEND CG_CMAKE_SCRIPT_ARGS "GXML_STRIPBLANKS_ALL") - endif() - if(CG_ARG_NO_STRIPBLANKS_ALL) - list(APPEND CG_CMAKE_SCRIPT_ARGS "-D") - list(APPEND CG_CMAKE_SCRIPT_ARGS "GXML_NO_STRIPBLANKS_ALL") - endif() - if(CG_ARG_TOPIXDATA_ALL) - list(APPEND CG_CMAKE_SCRIPT_ARGS "-D") - list(APPEND CG_CMAKE_SCRIPT_ARGS "GXML_TOPIXDATA_ALL") - endif() - if(CG_ARG_NO_TOPIXDATA_ALL) - list(APPEND CG_CMAKE_SCRIPT_ARGS "-D") - list(APPEND CG_CMAKE_SCRIPT_ARGS "GXML_NO_TOPIXDATA_ALL") - endif() - list(APPEND CG_CMAKE_SCRIPT_ARGS "-D") - list(APPEND CG_CMAKE_SCRIPT_ARGS "GXML_PREFIX=${Q}${CG_ARG_PREFIX}${Q}") - list(APPEND CG_CMAKE_SCRIPT_ARGS "-D") - list(APPEND CG_CMAKE_SCRIPT_ARGS - "GXML_RESOURCES=${Q}${CG_ARG_RESOURCES}${Q}") - list(APPEND CG_CMAKE_SCRIPT_ARGS "-P") - list(APPEND CG_CMAKE_SCRIPT_ARGS - "${Q}${GCR_CMAKE_MACRO_DIR}/BuildTargetScript.cmake${Q}") - - get_filename_component(CG_XML_FILE_PATH_ONLY_NAME - "${CG_XML_FILE_PATH}" NAME) - set(CG_XML_CUSTOM_COMMAND_COMMENT - "Creating gresources XML file (${CG_XML_FILE_PATH_ONLY_NAME})") - add_custom_command(OUTPUT ${CG_XML_FILE_PATH} - COMMAND ${CMAKE_COMMAND} - ARGS ${CG_CMAKE_SCRIPT_ARGS} - DEPENDS ${CG_RESOURCES_DEPENDENCIES} - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - COMMENT ${CG_XML_CUSTOM_COMMAND_COMMENT}) - - # Create target manually if not set (to make sure glib-compile-resources - # doesn't change behaviour with it's naming standards). - if (NOT CG_ARG_TARGET) - set(CG_ARG_TARGET "${CMAKE_BINARY_DIR}/resources") - set(CG_ARG_TARGET "${CG_ARG_TARGET}.${CG_TARGET_FILE_ENDING}") - endif() - - # Create source directory automatically if not set. - if (NOT CG_ARG_SOURCE_DIR) - set(CG_ARG_SOURCE_DIR "${CMAKE_SOURCE_DIR}") - endif() - - # Add compilation target for resources. - add_custom_command(OUTPUT ${CG_ARG_TARGET} - COMMAND ${GLIB_COMPILE_RESOURCES_EXECUTABLE} - ARGS - ${OPTIONS} - "--target=${Q}${CG_ARG_TARGET}${Q}" - "--sourcedir=${Q}${CG_ARG_SOURCE_DIR}${Q}" - ${CG_GENERATE_COMMAND_LINE} - ${CG_XML_FILE_PATH} - MAIN_DEPENDENCY ${CG_XML_FILE_PATH} - DEPENDS ${CG_RESOURCES_DEPENDENCIES} - WORKING_DIRECTORY ${CMAKE_BUILD_DIR}) - - # Set output and XML_OUT to parent scope. - set(${xml_out} ${CG_XML_FILE_PATH} PARENT_SCOPE) - set(${output} ${CG_ARG_TARGET} PARENT_SCOPE) - -endfunction() diff -Nru geary-0.12.4/cmake/GCR_CMake/macros/GenerateGXML.cmake geary-3.32.0/cmake/GCR_CMake/macros/GenerateGXML.cmake --- geary-0.12.4/cmake/GCR_CMake/macros/GenerateGXML.cmake 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/cmake/GCR_CMake/macros/GenerateGXML.cmake 1970-01-01 00:00:00.000000000 +0000 @@ -1,124 +0,0 @@ -include(CMakeParseArguments) - -# Generates the resource XML controlling file from resource list (and saves it -# to xml_path). It's not recommended to use this function directly, since it -# doesn't handle invalid arguments. It is used by the function -# COMPILE_GRESOURCES() to create a custom command, so that this function is -# invoked at build-time in script mode from CMake. -function(GENERATE_GXML xml_path) - # Available options: - # COMPRESS_ALL, NO_COMPRESS_ALL Overrides the COMPRESS flag in all - # registered resources. - # STRIPBLANKS_ALL, NO_STRIPBLANKS_ALL Overrides the STRIPBLANKS flag in all - # registered resources. - # TOPIXDATA_ALL, NO_TOPIXDATA_ALL Overrides the TOPIXDATA flag in all - # registered resources. - set(GXML_OPTIONS COMPRESS_ALL NO_COMPRESS_ALL - STRIPBLANKS_ALL NO_STRIPBLANKS_ALL - TOPIXDATA_ALL NO_TOPIXDATA_ALL) - - # Available one value options: - # PREFIX Overrides the resource prefix that is prepended to each - # relative file name in registered resources. - set(GXML_ONEVALUEARGS PREFIX) - - # Available multi-value options: - # RESOURCES The list of resource files. Whether absolute or relative path is - # equal, absolute paths are stripped down to relative ones. If the - # absolute path is not inside the given base directory SOURCE_DIR - # or CMAKE_SOURCE_DIR (if SOURCE_DIR is not overriden), this - # function aborts. - set(GXML_MULTIVALUEARGS RESOURCES) - - # Parse the arguments. - cmake_parse_arguments(GXML_ARG - "${GXML_OPTIONS}" - "${GXML_ONEVALUEARGS}" - "${GXML_MULTIVALUEARGS}" - "${ARGN}") - - # Variable to store the double-quote (") string. Since escaping - # double-quotes in strings is not possible we need a helper variable that - # does this job for us. - set(Q \") - - # Process resources and generate XML file. - # Begin with the XML header and header nodes. - set(GXML_XML_FILE "") - set(GXML_XML_FILE "${GXML_XML_FILE}") - - # Process each resource. - foreach(res ${GXML_ARG_RESOURCES}) - if ("${res}" STREQUAL "COMPRESS") - set(GXML_COMPRESSION_FLAG ON) - elseif ("${res}" STREQUAL "STRIPBLANKS") - set(GXML_STRIPBLANKS_FLAG ON) - elseif ("${res}" STREQUAL "TOPIXDATA") - set(GXML_TOPIXDATA_FLAG ON) - else() - # The file name. - set(GXML_RESOURCE_PATH "${res}") - - # Append to real resource file dependency list. - list(APPEND GXML_RESOURCES_DEPENDENCIES ${GXML_RESOURCE_PATH}) - - # Assemble node. - set(GXML_RES_LINE "${GXML_RESOURCE_PATH}") - - # Append to file string. - set(GXML_XML_FILE "${GXML_XML_FILE}${GXML_RES_LINE}") - - # Unset variables. - unset(GXML_COMPRESSION_FLAG) - unset(GXML_STRIPBLANKS_FLAG) - unset(GXML_TOPIXDATA_FLAG) - endif() - - endforeach() - - # Append closing nodes. - set(GXML_XML_FILE "${GXML_XML_FILE}") - - # Use "file" function to generate XML controlling file. - get_filename_component(xml_path_only_name "${xml_path}" NAME) - file(WRITE ${xml_path} ${GXML_XML_FILE}) - -endfunction() - diff -Nru geary-0.12.4/cmake/GCR_CMake/macros/GlibCompileResourcesSupport.cmake geary-3.32.0/cmake/GCR_CMake/macros/GlibCompileResourcesSupport.cmake --- geary-0.12.4/cmake/GCR_CMake/macros/GlibCompileResourcesSupport.cmake 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/cmake/GCR_CMake/macros/GlibCompileResourcesSupport.cmake 1970-01-01 00:00:00.000000000 +0000 @@ -1,11 +0,0 @@ -# Path to this file. -set(GCR_CMAKE_MACRO_DIR ${CMAKE_CURRENT_LIST_DIR}) - -# Finds the glib-compile-resources executable. -find_program(GLIB_COMPILE_RESOURCES_EXECUTABLE glib-compile-resources) -mark_as_advanced(GLIB_COMPILE_RESOURCES_EXECUTABLE) - -# Include the cmake files containing the functions. -include(${GCR_CMAKE_MACRO_DIR}/CompileGResources.cmake) -include(${GCR_CMAKE_MACRO_DIR}/GenerateGXML.cmake) - diff -Nru geary-0.12.4/cmake/GCR_CMake/README.md geary-3.32.0/cmake/GCR_CMake/README.md --- geary-0.12.4/cmake/GCR_CMake/README.md 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/cmake/GCR_CMake/README.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,260 +0,0 @@ -# GCR_CMake -A CMake extension supporting the **glib-compile-resources** tool. - -About ------ - -Inspired from the macros for Vala that I used to build a GTK application, I came -to the point where I needed resources for it. For that purpose the -**glib-compile-resources** tool seemed to be the right choice, but the extra XML -file you need to write bothered me. If I add a resource I don't want to mark it -explicitly in CMake and in the XML file. So I came up with this macro that does -everything for you. You just add your resource to a resource list (with -eventually some attributes like compression) and invoke the resource compilation -function. It generates the XML automatically for you. Quite simple, isn't it? - -Clone ------ - -To clone this repository, just type - -```shell -git clone https://github.com/Makman2/GCR_CMake -``` - -Usage ------ - -Just add the macro directory to your `CMAKE_MODULE_PATH`, include the CMake -file and you are ready to go. - -```cmake -list(APPEND CMAKE_MODULE_PATH - ${PATH_TO_GCR_CMAKE_PARENT_DIR}/GCR_CMake/macros) - -include(GlibCompileResourcesSupport) -``` - -Reference ---------- - -The resource compiling macro is quite powerful and handles as much errors as -possible to make error-finding easier. The function is defined as follows: - -``` -compile_gresources( - - [TYPE output_type] - [TARGET output_name] - [RESOURCES [resources...]] - [OPTIONS [command_line_options...]] - [PREFIX resource_prefix] - [SOURCE_DIR resource_directory] - [COMPRESS_ALL] [NO_COMPRESS_ALL] - [STRIPBLANKS_ALL] [NO_STRIPBLANKS_ALL] - [TOPIXDATA_ALL] [NO_TOPIXDATA_ALL]) -``` - -- ***output*** - - The variable name where to set the output file names. Pass this variable to a - target as a dependency (i.e. `add_custom_target`). - -- ***xml_out*** - - The variable name where to store the output file name of the intermediately - generated gresource-XML-file. - -- **TYPE** ***output_type*** - - The resource type to generate. Valid values are `EMBED_C`, `EMBED_H`, `BUNDLE` - or `AUTO`. Anything else will default to `AUTO`. - - - `EMBED_C`: Generate a C code file that can be compiled with your program. - - - `EMBED_H`: Generate a header file to include in your program. - - - `BUNDLE`: Generates a resource disk file to load at runtime. - - - `AUTO` (or anything else): Extract mode from file ending specified in - `TARGET`. - - If `TARGET` contains - an invalid file or file ending not detectable, the function results in a - **FATAL_ERROR**. - - Recognized file formats are: *.gresource*, *.c*, *.h*. - -- **TARGET** ***output_name*** - - Overrides the default output file name. If not specified (and not - `AUTO`-**TYPE** is set) the output name is *resources.[type-ending]*. - -- **RESOURCES** ***[resources...]*** - - The resource list to process. Each resource must be a relative path inside the - source directory. Each file can be preceded with resource flags. - - - `COMPRESS` flag - - Compress the following file. Effectively sets the attribute *compressed* in - the XML file to true. - - - `STRIPBLANKS` flag - - Strip whitespace characters in XML files. Sets the *preprocess* attribute in - XML with *xml-stripblanks* (requires XMLLint). - - - `TOPIXDATA` flag - - Generates a pixdata ready to use in Gdk. Sets the *preprocess* attribute in - XML with *to-pixdata*. - - Note: Using `STRIPBLANKS` and `TOPIXDATA` together results in a - **FATAL_ERROR**. - -- **OPTIONS** ***command_line_options*** - - Extra command line arguments passed to **glib_compile_resources**. For example - `--internal` or `--manual-register`. - -- **PREFIX** ***resource_prefix*** - - Overrides the resource prefix. The resource entries get inside the XML a - prefix that is prepended to each resource file and represents as a whole the - resource path. - -- **SOURCE_DIR** ***resource_directory*** - - The source directory where the resource files are. If not overridden, this - value is set to `CMAKE_SOURCE_DIR`. - -- **COMPRESS_ALL**, **NO_COMPRESS_ALL** - - Overrides the `COMPRESS` flag in all resources. If **COMPRESS_ALL** is - specified, `COMPRESS` is set everywhere regardless of the specified resource - flags. If **NO_COMPRESS_ALL** is specified, compression is deactivated in all - resources. - - Specifying **COMPRESS_ALL** and **NO_COMPRESS_ALL** together results in a - **FATAL_ERROR**. - -- **STRIPBLANKS_ALL**, **NO_STRIPBLANKS_ALL** - - Overrides the `STRIPBLANKS` flag in all resources. If **STRIPBLANKS_ALL** is - specified, `STRIPBLANKS` is set everywhere regardless of the specified - resource flags. If **NO_STRIPBLANKS_ALL** is specified, stripping away - whitespaces is deactivated in all resources. - - Specifying **STRIPBLANKS_ALL** and **NO_STRIPBLANKS_ALL** together results in - a **FATAL_ERROR**. - -- **TOPIXDATA_ALL**, **NO_TOPIXDATA_ALL** - - Overrides the `TOPIXDATA` flag in all resources. If **TOPIXDATA_ALL** is - specified, `TOPIXDATA` is set everywhere regardless of the specified resource - flags. If **NO_TOPIXDATA_ALL** is specified, converting into pixbufs is - deactivated in all resources. - - Specifying **TOPIXDATA_ALL** and **NO_TOPIXDATA_ALL** together results in a - **FATAL_ERROR**. - -Kickstart ---------- - -This is a quick start guide to provide you an easy start with this macro. - -Starting with a simple example: - -```cmake -set(RESOURCE_LIST - info.txt - img/image1.jpg - img/image2.jpg - data.xml) - -compile_gresources(RESOURCE_FILE - XML_OUT - TYPE BUNDLE - RESOURCES ${RESOURCE_LIST}) -``` - -What does this snippet do? First it sets some resource files to pack into a -resource file. They are located in the source directory passed to CMake at -invocation (`CMAKE_SOURCE_DIR`). -After that we compile the resources. Means we generate a *.gresource.xml*-file -(it's path is put inside the `XML_OUT` variable) automatically from our -`RESOURCE_LIST` and create a custom command that compiles the generated -*.gresource.xml*-file with the provided resources into a resource bundle. Since -no specific output file is specified via **TARGET** the output file is placed -into the `CMAKE_BINARY_DIR` with the name *resources.gresource*. The first -argument `RESOURCE_FILE` is a variable that is filled with the output file name, -so with *resources.gresource* inside the build directory. This variable is -helpful to create makefile targets (or to process the output file differently). - -So here comes a full *CMakeLists.txt* that creates the resources from before. - -```cmake -# Minimum support is guaranteed for CMake 2.8. Everything below needs to be -# tested. -cmake_minimum_required(2.8) - -project(MyResourceFile) - -# Include the extension module. -list(APPEND CMAKE_MODULE_PATH - ${PATH_TO_GCR_CMAKE_PARENT_DIR}/GCR_CMake/macros) - -include(GlibCompileResourcesSupport) - -# Set the resources to bundle. -set(RESOURCE_LIST - info.txt - img/image1.jpg - img/image2.jpg - data.xml) - -# Compile the resources. -compile_gresources(RESOURCE_FILE - XML_OUT - TYPE BUNDLE - RESOURCES ${RESOURCE_LIST}) - -# Add a custom target to the makefile. Now make builds our resource file. -# It depends on the output RESOURCE_FILE. -add_custom_target(resource ALL DEPENDS ${RESOURCE_FILE}) -``` - -A nice feature of the `compile_gresources`-macro is that it supports -individually setting flags on the resources. So we can extend our resource list -like that: - -```cmake -set(RESOURCE_LIST - info.txt - COMPRESS img/image1.jpg - COMPRESS img/image2.jpg - STRIPBLANKS data.xml) -``` - -This resource list not only simply includes the resources, it specifies also -that the two images should be compressed and in *data.xml* the whitespaces -should be removed. This resource list will include the same files but will -preprocess some of them. - -Very handy are the `COMPRESS_ALL`, `STRIPBLANKS_ALL` or `TOPIXDATA_ALL` -parameters (and their `NO_`-equivalents). If you are too lazy to write before -every file the flag, just invoke `compile_gresources` with them. - -```cmake -# Compile the resources. Compresses all files regardless if you specified it -# explicitly or not. -compile_gresources(RESOURCE_FILE - XML_OUT - TYPE BUNDLE - RESOURCES ${RESOURCE_LIST} - COMPRESS_ALL) -``` - -So that's a short introduction into the operating mode of the -`compile-gresources` macro. diff -Nru geary-0.12.4/cmake/Gettext.cmake geary-3.32.0/cmake/Gettext.cmake --- geary-0.12.4/cmake/Gettext.cmake 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/cmake/Gettext.cmake 1970-01-01 00:00:00.000000000 +0000 @@ -1,120 +0,0 @@ -# Gettext support: Create/Update pot file and -# -# To use: INCLUDE(Gettext) -# -# Most of the gettext support code is from FindGettext.cmake of cmake, -# but it is included here because: -# -# 1. Some system like RHEL5 does not have FindGettext.cmake -# 2. Bug of GETTEXT_CREATE_TRANSLATIONS make it unable to be include in 'All' -# 3. It does not support xgettext -# -#=================================================================== -# Constants: -# XGETTEXT_OPTIONS_DEFAULT: Default xgettext option: -#=================================================================== -# Variables: -# XGETTEXT_OPTIONS: Options pass to xgettext -# Default: XGETTEXT_OPTIONS_DEFAULT -# GETTEXT_MSGMERGE_EXECUTABLE: the full path to the msgmerge tool. -# GETTEXT_MSGFMT_EXECUTABLE: the full path to the msgfmt tool. -# GETTEXT_FOUND: True if gettext has been found. -# XGETTEXT_EXECUTABLE: the full path to the xgettext. -# XGETTEXT_FOUND: True if xgettext has been found. -# -#=================================================================== -# Macros: -# GETTEXT_CREATE_POT(potFile -# [OPTION xgettext_options] -# SRC list_of_source_file_that_contains_msgid -# ) -# -# Generate .pot file. -# OPTION xgettext_options: Override XGETTEXT_OPTIONS -# -# * Produced targets: pot_file -# -#------------------------------------------------------------------- -# GETTEXT_CREATE_TRANSLATIONS ( outputFile [ALL] locale1 ... localeN -# [COMMENT comment] ) -# -# This will create a target "translations" which will convert the -# given input po files into the binary output mo file. If the -# ALL option is used, the translations will also be created when -# building the default target. -# -# * Produced targets: translations -#------------------------------------------------------------------- - -FIND_PROGRAM(GETTEXT_MSGMERGE_EXECUTABLE msgmerge) -FIND_PROGRAM(GETTEXT_MSGFMT_EXECUTABLE msgfmt) -FIND_PROGRAM(GETTEXT_MSGCAT_EXECUTABLE msgcat) -FIND_PROGRAM(XGETTEXT_EXECUTABLE xgettext) - -SET(XGETTEXT_OPTIONS_DEFAULT - --language=C --keyword=_ --keyword=N_ --keyword=C_:1c,2 --keyword=NC_:1c,2 -s - --escape --add-comments="/" --package-name=${PROJECT_NAME} --package-version=${VERSION}) - -IF (GETTEXT_MSGMERGE_EXECUTABLE AND GETTEXT_MSGFMT_EXECUTABLE AND GETTEXT_MSGCAT_EXECUTABLE) - SET(GETTEXT_FOUND TRUE) -ELSE (GETTEXT_MSGMERGE_EXECUTABLE AND GETTEXT_MSGFMT_EXECUTABLE) - SET(GETTEXT_FOUND FALSE) - IF (GetText_REQUIRED) - MESSAGE(FATAL_ERROR "GetText not found") - ENDIF (GetText_REQUIRED) -ENDIF (GETTEXT_MSGMERGE_EXECUTABLE AND GETTEXT_MSGFMT_EXECUTABLE AND GETTEXT_MSGCAT_EXECUTABLE) - -IF(XGETTEXT_EXECUTABLE) - SET(XGETTEXT_FOUND TRUE) -ELSE(XGETTEXT_EXECUTABLE) - MESSAGE(STATUS "xgettext not found.") - SET(XGETTTEXT_FOUND FALSE) -ENDIF(XGETTEXT_EXECUTABLE) - -IF(NOT DEFINED XGETTEXT_OPTIONS) - SET(XGETTEXT_OPTIONS ${XGETTEXT_OPTIONS_DEFAULT}) -ENDIF(NOT DEFINED XGETTEXT_OPTIONS) - -IF(XGETTEXT_FOUND) - MACRO(GETTEXT_CREATE_TRANSLATIONS _firstLang) - SET(_gmoFiles) - SET(_addToAll) - SET(_is_comment FALSE) - - FOREACH (_currentLang ${_firstLang} ${ARGN}) - IF(_currentLang STREQUAL "ALL") - SET(_addToAll "ALL") - ELSEIF(_currentLang STREQUAL "COMMENT") - SET(_is_comment TRUE) - ELSEIF(_is_comment) - SET(_is_comment FALSE) - SET(_comment ${_currentLang}) - ELSE() - SET(_lang ${_currentLang}) - GET_FILENAME_COMPONENT(_absFile ${_currentLang}.po ABSOLUTE) - GET_FILENAME_COMPONENT(_abs_PATH ${_absFile} PATH) - SET(_gmoFile ${CMAKE_CURRENT_BINARY_DIR}/${_lang}.mo) - - #MESSAGE("_absFile=${_absFile} _abs_PATH=${_abs_PATH} _lang=${_lang} curr_bin=${CMAKE_CURRENT_BINARY_DIR}") - ADD_CUSTOM_COMMAND( - OUTPUT ${_gmoFile} - COMMAND ${GETTEXT_MSGFMT_EXECUTABLE} -o ${_gmoFile} ${_absFile} - DEPENDS ${_absFile} - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - ) - - INSTALL(FILES ${_gmoFile} DESTINATION share/locale/${_lang}/LC_MESSAGES RENAME ${GETTEXT_PACKAGE}.mo) - SET(_gmoFiles ${_gmoFiles} ${_gmoFile}) - ENDIF() - ENDFOREACH (_currentLang ) - - IF(DEFINED _comment) - ADD_CUSTOM_TARGET(translations ${_addToAll} DEPENDS ${_gmoFiles} COMMENT ${_comment}) - ELSE(DEFINED _comment) - ADD_CUSTOM_TARGET(translations ${_addToAll} DEPENDS ${_gmoFiles}) - ENDIF(DEFINED _comment) - ENDMACRO(GETTEXT_CREATE_TRANSLATIONS ) -ENDIF(XGETTEXT_FOUND) - - - diff -Nru geary-0.12.4/cmake/gitversion.cmake geary-3.32.0/cmake/gitversion.cmake --- geary-0.12.4/cmake/gitversion.cmake 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/cmake/gitversion.cmake 1970-01-01 00:00:00.000000000 +0000 @@ -1,14 +0,0 @@ -if (VERSION MATCHES "-dev$") - find_package(Git QUIET) - if (GIT_FOUND) - execute_process(COMMAND ${GIT_EXECUTABLE} describe --all --long --dirty - WORKING_DIRECTORY ${SRC_DIR} - OUTPUT_VARIABLE "GIT_VERSION" - OUTPUT_STRIP_TRAILING_WHITESPACE - ERROR_QUIET) - string(REGEX REPLACE "^heads\\/(.*)-0-(g.*)$" "\\1~\\2" GIT_VERSION "${GIT_VERSION}") - set(VERSION ${GIT_VERSION}) - endif() -endif() - -configure_file("${SRC_DIR}/geary-version.vala.in" "${DST_DIR}/geary-version.vala") diff -Nru geary-0.12.4/cmake/GSettings.cmake geary-3.32.0/cmake/GSettings.cmake --- geary-0.12.4/cmake/GSettings.cmake 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/cmake/GSettings.cmake 1970-01-01 00:00:00.000000000 +0000 @@ -1,112 +0,0 @@ -# GSettings.cmake -# Originally based on CMake macros written for Marlin -# Updated by Yorba for newer versions of GLib. -# -# NOTE: This module does an in-place compilation of GSettings; the -# resulting gschemas.compiled file will end up in the same -# source folder as the original schema(s). - -option(GSETTINGS_COMPILE "Compile GSettings schemas. Can be disabled for packaging reasons." ON) -option(GSETTINGS_COMPILE_IN_PLACE "Compile GSettings schemas in the build folder. This is used for running an appliction without installing the GSettings systemwide. The application will need to set GSETTINGS_SCHEMA_DIR" ON) - -if (GSETTINGS_COMPILE) - message(STATUS "GSettings schemas will be compiled.") -endif () - -if (GSETTINGS_COMPILE_IN_PLACE) - message(STATUS "GSettings schemas will be compiled in-place.") -endif () - -macro(add_schemas GSETTINGS_TARGET SCHEMA_DIRECTORY PREFIX) - find_package(PkgConfig) - - # Locate all schema files. - file(GLOB all_schema_files - "${SCHEMA_DIRECTORY}/*.gschema.xml" - ) - - if ("${PREFIX}" STREQUAL "") - # Find the GLib path for schema installation - execute_process( - COMMAND - ${PKG_CONFIG_EXECUTABLE} - glib-2.0 - --variable prefix - OUTPUT_VARIABLE - _glib_prefix - OUTPUT_STRIP_TRAILING_WHITESPACE - ) - else () - set(_glib_prefix ${PREFIX}) - endif () - - set(GSETTINGS_DIR "${_glib_prefix}/share/glib-2.0/schemas/" CACHE INTERNAL "") - - # Fetch path for schema compiler from pkg-config - execute_process( - COMMAND - ${PKG_CONFIG_EXECUTABLE} - gio-2.0 - --variable - glib_compile_schemas - OUTPUT_VARIABLE - _glib_compile_schemas - OUTPUT_STRIP_TRAILING_WHITESPACE - ) - - set(glib_schema_compiler ${_glib_compile_schemas} CACHE INTERNAL "") - - if (GSETTINGS_COMPILE_IN_PLACE) - set(COMPILE_IN_PLACE_DIR ${CMAKE_BINARY_DIR}/gsettings) - add_custom_command( - TARGET - ${GSETTINGS_TARGET} - COMMAND - ${CMAKE_COMMAND} -E make_directory "${COMPILE_IN_PLACE_DIR}" - ) - - # Copy all schemas to the build folder. - foreach(schema_file ${all_schema_files}) - add_custom_command( - TARGET - ${GSETTINGS_TARGET} - COMMAND - ${CMAKE_COMMAND} -E copy "${schema_file}" "${COMPILE_IN_PLACE_DIR}" - COMMENT "Copying schema ${schema_file} to ${COMPILE_IN_PLACE_DIR}" - ) - endforeach() - - # Compile schema in-place. - add_custom_command( - TARGET - ${GSETTINGS_TARGET} - COMMAND - ${glib_schema_compiler} ${COMPILE_IN_PLACE_DIR} - COMMENT "Compiling schemas in folder: ${COMPILE_IN_PLACE_DIR}" - ) - endif () - - # Install and recompile schemas - message(STATUS "GSettings schemas will be installed into ${GSETTINGS_DIR}") - - install( - FILES - ${all_schema_files} - DESTINATION - ${GSETTINGS_DIR} - OPTIONAL - ) - - if (GSETTINGS_COMPILE) - install( - CODE - "message (STATUS \"Compiling GSettings schemas\")" - ) - - install( - CODE - "execute_process (COMMAND ${glib_schema_compiler} ${GSETTINGS_DIR})" - ) - endif () -endmacro(add_schemas) - diff -Nru geary-0.12.4/cmake/ParseArguments.cmake geary-3.32.0/cmake/ParseArguments.cmake --- geary-0.12.4/cmake/ParseArguments.cmake 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/cmake/ParseArguments.cmake 1970-01-01 00:00:00.000000000 +0000 @@ -1,36 +0,0 @@ -## -# This is a helper Macro to parse optional arguments in Macros/Functions -# It has been taken from the public CMake wiki. -# See http://www.cmake.org/Wiki/CMakeMacroParseArguments for documentation and -# licensing. -## -macro(parse_arguments prefix arg_names option_names) - set(DEFAULT_ARGS) - foreach(arg_name ${arg_names}) - set(${prefix}_${arg_name}) - endforeach(arg_name) - foreach(option ${option_names}) - set(${prefix}_${option} FALSE) - endforeach(option) - - set(current_arg_name DEFAULT_ARGS) - set(current_arg_list) - foreach(arg ${ARGN}) - set(larg_names ${arg_names}) - list(FIND larg_names "${arg}" is_arg_name) - if(is_arg_name GREATER -1) - set(${prefix}_${current_arg_name} ${current_arg_list}) - set(current_arg_name ${arg}) - set(current_arg_list) - else(is_arg_name GREATER -1) - set(loption_names ${option_names}) - list(FIND loption_names "${arg}" is_option) - if(is_option GREATER -1) - set(${prefix}_${arg} TRUE) - else(is_option GREATER -1) - set(current_arg_list ${current_arg_list} ${arg}) - endif(is_option GREATER -1) - endif(is_arg_name GREATER -1) - endforeach(arg) - set(${prefix}_${current_arg_name} ${current_arg_list}) -endmacro(parse_arguments) diff -Nru geary-0.12.4/cmake/README.rst geary-3.32.0/cmake/README.rst --- geary-0.12.4/cmake/README.rst 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/cmake/README.rst 1970-01-01 00:00:00.000000000 +0000 @@ -1,173 +0,0 @@ -========== -Vala CMake -========== -:Author: - Jakob Westhoff -:Version: - Draft - - -Overview -======== - -Vala CMake is a collection of macros for the CMake_ build system to allow the -creation and management of projects developed using the Vala_ programming -language or its "Genie" flavor (less tested). - - -Installation -============ - -To use the Vala macros in your own project you need to copy the macro files to -an arbitrary folder in your projects directory and reference them in your -``CMakeLists.txt`` file. - -Assuming the macros are stored under ``cmake/vala`` in your projects folder you -need to add the following information to your base ``CMakeLists.txt``:: - - list(APPEND CMAKE_MODULE_PATH - ${CMAKE_SOURCE_DIR}/cmake/vala - ) - -After the new module path as been added you can simply include the provided -modules or use the provided find routines. - - -Finding Vala -============ - -The find module for vala works like any other Find module in CMake. -You can use it by simply calling the usual ``find_package`` function. Default -parameters like ``REQUIRED`` and ``QUIETLY`` are supported. - -:: - - find_package(Vala REQUIRED) - -After a successful call to the find_package function the following variables -will be set: - -VALA_FOUND - Whether the vala compiler has been found or not - -VALA_EXECUTABLE - Full path to the valac executable if it has been found - -VALA_VERSION - Version number of the available valac - - -Precompiling Vala sources -========================= - -CMake is mainly supposed to handle c or c++ based projects. Luckily every vala -program is translated into plain c code using the vala compiler, followed by -normal compilation of the generated c program using gcc. - -The macro ``vala_precompile`` uses that fact to create c files from your .vala -sources for further CMake processing. - -The first parameter provided is a variable, which will be filled with a list of -c files outputted by the vala compiler. This list can than be used in -conjunction with functions like ``add_executable`` or others to create the -necessary compile rules with CMake. - -The initial variable is followed by a list of .vala files to be compiled. -Please take care to add every vala file belonging to the currently compiled -project or library as Vala will otherwise not be able to resolve all -dependencies. - -The following sections may be specified afterwards to provide certain options -to the vala compiler: - -PACKAGES - A list of vala packages/libraries to be used during the compile cycle. The - package names are exactly the same, as they would be passed to the valac - "--pkg=" option. - -OPTIONS - A list of optional options to be passed to the valac executable. This can be - used to pass "--thread" for example to enable multi-threading support. - -DIRECTORY - Specify the directory where the output source files will be stored. If - ommitted, the source files will be stored in CMAKE_CURRENT_BINARY_DIR. - -CUSTOM_VAPIS - A list of custom vapi files to be included for compilation. This can be - useful to include freshly created vala libraries without having to install - them in the system. - -GENERATE_VAPI - Pass all the needed flags to the compiler to create an internal vapi for - the compiled library. The provided name will be used for this and a - .vapi file will be created. - -GENERATE_HEADER - Let the compiler generate a header file for the compiled code. There will - be a header file as well as an internal header file being generated called - .h and _internal.h - -The following call is a simple example to the vala_precompile macro showing an -example to every of the optional sections:: - - vala_precompile(VALA_C - source1.vala - source2.vala - source3.vala - PACKAGES - gtk+-2.0 - gio-1.0 - posix - OPTIONS - --thread - CUSTOM_VAPIS - some_vapi.vapi - GENERATE_VAPI - myvapi - GENERATE_HEADER - myheader - ) - -Most important is the variable VALA_C which will contain all the generated c -file names after the call. The easiest way to use this information is to tell -CMake to create an executable out of it. - -:: - - add_executable(myexecutable ${VALA_C}) - - -Further reading -=============== - -The `Pdf Presenter Console`__ , which is a vala based project of mine, makes -heavy usage of the here described macros. To look at a real world example of -these macros the mentioned project is the right place to take a look. The svn -trunk of it can be found at:: - - svn://pureenergy.cc/pdf_presenter_console/trunk - - -__ http://westhoffswelt.de/projects/pdf_presenter_console.html - - -Acknowledgments -=============== - -Thanks go out to Florian Sowade, a fellow local PHP-Usergroupie, who helped me -a lot with the initial version of this macros and always answered my mostly -dumb CMake questions. - -.. _CMake: http://cmake.org -.. _Vala: http://live.gnome.org/Vala -.. _Genie: http://live.gnome.org/Genie - - - -.. - Local Variables: - mode: rst - fill-column: 79 - End: - vim: et syn=rst tw=79 diff -Nru geary-0.12.4/cmake/ValaPrecompile.cmake geary-3.32.0/cmake/ValaPrecompile.cmake --- geary-0.12.4/cmake/ValaPrecompile.cmake 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/cmake/ValaPrecompile.cmake 1970-01-01 00:00:00.000000000 +0000 @@ -1,213 +0,0 @@ -## -# Copyright 2009-2010 Jakob Westhoff. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# 1. Redistributions of source code must retain the above copyright notice, -# this list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# THIS SOFTWARE IS PROVIDED BY JAKOB WESTHOFF ``AS IS'' AND ANY EXPRESS OR -# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO -# EVENT SHALL JAKOB WESTHOFF OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE -# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# -# The views and conclusions contained in the software and documentation are those -# of the authors and should not be interpreted as representing official policies, -# either expressed or implied, of Jakob Westhoff -## - -include(ParseArguments) -find_package(Vala REQUIRED) - -## -# Compile vala files to their c equivalents for further processing. -# -# The "vala_precompile" macro takes care of calling the valac executable on the -# given source to produce c files which can then be processed further using -# default cmake functions. -# -# The first parameter provided is a variable, which will be filled with a list -# of c files outputted by the vala compiler. This list can than be used in -# conjunction with functions like "add_executable" or others to create the -# necessary compile rules with CMake. -# -# The second parameter provided is a unique name for the source bundle, which -# is used to create a .stamp file that marks the last time the bundle was -# compiled to C. -# -# The initial variables are followed by a list of .vala files to be compiled. -# Please take care to add every vala file belonging to the currently compiled -# project or library as Vala will otherwise not be able to resolve all -# dependencies. -# -# The following sections may be specified afterwards to provide certain options -# to the vala compiler: -# -# PACKAGES -# A list of vala packages/libraries to be used during the compile cycle. The -# package names are exactly the same, as they would be passed to the valac -# "--pkg=" option. -# -# OPTIONS -# A list of optional options to be passed to the valac executable. This can be -# used to pass "--thread" for example to enable multi-threading support. -# -# CUSTOM_VAPIS -# A list of custom vapi files to be included for compilation. This can be -# useful to include freshly created vala libraries without having to install -# them in the system. -# -# GENERATE_VAPI -# Pass all the needed flags to the compiler to create an internal vapi for -# the compiled library. The provided name will be used for this and a -# .vapi file will be created. -# -# GENERATE_HEADER -# Let the compiler generate a header file for the compiled code. There will -# be a header file as well as an internal header file being generated called -# .h and _internal.h -# -# The following call is a simple example to the vala_precompile macro showing -# an example to every of the optional sections: -# -# vala_precompile(VALA_C mysourcebundle -# source1.vala -# source2.vala -# source3.vala -# PACKAGES -# gtk+-2.0 -# gio-1.0 -# posix -# DIRECTORY -# gen -# OPTIONS -# --thread -# CUSTOM_VAPIS -# some_vapi.vapi -# GENERATE_VAPI -# myvapi -# GENERATE_HEADER -# myheader -# ) -# -# Most important is the variable VALA_C which will contain all the generated c -# file names after the call. -## - -macro(vala_precompile output source_bundle_name) - parse_arguments(ARGS "PACKAGES;OPTIONS;DIRECTORY;GENERATE_HEADER;GENERATE_VAPI;CUSTOM_VAPIS" "" ${ARGN}) - - if(ARGS_DIRECTORY) - set(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${ARGS_DIRECTORY}) - else(ARGS_DIRECTORY) - set(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) - endif(ARGS_DIRECTORY) - - include_directories(${DIRECTORY}) - - set(vala_pkg_opts "") - foreach(pkg ${ARGS_PACKAGES}) - list(APPEND vala_pkg_opts "--pkg=${pkg}") - endforeach(pkg ${ARGS_PACKAGES}) - - - set(in_files "") - set(out_files "") - set(${output} "") - foreach(src ${ARGS_DEFAULT_ARGS}) - string(REPLACE ${CMAKE_CURRENT_SOURCE_DIR}/ "" src ${src}) - string(REGEX MATCH "^/" IS_MATCHED ${src}) - - if(${IS_MATCHED} MATCHES "/") - set(in_file ${src}) - else() - set(in_file "${CMAKE_CURRENT_SOURCE_DIR}/${src}") - endif() - - string(REPLACE ".vala" ".c" src ${src}) - string(REPLACE ".gs" ".c" src ${src}) - - if(${IS_MATCHED} MATCHES "/") - get_filename_component(VALA_FILE_NAME ${src} NAME) - set(out_file "${CMAKE_CURRENT_BINARY_DIR}/${VALA_FILE_NAME}") - else() - set(out_file "${DIRECTORY}/${src}") - endif() - - list(APPEND in_files ${in_file}) - list(APPEND out_files ${out_file}) - list(APPEND ${output} ${out_file}) - endforeach(src ${ARGS_DEFAULT_ARGS}) - - set(custom_vapi_arguments "") - if(ARGS_CUSTOM_VAPIS) - foreach(vapi ${ARGS_CUSTOM_VAPIS}) - SET(_srcdir_regexp "${CMAKE_SOURCE_DIR}") - SET(_bindir_regexp "${CMAKE_BINARY_DIR}") - STRING(REGEX REPLACE "\\+" "\\\\+" _srcdir_regexp "${_srcdir_regexp}") - STRING(REGEX REPLACE "\\+" "\\\\+" _bindir_regexp "${_bindir_regexp}") - if(${vapi} MATCHES ${_srcdir_regexp} OR ${vapi} MATCHES ${_bindir_regexp}) - list(APPEND custom_vapi_arguments ${vapi}) - else(${vapi} MATCHES ${_srcdir_regexp} OR ${vapi} MATCHES ${_bindir_regexp}) - list(APPEND custom_vapi_arguments ${CMAKE_CURRENT_SOURCE_DIR}/${vapi}) - endif(${vapi} MATCHES ${_srcdir_regexp} OR ${vapi} MATCHES ${_bindir_regexp}) - endforeach(vapi ${ARGS_CUSTOM_VAPIS}) - endif(ARGS_CUSTOM_VAPIS) - - set(STAMP_FILE ".${source_bundle_name}.stamp") - - set(vapi_arguments "") - if(ARGS_GENERATE_VAPI) - list(APPEND out_files "${DIRECTORY}/${ARGS_GENERATE_VAPI}.vapi") - set(vapi_arguments "--vapi=${ARGS_GENERATE_VAPI}.vapi") - - # Header and internal header is needed to generate internal vapi - if(NOT ARGS_GENERATE_HEADER) - set(ARGS_GENERATE_HEADER ${ARGS_GENERATE_VAPI}) - endif(NOT ARGS_GENERATE_HEADER) - endif(ARGS_GENERATE_VAPI) - - set(header_arguments "") - if(ARGS_GENERATE_HEADER) - list(APPEND out_files "${DIRECTORY}/${ARGS_GENERATE_HEADER}.h") - list(APPEND out_files "${DIRECTORY}/${ARGS_GENERATE_HEADER}_internal.h") - list(APPEND header_arguments "--header=${DIRECTORY}/${ARGS_GENERATE_HEADER}.h") - list(APPEND header_arguments "--internal-header=${DIRECTORY}/${ARGS_GENERATE_HEADER}_internal.h") - endif(ARGS_GENERATE_HEADER) - - add_custom_command(OUTPUT ${STAMP_FILE} - COMMAND - ${VALA_EXECUTABLE} - ARGS - "-C" - ${header_arguments} - ${vapi_arguments} - "-b" ${CMAKE_CURRENT_SOURCE_DIR} - "-d" ${DIRECTORY} - ${vala_pkg_opts} - ${ARGS_OPTIONS} - ${in_files} - ${custom_vapi_arguments} - COMMAND - touch - ARGS - ${STAMP_FILE} - DEPENDS - ${in_files} - ${ARGS_CUSTOM_VAPIS} - ) - - add_custom_command(OUTPUT ${out_files} DEPENDS ${STAMP_FILE}) -endmacro(vala_precompile) - diff -Nru geary-0.12.4/cmake/ValaVersion.cmake geary-3.32.0/cmake/ValaVersion.cmake --- geary-0.12.4/cmake/ValaVersion.cmake 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/cmake/ValaVersion.cmake 1970-01-01 00:00:00.000000000 +0000 @@ -1,96 +0,0 @@ -## -# Copyright 2009-2010 Jakob Westhoff. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# 1. Redistributions of source code must retain the above copyright notice, -# this list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# THIS SOFTWARE IS PROVIDED BY JAKOB WESTHOFF ``AS IS'' AND ANY EXPRESS OR -# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO -# EVENT SHALL JAKOB WESTHOFF OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE -# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# -# The views and conclusions contained in the software and documentation are those -# of the authors and should not be interpreted as representing official policies, -# either expressed or implied, of Jakob Westhoff -## - -include(ParseArguments) -find_package(Vala REQUIRED) - -## -# Ensure a certain valac version is available -# -# The initial argument is the version to check for -# -# It may be followed by a optional parameter to specifiy a version range. The -# following options are valid: -# -# EXACT -# Vala needs to be available in the exact version given -# -# MINIMUM -# The provided version is the minimum version. Therefore Vala needs to be -# available in the given version or any higher version -# -# MAXIMUM -# The provided version is the maximum. Therefore Vala needs to be available -# in the given version or any version older than this -# -# If no option is specified the version will be treated as a minimal version. -## -macro(ensure_vala_version version) - parse_arguments(ARGS "" "MINIMUM;MAXIMUM;EXACT" ${ARGN}) - set(compare_message "") - set(error_message "") - if(ARGS_MINIMUM) - set(compare_message "a minimum ") - set(error_message "or greater ") - elseif(ARGS_MAXIMUM) - set(compare_message "a maximum ") - set(error_message "or less ") - endif(ARGS_MINIMUM) - - message(STATUS - "checking for ${compare_message}Vala version of ${version}" - ) - - unset(version_accepted) - - # MINIMUM is the default if no option is specified - if(ARGS_EXACT) - if(${VALA_VERSION} VERSION_EQUAL ${version} ) - set(version_accepted TRUE) - endif(${VALA_VERSION} VERSION_EQUAL ${version}) - elseif(ARGS_MAXIMUM) - if(${VALA_VERSION} VERSION_LESS ${version} OR ${VALA_VERSION} VERSION_EQUAL ${version}) - set(version_accepted TRUE) - endif(${VALA_VERSION} VERSION_LESS ${version} OR ${VALA_VERSION} VERSION_EQUAL ${version}) - else(ARGS_MINIMUM) - if(${VALA_VERSION} VERSION_GREATER ${version} OR ${VALA_VERSION} VERSION_EQUAL ${version}) - set(version_accepted TRUE) - endif(${VALA_VERSION} VERSION_GREATER ${version} OR ${VALA_VERSION} VERSION_EQUAL ${version}) - endif(ARGS_EXACT) - - if (NOT version_accepted) - message(FATAL_ERROR - "Vala version ${version} ${error_message}is required." - ) - endif(NOT version_accepted) - - message(STATUS - " found Vala, version ${VALA_VERSION}" - ) -endmacro(ensure_vala_version) diff -Nru geary-0.12.4/CMakeLists.txt geary-3.32.0/CMakeLists.txt --- geary-0.12.4/CMakeLists.txt 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/CMakeLists.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,235 +0,0 @@ -# Geary build script -# Copyright 2016 Software Freedom Conservancy Inc. -# -# Check http://webdev.elementaryos.org/docs/developer-guide/cmake for documentation - -cmake_minimum_required(VERSION 2.8) -cmake_policy(VERSION 2.6) - -project(geary C) - -list(APPEND - CMAKE_MODULE_PATH - ${CMAKE_SOURCE_DIR}/cmake - ${CMAKE_SOURCE_DIR}/cmake/GCR_CMake/macros - ) - -# -# Base bits -# -set(GETTEXT_PACKAGE "geary") -set(RELEASE_NAME "Lightweight email client for GNOME.") -set(VERSION "0.12.4") -set(VERSION_INFO "Release") -set(LANGUAGE_SUPPORT_DIRECTORY ${CMAKE_INSTALL_PREFIX}/share/locale) - -# -# Primary library minimum version requirements. See src/CMakeLists.txt -# for others. -# -set(TARGET_GLIB 2.42) # Also passed to valac, so don't include a point rev -set(TARGET_GTK 3.14.0) -set(TARGET_WEBKIT 2.10) - -if (NOT ISO_CODE_639_XML) - find_path(ISOCODES_DIRECTORY NAMES iso_639.xml PATHS ${CMAKE_INSTALL_PREFIX} /usr/share/xml/iso-codes) - if (ISOCODES_DIRECTORY) - set(ISO_CODE_639_XML ${ISOCODES_DIRECTORY}/iso_639.xml) - else () - message(WARNING "File iso_639.xml not found. Please specify it manually using cmake -DISO_CODE_639_XML=/path/to/iso_639.xml") - endif () -else () - if (NOT EXISTS ${ISO_CODE_639_XML}) - message(WARNING "The path to iso_639.xml specified in ISO_CODE_639_XML is not valid.") - endif () -endif () - -if (NOT ISO_CODE_3166_XML) - find_path(ISOCODES_DIRECTORY NAMES iso_3166.xml PATHS ${CMAKE_INSTALL_PREFIX} /usr/share/xml/iso-codes) - if (ISOCODES_DIRECTORY) - set(ISO_CODE_3166_XML ${ISOCODES_DIRECTORY}/iso_3166.xml) - else () - message(WARNING "File iso_3166.xml not found. Please specify it manually using cmake -DISO_CODE_3166_XML=/path/to/iso_3166.xml") - endif () -else () - if (NOT EXISTS ${ISO_CODE_3166_XML}) - message(WARNING "The path to iso_3166.xml specified in ISO_CODE_3166_XML is not valid.") - endif () -endif () - -# Packaging filenamesnames. -set(ARCHIVE_BASE_NAME ${CMAKE_PROJECT_NAME}-${VERSION}) -set(ARCHIVE_FULL_NAME ${ARCHIVE_BASE_NAME}.tar.xz) -set(ARCHIVE_DEBUILD_FULL_NAME ${CMAKE_PROJECT_NAME}_${VERSION}.orig.tar.xz) - -if (NOT CMAKE_BUILD_TYPE) - #default build is -O2 -g - set(CMAKE_BUILD_TYPE "RelWithDebInfo") -endif() - -option(ICON_UPDATE "Run gtk-update-icon-cache after the install." ON) -option(DESKTOP_UPDATE "Run update-desktop-database after the install." ON) -option(DESKTOP_VALIDATE "Check generated desktop file for errors during build." ON) -option(TRANSLATE_HELP "Generate and install translated help documentation." ON) - -IF(CMAKE_BUILD_TYPE MATCHES Debug) - message(STATUS "Debug build") -endif () - -if (ICON_UPDATE) - message(STATUS "Icon cache will be updated") -endif () - -if (DESKTOP_UPDATE) - message(STATUS "Desktop database will be updated") -endif () - -if (DESKTOP_VALIDATE) - message(STATUS "Generated desktop file will be checked for errors") -endif () - -if (TRANSLATE_HELP) - message(STATUS "Help translations will be generated and installed") -endif () - -find_package(PkgConfig) -pkg_check_modules(LIBUNITY QUIET unity>=5.12.0) -pkg_check_modules(LIBMESSAGINGMENU QUIET messaging-menu>=12.10.2) - -pkg_check_modules(ENCHANT QUIET enchant) - -pkg_check_modules(SQLITE3 sqlite3 REQUIRED) -if (NOT ${SQLITE3_VERSION} VERSION_LESS 3.12) - include(CheckSymbolExists) - check_symbol_exists(SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER sqlite3.h HAVE_FTS3_TOKENIZER) - if (NOT HAVE_FTS3_TOKENIZER) - message(FATAL_ERROR "SQLite3 is missing FTS3 tokenizer support. Please compile it with -DSQLITE_ENABLE_FTS3." - " See https://bugzilla.gnome.org/show_bug.cgi?id=763203 for details.") - endif() -else() - # detect that the current sqlite3 library has FTS3 support (at run time) - include(CMakePushCheckState) - include(CheckCSourceRuns) - cmake_push_check_state(RESET) - set(CMAKE_REQUIRED_LIBRARIES sqlite3) - check_c_source_runs(" - #include - #include - int main() { - sqlite3 *db; - char tmpfile[] = \"sqliteXXXXXX\"; - mkstemp(tmpfile); - if (sqlite3_open(tmpfile, &db) == SQLITE_OK) { - return sqlite3_exec(db, \"CREATE VIRTUAL TABLE mail USING fts3(subject, body);\", 0, 0, 0); - } - return -1; - } - " HAVE_FTS3) - cmake_pop_check_state() - if (NOT HAVE_FTS3) - if (${SQLITE3_VERSION} VERSION_LESS 3.11) - message(FATAL_ERROR "SQLite3 is missing FTS3 support. Please compile it with -DSQLITE_ENABLE_FTS3.") - else() - message(FATAL_ERROR "SQLite3 is missing FTS3 tokenizer support. Please compile it with -DSQLITE_ENABLE_FTS3 -DSQLITE_ENABLE_FTS3_TOKENIZER.") - endif() - endif() -endif() - -find_package(Git QUIET) - -# intl -include(Gettext) -if (XGETTEXT_FOUND) - message(STATUS "xgettext found") -else () - message(STATUS "xgettext not found") -endif () - -# GResources -include(GlibCompileResourcesSupport) - -# -# Unit tests. -# -# We don't use CMake's enable_testing/add_test built-ins because they -# use ctest. It's not called "test" because that is cmake reserved. -add_custom_target(tests geary-test) -add_dependencies(tests geary-test) - -# -# Uninstall target -# -configure_file( - "${CMAKE_CURRENT_SOURCE_DIR}/cmake_uninstall.cmake.in" - "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" - IMMEDIATE @ONLY -) - -add_custom_target( - uninstall-base - COMMAND - ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake - COMMAND - ${glib_schema_compiler} ${GSETTINGS_DIR} -) - -add_custom_target( - uninstall -) - -# We add this dependency using add_dependencies (which makes it run first) rather than -# a depends clause in add_custom_target (which would make it run last). -add_dependencies(uninstall uninstall-base) - -# This gets fired in the root-level Makefile to ensure an post-uninstall cleanup happens after -# everything has has been removed -add_custom_target( - post-uninstall - ) - -# Dist -# This generates the dist tarballs -if (GIT_FOUND) - add_custom_target( - dist - COMMAND - ${GIT_EXECUTABLE} archive --prefix=${ARCHIVE_BASE_NAME}/ HEAD - | xz -z > ${CMAKE_BINARY_DIR}/${ARCHIVE_FULL_NAME} - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - ) -endif() - -# Ubuntu -# Builds the source Debian package used for the Launchpad PPA -add_custom_target( - ubuntu_pre - DEPENDS - dist - COMMAND - ${CMAKE_COMMAND} -E copy ${ARCHIVE_FULL_NAME} ${ARCHIVE_DEBUILD_FULL_NAME} - COMMAND - tar xvfx ${ARCHIVE_FULL_NAME} -) - -add_custom_target( - ubuntu - DEPENDS - ubuntu_pre - COMMAND - ${CMAKE_COMMAND} -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/debian" - "${CMAKE_CURRENT_BINARY_DIR}/${ARCHIVE_BASE_NAME}/debian" - COMMAND - debuild -S -k$ENV{GPGKEY} - COMMAND - - WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${ARCHIVE_BASE_NAME}" -) - -add_subdirectory(desktop) -add_subdirectory(help) -add_subdirectory(icons) -add_subdirectory(po) -add_subdirectory(sql) -add_subdirectory(ui) -add_subdirectory(src) -add_subdirectory(test) diff -Nru geary-0.12.4/cmake_uninstall.cmake.in geary-3.32.0/cmake_uninstall.cmake.in --- geary-0.12.4/cmake_uninstall.cmake.in 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/cmake_uninstall.cmake.in 1970-01-01 00:00:00.000000000 +0000 @@ -1,33 +0,0 @@ -# Generic uninstall script from the CMake manual -# Updated for CMake 2.8 - -cmake_policy(PUSH) - -# Ignore empty list items. -cmake_policy(SET CMP0007 OLD) - -if (NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") - message(FATAL_ERROR "Cannot find install manifest: \"@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt\"") -endif(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") - -file(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files) -string(REGEX REPLACE "\n" ";" files "${files}") -list(REVERSE files) -foreach (file ${files}) - message(STATUS "Uninstalling \"$ENV{DESTDIR}${file}\"") - if (EXISTS "$ENV{DESTDIR}${file}") - execute_process( - COMMAND @CMAKE_COMMAND@ -E remove "$ENV{DESTDIR}${file}" - OUTPUT_VARIABLE rm_out - RESULT_VARIABLE rm_retval - ) - if(NOT ${rm_retval} EQUAL 0) - message(FATAL_ERROR "Problem when removing \"$ENV{DESTDIR}${file}\"") - endif (NOT ${rm_retval} EQUAL 0) - else (EXISTS "$ENV{DESTDIR}${file}") - message(STATUS "File \"$ENV{DESTDIR}${file}\" does not exist.") - endif (EXISTS "$ENV{DESTDIR}${file}") -endforeach(file) - -cmake_policy(POP) - diff -Nru geary-0.12.4/configure geary-3.32.0/configure --- geary-0.12.4/configure 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/configure 1970-01-01 00:00:00.000000000 +0000 @@ -1,223 +0,0 @@ -#!/usr/bin/env bash -# -# Copyright 2016 Software Freedom Conservancy Inc. -# -# This software is licensed under the GNU LGPL (version 2.1 or later). -# See the COPYING file in this distribution. - -srcdir=$(cd $(dirname $0) && pwd) - -DEFAULT_PREFIX="/usr/local" - -PREFIX=$DEFAULT_PREFIX - -configure_help() { - cat <<- EOT - Usage: - ./configure [OPTIONS]... - - Options: - -h, --help Print this help and exit. - --prefix=PREFIX Prepend PREFIX to program installation paths. - [$DEFAULT_PREFIX] - --debug Build for debugging. - - --disable-fatal-warnings - Disable Vala fatal warnings when compiling. - --enable-ref-tracking - Enable reference tracking which is dumped to stdout when the program exits. - --disable-schemas-compile - Disable compiling the GSettings schema. - --disable-desktop-update - Disable desktop database update. - --disable-desktop-validate - Disable checking for errors in generated desktop file. - --disable-icon-update - Disable icon cache update. - --disable-documentation - Disable generating and installing translated help documentation. - --disable-contractor - Disable installing Contractor files. - --disable-poodle-ssl3 - Disable POODLE SSLv3 GnuTLS priority fix. (Not recommended.) - - Some influential environment variables: - PKG_CONFIG_PATH Adds directories to pkg-config's search path. - PKG_CONFIG_LIBDIR Overrides pkg-config's built-in search path. - - VALAC Name of the vala compiler to use, e.g. "valac-0.18". - VALADOC Name of the valadoc executable to use, e.g. "valadoc-0.18". - -EOT -} - -abort() { - printf "%s: Invalid argument %s\n" $0 $1 - configure_help - exit 1 -} - -while [ $# != 0 ] -do - if [[ "$1" = *=* ]] - then - option=${1%%=*} - value=${1#*=} - else - option=$1 - value= - fi - - case $option in - -h | --help) configure_help - exit 0 - ;; - - --prefix) if [ -z "$value" ]; then - # handle jhbuild's use of "--prefix /path" - value="$2" - shift - fi - [ ! "$value" ] && abort $1 - CMDLINE="${CMDLINE} -DCMAKE_INSTALL_PREFIX=${value}" - ;; - - # ignored for autotools compatibility - --bindir | --libdir | --sbindir | --datadir | --localstatedir | \ - --includedir | --infodir | --sysconfdir | --mandir | --libexecdir) - if [ -z "$value" ]; then - # handle jhbuild's use of "--option /path" - value="$2" - shift - fi - ;; - --build | --host | --target) ;; - --disable-gtk-doc | --disable-silent-rules | --disable-static ) ;; - - --debug) - CMDLINE="${CMDLINE} -DCMAKE_BUILD_TYPE=Debug" - ;; - - --disable-fatal-warnings) - CMDLINE="${CMDLINE} -DNO_FATAL_WARNINGS=ON" - ;; - - --enable-ref-tracking) - CMDLINE="${CMDLINE} -DREF_TRACKING=ON" - ;; - - --disable-schemas-compile) - CMDLINE="${CMDLINE} -DGSETTINGS_COMPILE=OFF" - CMDLINE="${CMDLINE} -DGSETTINGS_COMPILE_IN_PLACE=OFF" - ;; - - --disable-icon-update) - CMDLINE="${CMDLINE} -DICON_UPDATE=OFF" - ;; - - --disable-desktop-update) - CMDLINE="${CMDLINE} -DDESKTOP_UPDATE=OFF" - ;; - - --disable-desktop-validate) - CMDLINE="${CMDLINE} -DDESKTOP_VALIDATE=OFF" - ;; - - --disable-documentation) - CMDLINE="${CMDLINE} -DTRANSLATE_HELP=OFF" - ;; - - --disable-contractor) - CMDLINE="${CMDLINE} -DDISABLE_CONTRACT=ON" - ;; - - --disable-poodle-ssl3) - CMDLINE="${CMDLINE} -DDISABLE_POODLE=ON" - ;; - - VALAC) [ ! $value ] && abort $1 - VALAC=$value - ;; - - *) abort $option - ;; - esac - - shift -done - -# Verify use supplied vala executable -if [ -n "$VALAC" ] -then - VALA_EXECUTABLE=`type -p "$VALAC"` - - if [ -z "$VALA_EXECUTABLE" ] - then - printf "$VALAC is not an executable program.\n" - exit 1 - fi - - CMDLINE="${CMDLINE} -DVALA_EXECUTABLE='$VALA_EXECUTABLE'" -fi - -# Verify use supplied valadoc executable -if [ -n "$VALADOC" ] -then - VALADOC_EXECUTABLE=`type -p "$VALADOC"` - - if [ -z "$VALADOC_EXECUTABLE" ] - then - printf "$VALADOC is not an executable program.\n" - exit 1 - fi - - CMDLINE="${CMDLINE} -DVALADOC_EXECUTABLE='$VALADOC_EXECUTABLE'" -fi - - -# Verify cmake is installed -# TODO: Check for minimum version number -if ! cmake --version -then - printf "cmake must be installed to configure and build.\n" - exit 1 -fi - -using_srcdir_ne_builddir=false -# Simple check to verify this script is running in the root of the source tree -if [ -e Makefile.in ] -then - using_srcdir_ne_builddir=true - # Remove existing Makefile so it's not left around if configure fails - rm -f Makefile - - # Remove the build folder to force Cmake to update its cache. - rm -fr build - - if ! mkdir -p build - then - printf "Unable to create build directory.\n" - exit 1 - fi - - cd build -fi - -if ! (cmake $CMDLINE ${srcdir}) -then - printf "Unable to prepare build directory.\n" - exit 1 -fi - -if ${using_srcdir_ne_builddir} -then - cd .. - - if ! cp -f ${srcdir}/Makefile.in Makefile - then - printf "Unable to prepare Makefile.\n" - exit 1 - fi -fi - -printf "Configured. Type 'make' to build, 'make install' to install.\n" diff -Nru geary-0.12.4/configure.in geary-3.32.0/configure.in --- geary-0.12.4/configure.in 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/configure.in 1970-01-01 00:00:00.000000000 +0000 @@ -1,4 +0,0 @@ -# This file is merely to give Valencia (http://trac.yorba.org/wiki/Valencia) an indicator of the -# Geary project root. This file can be removed when this bug is fixed: -# -# http://trac.yorba.org/ticket/3410 diff -Nru geary-0.12.4/CONTRIBUTING.md geary-3.32.0/CONTRIBUTING.md --- geary-0.12.4/CONTRIBUTING.md 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/CONTRIBUTING.md 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,10 @@ +Contribute to Geary +------------------- + +Want to help improve Geary? There are a number of ways you can contribute: + + * [Bug Reporting](https://wiki.gnome.org/Apps/Geary/ReportingABug)—report new bugs or request new features + * [User Experience Design](https://wiki.gnome.org/Apps/Geary/Design)—research and develop Geary’s user experience + * [Development](https://wiki.gnome.org/Apps/Geary/Development)—fix bugs and add new features + * [Translating](https://wiki.gnome.org/Apps/Geary/Translating)—translate Geary’s user interface and user manual into new languages + * [Join the discussion](https://wiki.gnome.org/Apps/Geary/Contact) on the mailing list or IRC channel diff -Nru geary-0.12.4/debian/changelog geary-3.32.0/debian/changelog --- geary-0.12.4/debian/changelog 2018-12-24 17:07:05.000000000 +0000 +++ geary-3.32.0/debian/changelog 2019-09-19 14:22:54.000000000 +0000 @@ -1,3 +1,47 @@ +geary (3.32.0-1ubuntu1) eoan; urgency=medium + + * debian/patches/git_new_webkit.patch: + - backport a patch to fix issues with the new webkitgtk version + * debian/rules: + - skip tests on s390x, webkitgtk is buggy there + (lp: #1843524) + + -- Sebastien Bacher Thu, 19 Sep 2019 15:28:17 +0200 + +geary (3.32.0-1) experimental; urgency=medium + + * New upstream release + * Build-Depend on libfolks-dev + * Build-Depend on appstream-util for build tests + + -- Jeremy Bicha Sun, 17 Mar 2019 10:03:17 -0400 + +geary (0.13.1-1) experimental; urgency=medium + + * New upstream release + * Drop all patches: applied in new release + + -- Jeremy Bicha Thu, 21 Feb 2019 00:07:52 -0500 + +geary (0.13.0-2) experimental; urgency=medium + + * Cherry-pick 2 commits that make libunwind optional + * Only use libunwind on architectures where it is built + + -- Jeremy Bicha Wed, 20 Feb 2019 15:09:33 -0500 + +geary (0.13.0-1) experimental; urgency=medium + + * New upstream development release + * Build with meson + * Bump minimum libglib2.0-dev to 2.54 and libgtk-3-dev to 3.22.26 + * Add Build-Depends on libgoa-1.0-dev, libjson-glib-dev, libunwind-dev + and iso-codes + * Use xvfb for dh_auto_test + * Drop all patches: applied in new release + + -- Jeremy Bicha Sun, 17 Feb 2019 14:42:25 -0500 + geary (0.12.4-4) unstable; urgency=medium * Cherry-pick Actually-use-error-variable.patch: diff -Nru geary-0.12.4/debian/control geary-3.32.0/debian/control --- geary-0.12.4/debian/control 2018-12-24 17:07:05.000000000 +0000 +++ geary-3.32.0/debian/control 2019-03-17 14:03:17.000000000 +0000 @@ -7,28 +7,36 @@ Priority: optional Maintainer: Debian GNOME Maintainers Uploaders: Jeremy Bicha -Build-Depends: cmake (>= 2.8.0), - debhelper (>= 11), - desktop-file-utils, +Build-Depends: debhelper (>= 11), + appstream-util , + desktop-file-utils , gnome-doc-utils, gnome-pkg-tools, - intltool, + iso-codes, + itstool, libcanberra-dev (>= 0.28), libenchant-dev (>= 1.6), + libfolks-dev (>= 0.11), libgcr-3-dev (>= 3.10.1), libgee-0.8-dev (>= 0.8.5), libgirepository1.0-dev (>= 1.32.0), - libglib2.0-dev (>= 2.42.0), + libglib2.0-dev (>= 2.54), libgmime-2.6-dev (>= 2.6.14), - libgtk-3-dev (>= 3.14.0), + libgoa-1.0-dev, + libgtk-3-dev (>= 3.22.26), + libjson-glib-dev (>= 1.0), firmware-linux-free | libmessaging-menu-dev, firmware-linux-free | libunity-dev, libnotify-dev (>= 0.7.5), libsecret-1-dev (>= 0.11), libsqlite3-dev (>= 3.7.4), + libunwind-dev (>= 1.1) [amd64 arm64 armel armhf hppa i386 ia64 mips mips64 mips64el mipsel powerpc powerpcspe ppc64 ppc64el sh4], libwebkit2gtk-4.0-dev (>= 2.22), libxml2-dev (>= 2.7.8), - valac (>= 0.22.1) + meson (>= 0.43), + valac (>= 0.22.1), + xauth , + xvfb , Standards-Version: 4.3.0 X-Ubuntu-Use-Langpack: yes Homepage: https://wiki.gnome.org/Apps/Geary diff -Nru geary-0.12.4/debian/control.in geary-3.32.0/debian/control.in --- geary-0.12.4/debian/control.in 2018-12-24 17:07:05.000000000 +0000 +++ geary-3.32.0/debian/control.in 2019-03-17 14:03:17.000000000 +0000 @@ -3,28 +3,36 @@ Priority: optional Maintainer: Debian GNOME Maintainers Uploaders: @GNOME_TEAM@ -Build-Depends: cmake (>= 2.8.0), - debhelper (>= 11), - desktop-file-utils, +Build-Depends: debhelper (>= 11), + appstream-util , + desktop-file-utils , gnome-doc-utils, gnome-pkg-tools, - intltool, + iso-codes, + itstool, libcanberra-dev (>= 0.28), libenchant-dev (>= 1.6), + libfolks-dev (>= 0.11), libgcr-3-dev (>= 3.10.1), libgee-0.8-dev (>= 0.8.5), libgirepository1.0-dev (>= 1.32.0), - libglib2.0-dev (>= 2.42.0), + libglib2.0-dev (>= 2.54), libgmime-2.6-dev (>= 2.6.14), - libgtk-3-dev (>= 3.14.0), + libgoa-1.0-dev, + libgtk-3-dev (>= 3.22.26), + libjson-glib-dev (>= 1.0), firmware-linux-free | libmessaging-menu-dev, firmware-linux-free | libunity-dev, libnotify-dev (>= 0.7.5), libsecret-1-dev (>= 0.11), libsqlite3-dev (>= 3.7.4), + libunwind-dev (>= 1.1) [amd64 arm64 armel armhf hppa i386 ia64 mips mips64 mips64el mipsel powerpc powerpcspe ppc64 ppc64el sh4], libwebkit2gtk-4.0-dev (>= 2.22), libxml2-dev (>= 2.7.8), - valac (>= 0.22.1) + meson (>= 0.43), + valac (>= 0.22.1), + xauth , + xvfb , Standards-Version: 4.3.0 X-Ubuntu-Use-Langpack: yes Homepage: https://wiki.gnome.org/Apps/Geary diff -Nru geary-0.12.4/debian/copyright geary-3.32.0/debian/copyright --- geary-0.12.4/debian/copyright 2018-12-24 17:07:05.000000000 +0000 +++ geary-3.32.0/debian/copyright 2019-03-17 14:03:17.000000000 +0000 @@ -1,6 +1,8 @@ Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: geary Source: https://download.gnome.org/sources/geary/ +Files-Excluded: debian +Comment: Debian maintains its own packaging. Files: * Copyright: 2016 Software Freedom Conservancy Inc. @@ -25,17 +27,6 @@ 2002 Richard Boulton License: BSD-2-clause -Files: cmake/FindVala.cmake - cmake/ValaPrecompile.cmake - cmake/ValaVersion.cmake - cmake/README.rst -Copyright: 2009-2010 Jakob Westhoff -License: BSD-2-clause - -Files: cmake/GCR_CMake/* -Copyright: 2015 Mischa Krüger -License: GPL-3.0+ - Files: icons/* Copyright: SuperAtic LABS from The Noun Project Anisha Varghese from The Noun Project @@ -72,23 +63,6 @@ On Debian systems, the complete text of the GNU Lesser General Public License can be found in "/usr/share/common-licenses/LGPL-2.1". -License: GPL-3.0+ - 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 3 of the License, or - (at your option) any later version. - . - This package 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, see . - . - On Debian systems, the complete text of the GNU General - Public License version 3 can be found in "/usr/share/common-licenses/GPL-3". - License: BSD-2-clause Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff -Nru geary-0.12.4/debian/gbp.conf geary-3.32.0/debian/gbp.conf --- geary-0.12.4/debian/gbp.conf 2018-12-24 17:07:05.000000000 +0000 +++ geary-3.32.0/debian/gbp.conf 2019-03-17 14:03:17.000000000 +0000 @@ -2,13 +2,16 @@ pristine-tar = True debian-branch = debian/master upstream-branch = upstream/latest -upstream-vcs-tag = geary-%(version)s [buildpackage] sign-tags = True +[dch] +multimaint-merge = True + [import-orig] postimport = dch -v%(version)s New upstream release; git add debian/changelog; debcommit +upstream-vcs-tag = %(version)s [pq] patch-numbers = False diff -Nru geary-0.12.4/debian/patches/Actually-use-error-variable.patch geary-3.32.0/debian/patches/Actually-use-error-variable.patch --- geary-0.12.4/debian/patches/Actually-use-error-variable.patch 2018-12-24 17:07:05.000000000 +0000 +++ geary-3.32.0/debian/patches/Actually-use-error-variable.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,21 +0,0 @@ -From: Rico Tzschichholz -Date: Tue, 27 Nov 2018 14:53:56 +0100 -Subject: Actually use error variable to check for IOError.CANCELLED - ---- - src/client/application/geary-controller.vala | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/src/client/application/geary-controller.vala b/src/client/application/geary-controller.vala -index d73afa3..f9a4565 100644 ---- a/src/client/application/geary-controller.vala -+++ b/src/client/application/geary-controller.vala -@@ -2522,7 +2522,7 @@ public class GearyController : Geary.BaseObject { - yield do_empty_folder_async(emptyable, cancellable); - } catch (Error err) { - // don't report to user if cancelled -- if (cancellable is IOError.CANCELLED) -+ if (err is IOError.CANCELLED) - return; - - ErrorDialog dialog = new ErrorDialog(main_window, diff -Nru geary-0.12.4/debian/patches/Adjust-to-upstream-javascriptcore-4.0-bindings.patch geary-3.32.0/debian/patches/Adjust-to-upstream-javascriptcore-4.0-bindings.patch --- geary-0.12.4/debian/patches/Adjust-to-upstream-javascriptcore-4.0-bindings.patch 2018-12-24 17:07:05.000000000 +0000 +++ geary-3.32.0/debian/patches/Adjust-to-upstream-javascriptcore-4.0-bindings.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,184 +0,0 @@ -From: Rico Tzschichholz -Date: Mon, 23 Apr 2018 19:56:18 +0200 -Subject: Adjust to upstream javascriptcore-4.0 bindings - ---- - .../conversation-viewer/conversation-web-view.vala | 2 +- - src/client/util/util-webkit.vala | 10 ++++---- - src/client/web-process/web-process-extension.vala | 17 +++++------- - src/engine/util/util-js.vala | 30 +++++++++++++++------- - 4 files changed, 33 insertions(+), 26 deletions(-) - -diff --git a/src/client/conversation-viewer/conversation-web-view.vala b/src/client/conversation-viewer/conversation-web-view.vala -index 3d2ac5f..ef69f44 100644 ---- a/src/client/conversation-viewer/conversation-web-view.vala -+++ b/src/client/conversation-viewer/conversation-web-view.vala -@@ -183,7 +183,7 @@ public class ConversationWebView : ClientWebView { - - private void on_deceptive_link_clicked(WebKit.JavascriptResult result) { - try { -- JS.GlobalContext context = result.get_global_context(); -+ unowned JS.GlobalContext context = result.get_global_context(); - JS.Object details = WebKitUtil.to_object(result); - - uint reason = (uint) Geary.JS.to_number( -diff --git a/src/client/util/util-webkit.vala b/src/client/util/util-webkit.vala -index 319e28a..cba9eaf 100644 ---- a/src/client/util/util-webkit.vala -+++ b/src/client/util/util-webkit.vala -@@ -18,8 +18,8 @@ namespace WebKitUtil { - */ - public bool to_bool(WebKit.JavascriptResult result) - throws Geary.JS.Error { -- JS.GlobalContext context = result.get_global_context(); -- JS.Value value = result.get_value(); -+ unowned JS.GlobalContext context = result.get_global_context(); -+ unowned JS.Value value = result.get_value(); - if (!value.is_boolean(context)) { - throw new Geary.JS.Error.TYPE("Result is not a JS Boolean object"); - } -@@ -59,12 +59,12 @@ namespace WebKitUtil { - */ - public string as_string(WebKit.JavascriptResult result) - throws Geary.JS.Error { -- JS.GlobalContext context = result.get_global_context(); -- JS.Value js_str_value = result.get_value(); -+ unowned JS.GlobalContext context = result.get_global_context(); -+ unowned JS.Value js_str_value = result.get_value(); - JS.Value? err = null; - JS.String js_str = js_str_value.to_string_copy(context, out err); - Geary.JS.check_exception(context, err); -- return Geary.JS.to_string_released(js_str); -+ return Geary.JS.to_string_released((owned) js_str); - } - - /** -diff --git a/src/client/web-process/web-process-extension.vala b/src/client/web-process/web-process-extension.vala -index ee89139..1f478a6 100644 ---- a/src/client/web-process/web-process-extension.vala -+++ b/src/client/web-process/web-process-extension.vala -@@ -87,10 +87,9 @@ public class GearyWebExtension : Object { - bool should_load = false; - WebKit.Frame frame = page.get_main_frame(); - // Explicit cast fixes build on s390x/ppc64. Bug 783882 -- JS.GlobalContext context = (JS.GlobalContext) -- frame.get_javascript_global_context(); -+ unowned JS.GlobalContext context = frame.get_javascript_global_context(); - try { -- JS.Value ret = execute_script( -+ unowned JS.Value ret = execute_script( - context, "geary.allowRemoteImages", int.parse("__LINE__") - ); - should_load = ret.to_boolean(context); -@@ -106,8 +105,7 @@ public class GearyWebExtension : Object { - private void remote_image_load_blocked(WebKit.WebPage page) { - WebKit.Frame frame = page.get_main_frame(); - // Explicit cast fixes build on s390x/ppc64. Bug 783882 -- JS.GlobalContext context = (JS.GlobalContext) -- frame.get_javascript_global_context(); -+ unowned JS.GlobalContext context = frame.get_javascript_global_context(); - try { - execute_script( - context, "geary.remoteImageLoadBlocked();", int.parse("__LINE__") -@@ -123,8 +121,7 @@ public class GearyWebExtension : Object { - private void selection_changed(WebKit.WebPage page) { - WebKit.Frame frame = page.get_main_frame(); - // Explicit cast fixes build on s390x/ppc64. Bug 783882 -- JS.GlobalContext context = (JS.GlobalContext) -- frame.get_javascript_global_context(); -+ unowned JS.GlobalContext context = frame.get_javascript_global_context(); - try { - execute_script( - context, "geary.selectionChanged();", int.parse("__LINE__") -@@ -136,20 +133,18 @@ public class GearyWebExtension : Object { - - // Return type is nullable as a workaround for Bug 778046, it will - // never actually be null. -- private JS.Value? execute_script(JS.Context context, string script, int line) -+ private unowned JS.Value? execute_script(JS.Context context, string script, int line) - throws Geary.JS.Error { - JS.String js_script = new JS.String.create_with_utf8_cstring(script); - JS.String js_source = new JS.String.create_with_utf8_cstring("__FILE__"); - JS.Value? err = null; - try { -- JS.Value ret = context.evaluate_script( -+ unowned JS.Value ret = context.evaluate_script( - js_script, null, js_source, line, out err - ); - Geary.JS.check_exception(context, err); - return ret; - } finally { -- js_script.release(); -- js_source.release(); - } - } - -diff --git a/src/engine/util/util-js.vala b/src/engine/util/util-js.vala -index 4d22429..ea955e9 100644 ---- a/src/engine/util/util-js.vala -+++ b/src/engine/util/util-js.vala -@@ -10,6 +10,16 @@ - */ - namespace Geary.JS { - -+#if !VALA_0_42 -+ // Workaround broken version of this in the vala bindings. See Bug -+ // 788113. -+ [CCode (cname = "JSStringGetUTF8CString")] -+ private extern size_t js_string_get_utf8_cstring( -+ global::JS.String js, -+ [CCode (array_length_type = "gsize")] char[] buffer -+ ); -+#endif -+ - /** - * Errors produced by functions in {@link Geary.JS}. - */ -@@ -72,7 +82,7 @@ namespace Geary.JS { - global::JS.String js_str = value.to_string_copy(context, out err); - Geary.JS.check_exception(context, err); - -- return Geary.JS.to_string_released(js_str); -+ return Geary.JS.to_string_released((owned) js_str); - } - - /** -@@ -101,12 +111,15 @@ namespace Geary.JS { - /** - * Returns a JSC {@link JS.String} as a Vala {@link string}. - */ -- public inline string to_string_released(global::JS.String js) { -- int len = js.get_maximum_utf8_cstring_size(); -- string str = string.nfill(len, 0); -- js.get_utf8_cstring(str, len); -- js.release(); -- return str; -+ public inline string to_string_released(owned global::JS.String js) { -+ size_t len = js.get_maximum_utf8_cstring_size(); -+ uint8[] str = new uint8[len]; -+#if VALA_0_42 -+ js.get_utf8_cstring(str); -+#else -+ js_string_get_utf8_cstring(js, (char[]) str); -+#endif -+ return (string) str; - } - - /** -@@ -128,7 +141,6 @@ namespace Geary.JS { - try { - Geary.JS.check_exception(context, err); - } finally { -- js_name.release(); - } - return prop; - } -@@ -157,7 +169,7 @@ namespace Geary.JS { - - throw new Error.EXCEPTION( - "JS exception thrown [%s]: %s" -- .printf(err_type.to_string(), to_string_released(err_str)) -+ .printf(err_type.to_string(), to_string_released((owned) err_str)) - ); - } - } diff -Nru geary-0.12.4/debian/patches/bindings-Drop-custom-javascriptcore-4.0-and-webkit2gtk-4..patch geary-3.32.0/debian/patches/bindings-Drop-custom-javascriptcore-4.0-and-webkit2gtk-4..patch --- geary-0.12.4/debian/patches/bindings-Drop-custom-javascriptcore-4.0-and-webkit2gtk-4..patch 2018-12-24 17:07:05.000000000 +0000 +++ geary-3.32.0/debian/patches/bindings-Drop-custom-javascriptcore-4.0-and-webkit2gtk-4..patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,327 +0,0 @@ -From: Rico Tzschichholz -Date: Thu, 22 Jun 2017 15:01:19 +0200 -Subject: bindings: Drop custom javascriptcore-4.0 and webkit2gtk-4.0 vapi - ---- - bindings/metadata/Soup-2.4.metadata | 3 - - bindings/metadata/WebKit2-4.0.metadata | 15 -- - .../metadata/WebKit2WebExtension-4.0-custom.vala | 5 - - bindings/metadata/WebKit2WebExtension-4.0.metadata | 9 -- - bindings/vapi/javascriptcore-4.0.vapi | 155 --------------------- - src/CMakeLists.txt | 39 +----- - test/CMakeLists.txt | 2 +- - 7 files changed, 4 insertions(+), 224 deletions(-) - delete mode 100644 bindings/metadata/Soup-2.4.metadata - delete mode 100644 bindings/metadata/WebKit2-4.0.metadata - delete mode 100644 bindings/metadata/WebKit2WebExtension-4.0-custom.vala - delete mode 100644 bindings/metadata/WebKit2WebExtension-4.0.metadata - delete mode 100644 bindings/vapi/javascriptcore-4.0.vapi - -diff --git a/bindings/metadata/Soup-2.4.metadata b/bindings/metadata/Soup-2.4.metadata -deleted file mode 100644 -index f3e72e8..0000000 ---- a/bindings/metadata/Soup-2.4.metadata -+++ /dev/null -@@ -1,3 +0,0 @@ --AuthDomain.accepts skip --AuthDomain.challenge skip -- -diff --git a/bindings/metadata/WebKit2-4.0.metadata b/bindings/metadata/WebKit2-4.0.metadata -deleted file mode 100644 -index 3e3044f..0000000 ---- a/bindings/metadata/WebKit2-4.0.metadata -+++ /dev/null -@@ -1,15 +0,0 @@ -- --JavascriptResult -- .get_global_context nullable=false unowned=true -- .get_value nullable=false unowned=true -- --//Forward upstream --Download -- .failed#signal.error type="WebKit.DownloadError" --PrintOperation -- .failed#signal.error type="WebKit.PrintError" --WebResource -- .failed#signal.error type="GLib.Error" --WebView -- .load_failed#signal.error type="GLib.Error" -- .show_option_menu#signal skip -diff --git a/bindings/metadata/WebKit2WebExtension-4.0-custom.vala b/bindings/metadata/WebKit2WebExtension-4.0-custom.vala -deleted file mode 100644 -index a994a77..0000000 ---- a/bindings/metadata/WebKit2WebExtension-4.0-custom.vala -+++ /dev/null -@@ -1,5 +0,0 @@ --namespace WebKit { -- namespace DOM { -- public delegate void EventTargetFunc (WebKit.DOM.EventTarget target, WebKit.DOM.Event event); -- } --} -diff --git a/bindings/metadata/WebKit2WebExtension-4.0.metadata b/bindings/metadata/WebKit2WebExtension-4.0.metadata -deleted file mode 100644 -index c496dba..0000000 ---- a/bindings/metadata/WebKit2WebExtension-4.0.metadata -+++ /dev/null -@@ -1,9 +0,0 @@ --DOM* parent="WebKit.DOM" name="DOM(.+)" -- --DOMEventTarget.add_event_listener skip --_ContextMenu skip --_ContextMenuItem skip -- --Frame.get_javascript_* nullable=false unowned=true -- --DOMEventTarget.add_event_listener_with_closure.handler type="owned WebKit.DOM.EventTargetFunc" -diff --git a/bindings/vapi/javascriptcore-4.0.vapi b/bindings/vapi/javascriptcore-4.0.vapi -deleted file mode 100644 -index d152ce2..0000000 ---- a/bindings/vapi/javascriptcore-4.0.vapi -+++ /dev/null -@@ -1,155 +0,0 @@ --/* -- * Copyright 2017 Michael Gratton -- * -- * This software is licensed under the GNU Lesser General Public License -- * (version 2.1 or later). See the COPYING file in this distribution. -- */ -- --[CCode (cprefix = "JS", -- gir_namespace = "JavaScriptCore", -- gir_version = "4.0", -- lower_case_cprefix = "JS_", -- cheader_filename = "JavaScriptCore/JavaScript.h")] --namespace JS { -- -- [CCode (cname = "JSContextRef")] -- [SimpleType] -- public struct Context { -- -- [CCode (cname = "JSEvaluateScript")] -- public Value evaluate_script(String script, -- Object? thisObject, -- String? sourceURL, -- int startingLineNumber, -- out Value? exception); -- -- [CCode (cname = "JSCheckScriptSyntax")] -- public Value check_script_syntax(String script, -- String? sourceURL, -- int startingLineNumber, -- out Value? exception); -- -- } -- -- [CCode (cname = "JSGlobalContextRef")] -- [SimpleType] -- public struct GlobalContext : Context { -- -- [CCode (cname = "JSGlobalContextRetain")] -- public bool retain(); -- -- [CCode (cname = "JSGlobalContextRelease")] -- public bool release(); -- -- } -- -- [CCode (cname = "JSType", has_type_id = false)] -- public enum Type { -- -- [CCode (cname = "kJSTypeUndefined")] -- UNDEFINED, -- -- [CCode (cname = "kJSTypeNull")] -- NULL, -- -- [CCode (cname = "kJSTypeBoolean")] -- BOOLEAN, -- -- [CCode (cname = "kJSTypeNumber")] -- NUMBER, -- -- [CCode (cname = "kJSTypeString")] -- STRING, -- -- [CCode (cname = "kJSTypeObject")] -- OBJECT -- } -- -- [CCode (cname = "JSObjectRef")] -- [SimpleType] -- public struct Object { -- -- [CCode (cname = "JSObjectMakeFunction")] -- public Object.make_function(String? name, -- [CCode (array_length_pos=1.5)] -- String[]? parameterNames, -- String body, -- String? sourceURL, -- int startingLineNumber, -- out Value? exception); -- -- [CCode (cname = "JSObjectCallAsFunction", instance_pos = 1.1)] -- public Value call_as_function(Context ctx, -- Object? thisObject, -- [CCode (array_length_pos=2.5)] -- Value[]? arguments, -- out Value? exception); -- -- [CCode (cname = "JSObjectHasProperty", instance_pos = 1.1)] -- public bool has_property(Context ctx, String property_name); -- -- [CCode (cname = "JSObjectGetProperty", instance_pos = 1.1)] -- public Value get_property(Context ctx, -- String property_name, -- out Value? exception); -- -- } -- -- [CCode (cname = "JSValueRef")] -- [SimpleType] -- public struct Value { -- -- [CCode (cname = "JSValueGetType", instance_pos = 1.1)] -- public Type get_type(Context context); -- -- [CCode (cname = "JSValueIsBoolean", instance_pos = 1.1)] -- public bool is_boolean(Context ctx); -- -- [CCode (cname = "JSValueIsNumber", instance_pos = 1.1)] -- public bool is_number(Context ctx); -- -- [CCode (cname = "JSValueIsObject", instance_pos = 1.1)] -- public bool is_object(Context ctx); -- -- [CCode (cname = "JSValueIsString", instance_pos = 1.1)] -- public bool is_string(Context ctx); -- -- [CCode (cname = "JSValueToBoolean", instance_pos = 1.1)] -- public bool to_boolean(Context ctx); -- -- [CCode (cname = "JSValueToNumber", instance_pos = 1.1)] -- public double to_number(Context ctx, out Value exception); -- -- [CCode (cname = "JSValueToObject", instance_pos = 1.1)] -- public Object to_object(Context ctx, out Value exception); -- -- [CCode (cname = "JSValueToStringCopy", instance_pos = 1.1)] -- public String to_string_copy(Context ctx, out Value exception); -- -- } -- -- [CCode (cname = "JSStringRef")] -- [SimpleType] -- public struct String { -- -- [CCode (cname = "JSStringCreateWithUTF8CString")] -- public String.create_with_utf8_cstring(string str); -- -- [CCode (cname = "JSStringGetLength")] -- public int String.get_length(); -- -- [CCode (cname = "JSStringGetMaximumUTF8CStringSize")] -- public int String.get_maximum_utf8_cstring_size(); -- -- [CCode (cname = "JSStringGetUTF8CString")] -- public void String.get_utf8_cstring(string* buffer, int bufferSize); -- -- [CCode (cname = "JSStringRetain")] -- public void String.retain(); -- -- [CCode (cname = "JSStringRelease")] -- public void String.release(); -- -- } -- --} -diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt -index bce938d..6793129 100644 ---- a/src/CMakeLists.txt -+++ b/src/CMakeLists.txt -@@ -525,7 +525,7 @@ set(ENGINE_PACKAGES - gio-2.0 - glib-2.0 - gmime-2.6 -- javascriptcore-4.0 -+ javascriptcoregtk-4.0 - libxml-2.0 - posix - sqlite3 -@@ -550,7 +550,7 @@ set(WEB_PROCESS_PACKAGES - geary-engine - gee-0.8 - gtk+-3.0 -- javascriptcore-4.0 -+ javascriptcoregtk-4.0 - libsoup-2.4 - webkit2gtk-web-extension-4.0 - ) -@@ -617,7 +617,6 @@ add_definitions(${CFLAGS}) - set(VALAC_OPTIONS - --vapidir=${CMAKE_BINARY_DIR}/src - --vapidir=${CMAKE_SOURCE_DIR}/bindings/vapi -- --metadatadir=${CMAKE_SOURCE_DIR}/bindings/metadata - --target-glib=${TARGET_GLIB} - --thread - --debug -@@ -649,38 +648,6 @@ set_property( - ) - target_link_libraries(geary-engine m ${DEPS_LIBRARIES} sqlite3-unicodesn) - --# WebKit2GTK VAPI generation --################################################# --add_custom_target(webkit2gtk-vapi -- DEPENDS -- "${CMAKE_BINARY_DIR}/src/webkit2gtk-4.0.vapi" -- "${CMAKE_BINARY_DIR}/src/webkit2gtk-web-extension-4.0.vapi" -- "${CMAKE_SOURCE_DIR}/bindings/vapi/javascriptcore-4.0.vapi" --) --add_custom_command( -- OUTPUT -- ${CMAKE_BINARY_DIR}/src/webkit2gtk-4.0.vapi -- DEPENDS -- "${CMAKE_SOURCE_DIR}/bindings/metadata/WebKit2-4.0.metadata" -- "${CMAKE_SOURCE_DIR}/bindings/vapi/javascriptcore-4.0.vapi" -- WORKING_DIRECTORY -- "${CMAKE_SOURCE_DIR}/bindings/metadata" -- COMMAND -- vapigen --library=webkit2gtk-4.0 --pkg gtk+-3.0 --pkg libsoup-2.4 --pkg javascriptcore-4.0 --vapidir=${CMAKE_SOURCE_DIR}/bindings/vapi --metadatadir=${CMAKE_SOURCE_DIR}/bindings/metadata --directory=${CMAKE_BINARY_DIR}/src `${PKG_CONFIG_EXECUTABLE} --variable=girdir gobject-introspection-1.0`/WebKit2-4.0.gir --) --add_custom_command( -- OUTPUT -- "${CMAKE_BINARY_DIR}/src/webkit2gtk-web-extension-4.0.vapi" -- DEPENDS -- "${CMAKE_SOURCE_DIR}/bindings/metadata/WebKit2WebExtension-4.0.metadata" -- "${CMAKE_SOURCE_DIR}/bindings/metadata/WebKit2WebExtension-4.0-custom.vala" -- "${CMAKE_SOURCE_DIR}/bindings/vapi/javascriptcore-4.0.vapi" -- WORKING_DIRECTORY -- "${CMAKE_SOURCE_DIR}/bindings/metadata" -- COMMAND -- vapigen --library=webkit2gtk-web-extension-4.0 --pkg gtk+-3.0 --pkg libsoup-2.4 --pkg javascriptcore-4.0 --vapidir=${CMAKE_SOURCE_DIR}/bindings/vapi --metadatadir=${CMAKE_SOURCE_DIR}/bindings/metadata --directory=${CMAKE_BINARY_DIR}/src `${PKG_CONFIG_EXECUTABLE} --variable=girdir gobject-introspection-1.0`/WebKit2WebExtension-4.0.gir WebKit2WebExtension-4.0-custom.vala --) -- - # Client library (static lib used for building client and unit tests) - ################################################# - -@@ -697,7 +664,7 @@ OPTIONS - ) - - add_library(geary-client STATIC ${CLIENT_VALA_C}) --add_dependencies(geary-client resource_copy webkit2gtk-vapi) -+add_dependencies(geary-client resource_copy) - target_link_libraries(geary-client m ${DEPS_LIBRARIES} geary-engine) - - # Main client application binary -diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt -index f2d3866..6524cfb 100644 ---- a/test/CMakeLists.txt -+++ b/test/CMakeLists.txt -@@ -58,7 +58,7 @@ set(TEST_PACKAGES - glib-2.0 - gmime-2.6 - gtk+-3.0 -- javascriptcore-4.0 -+ javascriptcoregtk-4.0 - libsoup-2.4 - webkit2gtk-4.0 - ) diff -Nru geary-0.12.4/debian/patches/Clean-up-JS-util-API-courtesy-the-new-bindings.patch geary-3.32.0/debian/patches/Clean-up-JS-util-API-courtesy-the-new-bindings.patch --- geary-0.12.4/debian/patches/Clean-up-JS-util-API-courtesy-the-new-bindings.patch 2018-12-24 17:07:05.000000000 +0000 +++ geary-3.32.0/debian/patches/Clean-up-JS-util-API-courtesy-the-new-bindings.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,66 +0,0 @@ -From: Michael James Gratton -Date: Sun, 20 May 2018 19:07:56 +1000 -Subject: Clean up JS util API courtesy the new bindings. - ---- - src/client/util/util-webkit.vala | 2 +- - src/engine/util/util-js.vala | 12 +++++------- - 2 files changed, 6 insertions(+), 8 deletions(-) - -diff --git a/src/client/util/util-webkit.vala b/src/client/util/util-webkit.vala -index cba9eaf..45a27c4 100644 ---- a/src/client/util/util-webkit.vala -+++ b/src/client/util/util-webkit.vala -@@ -64,7 +64,7 @@ namespace WebKitUtil { - JS.Value? err = null; - JS.String js_str = js_str_value.to_string_copy(context, out err); - Geary.JS.check_exception(context, err); -- return Geary.JS.to_string_released((owned) js_str); -+ return Geary.JS.to_native_string(js_str); - } - - /** -diff --git a/src/engine/util/util-js.vala b/src/engine/util/util-js.vala -index ea955e9..a98d798 100644 ---- a/src/engine/util/util-js.vala -+++ b/src/engine/util/util-js.vala -@@ -82,7 +82,7 @@ namespace Geary.JS { - global::JS.String js_str = value.to_string_copy(context, out err); - Geary.JS.check_exception(context, err); - -- return Geary.JS.to_string_released((owned) js_str); -+ return to_native_string(js_str); - } - - /** -@@ -111,7 +111,7 @@ namespace Geary.JS { - /** - * Returns a JSC {@link JS.String} as a Vala {@link string}. - */ -- public inline string to_string_released(owned global::JS.String js) { -+ public inline string to_native_string(global::JS.String js) { - size_t len = js.get_maximum_utf8_cstring_size(); - uint8[] str = new uint8[len]; - #if VALA_0_42 -@@ -138,10 +138,8 @@ namespace Geary.JS { - global::JS.String js_name = new global::JS.String.create_with_utf8_cstring(name); - global::JS.Value? err = null; - global::JS.Value prop = object.get_property(context, js_name, out err); -- try { -- Geary.JS.check_exception(context, err); -- } finally { -- } -+ Geary.JS.check_exception(context, err); -+ - return prop; - } - -@@ -169,7 +167,7 @@ namespace Geary.JS { - - throw new Error.EXCEPTION( - "JS exception thrown [%s]: %s" -- .printf(err_type.to_string(), to_string_released((owned) err_str)) -+ .printf(err_type.to_string(), to_native_string(err_str)) - ); - } - } diff -Nru geary-0.12.4/debian/patches/git_new_webkit.patch geary-3.32.0/debian/patches/git_new_webkit.patch --- geary-0.12.4/debian/patches/git_new_webkit.patch 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/debian/patches/git_new_webkit.patch 2019-09-19 14:13:35.000000000 +0000 @@ -0,0 +1,63 @@ +From a268b05d35eeef934e0dad66fe308a6230efc469 Mon Sep 17 00:00:00 2001 +From: Michael Gratton +Date: Sat, 7 Sep 2019 10:31:39 +1000 +Subject: [PATCH 1/2] Disable WebKitGTK hardware accelleration using API, not + env var + +Stop setting the olde WEBKIT_DISABLE_COMPOSITING_MODE envrionment +variable, use the API instead. +--- + src/client/application/main.vala | 9 --------- + src/client/components/client-web-view.vala | 2 ++ + 2 files changed, 2 insertions(+), 9 deletions(-) + +Index: geary-3.32.0/src/client/application/main.vala +=================================================================== +--- geary-3.32.0.orig/src/client/application/main.vala ++++ geary-3.32.0/src/client/application/main.vala +@@ -20,13 +20,10 @@ int main(string[] args) { + Environment.set_variable("G_TLS_GNUTLS_PRIORITY", "NORMAL:%COMPAT:!VERS-SSL3.0", false); + #endif + +- // Disable WebKit2 accelerated compositing here while we can't +- // depend on there being an API to do it. AC isn't appropriate +- // since Geary is likely to be doing anything that requires +- // acceleration, and it is costs a lot in terms of performance +- // and memory: +- // https://lists.webkit.org/pipermail/webkit-gtk/2016-November/002863.html +- Environment.set_variable("WEBKIT_DISABLE_COMPOSITING_MODE", "1", true); ++ // Temporary workaround for WebKitGTK deprecation of the ++ // shared-secondary process model. Pull this out in 3.36 when the ++ // proper fix lands. See GNOME/geary#558. ++ Environment.set_variable("WEBKIT_USE_SINGLE_WEB_PROCESS", "1", true); + + GearyApplication app = new GearyApplication(); + +@@ -38,4 +35,3 @@ int main(string[] args) { + + return ec; + } +- +Index: geary-3.32.0/src/client/components/client-web-view.vala +=================================================================== +--- geary-3.32.0.orig/src/client/components/client-web-view.vala ++++ geary-3.32.0/src/client/components/client-web-view.vala +@@ -77,9 +77,6 @@ public abstract class ClientWebView : We + bool enable_logging) { + WebsiteDataManager data_manager = new WebsiteDataManager(cache_dir.get_path()); + WebKit.WebContext context = new WebKit.WebContext.with_website_data_manager(data_manager); +- // Use a shared process so we don't spawn N WebProcess instances +- // when showing N messages in a conversation. +- context.set_process_model(WebKit.ProcessModel.SHARED_SECONDARY_PROCESS); + // Use the doc viewer model since each web view instance only + // ever shows a single HTML document. + context.set_cache_model(WebKit.CacheModel.DOCUMENT_VIEWER); +@@ -310,6 +307,8 @@ public abstract class ClientWebView : We + setts.enable_offline_web_application_cache = false; + setts.enable_page_cache = false; + setts.enable_plugins = false; ++ setts.hardware_acceleration_policy = ++ WebKit.HardwareAccelerationPolicy.NEVER; + setts.javascript_can_access_clipboard = true; + + WebKit.UserContentManager content_manager = diff -Nru geary-0.12.4/debian/patches/series geary-3.32.0/debian/patches/series --- geary-0.12.4/debian/patches/series 2018-12-24 17:07:05.000000000 +0000 +++ geary-3.32.0/debian/patches/series 2019-09-19 14:10:33.000000000 +0000 @@ -1,4 +1 @@ -bindings-Drop-custom-javascriptcore-4.0-and-webkit2gtk-4..patch -Adjust-to-upstream-javascriptcore-4.0-bindings.patch -Clean-up-JS-util-API-courtesy-the-new-bindings.patch -Actually-use-error-variable.patch +git_new_webkit.patch diff -Nru geary-0.12.4/debian/rules geary-3.32.0/debian/rules --- geary-0.12.4/debian/rules 2018-12-24 17:07:05.000000000 +0000 +++ geary-3.32.0/debian/rules 2019-09-19 13:27:55.000000000 +0000 @@ -3,8 +3,20 @@ export DEB_BUILD_MAINT_OPTIONS = hardening=+all export DEB_LDFLAGS_MAINT_APPEND = -Wl,-O1 -Wl,-z,defs -Wl,--as-needed +ifeq (,$(findstring $(DEB_HOST_ARCH), amd64 arm64 armel armhf hppa i386 ia64 mips mips64 mips64el mipsel powerpc powerpcspe ppc64 ppc64el sh4)) + BUILD_UNWIND += -Dlibunwind_optional=true +endif + %: - dh $@ --buildsystem=cmake --with gnome + dh $@ --with gnome --buildsystem=meson override_dh_auto_configure: - dh_auto_configure -- -DNO_FATAL_WARNINGS=true + dh_auto_configure -- $(BUILD_UNWIND) + +override_dh_auto_test: +ifeq (,$(filter s390x,$(DEB_HOST_ARCH))) + xvfb-run -a dh_auto_test +else + echo 'webkit is buggy on s390x (lp: #1843524)' +endif + diff -Nru geary-0.12.4/desktop/CMakeLists.txt geary-3.32.0/desktop/CMakeLists.txt --- geary-0.12.4/desktop/CMakeLists.txt 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/desktop/CMakeLists.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,68 +0,0 @@ -# -# Build and install org.gnome.Geary.desktop -# - -include (FindIntltool) -include (FindDesktopFileValidate) -if (INTLTOOL_MERGE_FOUND) - INTLTOOL_MERGE_APPDATA (org.gnome.Geary.appdata.xml po) - INTLTOOL_MERGE_DESKTOP (org.gnome.Geary.desktop po) - INTLTOOL_MERGE_AUTOSTART_DESKTOP (geary-autostart.desktop po) - - if (DESKTOP_VALIDATE) - if (DESKTOP_FILE_VALIDATE_FOUND) - VALIDATE_DESKTOP_FILE (org.gnome.Geary.desktop) - VALIDATE_DESKTOP_FILE (geary-autostart.desktop) - else (DESKTOP_FILE_VALIDATE_FOUND) - message (FATAL_ERROR "desktop-file-validate must be installed to validate generated .desktop file") - endif (DESKTOP_FILE_VALIDATE_FOUND) - endif (DESKTOP_VALIDATE) -else (INTLTOOL_MERGE_FOUND) - message (FATAL_ERROR "intltool must be installed to generate .desktop file") -endif (INTLTOOL_MERGE_FOUND) - -# Optional: run update-desktop-database at install time. -# (This has to happen after the org.gnome.Geary.desktop file is installed.) -if (DESKTOP_UPDATE) - install( - CODE - "execute_process (COMMAND update-desktop-database)" - CODE - "message (STATUS \"Updating desktop database\")" - ) - - add_custom_target( - uninstall-desktop-update - COMMAND - update-desktop-database - ) - - add_dependencies(post-uninstall uninstall-desktop-update) -else () - install( - CODE "message (STATUS \"Not updating desktop database\")" - ) -endif () - -if (DISABLE_CONTRACT) - message (STATUS "Install Contractor contract: OFF") -else (DISABLE_CONTRACT) - message (STATUS "Install Contractor contract: ON") - if (INTLTOOL_MERGE_FOUND) - INTLTOOL_MERGE_CONTRACT (geary-attach.contract po) - -# Can't validate Contractor file since it isn't a valid Desktop -# file according to desktop-file-validate from desktop-file-utils 0.22: -# - geary-attach.contract: error: first group is not "Desktop Entry" -# - geary-attach.contract: error: file contains group "Contractor Entry", -# but groups extending the format should start with "X-" -# - geary-attach.contract: error: filename does not have a .desktop extension -# -# if (DESKTOP_VALIDATE) -# if (DESKTOP_FILE_VALIDATE_FOUND) -# VALIDATE_DESKTOP_FILE (geary-attach.contract) -# endif (DESKTOP_FILE_VALIDATE_FOUND) -# endif (DESKTOP_VALIDATE) - endif (INTLTOOL_MERGE_FOUND) - install (PROGRAMS geary-attach DESTINATION bin) -endif (DISABLE_CONTRACT) diff -Nru geary-0.12.4/desktop/geary-attach.contract.desktop.in geary-3.32.0/desktop/geary-attach.contract.desktop.in --- geary-0.12.4/desktop/geary-attach.contract.desktop.in 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/desktop/geary-attach.contract.desktop.in 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,7 @@ +[Contractor Entry] +Name=Send by email +# Translators: Do NOT translate or transliterate this text (this is an icon file name)! +Icon=mail-send +Description=Send files using Geary +MimeType=!inode; +Exec=geary-attach %F diff -Nru geary-0.12.4/desktop/geary-attach.contract.in geary-3.32.0/desktop/geary-attach.contract.in --- geary-0.12.4/desktop/geary-attach.contract.in 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/desktop/geary-attach.contract.in 1970-01-01 00:00:00.000000000 +0000 @@ -1,6 +0,0 @@ -[Contractor Entry] -_Name=Send by email -Icon=mail-send -_Description=Send files using Geary -MimeType=!inode; -Exec=geary-attach %F diff -Nru geary-0.12.4/desktop/geary-autostart.desktop.in geary-3.32.0/desktop/geary-autostart.desktop.in --- geary-0.12.4/desktop/geary-autostart.desktop.in 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/desktop/geary-autostart.desktop.in 2019-03-17 13:39:29.000000000 +0000 @@ -1,9 +1,10 @@ [Desktop Entry] -_Name=Geary -_GenericName=Email -_X-GNOME-FullName=Geary Mail -_Comment=Send and receive email -_Keywords=Email;E-mail;Mail; +Name=Geary +GenericName=Email +Comment=Send and receive email +# Translators: These are desktop search terms. Do not translate semicolons, end line with a semicolon. +Keywords=Email;E-mail;Mail; +# Translators: Do NOT translate or transliterate this text (this is an icon file name)! Icon=org.gnome.Geary TryExec=geary Exec=geary --hidden diff -Nru geary-0.12.4/desktop/meson.build geary-3.32.0/desktop/meson.build --- geary-0.12.4/desktop/meson.build 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/desktop/meson.build 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,89 @@ +# +# Desktop files +# + +desktop_src = [ + 'org.gnome.Geary.desktop', + 'geary-autostart.desktop' +] + +foreach desktop_file: desktop_src + desktop_merged = i18n.merge_file( + input: desktop_file + '.in', + output: desktop_file, + type: 'desktop', + po_dir: po_dir, + install: true, + install_dir: join_paths(datadir, 'applications') + ) + if desktop_file_validate.found() + test( + desktop_file + '-validate', + desktop_file_validate, + args: [ desktop_merged.full_path() ] + ) + endif +endforeach + +# +# Appdata file +# + +appdata_file = 'org.gnome.Geary.appdata.xml' + +appdata_merged = i18n.merge_file( + input: appdata_file + '.in', + output: appdata_file, + type: 'xml', + po_dir: po_dir, + install: true, + install_dir: join_paths(datadir, 'metainfo') +) + +if appstream_util.found() + test( + appdata_file + '-validate', + appstream_util, + args: [ + 'validate-relax', '--nonet', appdata_merged.full_path() + ] + ) +endif + +# +# Contractor file (Elementary OS) +# + +if install_contractor_file + # Call msgfmt manually since gettext won't otherwise translate the + # Description field. See merge req !50. + msgfmt = find_program('msgfmt') + + custom_target('geary-attach-contract', + input: 'geary-attach.contract.desktop.in', + output: 'geary-attach.contract', + command: [msgfmt, '--desktop', '--keyword=Description', '--template', '@INPUT@', '-d', po_dir, '-o', '@OUTPUT@'], + install: true, + install_dir: join_paths(datadir, 'contractor') + ) + + install_data('geary-attach', + install_dir: bindir, + ) +endif + +# GSettings schemas. +# +# Compile since it makes sure the schema is valid and is used for both +# running the client locally and for tests. +# +# Note the use of depend_files here is a kludge to ensure that the +# schema is re-compiled if the source changes. This is not supported +# by Meson but it works, so request for official support has been +# added, see: https://github.com/mesonbuild/meson/issues/2770 +geary_compiled_schema = gnome.compile_schemas( + depend_files: files('org.gnome.Geary.gschema.xml'), +) +install_data('org.gnome.Geary.gschema.xml', + install_dir: join_paths(datadir, 'glib-2.0', 'schemas'), +) diff -Nru geary-0.12.4/desktop/org.gnome.Geary.appdata.xml.in geary-3.32.0/desktop/org.gnome.Geary.appdata.xml.in --- geary-0.12.4/desktop/org.gnome.Geary.appdata.xml.in 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/desktop/org.gnome.Geary.appdata.xml.in 2019-03-17 13:39:29.000000000 +0000 @@ -1,36 +1,36 @@ - - org.gnome.Geary.desktop + + org.gnome.Geary CC0-1.0 LGPL-2.1+ geary-list@gnome.org - <_name>Geary + Geary - <_developer_name>Geary Development Team + Geary Development Team - <_summary>Send and receive email + Send and receive email - <_p> +

Geary is an email application built around conversations, for the GNOME 3 desktop. It allows you to read, find and send email with a straightforward, modern interface. - - <_p> +

+

Conversations allow you to read a complete discussion without having to find and click from message to message. - - <_p>Geary’s features include: +

+

Geary’s features include:

    - <_li>Quick email account setup - <_li>Shows related messages together in conversations - <_li>Fast, full text and keyword search - <_li>Full-featured HTML and plain text message composer - <_li>Desktop notification of new mail - <_li>Compatible with GMail, Yahoo! Mail, Outlook.com and other IMAP servers +
  • Quick email account setup
  • +
  • Shows related messages together in conversations
  • +
  • Fast, full text and keyword search
  • +
  • Full-featured HTML and plain text message composer
  • +
  • Desktop notification of new mail
  • +
  • Compatible with GMail, Yahoo! Mail, Outlook.com and other IMAP servers
@@ -40,29 +40,32 @@ https://wiki.gnome.org/Apps/Geary/ReportingABug https://wiki.gnome.org/Apps/Geary/Translating https://www.gnome.org/friends/ - + - <_caption>Geary displaying a conversation - https://wiki.gnome.org/Apps/Geary?action=AttachFile&do=get&target=geary-conversation-0.12.png + Geary displaying a conversation + https://wiki.gnome.org/Apps/Geary?action=AttachFile&do=get&target=geary-3-32-main-window.png + + + https://wiki.gnome.org/Apps/Geary?action=AttachFile&do=get&target=geary-3-32-avatars.png + + + https://wiki.gnome.org/Apps/Geary?action=AttachFile&do=get&target=geary-3-32-search.png - <_caption>Geary showing the rich text composer - https://wiki.gnome.org/Apps/Geary?action=AttachFile&do=get&target=geary-composer-0.12.png + Geary showing the rich text composer + https://wiki.gnome.org/Apps/Geary?action=AttachFile&do=get&target=geary-3-32-composer.png + + + https://wiki.gnome.org/Apps/Geary?action=AttachFile&do=get&target=geary-3-32-add-account.png GNOME - - Office - Network - Email - ​ - AppMenu HiDpiIcon HighContrast ModernToolkit @@ -71,105 +74,52 @@ org.gnome.Geary.desktop - - geary - + geary - - -

Bug fixes included in this release:

-
    -
  • Fix handling folder names with IMAP reserved characters, such as backslashes. Issue #40
  • -
  • Fix dialog windows not focused after being first shown. Issue #43
  • -
  • Actually include the fix for "Move to folder" selection bug. Issue #24
  • -
  • Fix build under vala >= 0.41. Issue #86
  • -
  • Fixes for miscellaneous crashers
  • -
-
-
- - - -

Bug fixes included in this release:

-
    -
  • Not syncing mail using Turkish locale. Bug 795906
  • -
  • Fix crash saving an attachment with unknown content type
  • -
  • Fix crash in secret_collection_get_locked. Bug 795328
  • -
  • "Move to folder" selection bug. Issue #24
  • -
  • Subfolders with special folders not displayed in - list. Issue #11
  • -
  • Add OARS metadata for Flathub
  • -
-
-
- - + -

Bug fixes included in this release:

+

Enhancements included in this release:

    -
  • Fix being unable to remove attachments from a draft. Bug - 792555.
  • -
  • Ensure drafts are removed when composer from address - changes accounts. Bug 778976.
  • -
  • Workaround composer info label being too long. Bug - 790435.
  • -
  • Ensure embedded composer is always scrolled to when - opened. Bug 778027.
  • -
  • Don't display quote expander buttons when printing a - message. Bug 795216.
  • -
  • Fix composer detach button position and visibility. Bug - 793710.
  • -
  • Actually fix second multipart/digest message body not - being displayed. Bug 788637.
  • -
  • Ensure gnome-control-centre knows in advance Geary uses - notifications
  • -
  • Fix gnome-shell notifications missing an icon under - flatpak. Bug 790103.
  • -
  • Fix message body quote button styling under WebKitGTK - 2.20.
  • -
  • Don't show unused header widgets when showing a message - via notifications
  • -
  • Work around present() not actually raising windows under - Wayland. Bug 776881.
  • -
  • Reduce CPU use when idle. Bug 783025.
  • -
  • Fix some serious run-time memory leaks
  • -
  • Update translation files (es, ru, sr, sr@latin)
  • +
  • Application menu moved to the main window
  • +
  • Desktop contacts are used for sender images
  • +
  • Unknown contacts are given personalised initials and colour
  • +
  • Updated application icons
  • +
  • Improved server compatibility
  • +
  • Custom email CSS now applied to Composer view
- - + -

Bug fixes included in this release:

+

Enhancements included in this release:

    -
  • Parts of multipart/digest message do not expand when - clicked upon. Bug 788637.
  • -
  • Geary does not unlock keyring at start. Bug 784300.
  • -
  • Syntax error in IMAP greeting from AliYun IMAP - server. Bug 781488.
  • -
  • Message body text caret (cursor) not initially - visible. Bug 788797.
  • -
  • Losing focus when clicking in empty part of the - composer. Bug 779369.
  • -
  • Line breaks lost when selecting and replying to certain - messages. Bug 781178.
  • -
  • Always display an in-window app-menu under Unity. Bug - 770618.
  • -
  • Crash in SoupCacheInputStream when cancelling a message - load. Bug 778720.
  • -
  • Do not show Labels on sidebar if no label is - present. Bug 754802.
  • -
  • Unable to use Ctrl+C shortcut to copy e-mail subject; - must use context menu instead. Bug 788494.
  • -
  • After clicking on mailto link in Geary, the body in the - composer is not writable. Bug 771504.
  • -
  • Editing message does not support RTL. Bug 713607.
  • +
  • Unread email count is now updated correctly
  • +
  • Conversations load faster, smoother with better feedback
  • +
  • Support for email accounts added via GNOME Online Accounts
  • +
  • Improved account creation and management user interface
  • +
  • Email flagged as deleted but not removed by other apps now hidden
  • +
  • Individual messages in a conversation can be deleted
  • +
  • Internal links in HTML email now work
  • +
  • Supported ordered and unordered lists in the composer
  • +
  • Rich text pasting improvements in the composer
  • +
  • Plain text versions of rich text mail includes formatting
  • +
  • Detached composers now remember their last used size
  • +
  • Better reporting when a login, security or other problem occurs
  • +
  • Reduced background synchronisation CPU use
  • +
  • Improved handling when going online and offline
  • +
  • Show an in-application notification when email has been sent
  • +
  • Flag possibly spoofed email addresses
  • +
  • Improve privacy when sending email using an alias
  • +
  • Subject, sender and date are being shown when printed again
  • +
  • Server compatibility improvements
  • +
  • Build, testing and other infrastructure improvements
  • +
  • Numerous bug fixes and minor user interface improvements
  • +
  • Numerous user interface translation updates
-

Enhancements included in this release:

diff -Nru geary-0.12.4/desktop/org.gnome.Geary.desktop.in geary-3.32.0/desktop/org.gnome.Geary.desktop.in --- geary-0.12.4/desktop/org.gnome.Geary.desktop.in 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/desktop/org.gnome.Geary.desktop.in 2019-03-17 13:39:29.000000000 +0000 @@ -1,10 +1,10 @@ [Desktop Entry] -_Name=Geary -_GenericName=Email -_X-GNOME-FullName=Geary Email -_Comment=Send and receive email +Name=Geary +GenericName=Email +Comment=Send and receive email # Translators: These are desktop search terms. Do not translate semicolons, end line with a semicolon. -_Keywords=Mail;E-mail;IMAP;GMail;Yahoo;Hotmail;Outlook; +Keywords=Mail;E-mail;IMAP;GMail;Yahoo;Hotmail;Outlook; +# Translators: Do NOT translate or transliterate this text (this is an icon file name)! Icon=org.gnome.Geary TryExec=geary Exec=geary %U @@ -17,5 +17,5 @@ Actions=Compose; [Desktop Action Compose] -_Name=Compose Message +Name=Compose Message Exec=geary mailto: diff -Nru geary-0.12.4/desktop/org.gnome.Geary.gschema.xml geary-3.32.0/desktop/org.gnome.Geary.gschema.xml --- geary-0.12.4/desktop/org.gnome.Geary.gschema.xml 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/desktop/org.gnome.Geary.gschema.xml 2019-03-17 13:39:29.000000000 +0000 @@ -1,137 +1,132 @@ + - - '' - Default attachments directory - Location used when opening and saving attachments - - - - '' - Default print output directory - Location used when printing to a file - - false - maximize window - True if library application is maximized, false otherwise. + Maximize window + True if the application window is maximized, false otherwise. 1024 - width of window + Width of window The last recorded width of the application window. 768 - height of window + Height of window The last recorded height of the application window. 100 - position of folder list pane + Position of folder list pane Position of the folder list Paned grabber. -1 - position of folder list pane when horizontal + Position of folder list pane when horizontal Position of the folder list Paned grabber in the horizontal orientation. 200 - position of folder list pane when vertical + Position of folder list pane when vertical Position of the folder list Paned grabber in the vertical orientation. true - orientation of the folder list pane + Orientation of the folder list pane True if the folder list Paned is in the horizontal orientation. 250 - position of message list pane + Position of message list pane Position of the message list Paned grabber. true - autoselect next message + Autoselect next message True if we should autoselect the next available conversation. true - display message previews + Display message previews True if we should display a short preview of each message. [] Languages that shall be used in the spell checker - List of the languages to use in the spell checker + List of the languages to use in the spell checker. [] - Languages that are displayed in the spell checker popover. + Languages that are displayed in the spell checker popover List of languages that are always displayed in the popover of the spell checker. true - enable notification sounds + Enable notification sounds True to play sounds for notifications and sending. true - show notifications for new mail + Show notifications for new mail True to show notification bubbles. false - notify of new mail at startup + Notify of new mail at startup True to notify of new mail at startup. true - ask when opening an attachment + Ask when opening an attachment True to ask when opening an attachment. true - whether to compose emails in HTML + Whether to compose emails in HTML True to compose emails in HTML; false for plain text. "conservative" Advisory strategy for full-text searching - Acceptable values are EXACT, CONSERVATIVE, AGGRESSIVE, and HORIZON. + Acceptable values are “exact”, “conservative”, “aggressive”, and “horizon”. 1 - zoom of conversation viewer + Zoom of conversation viewer The zoom to apply on the conservation view. + + [-1,-1] + Size of detached composer window + The last recorded size of the detached composer window. + + false Whether we migrated the old settings - False to check for the old 'org.yorba.geary'-schema and copy its values + False to check for the old “org.yorba.geary”-schema and copy its values. diff -Nru geary-0.12.4/geary.doap geary-3.32.0/geary.doap --- geary-0.12.4/geary.doap 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/geary.doap 2019-03-17 13:39:29.000000000 +0000 @@ -15,7 +15,7 @@ - + Vala diff -Nru geary-0.12.4/.gitlab-ci.yml geary-3.32.0/.gitlab-ci.yml --- geary-0.12.4/.gitlab-ci.yml 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/.gitlab-ci.yml 1970-01-01 00:00:00.000000000 +0000 @@ -1,110 +0,0 @@ -# -# Geary CI config. -# - -stages: - - build - -variables: - BUILD_DIR: build - CONFIG_CMD: ./configure - BUILD_CMD: make - TEST_CMD: xvfb-run make test - INSTALL_CMD: make install - FEDORA_BUILD_DEPS: gcc make - FEDORA_DEPS: vala gobject-introspection-devel intltool cmake - desktop-file-utils gnome-doc-utils libcanberra-devel - libgee-devel glib2-devel gmime-devel gtk3-devel - libnotify-devel sqlite-devel webkitgtk4-devel - libsecret-devel libxml2-devel vala-tools gcr-devel - enchant-devel - FEDORA_TEST_DEPS: Xvfb - DEBIAN_DEPS: valac libgirepository1.0-dev intltool cmake - desktop-file-utils gnome-doc-utils libcanberra-dev - libgee-0.8-dev libglib2.0-dev libgmime-2.6-dev - libgtk-3-dev libsecret-1-dev libxml2-dev libnotify-dev - libsqlite3-dev libwebkit2gtk-4.0-dev libgcr-3-dev - libenchant-dev - DEBIAN_TEST_DEPS: xauth xvfb - UBUNTU_DEPS: $DEBIAN_DEPS libunity-dev libmessaging-menu-dev - UBUNTU_TEST_DEPS: xauth xvfb - -# -# Stages -# - -fedora: - stage: build - image: fedora:latest - before_script: - - dnf update -y --nogpgcheck - - dnf install -y --nogpgcheck $FEDORA_BUILD_DEPS $FEDORA_DEPS $FEDORA_TEST_DEPS - script: - - $CONFIG_CMD - - $BUILD_CMD - - $TEST_CMD - - $INSTALL_CMD - -debian: - stage: build - image: debian:stable - before_script: - - apt-get update - - apt-get install -q -y --no-install-recommends $DEBIAN_DEPS $DEBIAN_TEST_DEPS - script: - - $CONFIG_CMD - - $BUILD_CMD - - $TEST_CMD - - $INSTALL_CMD - -ubuntu: - stage: build - image: ubuntu:xenial - before_script: - - apt-get update - - apt-get install -q -y --no-install-recommends $UBUNTU_DEPS $UBUNTU_TEST_DEPS - script: - - $CONFIG_CMD - - $BUILD_CMD - - $TEST_CMD - - $INSTALL_CMD - -deb-package: - stage: build - image: ubuntu:xenial - before_script: - - apt-get update - - apt-get install -q -y --no-install-recommends packaging-dev $UBUNTU_DEPS - script: - - dpkg-buildpackage -b -us -uc - -flatpak-package: - image: registry.gitlab.gnome.org/gnome/gnome-runtime-images/gnome:3.28 - stage: build - - variables: - GIT_SUBMODULE_STRATEGY: normal - FLATPAK_ARTIFACT: geary-git.flatpak - - script: - - flatpak-builder flatpak-build org.gnome.Geary.json - - flatpak build-export flatpak-repo flatpak-build --update-appstream - - flatpak build-bundle flatpak-repo $FLATPAK_ARTIFACT - --runtime-repo=https://sdk.gnome.org/gnome-nightly.flatpakrepo - org.gnome.Geary - - artifacts: - paths: - - $FLATPAK_ARTIFACT - expire_in: 2 days - - cache: - # JOB_NAME - Each job will have it's own cache - # COMMIT_REF_SLUG = Lowercase name of the branch - # ^ Keep diffrerent caches for each branch - key: "$CI_JOB_NAME-$CI_COMMIT_REF_SLUG" - paths: - # Cache .flatpak-builder - - .flatpak-builder/cache/ - - .flatpak-builder/downloads/ - - .flatpak-builder/git/ diff -Nru geary-0.12.4/help/C/accounts.page geary-3.32.0/help/C/accounts.page --- geary-0.12.4/help/C/accounts.page 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/help/C/accounts.page 2019-03-17 13:39:29.000000000 +0000 @@ -19,9 +19,8 @@

Additional accounts can be added from the Accounts dialog. The Accounts option is available in either Geary's application menu or the gear menu in the upper-right of the toolbar. (The location depends on the install desktop shell. For GNOME Shell and Unity, the - application menu is available near the top-left corner of the screen.) Alternately, - CtrlM will open the Accounts - dialog. To add an account, click the + button.

+ application menu is available near the top-left corner of the screen.) + To add an account, click the + button.

diff -Nru geary-0.12.4/help/C/bugs.page geary-3.32.0/help/C/bugs.page --- geary-0.12.4/help/C/bugs.page 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/help/C/bugs.page 2019-03-17 13:39:29.000000000 +0000 @@ -2,21 +2,32 @@ type="guide" id="bugs"> - - - - Think you've found a bug? -

If you suspect you've found a bug in Geary, follow these steps to report it:

- -

Search Geary's bug - database to see if someone else has reported the bug.

-

Don't see your bug listed? Congratulations! You've found a new bug. To create an bug report, - create an account on GNOME's Bugzilla and - file a new bug. Be as - specific as you can and describe the steps to reproduce it. Don't forget to include details about - your operating system and what version of Geary you're running.

-

For general inquiries, please join the - Geary mailing - list.

-
+ + + + + + Found a bug? + +

If you suspect you've found a bug in Geary, please get in touch + about it so it can be fixed.

+ +

To help diagnose the problem as fast as possible, please include + the following information:

+ + +

Geary version and installation method (Package? Flathub? + Source code?)

+

Your desktop (GNOME? KDE? Something else?)

+

Your operating system and version (Ubuntu 16.04? Fedora + 28? Rolled your own?)

+

Email provider (Gmail, Yahoo!, Outlook.com, or someone + else?)

+

Steps to reproduce the bug

+

What happened?

+

What did you expect to happen?

+
+ +

Thanks for your help!

diff -Nru geary-0.12.4/help/C/contributing.page geary-3.32.0/help/C/contributing.page --- geary-0.12.4/help/C/contributing.page 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/help/C/contributing.page 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,26 @@ + + + + + + + + Contribute to Geary + +

Want to help improve Geary? There are a number of ways you can + contribute:

+ + +

Bug + reporting—report new bugs or request new features

+

User Experience Design—research and develop Geary’s user experience

+

Development—fix bugs and add new features

+

Translating—translate Geary’s user interface and user manual into new languages

+

Join the discussion—on the mailing list or IRC channel

+
+ +

Thanks for your help making Geary better!

+
diff -Nru geary-0.12.4/help/C/index.page geary-3.32.0/help/C/index.page --- geary-0.12.4/help/C/index.page 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/help/C/index.page 2019-03-17 13:39:29.000000000 +0000 @@ -2,8 +2,7 @@ type="guide" id="index"> -<media type="image" src="figures/geary.svg"></media> -Geary +<media type="image" src="figures/geary.svg"></media> Geary
Introduction @@ -13,8 +12,8 @@ Using Geary
-
- Bugs +
+ Contributing and bug reporting
diff -Nru geary-0.12.4/help/C/limits.page geary-3.32.0/help/C/limits.page --- geary-0.12.4/help/C/limits.page 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/help/C/limits.page 2019-03-17 13:39:29.000000000 +0000 @@ -2,17 +2,20 @@ type="topic" id="limits"> - - - - - - - Limitations -

Geary is still in early development. Geary supports IMAP and has been tested with Gmail, Yahoo, and the free Dovecot mail server. Experimental support for Outlook.com is provided. Geary may not yet work well with some IMAP servers. At this time Geary is still missing numerous features including offline mode.

- -

To learn more about the features we're working on and the future of Geary, please visit Geary's wiki page.

- - + + + + + Limitations +

Geary is still in early development. Geary supports IMAP and has + been tested with Gmail, Yahoo, and the free Dovecot mail server. + Experimental support for Outlook.com is provided. Geary may not yet + work well with some IMAP servers. At this time Geary is still + missing numerous features including offline mode.

+

To learn more about the features we're working on and the future + of Geary, please visit Geary's wiki page.

+ + diff -Nru geary-0.12.4/help/C/preferences.page geary-3.32.0/help/C/preferences.page --- geary-0.12.4/help/C/preferences.page 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/help/C/preferences.page 2019-03-17 13:39:29.000000000 +0000 @@ -35,18 +35,7 @@
- -
- Composer - - - <gui>Enable spell checking</gui> -

When set, Geary automatically spell checks a message as you write it, underlying - each misspelled word in red.

-
-
-
- +
Notifications @@ -62,12 +51,12 @@ In Ubuntu Unity, notifications appear at the upper right of the display.

- <gui>Always watch for new mail</gui> + <gui>Watch for new mail when closed</gui>

Geary will watch your accounts for new mail even when the main window is not open. To do this, it will silently start when you log in to your computer, and it will continue to run - after you close the main window.

+ after you close all windows.

- + diff -Nru geary-0.12.4/help/C/search.page geary-3.32.0/help/C/search.page --- geary-0.12.4/help/C/search.page 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/help/C/search.page 2019-03-17 13:39:29.000000000 +0000 @@ -61,10 +61,10 @@

to:recipient

-

Finds messages where sender matches the To, CC, or BCC header.

+

Finds messages where recipient matches the To, CC, or BCC header.

- +

As a special case, the bcc, cc, from, and to operators support me as their argument, which searches for the account's email address in the appropriate context.

diff -Nru geary-0.12.4/help/C/shortcuts.page geary-3.32.0/help/C/shortcuts.page --- geary-0.12.4/help/C/shortcuts.page 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/help/C/shortcuts.page 2019-03-17 13:39:29.000000000 +0000 @@ -2,205 +2,33 @@ type="topic" id="shortcuts"> - - - - - - - Keyboard shortcuts -

Geary has keyboard shortcuts for most common operations.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Compose a new message

CtrlN or N

Reply to sender

CtrlR or R

Reply to all

CtrlShiftR or ShiftR

Forward

CtrlL or F

Archive

A

Trash

Delete or Backspace

Delete

ShiftDelete or ShiftBackspace

Star

S

Unstar

D

Mark read

CtrlI or ShiftI

Mark unread

CtrlU or ShiftU

Move the conversation

M

Label the conversation

L

Jump to next (older) conversation

J

Jump to previous (newer) conversation

K

Toggle spam

CtrlJ or !

Quit

CtrlQ

Zoom in

Ctrl= or =

Zoom out

Ctrl- or -

Reset zoom

Ctrl0 or 0

Close composer window

CtrlW

Jump to search box

CtrlS

Find in current conversation

CtrlF

Find next in current conversation

CtrlG

Find previous in current conversation

CtrlShiftG

+ + + + -
- Composer shortcuts -

These shortcuts are active whenever focus is in a composer.

- - - - - - - - - - - - - - - - - - - - - -

Attach file

CtrlT

Quote text

Ctrl]

Unquote text

Ctrl[

Close composer

CtrlW or Esc

Detach composer

CtrlD

+ Keyboard shortcuts -

These shortcuts are only active in composers in rich text mode.

- - - - - - - - - - - - - - - - - - - - - - - - - -

Bold text

CtrlB

Italicize text

CtrlI

Underline text

CtrlU

Strike text

CtrlK

Insert a link

CtrlL

Remove formatting

CtrlSpace

-
+

Geary has keyboard shortcuts for most common operations. Use the + built-in keyboard shortcuts help in Geary to discover the full list. + This can be accessed via the application menu: + GearyKeyboard Shortcuts or + using the keyboard shortcuts listed below.

-
- Keyboard navigation -

These shortcuts can be used to move the keyboard focus in the main window.

- - - - - - - - - - - - - - - - - - - - - -

Move focus to the next/previous pane

F6 / ShiftF6

Move focus to conversation list

CtrlB

Move to the next message in a conversation

- Space -

Move to the next/previous message in a conversation

- CtrlDown / - CtrlUp -

Move to the first/last message in a conversation

- CtrlHome / - CtrlEnd -

-
+

The following keyboard shortcuts can be used to access on-line + help from Geary:

+ + + + + + + + + +

Display this User Manual

F1

Display all keyboard shortcuts

+ Ctrl? or + CtrlF1 +

diff -Nru geary-0.12.4/help/C/write.page geary-3.32.0/help/C/write.page --- geary-0.12.4/help/C/write.page 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/help/C/write.page 2019-03-17 13:39:29.000000000 +0000 @@ -33,7 +33,7 @@ either on the text fields at the top of the window or on the toolbar at the bottom.

-

A number of keyboard shortcuts are available in the composer; see for details.

+

A number of keyboard shortcuts are available in the composer; see for details.

You may specify a signature to be inserted into the composer in the dialog.

diff -Nru geary-0.12.4/help/CMakeLists.txt geary-3.32.0/help/CMakeLists.txt --- geary-0.12.4/help/CMakeLists.txt 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/help/CMakeLists.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,89 +0,0 @@ -set(HELP_FILES - accounts.page - archive.page - bugs.page - index.page - label.page - limits.page - overview.page - preferences.page - search.page - shortcuts.page - star.page - write.page -) - -# FIXME: don't re-specify this here, instead read it from Makefile.am. -set(TRANSLATED - cs - de - el - es - fr - it - pl - pt_BR - sv -) - -set(HELP_DEST share/gnome/help/geary) - -set(HELP_SOURCE) -foreach(_page ${HELP_FILES}) - set(HELP_SOURCE ${HELP_SOURCE} C/${_page}) -endforeach() - -install(FILES ${HELP_SOURCE} DESTINATION ${HELP_DEST}/C) -install(FILES C/figures/geary.svg DESTINATION ${HELP_DEST}/C/figures) - -# Hacked together from the similar macro in cmake/Gettext.cmake. -MACRO(HELP_CREATE_TRANSLATIONS _firstLang) - SET(_translatedPages) - SET(_addToAll) - SET(_isComment FALSE) - - FOREACH(_lang ${_firstLang} ${ARGN}) - IF(_lang STREQUAL "ALL") - SET(_addToAll "ALL") - ELSEIF(_lang STREQUAL "COMMENT") - SET(_isComment TRUE) - ELSEIF(_isComment) - SET(_isComment FALSE) - SET(_comment ${_lang}) - ELSE() - GET_FILENAME_COMPONENT(_absPo ${_lang}/${_lang}.po ABSOLUTE) - - FOREACH(_page ${HELP_FILES}) - GET_FILENAME_COMPONENT(_absSourcePage C/${_page} ABSOLUTE) - SET(_destPage ${CMAKE_CURRENT_BINARY_DIR}/${_lang}/${_page}) - GET_FILENAME_COMPONENT(_destPath ${_destPage} PATH) - - #MESSAGE("_absPo=${_absPo} _absSourcePage=${_absSourcePage} _destPage=${_destPage} _lang=${_lang} _page=${_page} curr_bin=${CMAKE_CURRENT_BINARY_DIR}\n") - ADD_CUSTOM_COMMAND( - OUTPUT ${_destPage} - COMMAND mkdir -p ${_destPath} && ${XML2PO_BIN} -m mallard -p ${_absPo} -o ${_destPage} ${_absSourcePage} - DEPENDS ${_absPo} ${_absSourcePage} - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - ) - - INSTALL(FILES ${_destPage} DESTINATION ${HELP_DEST}/${_lang}) - SET(_translatedPages ${_translatedPages} ${_destPage}) - ENDFOREACH() - ENDIF() - ENDFOREACH() - - IF(DEFINED _comment) - ADD_CUSTOM_TARGET(help_translations ${_addToAll} DEPENDS ${_translatedPages} COMMENT ${_comment}) - ELSE() - ADD_CUSTOM_TARGET(help_translations ${_addToAll} DEPENDS ${_translatedPages}) - ENDIF() -ENDMACRO() - -IF(TRANSLATE_HELP) - FIND_PROGRAM(XML2PO_BIN xml2po) - IF(NOT XML2PO_BIN) - MESSAGE(FATAL_ERROR "xml2po not found") - ENDIF() - - HELP_CREATE_TRANSLATIONS(ALL ${TRANSLATED} COMMENT "Translating help docs.") -ENDIF() diff -Nru geary-0.12.4/help/cs/cs.po geary-3.32.0/help/cs/cs.po --- geary-0.12.4/help/cs/cs.po 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/help/cs/cs.po 2019-03-17 13:39:29.000000000 +0000 @@ -1,13 +1,13 @@ # Czech translation for geary. # Copyright (C) 2014 geary's COPYRIGHT HOLDER # This file is distributed under the same license as the geary package. -# Marek Černocký , 2014, 2015, 2016, 2017. +# Marek Černocký , 2014, 2015, 2016, 2017, 2018, 2019. # msgid "" msgstr "" "Project-Id-Version: geary master\n" -"POT-Creation-Date: 2017-09-25 16:02+0000\n" -"PO-Revision-Date: 2017-09-25 19:00+0200\n" +"POT-Creation-Date: 2019-01-29 05:49+0000\n" +"PO-Revision-Date: 2019-01-29 10:40+0100\n" "Last-Translator: Marek Černocký \n" "Language-Team: čeština \n" "Language: cs\n" @@ -17,564 +17,760 @@ "Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" "X-Generator: Gtranslator 2.91.7\n" -#: C/write.page:9(title) -msgid "Write a message" -msgstr "Psaní zprávy" +#. Put one translator per line, in the form NAME , YEAR1, YEAR2 +msgctxt "_" +msgid "translator-credits" +msgstr "Marek Černocký " -#: C/write.page:12(title) -msgid "Composing and replying" -msgstr "Psaní zpráv a odpovídání" +#. (itstool) path: page/title +#: C/accounts.page:10 +msgid "Accounts" +msgstr "Účty" -#: C/write.page:13(p) +#. (itstool) path: section/title +#: C/accounts.page:13 +msgid "Adding accounts" +msgstr "Přidávání účtů" + +#. (itstool) path: section/p +#: C/accounts.page:15 msgid "" -"To compose a new message in Geary, press the New Message button " -"on the toolbar." +"The first time you start Geary, you will be prompted to add an email " +"account. On this screen, select if your account is Gmail, Yahoo, Outlook." +"com, or other. For other account types, you will need to enter your IMAP and " +"SMTP login settings manually." msgstr "" -"Když chcete v Geary napsat novou zprávu, zmáčkněte na nástrojové liště " -"tlačítko Nová zpráva." +"Když Geary spustíte poprvé, budete dotázáni na přidání poštovního účtu. Na " +"této obrazovce vyberte, jestli máte účet Gmail, Yahoo, Outlook.com nebo " +"jiný. U účtu jiného typu budete muset zadat své nastavení IMAP a SMTP ručně." -#: C/write.page:16(p) +#. (itstool) path: section/p +#: C/accounts.page:19 msgid "" -"To reply to a message, open the message menu in the upper right corner of " -"the message and choose Reply, Reply All or " -"Forward. You can also reply to the last message in a conversation " -"via the Reply, Reply All or Forward buttons " -"on the toolbar." +"Additional accounts can be added from the Accounts dialog. The " +"Accounts option is available in either Geary's application menu " +"or the gear menu in the upper-right of the toolbar. (The location depends on " +"the install desktop shell. For GNOME Shell and Unity, the application menu " +"is available near the top-left corner of the screen.) To add an account, " +"click the + button." msgstr "" -"Když chcete odpovědět na zprávu, otevřete nabídku zprávy v pravém horním " -"rohu zprávy a zvolte Odpovědět, Odpovědět všem nebo " -"Přeposlat. Můžete také odpovědět na poslední zprávu v konverzaci " -"přes tlačítka Odpovědět, Odpovědět všem nebo " -"Přeposlat na nástrojové liště." +"Další účty můžete přidat v dialogovém okně Účty. Volbu Účty " +"najdete buď v aplikační nabídce Geary nebo v nabídce v pravém horním rohu " +"nástrojové lišty. (Přesné místo závisí na používaném grafickém shellu. V " +"GNOME Shell a Unity se aplikační nabídka nachází poblíž levého horního rohu " +"obrazovky.) Nový účet přidáte kliknutím na tlačítko +." -#: C/write.page:21(title) -msgid "Features" -msgstr "Funkce" +#. (itstool) path: section/title +#: C/accounts.page:27 +msgid "Editing existing accounts" +msgstr "Úprava stávajících účtů" -#: C/write.page:23(p) +#. (itstool) path: section/p +#: C/accounts.page:29 msgid "" -"Geary's email composer lets you adjust the font, size and color of text. You " -"can also insert hyperlinks into messages." +"From the Accounts dialog, select an account and click the pencil icon to " +"change various settings. Please note that Geary cannot change server " +"settings on an existing account. If you need to change your IMAP or SMTP " +"server, you will need to delete the account and re-add it." msgstr "" -"Při psaní e-mailů v Geary si můžete přizpůsobit písmo, velikost a barvu " -"textu. Rovněž můžete do zpráv vkládat hypertextové odkazy." +"V dialogovém okně Účty vyberte účet a po kliknutí na ikonu tužky můžete " +"měnit různá nastavení. Vezměte ale prosím na vědomí, že Geary neumí změnit u " +"existujícího účtu server. Pokud potřebujete změnit server IMAP nebo SMTP, " +"budete muset účet smazat a znovu jej přidat." -#: C/write.page:25(p) +#. (itstool) path: section/p +#: C/accounts.page:33 msgid "" -"Geary can also send plain text messages. In the drop-down menu, check or " -"uncheck \"Rich Text\" to toggle between plain text and rich text mode." +"To change the order that accounts are displayed in the folder list, drag the " +"accounts in the Accounts dialog to the desired order." msgstr "" -"Zprávy je možné posílat i jako čistě textové. V rozbalovací nabídce " -"zaškrtněte nebo zrušte zaškrtnutí „Formátovaný text“, abyste se přepnuli " -"mezi režimy prostého textu a formátovaného textu." +"Pro změnu pořadí zobrazení účtů v seznamu složek, stačí účet v dialogovém " +"okně Účty přetáhnout na požadované místo." -#: C/write.page:28(p) -msgid "" -"You can attach a file to a message you're writing in either of these ways:" -msgstr "K napsané zprávě můžete přiložit soubory a to následujícími způsoby:" +#. (itstool) path: section/p +#: C/accounts.page:36 +msgid "There are some advanced options available when editing accounts:" +msgstr "Během úprav účtu jsou k dispozici některá pokročilá nastavení:" -#: C/write.page:30(p) +#. (itstool) path: item/p +#: C/accounts.page:38 msgid "" -"Press the Attach File button at the lower left of the composer " -"window, then select a file to attach." +"The Save sent mail checkbox controls whether Geary will push " +"successfully sent messages up to the account's Sent Mail folder. " +"For Gmail accounts, this happens automatically. Yahoo and some other " +"accounts can be configured to do this automatically as well. For other " +"accounts, if you disable this setting, you may be unable to view messages " +"you've sent." msgstr "" -"Zmáčkněte tlačítko Přiložit soubor v levém dolním rohu okna " -"editoru a následně vyberte soubor, který chcete přiložit." +"Zaškrtávací políčko Ukládat odeslanou poštu řídí, jestli se mají " +"úspěšně odeslané zprávy vkládat do složky Odeslaná pošta " +"náležející k účtu. U Gmailu se tak děje automaticky. Yahoo a některé další " +"účty lze nastavit, aby se tak dělo také automaticky. U ostatních účtů " +"nebudete při vypnutí této volby moci procházet zprávy, které jste odeslali." -#: C/write.page:32(p) +#. (itstool) path: item/p +#: C/accounts.page:44 msgid "" -"Drag the file from the Nautilus file manager to the composer window, and " -"drop it either on the text fields at the top of the window or on the toolbar " -"at the bottom." +"The Sign emails checkbox indicates whether a signature will be " +"automatically inserted when a composer is opened. You may enter the " +"signature into the box immediately below. You may use HTML tags to style the " +"text. Switch to a preview of the signature using the buttons to the right." msgstr "" -"Přetáhněte soubor ze správce souborů Nautilus do okna editoru a upusťte jej " -"buď na textových polích v horní části okna nebo na nástrojové liště dole." +"Zaškrtávací políčko Patička e-mailů říká, jestli se má při " +"otevření editoru automaticky vložit patička s podpisem. Obsah patičky můžete " +"zadat do pole níže. Můžete při tom určovat styl pomocí značek HTML. " +"Tlačítkem napravo si můžete zobrazit náhled patičky." -#: C/write.page:36(p) +#. (itstool) path: item/p +#: C/accounts.page:49 msgid "" -"A number of keyboard shortcuts are available in the composer; see for details." +"If you leave the signature in the Accounts dialog blank, Geary will use the " +".signature file in your home directory, if it exists. This file " +"may contain either plain text or HTML markup. In the latter case, the markup " +"will be inserted directly into the composer, without any escaping." msgstr "" -"Řada klávesových zkratek je dostupná v editoru zpráv, podrobnosti viz ." +"Pokud ponecháte patičku v dialogovém okně Účty prázdnou, bude Geary používat " +"soubor .signature ve vaší domovské složce (za předpokladu, že " +"soubor existuje). Může obsahovat buď prostý text nebo text formátovaný jako " +"HTML. Ve druhém případě bude vložen do editoru tak, jak je, bez jakéhokoliv " +"ošetření speciálních znaků." -#: C/write.page:38(p) +#. (itstool) path: item/p +#: C/accounts.page:54 msgid "" -"You may specify a signature to be inserted into the composer in the dialog." +"The Download mail drop-down allows you to configure how much mail " +"Geary will keep locally. Geary can only use locally available mail when " +"searching and forming conversations." msgstr "" -"V dialogovém okně můžete zadat patičku, která se " -"bude vkládat do editoru." +"V rozbalovacím seznamu Stahnout e-mail je možné nastavit, kolik " +"zpráv bude Geary uchovávat místně. Jen v místně uložených zprávám může Geary " +"vychledávat a sestavovat z nich konverzace." -#: C/write.page:43(title) -msgid "Drafts" -msgstr "Koncepty" +#. (itstool) path: section/title +#: C/accounts.page:62 +msgid "Removing accounts" +msgstr "Odebírání účtů" -#: C/write.page:45(p) +#. (itstool) path: section/p +#: C/accounts.page:64 msgid "" -"For mail servers that support drafts, Geary will automatically save the " -"message as you type. If you close the composer without sending, Geary will " -"prompt you to keep the draft or to discard it." +"To delete an account, open the Accounts dialog, select the account, and " +"press the - button. Geary will delete all information associated with the " +"account." msgstr "" -"Na serverech, které podporují koncepty, ukládá Gerary zprávy automaticky " -"během psaní. Pokud zavřete okno editoru, aniž byste zprávu odeslali, Geary " -"se vás dotáže, jestli má zprávu uchovat v konceptech nebo ji zahodit." +"Když chcete účet smazat, otevřte dialogové okno Účty, vyberte účet a " +"zmáčkněte tlačítko -. Geary smaže všechny informace související s daným " +"účtem." + +#. (itstool) path: page/title +#: C/archive.page:10 +msgid "Delete or archive a message" +msgstr "Mazání a archivace zpráv" -#: C/write.page:48(p) +#. (itstool) path: page/p +#: C/archive.page:12 msgid "" -"To edit an existing draft, select the Drafts folder in the folder list, " -"select the message, and click \"Edit Draft\" in the message viewer." +"When you use Geary with a Gmail account, Geary lets you archive " +"messages. The Archive toolbar button archives the selected " +"conversation(s). Archived messages appear in the All Mail folder." msgstr "" -"Až chcete ve psaní stávajícího konceptu pokračovat, vyberte v seznamu složek " -"složku Koncepty, vyberte zprávu a klikněte v prohlížeči zpráv na „Upravit " -"koncept“ ." - -#: C/write.page:51(p) -msgid "Geary deletes the draft when you send the message." -msgstr "Po odeslání zprávy Geary koncept smaže." - -#: C/star.page:10(title) -msgid "Star a message or mark it as read/unread" -msgstr "Označení zprávy hvězdičkou nebo jako přečtená/nepřečtená" - -#: C/star.page:12(title) -msgid "Star messages" -msgstr "Označení zpráv hvězdičkou" +"Pokud používáte Geary s účtem Gmail, umožní vám Geary zprávy archivovat. Tlačítko Archivovat na nástrojové liště archivuje vybranou " +"konverzaci. Archivované zprávy se objeví ve složce Všechny zprávy." -#: C/star.page:13(p) +#. (itstool) path: page/p +#: C/archive.page:16 msgid "" -"You can star messages to indicate that they're important to you. To mark a " -"conversation with a star, click its star icon in the conversation list. You " -"can star an individual message by clicking the star at the upper right of " -"the message itself." +"With other mail servers, you can trash or delete, but not archive, messages. " +"To move one or more conversations to the Trash folder, select " +"them and press the Trash button on the toolbar. To permanently " +"delete the conversations, hold down Shift and press the " +"Delete button that appears in place of the Trash " +"button." msgstr "" -"Zprávy si můžete označovat hvězdičkou, abyste věděli, že jsou pro vás " -"důležité. Pokud tak chcete učinit, klikněte na ikonu hvězdičky u konverzace " -"v seznamu konverzací. Hvězdičkou můžete označit i jednotlivou zprávu, stačí " -"kliknout na hvězdičku v pravém horním rohu zprávy." +"U ostatních poštovních serverů můžete zprávy mazat a přesouvat do koše, ale " +"ne je archivovat. Když chcete jednu nebo více konverzací přesunout do složky " +"Koš, vyberte je a zmáčkněte tlačítko Koš na nástrojové " +"liště. Abyste konverzaci smazali trvale, držte zmáčknutý Shift a " +"zmáčkněte tlačítko Smazat vyskytující se vedle tlačítka Koš." -#: C/star.page:15(p) +#. (itstool) path: page/p +#: C/archive.page:21 msgid "" -"With Gmail accounts, starred messages appear in the Starred folder in the " -"folder list." +"Delete is not available from every folder, such as Search. Delete is also " +"unavailable for Gmail. For Gmail, Trash will move messages to the " +"Trash folder on the server, where the user can then manually delete them. " +"The server will automatically remove trashed messages after 30 days." msgstr "" -"V účtech Gmail se zprávy označené hvězdičkou objeví ve složce „S hvězdičkou“ " -"v seznamu složek." +"Mázání není k dispozici u každé složky, např. není u složky Hledání. Rovněž " +"není k dispozici pro Gmail. U něj tlačítko Koš přesune zprávy do " +"složky koš na serveru, kde je uživatel může smazat ručně. Případně je smaže " +"server automaticky po 30 dnech." -#: C/star.page:18(title) -msgid "Mark messages as read or unread" -msgstr "Označování zpráv, jako přečtené nebo nepřečtené" +#. (itstool) path: page/title +#: C/bugs.page:10 +msgid "Found a bug?" +msgstr "Našli jste chybu?" -#: C/star.page:19(p) +#. (itstool) path: page/p +#: C/bugs.page:12 msgid "" -"Geary marks messages as read automatically as you read them. To manually " -"toggle a conversation as read or unread, click the circle icon in the " -"conversation list." +"If you suspect you've found a bug in Geary, please get in touch about it so it can be " +"fixed." msgstr "" -"Geary označuje zprávy automaticky jako přečtené, když si je přečtete. Když " -"chcete zprávu jako přečtenou či nepřečtenou označit ručně, klikněte na ikonu " -"kolečka v seznamu konverzace." +"Jestliže předpokládáte, že jste v Geary našli chybu, informujte o tom vývojáře, aby ji " +"mohli opravit." -#: C/star.page:22(p) +#. (itstool) path: page/p +#: C/bugs.page:16 msgid "" -"Alternately, the Mark as Unread in the Mark menu on " -"the toolbar can be used to toggle the read status of the selected " -"conversation(s)." +"To help diagnose the problem as fast as possible, please include the " +"following information:" msgstr "" -"Případně můžete k přepnutí stavu přečteno/nepřečteno u vybrané konverzace " -"použít Označit jako přečtené či Označit jako nepřečtené v nabídce Označit na nástrojové liště." +"Abyste jim pomohli problém co nejrychleji diagnostikovat, doplňte prosím do " +"zprávy tyto informace:" -#: C/star.page:25(p) -msgid "" -"To mark an individual message as read, select Mark as Read from " -"the dropdown menu." +#. (itstool) path: item/p +#: C/bugs.page:20 +msgid "Geary version and installation method (Package? Flathub? Source code?)" msgstr "" -"Pro označení jednotlivé zprávy jako přečtené vyberte Označit jako " -"přečtené v rozbalovací nabídce." - -#: C/shortcuts.page:11(title) -msgid "Keyboard shortcuts" -msgstr "Klávesové zkratky" - -#: C/shortcuts.page:12(p) -msgid "Geary has keyboard shortcuts for most common operations." -msgstr "Geary má klávesové zkratky pro většinu běžných činností." - -#: C/shortcuts.page:15(p) -msgid "Compose a new message" -msgstr "Napsat novou zprávu" - -#: C/shortcuts.page:16(p) -msgid "CtrlN or N" -msgstr "CtrlN nebo N" - -#: C/shortcuts.page:19(p) -msgid "Reply to sender" -msgstr "Odpovědět odesilateli" +"Verze aplikace Geary a způsob její instalace (distribuční balíček, Flathub, " +"zdrojový kód, …)" -#: C/shortcuts.page:20(p) -msgid "CtrlR or R" -msgstr "CtrlR nebo R" +#. (itstool) path: item/p +#: C/bugs.page:22 +msgid "Your desktop (GNOME? KDE? Something else?)" +msgstr "Vaše uživatelské prostředí (GNOME, KDE, Xfce, …)" -#: C/shortcuts.page:23(p) -msgid "Reply to all" -msgstr "Odpovědět všem" - -#: C/shortcuts.page:24(p) +#. (itstool) path: item/p +#: C/bugs.page:23 msgid "" -"CtrlShiftR or " -"ShiftR" -msgstr "" -"CtrlShiftR nebo " -"ShiftR" +"Your operating system and version (Ubuntu 16.04? Fedora 28? Rolled your own?)" +msgstr "Váš operační systém a jeho verze (Ubuntu 18.04, Fedora 28, FreeBSD, …)" -#: C/shortcuts.page:27(p) -msgid "Forward" -msgstr "Přeposlat" +#. (itstool) path: item/p +#: C/bugs.page:25 +msgid "Email provider (Gmail, Yahoo!, Outlook.com, or someone else?)" +msgstr "Poskytovatel e-mailu (Gmail, Yahoo!, Outlook.com, …)" -#: C/shortcuts.page:28(p) -msgid "CtrlL or F" -msgstr "CtrlL nebo F" +#. (itstool) path: item/p +#: C/bugs.page:27 +msgid "Steps to reproduce the bug" +msgstr "Kroky vedoucí k zopakování chyby" -#: C/shortcuts.page:31(p) -msgid "Archive" -msgstr "Archivovat" +#. (itstool) path: item/p +#: C/bugs.page:28 +msgid "What happened?" +msgstr "Co se stalo" -#: C/shortcuts.page:32(key) -msgid "A" -msgstr "A" +#. (itstool) path: item/p +#: C/bugs.page:29 +msgid "What did you expect to happen?" +msgstr "Co jste očekávali, že se má stát" -#: C/shortcuts.page:35(p) -msgid "Trash" -msgstr "Přesunout do koše" +#. (itstool) path: page/p +#: C/bugs.page:32 +msgid "Thanks for your help!" +msgstr "Děkujeme za vaši pomoc!" -#: C/shortcuts.page:36(p) -msgid "Delete or Backspace" -msgstr "Delete nebo Backspace" +#. (itstool) path: page/title +#: C/contributing.page:10 +msgid "Contribute to Geary" +msgstr "Přispívání do Geary" -#: C/shortcuts.page:39(p) -msgid "Delete" -msgstr "Smazat" - -#: C/shortcuts.page:40(p) +#. (itstool) path: page/p +#: C/contributing.page:12 msgid "" -"ShiftDelete or ShiftBackspace" +"Want to help improve Geary? There are a number of ways you can contribute:" msgstr "" -"ShiftDelete nebo ShiftBackspace" - -#: C/shortcuts.page:43(p) -msgid "Star" -msgstr "Přidělit hvězdičku" - -#: C/shortcuts.page:44(key) C/shortcuts.page:100(key) -msgid "S" -msgstr "S" - -#: C/shortcuts.page:47(p) -msgid "Unstar" -msgstr "Odebrat hvězdičku" - -#: C/shortcuts.page:48(key) C/shortcuts.page:138(key) -msgid "D" -msgstr "D" - -#: C/shortcuts.page:51(p) -msgid "Mark read" -msgstr "Označit jako přečtené" +"Chcete pomáhat vylepšovat aplikaci Geary? Zde je několik způsobů, jak můžete " +"přispět:" -#: C/shortcuts.page:52(p) +#. (itstool) path: item/p +#: C/contributing.page:16 msgid "" -"CtrlI or ShiftI" +"Bug " +"reporting—report new bugs or request new features" msgstr "" -"CtrlI nebo ShiftI" +"Hlášení chyb — hlaste nové chyby nebo navrhujte novou funkcionalitu" -#: C/shortcuts.page:55(p) -msgid "Mark unread" -msgstr "Označit jako nepřečtené" - -#: C/shortcuts.page:56(p) +#. (itstool) path: item/p +#: C/contributing.page:19 msgid "" -"CtrlU or ShiftU" +"User Experience " +"Design—research and develop Geary’s user experience" msgstr "" -"CtrlU nebo ShiftU" +"Návrh grafiky a " +"ovládání — zkoumejte a rozvíjejte uživatelskou přívětivost aplikace " +"Geary" -#: C/shortcuts.page:59(p) -msgid "Move the conversation" -msgstr "Přesunout konverzaci" +#. (itstool) path: item/p +#: C/contributing.page:20 +msgid "" +"Development—fix bugs and add new features" +msgstr "" +"Programátorský " +"vývoj — opravujte chyby a přidávejte nové funkce" -#: C/shortcuts.page:60(key) -msgid "M" -msgstr "M" +#. (itstool) path: item/p +#: C/contributing.page:21 +msgid "" +"Translating—translate Geary’s user interface and user manual into new languages" +msgstr "" +"Překládání — překládejte uživatelské rozhraní aplikace Geary a její uživatelskou " +"příručku do svého rodného jazyka" -#: C/shortcuts.page:63(p) -msgid "Label the conversation" -msgstr "Přidat konverzaci štítek" +#. (itstool) path: item/p +#: C/contributing.page:22 +msgid "" +"Join the " +"discussion—on the mailing list or IRC channel" +msgstr "" +"Zapojení do " +"diskuzí — v poštovní konferenci nebo na kanále IRC" -#: C/shortcuts.page:64(key) C/shortcuts.page:162(key) -msgid "L" -msgstr "L" +#. (itstool) path: page/p +#: C/contributing.page:25 +msgid "Thanks for your help making Geary better!" +msgstr "Děkujeme za vaši pomoc ve snaze udělat Geary stále lepším!" -#: C/shortcuts.page:67(p) -msgid "Jump to next (older) conversation" -msgstr "Přejít na následující (starší) konverzaci" +#. (itstool) path: title/media +#. This is a reference to an external file such as an image or video. When +#. the file changes, the md5 hash will change to let you know you need to +#. update your localized copy. The msgstr is not used at all. Set it to +#. whatever you like once you have updated your copy of the file. +#: C/index.page:5 +msgctxt "_" +msgid "external ref='figures/geary.svg' md5='18b50c9e10fe5256ae1cb12aaa3a7600'" +msgstr "" +"external ref='figures/geary.svg' md5='18b50c9e10fe5256ae1cb12aaa3a7600'" -#: C/shortcuts.page:68(key) -msgid "J" -msgstr "J" +#. (itstool) path: page/title +#: C/index.page:5 +msgid " Geary" +msgstr " Geary" -#: C/shortcuts.page:71(p) -msgid "Jump to previous (newer) conversation" -msgstr "Přejít na předchozí (novější) konverzaci" +#. (itstool) path: section/title +#: C/index.page:8 +msgid "Introduction" +msgstr "Úvod" -#: C/shortcuts.page:72(key) C/shortcuts.page:158(key) -msgid "K" -msgstr "K" +#. (itstool) path: section/title +#: C/index.page:12 +msgid "Using Geary" +msgstr "Používání Geary" -#: C/shortcuts.page:75(p) -msgid "Toggle spam" -msgstr "Označit jako spam/zrušit označení spamu" +#. (itstool) path: section/title +#: C/index.page:16 +msgid "Contributing and bug reporting" +msgstr "Přispívání a hlášení chyb" -#: C/shortcuts.page:76(p) -msgid "CtrlJ or !" -msgstr "CtrlJ nebo !" +#. (itstool) path: page/title +#: C/label.page:10 +msgid "Label or move a conversation" +msgstr "Přiřazování štítků a přesun konverzací" -#: C/shortcuts.page:79(p) -msgid "Quit" -msgstr "Ukončit" +#. (itstool) path: section/title +#: C/label.page:12 +msgid "Label a conversation" +msgstr "Přidělení štítku konverzaci" -#: C/shortcuts.page:80(key) C/shortcuts.page:96(key) C/shortcuts.page:100(key) -#: C/shortcuts.page:104(key) C/shortcuts.page:108(key) -#: C/shortcuts.page:112(key) C/shortcuts.page:122(key) -#: C/shortcuts.page:126(key) C/shortcuts.page:130(key) -#: C/shortcuts.page:138(key) C/shortcuts.page:146(key) -#: C/shortcuts.page:150(key) C/shortcuts.page:154(key) -#: C/shortcuts.page:158(key) C/shortcuts.page:162(key) -#: C/shortcuts.page:166(key) C/shortcuts.page:181(key) -msgid "Ctrl" -msgstr "Ctrl" +#. (itstool) path: section/p +#: C/label.page:13 +msgid "" +"Geary lets you apply one or more labels to each conversation. Geary " +"labels correspond to labels in Gmail, or ordinary folders in other mail " +"services." +msgstr "" +"Geary umožňuje každé konverzaci přidělit jeden nebo i více štítků. " +"Štítky jsou u Geary to stejné jako štítky u Gmailu nebo běžné složky u " +"jiných poštovních služeb." -#: C/shortcuts.page:80(key) -msgid "Q" -msgstr "Q" +#. (itstool) path: section/p +#: C/label.page:15 +msgid "" +"To label one or more conversations, first select the conversation(s), then " +"do either of the following:" +msgstr "" +"Když chcte jedné či více konverzacím přidělit štítek, nejprve je vyberte a " +"potom použijte jeden z následujících postupů:" -#: C/shortcuts.page:83(p) -msgid "Zoom in" -msgstr "Přiblížit" - -#: C/shortcuts.page:84(p) -msgid "Ctrl= or =" -msgstr "Ctrl= nebo =" - -#: C/shortcuts.page:87(p) -msgid "Zoom out" -msgstr "Oddálit" - -#: C/shortcuts.page:88(p) -msgid "Ctrl- or -" -msgstr "Ctrl- nebo -" - -#: C/shortcuts.page:91(p) -msgid "Reset zoom" -msgstr "Přiblížit na výchozí velikost" - -#: C/shortcuts.page:92(p) -msgid "Ctrl0 or 0" -msgstr "Ctrl0 nebo 0" +#. (itstool) path: item/p +#: C/label.page:18 +msgid "" +"Click the Label button on the toolbar and select a label from the " +"resulting drop-down menu." +msgstr "" +"Klikněte na tlačítko Štítek na nástrojové liště a vyberte štítek " +"z výsledné rozbalovací nabídky." -#: C/shortcuts.page:95(p) -msgid "Close composer window" -msgstr "Zavřít okno editoru" +#. (itstool) path: item/p +#: C/label.page:20 +msgid "" +"Hold down the Ctrl key and drag the conversation(s) from the " +"conversation list to the label in the sidebar." +msgstr "" +"Držte zmáčknutou klávesu Ctrl a přetáhněte konverzaci či " +"konverzace ze seznamu konverzací do štítku v postranním panelu." -#: C/shortcuts.page:96(key) -msgid "W" -msgstr "W" +#. (itstool) path: section/title +#: C/label.page:25 +msgid "Move a conversation to a folder or label" +msgstr "Přesunutí konverzace do složky nebo štítku" -#: C/shortcuts.page:99(p) -msgid "Jump to search box" -msgstr "Přejít do vyhledávacího pole" +#. (itstool) path: section/p +#: C/label.page:26 +msgid "" +"To move one or more conversations to a folder or label, first select the " +"conversation(s), then do either of the following:" +msgstr "" +"Když chcete jednu nebo více konverzací přesunout do složky nebo štítku, " +"nejprve je vyberte a pak použijte jeden z následujících postupů:" -#: C/shortcuts.page:103(p) -msgid "Find in current conversation" -msgstr "Hledat v aktuální konverzaci" +#. (itstool) path: item/p +#: C/label.page:29 +msgid "" +"Click the Move button on the toolbar and select a folder or label " +"from the resulting drop-down menu." +msgstr "" +"Klikněte na tlačítko Přesunout na nástrojové liště a vyberte " +"složku nebo štítek z výsledné rozbalovací nabídky." -#: C/shortcuts.page:104(key) -msgid "F" -msgstr "F" +# Poznámky: +# Přidat poznámku +#. (itstool) path: item/p +#: C/label.page:31 +msgid "" +"Drag the conversation(s) from the conversation list to the folder or label " +"in the sidebar." +msgstr "" +"Přetáhněte konverzaci či konverzace ze seznamu konverzací do složky nebo " +"štítku v postranním panelu." -#: C/shortcuts.page:107(p) -msgid "Find next in current conversation" -msgstr "Najít následující v aktuální konverzaci" +#. (itstool) path: page/title +#: C/limits.page:9 +msgid "Limitations" +msgstr "Omezení" -#: C/shortcuts.page:108(key) C/shortcuts.page:112(key) -msgid "G" -msgstr "G" +#. (itstool) path: page/p +#: C/limits.page:11 +msgid "" +"Geary is still in early development. Geary supports IMAP and has been tested " +"with Gmail, Yahoo, and the free Dovecot mail server. Experimental support " +"for Outlook.com is provided. Geary may not yet work well with some IMAP " +"servers. At this time Geary is still missing numerous features including " +"offline mode." +msgstr "" +"Geary je prozatím ve stádiu ranného vývoje. Podporuje IMAP a byl testován " +"vůči poštovním serverům Gmail, Yahoo a svobodnému Dovecotu. Pokusně je " +"poskytována podpora pro Outlook.com. S některými jinými servery IMAP nemusí " +"Geary pracovat úplně správně. V současnosti schází také řada různých funkcí, " +"včetně práce při odpojení od sítě." -#: C/shortcuts.page:111(p) -msgid "Find previous in current conversation" -msgstr "Najít předchozí v aktuální konverzaci" +#. (itstool) path: page/p +#: C/limits.page:17 +msgid "" +"To learn more about the features we're working on and the future of Geary, " +"please visit Geary's wiki " +"page." +msgstr "" +"Jestli se chcete dozvědět více od funkcích, na kterých se pracuje a o " +"budoucnosti Geary, navštivte wikistránku projektu Geary." -#: C/shortcuts.page:112(key) -msgid "Shift" -msgstr "Shift" +#. (itstool) path: page/title +#: C/overview.page:8 +msgid "Overview" +msgstr "Přehled" -#: C/shortcuts.page:117(title) -msgid "Composer shortcuts" -msgstr "Klávesové zkratky editoru" +#. (itstool) path: page/p +#: C/overview.page:10 +msgid "" +"Geary is a lightweight email reader for the GNOME desktop. It works with mail servers that support the IMAP " +"protocol, including popular services such as Gmail, Yahoo Mail, and Outlook." +"com." +msgstr "" +"Geary je lehká čtečka e-mailů pro pracovní prostředí GNOME. Pracuje s poštovními servery, které podporují " +"protokol IMAP, včetně populárních služeb jako je Gmail, Yahoo Mail a Outlook." +"com." -#: C/shortcuts.page:118(p) -msgid "These shortcuts are active whenever focus is in a composer." +#. (itstool) path: page/p +#: C/overview.page:14 +msgid "" +"Geary groups mail messages into conversations. A conversation " +"contains all messages in a single thread of discussion." msgstr "" -"Následující klávesové zkratky jsou funkční, kdykoliv je aktivní editor zpráv." +"Geary seskupuje poštu do takzvaných konverzací. Konverzace obsahuje " +"všechny zprávy v jednom vlákně diskuze." -#: C/shortcuts.page:121(p) -msgid "Attach file" -msgstr "Přiložit soubor" +#. (itstool) path: page/p +#: C/overview.page:17 +msgid "The main Geary window is divided into several areas:" +msgstr "Hlavní okno Geary je rozděleno na několik částí:" -#: C/shortcuts.page:122(key) -msgid "T" -msgstr "T" +#. (itstool) path: section/title +#: C/overview.page:20 +msgid "Folder list" +msgstr "Seznam složek" -#: C/shortcuts.page:125(p) -msgid "Quote text" -msgstr "Citovat text" +#. (itstool) path: section/p +#: C/overview.page:21 +msgid "" +"The folder list at the left displays all folders and " +"labels in your mail account. Geary uses the term label for " +"any folder that you have created to help organize your messages. (The Gmail " +"web interface also uses this term; most other mail services do not.)" +msgstr "" +"Seznam složek nalevo zobrazuje všechny složky a " +"štítky ve vašem poštovním účtu. Geary používá výraz štítek " +"pro libovolnou složku, kterou jste vytvořili kvůli roztřídění svých zpráv. " +"(Webové rozhraní Gmail používá tento výraz také, většina ostatních služeb " +"jej ale nepoužívá.)" -#: C/shortcuts.page:126(key) -msgid "]" -msgstr "]" +#. (itstool) path: section/title +#: C/overview.page:28 +msgid "Conversation list" +msgstr "Seznam konverzací" -#: C/shortcuts.page:129(p) -msgid "Unquote text" -msgstr "Ukončit citaci textu" +#. (itstool) path: section/p +#: C/overview.page:29 +msgid "" +"The conversation list displays a list of conversations in the " +"selected folder. Newer conversations appear at the top." +msgstr "" +"Seznam konverzací zobrazuje seznam konverzací ve vybrané složce. " +"Nejnovější konverzace se zobrazují nahoře." -#: C/shortcuts.page:130(key) -msgid "[" -msgstr "[" +#. (itstool) path: section/p +#: C/overview.page:31 +msgid "" +"Each sender's name appears bold if there are unread messages from that " +"sender. If a conversation has more than one message, Geary displays a count " +"of messages in the conversation." +msgstr "" +"Jméno každého odesilatele, od kterého máte nějakou nepřečtenou zprávu, se " +"zobrazí tučně. Pokud je v konverzaci více než jedna zpráva, zobrazí Geary " +"počet zpráv v konverzaci." -#: C/shortcuts.page:133(p) -msgid "Close composer" -msgstr "Zavřít okno editoru" +#. (itstool) path: section/p +#: C/overview.page:34 +msgid "" +"Geary does not automatically download all messages in all of your mail " +"folders. When you first visit your Inbox or any other folder, Geary " +"downloads the 50 most recent messages in that folder. To see more messages, " +"simply scroll down the conversation list and Geary will fetch more messages " +"automatically." +msgstr "" +"Geary automaticky nestahuje všechny zprávy ve všech vašich poštovních " +"složkách. Když poprvé navštívíte svoji složku s doručenou poštou (nebo " +"nějakou jinou), Geary stáhne 50 nejnovějších zpráv ve složce. Abyste viděli " +"více zpráv, stačí se v seznamu konverzace posouvat dolů a další zprávy se " +"boudou automaticky získávat." -#: C/shortcuts.page:134(p) -msgid "CtrlW or Esc" -msgstr "CtrlW nebo Esc" +#. (itstool) path: section/p +#: C/overview.page:36 +msgid "" +"Some commands in Geary can act on a group of conversations. To select " +"multiple conversations, hold down the Ctrl key and click each " +"conversation in turn in the conversation list. Alternatively, click the " +"first conversation in a range, then hold down Shift and click the " +"last conversation." +msgstr "" +"Některé příkazy lze v Geary použít na skupinu konverzací. Abyste označili " +"více konverzací, držte zmáčknutou klávesu Ctrl a postupně v " +"seznamu konverzací klikněte na každou konverzaci, kterou chcete vybrat. " +"Případně můžete označit celý blok tak, že kliknete na první konverzaci, " +"podržíte Shift a kliknete na poslední konverzaci." -#: C/shortcuts.page:137(p) -msgid "Detach composer" -msgstr "Odpojit okno editor" +#. (itstool) path: section/title +#: C/overview.page:44 +msgid "Message area" +msgstr "Oblast zpráv" -#: C/shortcuts.page:142(p) -msgid "These shortcuts are only active in composers in rich text mode." +#. (itstool) path: section/p +#: C/overview.page:45 +msgid "" +"The message area displays all messages in the selected " +"conversation, with the oldest message at the top." msgstr "" -"Následující klávesové zkratky jsou funkční pouze když je editor v režimu " -"formátovaného textu." - -#: C/shortcuts.page:145(p) -msgid "Bold text" -msgstr "Tučný text" +"V oblasti zpráv jsou zobrazeny všechny zprávy z vybrané konverzace, " +"přičemž nejstarší zpráva je nejvíc nahoře." -#: C/shortcuts.page:146(key) C/shortcuts.page:181(key) -msgid "B" -msgstr "B" +#. (itstool) path: section/p +#: C/overview.page:47 +msgid "" +"At the upper right of each message, Geary displays a dropdown arrow that " +"lets you open the message menu with commands that operate on the " +"message." +msgstr "" +"V pravém horním rohu každé zprávy zobrazuje Geary rozbalovací šipku, pomocí " +"které můžete otevřít nabídku zprávy s příkazy týkajícími se zprávy." -#: C/shortcuts.page:149(p) -msgid "Italicize text" -msgstr "Text kurzívou" +#. (itstool) path: section/p +#: C/overview.page:49 +msgid "" +"When you view a conversation, Geary collapses messages that you've already " +"read. Click collapsed messages to expand them. Click an expanded message's " +"header to collapse it." +msgstr "" +"Když si zobrazíte konverzaci, Geary sbalí zprávy, které jste již četli. " +"Kliknutím na sbalené zprávy je rozbalíte. Kliknutím na hlavičku rozbalené " +"zprávy je sbalíte." -#: C/shortcuts.page:150(key) -msgid "I" -msgstr "I" +#. (itstool) path: section/p +#: C/overview.page:50 +msgid "" +"Any attachments in a message appear at the bottom of the message. You can " +"click an attachment to open it or right-click to save it." +msgstr "" +"Případné přílohy zprávy se objeví ve spodní části zprávy. Kliknutím na " +"přílohu ji otevřete, kliknutím pravým tlačítkem ji uložíte." -#: C/shortcuts.page:153(p) -msgid "Underline text" -msgstr "Podtržený text" +#. (itstool) path: section/p +#: C/overview.page:52 +msgid "" +"Geary uses Gravatar to " +"display an avatar for each message's sender in its header." +msgstr "" +"Geary používá Gravatar " +"pro zobrazení avatara u odesilatele zprávy v hlavičce zprávy." -#: C/shortcuts.page:154(key) -msgid "U" -msgstr "U" +#. (itstool) path: page/title +#: C/preferences.page:10 +msgid "Preferences" +msgstr "Předvolby" -#: C/shortcuts.page:157(p) -msgid "Strike text" -msgstr "Přeškrtnutý text" +#. (itstool) path: page/p +#: C/preferences.page:11 +msgid "" +"The Preferences option is available in either Geary's application " +"menu or the gear menu in the upper-right of the toolbar. (The location " +"depends on the install desktop shell. For GNOME Shell and Unity, the " +"application menu is available near the top-left corner of the screen.)" +msgstr "" +"Položku Předvolby najdete v aplikační nabídce Geary a v nabídce v " +"pravém horním rohu nástrojové lišty. (Přesné umístění záleží na " +"nainstalovaném pracovním prostředí. V GNOME Shell a Unity se aplikační " +"nabídky nachází poblíž levého horního rohu obrazovky.)" -#: C/shortcuts.page:161(p) -msgid "Insert a link" -msgstr "Vložit odkaz" +#. (itstool) path: section/title +#: C/preferences.page:17 +msgid "Reading" +msgstr "Čtení" -#: C/shortcuts.page:165(p) -msgid "Remove formatting" -msgstr "Odstranit formátování" +#. (itstool) path: item/title +#: C/preferences.page:20 +msgid "Automatically select next message" +msgstr "Automaticky vybrat další zprávu" -#: C/shortcuts.page:166(key) C/shortcuts.page:186(key) -msgid "Space" -msgstr "mezerník" +#. (itstool) path: item/p +#: C/preferences.page:21 +msgid "" +"When this option is enabled, Geary automatically selects the latest message " +"in a folder when you enter the folder. In addition, after archiving a " +"message, Geary automatically selects an adjacent message." +msgstr "" +"Když je tato volba zapnutá, Geary, po té co vstoupíte do složky, automaticky " +"vybere nejnovější zprávu. Navíc po archivaci zprávy, vybere Geary " +"automaticky sousední zprávu." -#: C/shortcuts.page:172(title) -msgid "Keyboard navigation" -msgstr "Navigace" +#. (itstool) path: item/title +#: C/preferences.page:26 +msgid "Display conversation preview" +msgstr "Zobrazit náhled konverzace" -#: C/shortcuts.page:173(p) +#. (itstool) path: item/p +#: C/preferences.page:27 msgid "" -"These shortcuts can be used to move the keyboard focus in the main window." +"Enables message previews in the conversation list. Previews show the first " +"few lines of each message." msgstr "" -"Následující klávesové zkratky se dají použít k přesunu zaměření v hlavním " -"okně." +"Povoluje náhled zpráv v seznamu konverzací. V náhledu se zobrazuje pár " +"prvních řádků od každé zprávy." -#: C/shortcuts.page:176(p) -msgid "Move focus to the next/previous pane" -msgstr "Přesunout zaměření na následující/předchozí panel" +#. (itstool) path: item/title +#: C/preferences.page:31 +msgid "Use three pane view" +msgstr "Používat třípanelové zobrazení" -#: C/shortcuts.page:177(p) +#. (itstool) path: item/p +#: C/preferences.page:32 msgid "" -"F6 / ShiftF6" +"Show the folder list, the conversation list, and the messages side-by-side-" +"by-side in three panes. If not selected, the folder list and conversation " +"list will be stacked vertically in a single pane." msgstr "" -"F6 / ShiftF6" +"Zobrazí vedle sebe tři panely – seznam složek, seznam konverzací a zprávy. " +"Když není vybráno, budou seznam složek a seznam konverzací svisle nad sebou " +"v jediném panelu." -#: C/shortcuts.page:180(p) -msgid "Move focus to conversation list" -msgstr "Přesunout zaměření na seznam konverzací" +#. (itstool) path: section/title +#: C/preferences.page:40 +msgid "Notifications" +msgstr "Upozornění" -#: C/shortcuts.page:184(p) -msgid "Move to the next message in a conversation" -msgstr "Přesunout se na následující zprávu v konverzaci" +#. (itstool) path: item/title +#: C/preferences.page:43 +msgid "Play notification sounds" +msgstr "Přehrávat zvuková upozornění" + +#. (itstool) path: item/p +#: C/preferences.page:44 +msgid "When set, Geary plays a sound whenever a new message arrives." +msgstr "Když je zapnuto, Geary přehraje zvuk pokaždé, když dorazí nová zpráva." -#: C/shortcuts.page:190(p) -msgid "Move to the next/previous message in a conversation" -msgstr "Přesunout se na následující/předchozí zprávu v konverzaci" +#. (itstool) path: item/title +#: C/preferences.page:47 +msgid "Show notifications for new mail" +msgstr "Zobrazit upozornění na nový e-mail" -#: C/shortcuts.page:191(p) +#. (itstool) path: item/p +#: C/preferences.page:48 msgid "" -"CtrlDown / CtrlUp" +"When set, Geary displays a notification each time a new message " +"arrives. Notifications are displayed in a system-dependent manner. On GNOME " +"Shell, notifications appear at the bottom of the display (older versions) or " +"centered just below the top bar (newer versions). In Ubuntu Unity, " +"notifications appear at the upper right of the display." msgstr "" -"Ctrl / Ctrl" +"Když je zapnuto, zobrazí Geary upozornění pokaždé, když dorazí nová " +"zpráva. Upozornění se zobrazují podle zvyklostí systému. V GNOME Shell se " +"zobrazují ve spodní části obrazovky (starší verze) nebo uprostřed přímo pod " +"horní lištou (novější verze). V Ubuntu Unity se zobrazují v pravém horním " +"rohu obrazovky." -#: C/shortcuts.page:197(p) -msgid "Move to the first/last message in a conversation" -msgstr "Přesunout se na první/poslední zprávu v konverzaci" +#. (itstool) path: item/title +#: C/preferences.page:54 +msgid "Watch for new mail when closed" +msgstr "Sledovat novou poštu při zavřené aplikaci" -#: C/shortcuts.page:198(p) +#. (itstool) path: item/p +#: C/preferences.page:55 msgid "" -"CtrlHome / CtrlEnd" +"Geary will watch your accounts for new mail even when the main window is not " +"open. To do this, it will silently start when you log in to your computer, " +"and it will continue to run after you close all windows." msgstr "" -"CtrlHome / CtrlEnd" +"Geary bude sledovat vaše účty ohledně nové pošty, i když není otevřeno " +"hlavní okno. Aby to mohl dělat, bude tiše spuštěn jakmile se přihlásíte k " +"počítači a bude pokračovat v běhu i po té, co zavřete všechna okna." -#: C/search.page:10(title) +#. (itstool) path: page/title +#: C/search.page:10 msgid "Search" msgstr "Hledání" -#: C/search.page:12(p) +#. (itstool) path: page/p +#: C/search.page:12 msgid "" "Geary supports a per-account full text search. To start a search, select a " "folder associated with the account you'd like to search against. Then click " @@ -587,7 +783,8 @@ "CtrlS) a začněte psát. Po chvilce se " "začnou objevovat výsledky." -#: C/search.page:16(p) +#. (itstool) path: page/p +#: C/search.page:16 msgid "" "The full text search includes email text, email addresses (to, from, and " "cc), subject lines and attachment filenames." @@ -595,7 +792,8 @@ "Rozsah vyhledávání pokrývá text zpráv, e-mailové adresy (od, komu a kopie), " "řádek s předmětem a názvy souborů v přílohách." -#: C/search.page:19(p) +#. (itstool) path: page/p +#: C/search.page:19 msgid "" "Keywords that match your search are highlighted in the message view. Geary " "will match different forms of the same word, for example searching for \"walk" @@ -605,122 +803,129 @@ "zvýrazní. Geary umí rozpoznat i různé varianty téhož slova, například při " "hledání „oběd“ bude vyhovovat i „obědvat“ a „obědový“." -#: C/search.page:23(title) +#. (itstool) path: section/title +#: C/search.page:23 msgid "Search operators" msgstr "Operátory vyhledávání" -#: C/search.page:24(p) +#. (itstool) path: section/p +#: C/search.page:24 msgid "Geary supports the following operators to limit the scope of searches:" msgstr "Geary podporuje následující operátory omezující rozsah vyhledávání:" -#: C/search.page:27(var) -msgid "filename" -msgstr "název_souboru" - -#: C/search.page:27(input) -msgid "attachment:" -msgstr "attachment:" +#. (itstool) path: td/p +#: C/search.page:27 +msgid "attachment:filename" +msgstr "attachment:název_souboru" -#: C/search.page:28(p) +#. (itstool) path: td/p +#: C/search.page:28 msgid "Finds messages with attachments whose name matches filename." msgstr "" "Najít zprávy, jejich přílohy mají název odpovídající názvu_souboru." -#: C/search.page:31(var) C/search.page:39(var) C/search.page:63(var) -msgid "recipient" -msgstr "příjemce" - -#: C/search.page:31(input) -msgid "bcc:" -msgstr "bcc:" +#. (itstool) path: td/p +#: C/search.page:31 +msgid "bcc:recipient" +msgstr "bcc:příjemce" -#: C/search.page:32(p) +#. (itstool) path: td/p +#: C/search.page:32 msgid "Finds messages where recipient matches the BCC header." msgstr "" "Najít zprávy, ve kterých příjemce odpovídá údaji „Skrytá kopie“ v " "hlavičce." -#: C/search.page:35(var) C/search.page:59(var) -msgid "text" -msgstr "text" - -#: C/search.page:35(input) -msgid "body:" -msgstr "body:" +#. (itstool) path: td/p +#: C/search.page:35 +msgid "body:text" +msgstr "body:text" -#: C/search.page:36(p) +#. (itstool) path: td/p +#: C/search.page:36 msgid "Finds messages whose body contains text." msgstr "Najít zprávy, jejichž tělo obsahuje text." -#: C/search.page:39(input) -msgid "cc:" -msgstr "cc:" +#. (itstool) path: td/p +#: C/search.page:39 +msgid "cc:recipient" +msgstr "cc:příjemce" -#: C/search.page:40(p) +#. (itstool) path: td/p +#: C/search.page:40 msgid "Finds messages where recipient matches the CC header." msgstr "" "Najít zprávy, ve kterých příjemce odpovídá údaji „Kopie“ v " "hlavičce." -#: C/search.page:43(var) -msgid "sender" -msgstr "odesilatel" - -#: C/search.page:43(input) -msgid "from:" -msgstr "from:" +#. (itstool) path: td/p +#: C/search.page:43 +msgid "from:sender" +msgstr "from:odesilatel" -#: C/search.page:44(p) +#. (itstool) path: td/p +#: C/search.page:44 msgid "Finds messages where sender matches the From header." msgstr "" "Najít zprávy, ve kterých odesilatel odpovídá údaji „Od“ v " "hlavičce." -#: C/search.page:47(input) -msgid "is:read" -msgstr "is:read" +#. (itstool) path: td/p +#: C/search.page:47 +msgid "is:read" +msgstr "is:read" -#: C/search.page:48(p) +#. (itstool) path: td/p +#: C/search.page:48 msgid "Finds messages that have been marked as read." msgstr "Najít zprávy, které jsou označené jako přečtené." -#: C/search.page:51(input) -msgid "is:starred" -msgstr "is:starred" +#. (itstool) path: td/p +#: C/search.page:51 +msgid "is:starred" +msgstr "is:starred" -#: C/search.page:52(p) +#. (itstool) path: td/p +#: C/search.page:52 msgid "Finds messages that have been marked as starred." msgstr "Najít zprávy, které jsou označené hvězdičkou" -#: C/search.page:55(input) -msgid "is:unread" -msgstr "is:unread" +#. (itstool) path: td/p +#: C/search.page:55 +msgid "is:unread" +msgstr "is:unread" -#: C/search.page:56(p) +#. (itstool) path: td/p +#: C/search.page:56 msgid "Finds messages that have been marked as not read." msgstr "Najít zprávy, které jsou označené jako nepřečtené." -#: C/search.page:59(input) -msgid "subject:" -msgstr "subject:" +#. (itstool) path: td/p +#: C/search.page:59 +msgid "subject:text" +msgstr "subject:text" -#: C/search.page:60(p) +#. (itstool) path: td/p +#: C/search.page:60 msgid "Finds messages whose subject contains text." msgstr "Najít zprávy, jejichž předmět obsahuje text." -#: C/search.page:63(input) -msgid "to:" -msgstr "to:" +#. (itstool) path: td/p +#: C/search.page:63 +msgid "to:recipient" +msgstr "to:příjemce" -#: C/search.page:64(p) +#. (itstool) path: td/p +#: C/search.page:64 msgid "" -"Finds messages where sender matches the To, CC, or BCC header." +"Finds messages where recipient matches the To, CC, or BCC header." msgstr "" -"Najít zprávy, ve kterých odesilatel odpovídá údajům „Komu“, " +"Najít zprávy, ve kterých příjemce odpovídá údajům „Komu“, " "„Kopi“ nebo „Skrytá kopie“ v hlavičce." -#: C/search.page:68(p) +#. (itstool) path: section/p +#: C/search.page:68 msgid "" "As a special case, the bcc, cc, from, and to operators support me as their " @@ -731,607 +936,342 @@ "input>, from a to argument me, " "který hledá e-mailové adresy účtu v příslušném kontextu." -#: C/preferences.page:10(title) -msgid "Preferences" -msgstr "Předvolby" +#. (itstool) path: page/title +#: C/shortcuts.page:10 +msgid "Keyboard shortcuts" +msgstr "Klávesové zkratky" -#: C/preferences.page:11(p) +#. (itstool) path: page/p +#: C/shortcuts.page:12 msgid "" -"The Preferences option is available in either Geary's application " -"menu or the gear menu in the upper-right of the toolbar. (The location " -"depends on the install desktop shell. For GNOME Shell and Unity, the " -"application menu is available near the top-left corner of the screen.)" +"Geary has keyboard shortcuts for most common operations. Use the built-in " +"keyboard shortcuts help in Geary to discover the full list. This can be " +"accessed via the application menu: GearyKeyboard " +"Shortcuts or using the keyboard shortcuts listed below." +msgstr "" +"Geary má pro nejpoužívanější operace klávesové zkratky. Jejich seznam " +"najdete v zabudované nápovědě ke klávesovým zkratkám. K té se dostanete přes " +"nabídku aplikace: Geary Keyboard Shortcuts nebo pomocí níže uvedených klávesových zkratek." + +#. (itstool) path: page/p +#: C/shortcuts.page:18 +msgid "" +"The following keyboard shortcuts can be used to access on-line help from " +"Geary:" +msgstr "" +"Následující klávesové zkratky můžete použít pro přístup k nápovědě on-line z " +"aplikace Geary:" + +#. (itstool) path: td/p +#: C/shortcuts.page:22 +msgid "Display this User Manual" +msgstr "Zobrazit tuto uživatelskou příručku" + +#. (itstool) path: td/p +#: C/shortcuts.page:23 +msgid "F1" +msgstr "F1" + +#. (itstool) path: td/p +#: C/shortcuts.page:26 +msgid "Display all keyboard shortcuts" +msgstr "Zobrazit všechny klávesové zkratky" + +#. (itstool) path: td/p +#: C/shortcuts.page:27 +msgid "" +"Ctrl? or CtrlF1" msgstr "" -"Položku Předvolby najdete v aplikační nabídce Geary a v nabídce v " -"pravém horním rohu nástrojové lišty. (Přesné umístění záleží na " -"nainstalovaném pracovním prostředí. V GNOME Shell a Unity se aplikační " -"nabídky nachází poblíž levého horního rohu obrazovky.)" +"Ctrl? nebo CtrlF1" -#: C/preferences.page:17(title) -msgid "Reading" -msgstr "Čtení" +#. (itstool) path: page/title +#: C/star.page:10 +msgid "Star a message or mark it as read/unread" +msgstr "Označení zprávy hvězdičkou nebo jako přečtená/nepřečtená" -#: C/preferences.page:20(gui) -msgid "Automatically select next message" -msgstr "Automaticky vybrat další zprávu" +#. (itstool) path: section/title +#: C/star.page:12 +msgid "Star messages" +msgstr "Označení zpráv hvězdičkou" -#: C/preferences.page:21(p) +#. (itstool) path: section/p +#: C/star.page:13 msgid "" -"When this option is enabled, Geary automatically selects the latest message " -"in a folder when you enter the folder. In addition, after archiving a " -"message, Geary automatically selects an adjacent message." +"You can star messages to indicate that they're important to you. To mark a " +"conversation with a star, click its star icon in the conversation list. You " +"can star an individual message by clicking the star at the upper right of " +"the message itself." msgstr "" -"Když je tato volba zapnutá, Geary, po té co vstoupíte do složky, automaticky " -"vybere nejnovější zprávu. Navíc po archivaci zprávy, vybere Geary " -"automaticky sousední zprávu." - -#: C/preferences.page:26(gui) -msgid "Display conversation preview" -msgstr "Zobrazit náhled konverzace" +"Zprávy si můžete označovat hvězdičkou, abyste věděli, že jsou pro vás " +"důležité. Pokud tak chcete učinit, klikněte na ikonu hvězdičky u konverzace " +"v seznamu konverzací. Hvězdičkou můžete označit i jednotlivou zprávu, stačí " +"kliknout na hvězdičku v pravém horním rohu zprávy." -#: C/preferences.page:27(p) +#. (itstool) path: section/p +#: C/star.page:15 msgid "" -"Enables message previews in the conversation list. Previews show the first " -"few lines of each message." -msgstr "" -"Povoluje náhled zpráv v seznamu konverzací. V náhledu se zobrazuje pár " -"prvních řádků od každé zprávy." - -#: C/preferences.page:31(gui) -msgid "Use three pane view" -msgstr "Používat třípanelové zobrazení" - -#: C/preferences.page:32(p) -msgid "" -"Show the folder list, the conversation list, and the messages side-by-side-" -"by-side in three panes. If not selected, the folder list and conversation " -"list will be stacked vertically in a single pane." -msgstr "" -"Zobrazí vedle sebe tři panely – seznam složek, seznam konverzací a zprávy. " -"Když není vybráno, budou seznam složek a seznam konverzací svisle nad sebou " -"v jediném panelu." - -#: C/preferences.page:40(title) -msgid "Composer" -msgstr "Editor" - -#: C/preferences.page:43(gui) -msgid "Enable spell checking" -msgstr "Zapnout kontrolu pravopisu" - -#: C/preferences.page:44(p) -msgid "" -"When set, Geary automatically spell checks a message as you write it, " -"underlying each misspelled word in red." -msgstr "" -"Když je zapnuto, Geary automaticky kontroluje pravopis ve zprávě, kterou " -"píšete a chybná slova podtrhává červeně." - -#: C/preferences.page:51(title) -msgid "Notifications" -msgstr "Upozornění" - -#: C/preferences.page:54(gui) -msgid "Play notification sounds" -msgstr "Přehrávat zvuková upozornění" - -#: C/preferences.page:55(p) -msgid "When set, Geary plays a sound whenever a new message arrives." -msgstr "Když je zapnuto, Geary přehraje zvuk pokaždé, když dorazí nová zpráva." - -#: C/preferences.page:58(gui) -msgid "Show notifications for new mail" -msgstr "Zobrazit upozornění na nový e-mail" - -#: C/preferences.page:59(p) -msgid "" -"When set, Geary displays a notification each time a new message " -"arrives. Notifications are displayed in a system-dependent manner. On GNOME " -"Shell, notifications appear at the bottom of the display (older versions) or " -"centered just below the top bar (newer versions). In Ubuntu Unity, " -"notifications appear at the upper right of the display." -msgstr "" -"Když je zapnuto, zobrazí Geary upozornění pokaždé, když dorazí nová " -"zpráva. Upozornění se zobrazují podle zvyklostí systému. V GNOME Shell se " -"zobrazují ve spodní části obrazovky (starší verze) nebo uprostřed přímo pod " -"horní lištou (novější verze). V Ubuntu Unity se zobrazují v pravém horním " -"rohu obrazovky." - -#: C/preferences.page:65(gui) -msgid "Always watch for new mail" -msgstr "Vždy sledovat novou poštu" - -#: C/preferences.page:66(p) -msgid "" -"Geary will watch your accounts for new mail even when the main window is not " -"open. To do this, it will silently start when you log in to your computer, " -"and it will continue to run after you close the main window." -msgstr "" -"Geary bude sledovat vaše účty ohledně nové pošty, i když není otevřeno " -"hlavní okno. Aby to mohl dělat, bude tiše spuštěn jakmile se přihlásíte k " -"počítači a bude pokračovat v běhu i po té, co zavřete hlavní okno." - -#: C/overview.page:8(title) -msgid "Overview" -msgstr "Přehled" - -#: C/overview.page:10(p) -msgid "" -"Geary is a lightweight email reader for the GNOME desktop. It works with mail servers that support the IMAP " -"protocol, including popular services such as Gmail, Yahoo Mail, and Outlook." -"com." -msgstr "" -"Geary je lehká čtečka e-mailů pro pracovní prostředí GNOME. Pracuje s poštovními servery, které podporují " -"protokol IMAP, včetně populárních služeb jako je Gmail, Yahoo Mail a Outlook." -"com." - -#: C/overview.page:14(p) -msgid "" -"Geary groups mail messages into conversations. A conversation " -"contains all messages in a single thread of discussion." -msgstr "" -"Geary seskupuje poštu do takzvaných konverzací. Konverzace obsahuje " -"všechny zprávy v jednom vlákně diskuze." - -#: C/overview.page:17(p) -msgid "The main Geary window is divided into several areas:" -msgstr "Hlavní okno Geary je rozděleno na několik částí:" - -#: C/overview.page:20(title) -msgid "Folder list" -msgstr "Seznam složek" - -#: C/overview.page:21(p) -msgid "" -"The folder list at the left displays all folders and " -"labels in your mail account. Geary uses the term label for " -"any folder that you have created to help organize your messages. (The Gmail " -"web interface also uses this term; most other mail services do not.)" -msgstr "" -"Seznam složek nalevo zobrazuje všechny složky a " -"štítky ve vašem poštovním účtu. Geary používá výraz štítek " -"pro libovolnou složku, kterou jste vytvořili kvůli roztřídění svých zpráv. " -"(Webové rozhraní Gmail používá tento výraz také, většina ostatních služeb " -"jej ale nepoužívá.)" - -#: C/overview.page:28(title) -msgid "Conversation list" -msgstr "Seznam konverzací" - -#: C/overview.page:29(p) -msgid "" -"The conversation list displays a list of conversations in the " -"selected folder. Newer conversations appear at the top." -msgstr "" -"Seznam konverzací zobrazuje seznam konverzací ve vybrané složce. " -"Nejnovější konverzace se zobrazují nahoře." - -#: C/overview.page:31(p) -msgid "" -"Each sender's name appears bold if there are unread messages from that " -"sender. If a conversation has more than one message, Geary displays a count " -"of messages in the conversation." -msgstr "" -"Jméno každého odesilatele, od kterého máte nějakou nepřečtenou zprávu, se " -"zobrazí tučně. Pokud je v konverzaci více než jedna zpráva, zobrazí Geary " -"počet zpráv v konverzaci." - -#: C/overview.page:34(p) -msgid "" -"Geary does not automatically download all messages in all of your mail " -"folders. When you first visit your Inbox or any other folder, Geary " -"downloads the 50 most recent messages in that folder. To see more messages, " -"simply scroll down the conversation list and Geary will fetch more messages " -"automatically." -msgstr "" -"Geary automaticky nestahuje všechny zprávy ve všech vašich poštovních " -"složkách. Když poprvé navštívíte svoji složku s doručenou poštou (nebo " -"nějakou jinou), Geary stáhne 50 nejnovějších zpráv ve složce. Abyste viděli " -"více zpráv, stačí se v seznamu konverzace posouvat dolů a další zprávy se " -"boudou automaticky získávat." - -#: C/overview.page:36(p) -msgid "" -"Some commands in Geary can act on a group of conversations. To select " -"multiple conversations, hold down the Ctrl key and click each " -"conversation in turn in the conversation list. Alternatively, click the " -"first conversation in a range, then hold down Shift and click the " -"last conversation." -msgstr "" -"Některé příkazy lze v Geary použít na skupinu konverzací. Abyste označili " -"více konverzací, držte zmáčknutou klávesu Ctrl a postupně v " -"seznamu konverzací klikněte na každou konverzaci, kterou chcete vybrat. " -"Případně můžete označit celý blok tak, že kliknete na první konverzaci, " -"podržíte Shift a kliknete na poslední konverzaci." - -#: C/overview.page:44(title) -msgid "Message area" -msgstr "Oblast zpráv" - -#: C/overview.page:45(p) -msgid "" -"The message area displays all messages in the selected " -"conversation, with the oldest message at the top." -msgstr "" -"V oblasti zpráv jsou zobrazeny všechny zprávy z vybrané konverzace, " -"přičemž nejstarší zpráva je nejvíc nahoře." - -#: C/overview.page:47(p) -msgid "" -"At the upper right of each message, Geary displays a dropdown arrow that " -"lets you open the message menu with commands that operate on the " -"message." -msgstr "" -"V pravém horním rohu každé zprávy zobrazuje Geary rozbalovací šipku, pomocí " -"které můžete otevřít nabídku zprávy s příkazy týkajícími se zprávy." - -#: C/overview.page:49(p) -msgid "" -"When you view a conversation, Geary collapses messages that you've already " -"read. Click collapsed messages to expand them. Click an expanded message's " -"header to collapse it." -msgstr "" -"Když si zobrazíte konverzaci, Geary sbalí zprávy, které jste již četli. " -"Kliknutím na sbalené zprávy je rozbalíte. Kliknutím na hlavičku rozbalené " -"zprávy je sbalíte." - -#: C/overview.page:50(p) -msgid "" -"Any attachments in a message appear at the bottom of the message. You can " -"click an attachment to open it or right-click to save it." -msgstr "" -"Případné přílohy zprávy se objeví ve spodní části zprávy. Kliknutím na " -"přílohu ji otevřete, kliknutím pravým tlačítkem ji uložíte." - -#: C/overview.page:52(p) -msgid "" -"Geary uses Gravatar to " -"display an avatar for each message's sender in its header." -msgstr "" -"Geary používá Gravatar " -"pro zobrazení avatara u odesilatele zprávy v hlavičce zprávy." - -#: C/limits.page:11(title) -msgid "Limitations" -msgstr "Omezení" - -#: C/limits.page:12(p) -msgid "" -"Geary is still in early development. Geary supports IMAP and has been tested " -"with Gmail, Yahoo, and the free Dovecot mail server. Experimental support " -"for Outlook.com is provided. Geary may not yet work well with some IMAP " -"servers. At this time Geary is still missing numerous features including " -"offline mode." -msgstr "" -"Geary je prozatím ve stádiu ranného vývoje. Podporuje IMAP a byl testován " -"vůči poštovním serverům Gmail, Yahoo a svobodnému Dovecotu. Pokusně je " -"poskytována podpora pro Outlook.com. S některými jinými servery IMAP nemusí " -"Geary pracovat úplně správně. V současnosti schází také řada různých funkcí, " -"včetně práce při odpojení od sítě." - -#: C/limits.page:14(p) -msgid "" -"To learn more about the features we're working on and the future of Geary, " -"please visit Geary's wiki " -"page." -msgstr "" -"Jestli se chcete dozvědět více od funkcích, na kterých se pracuje a o " -"budoucnosti Geary, navštivte wikistránku projektu Geary." - -#: C/label.page:10(title) -msgid "Label or move a conversation" -msgstr "Přiřazování štítků a přesun konverzací" - -#: C/label.page:12(title) -msgid "Label a conversation" -msgstr "Přidělení štítku konverzaci" - -#: C/label.page:13(p) -msgid "" -"Geary lets you apply one or more labels to each conversation. Geary " -"labels correspond to labels in Gmail, or ordinary folders in other mail " -"services." -msgstr "" -"Geary umožňuje každé konverzaci přidělit jeden nebo i více štítků. " -"Štítky jsou u Geary to stejné jako štítky u Gmailu nebo běžné složky u " -"jiných poštovních služeb." - -#: C/label.page:15(p) -msgid "" -"To label one or more conversations, first select the conversation(s), then " -"do either of the following:" -msgstr "" -"Když chcte jedné či více konverzacím přidělit štítek, nejprve je vyberte a " -"potom použijte jeden z následujících postupů:" - -#: C/label.page:18(p) -msgid "" -"Click the Label button on the toolbar and select a label from the " -"resulting drop-down menu." -msgstr "" -"Klikněte na tlačítko Štítek na nástrojové liště a vyberte štítek " -"z výsledné rozbalovací nabídky." - -#: C/label.page:20(p) -msgid "" -"Hold down the Ctrl key and drag the conversation(s) from the " -"conversation list to the label in the sidebar." +"With Gmail accounts, starred messages appear in the Starred folder in the " +"folder list." msgstr "" -"Držte zmáčknutou klávesu Ctrl a přetáhněte konverzaci či " -"konverzace ze seznamu konverzací do štítku v postranním panelu." +"V účtech Gmail se zprávy označené hvězdičkou objeví ve složce „S hvězdičkou“ " +"v seznamu složek." -#: C/label.page:25(title) -msgid "Move a conversation to a folder or label" -msgstr "Přesunutí konverzace do složky nebo štítku" +#. (itstool) path: section/title +#: C/star.page:18 +msgid "Mark messages as read or unread" +msgstr "Označování zpráv, jako přečtené nebo nepřečtené" -#: C/label.page:26(p) +#. (itstool) path: section/p +#: C/star.page:19 msgid "" -"To move one or more conversations to a folder or label, first select the " -"conversation(s), then do either of the following:" +"Geary marks messages as read automatically as you read them. To manually " +"toggle a conversation as read or unread, click the circle icon in the " +"conversation list." msgstr "" -"Když chcete jednu nebo více konverzací přesunout do složky nebo štítku, " -"nejprve je vyberte a pak použijte jeden z následujících postupů:" +"Geary označuje zprávy automaticky jako přečtené, když si je přečtete. Když " +"chcete zprávu jako přečtenou či nepřečtenou označit ručně, klikněte na ikonu " +"kolečka v seznamu konverzace." -#: C/label.page:29(p) +#. (itstool) path: section/p +#: C/star.page:22 msgid "" -"Click the Move button on the toolbar and select a folder or label " -"from the resulting drop-down menu." +"Alternately, the Mark as Unread in the Mark menu on " +"the toolbar can be used to toggle the read status of the selected " +"conversation(s)." msgstr "" -"Klikněte na tlačítko Přesunout na nástrojové liště a vyberte " -"složku nebo štítek z výsledné rozbalovací nabídky." +"Případně můžete k přepnutí stavu přečteno/nepřečteno u vybrané konverzace " +"použít Označit jako přečtené či Označit jako nepřečtené v nabídce Označit na nástrojové liště." -# Poznámky: -# Přidat poznámku -#: C/label.page:31(p) +#. (itstool) path: section/p +#: C/star.page:25 msgid "" -"Drag the conversation(s) from the conversation list to the folder or label " -"in the sidebar." +"To mark an individual message as read, select Mark as Read from " +"the dropdown menu." msgstr "" -"Přetáhněte konverzaci či konverzace ze seznamu konverzací do složky nebo " -"štítku v postranním panelu." - -#. When image changes, this message will be marked fuzzy or untranslated for you. -#. It doesn't matter what you translate it to: it's not used at all. -#: C/index.page:5(None) -msgid "@@image: 'figures/geary.svg'; md5=18b50c9e10fe5256ae1cb12aaa3a7600" -msgstr "@@image: 'figures/geary.svg'; md5=18b50c9e10fe5256ae1cb12aaa3a7600" - -#: C/index.page:5(title) -msgid " Geary" -msgstr " Geary" - -#: C/index.page:9(title) -msgid "Introduction" -msgstr "Úvod" - -#: C/index.page:13(title) -msgid "Using Geary" -msgstr "Používání Geary" - -#: C/index.page:17(title) -msgid "Bugs" -msgstr "Chyby" - -#: C/bugs.page:8(title) -msgid "Think you've found a bug?" -msgstr "Myslíte, že jste našli chybu?" +"Pro označení jednotlivé zprávy jako přečtené vyberte Označit jako " +"přečtené v rozbalovací nabídce." -#: C/bugs.page:9(p) -msgid "" -"If you suspect you've found a bug in Geary, follow these steps to report it:" -msgstr "" -"Pokud předpokládáte, že jste našli v aplikaci Geary chybu, postupujte " -"následovně:" +#. (itstool) path: page/title +#: C/write.page:9 +msgid "Write a message" +msgstr "Psaní zprávy" -#: C/bugs.page:11(p) -msgid "" -"Search Geary's bug database to see if someone else has reported the " -"bug." -msgstr "" -"Podívejte se do databáze chyb aplikace Geary, jestli stejnou chybu " -"nenahlásil již někdo jiný." +#. (itstool) path: section/title +#: C/write.page:12 +msgid "Composing and replying" +msgstr "Psaní zpráv a odpovídání" -#: C/bugs.page:13(p) +#. (itstool) path: section/p +#: C/write.page:13 msgid "" -"Don't see your bug listed? Congratulations! You've found a new bug. To " -"create an bug report, create an account on GNOME's Bugzilla and file a new bug. Be as specific as you can and describe the steps to reproduce it. " -"Don't forget to include details about your operating system and what version " -"of Geary you're running." +"To compose a new message in Geary, press the New Message button " +"on the toolbar." msgstr "" -"Nenašli jste ji? Gratulujeme! Našli jste zbrusu novou chybu. Abyste ji mohli " -"nahlásit, vytvořte si účet v Bugzille projektu GNOME a vyplňte údaje o nové chybě. Buďte co nejvíce konkrétní a popište kroky, kterými se dá chyba " -"reprodukovat. Nezapomeňte zahrnout informace o svém operačním systému a " -"verzi Geary." +"Když chcete v Geary napsat novou zprávu, zmáčkněte na nástrojové liště " +"tlačítko Nová zpráva." -#: C/bugs.page:18(p) +#. (itstool) path: section/p +#: C/write.page:16 msgid "" -"For general inquiries, please join the Geary mailing list." +"To reply to a message, open the message menu in the upper right corner of " +"the message and choose Reply, Reply All or " +"Forward. You can also reply to the last message in a conversation " +"via the Reply, Reply All or Forward buttons " +"on the toolbar." msgstr "" -"Ohledně obecných dotazů se prosím připojte do poštovní konference Geary." +"Když chcete odpovědět na zprávu, otevřete nabídku zprávy v pravém horním " +"rohu zprávy a zvolte Odpovědět, Odpovědět všem nebo " +"Přeposlat. Můžete také odpovědět na poslední zprávu v konverzaci " +"přes tlačítka Odpovědět, Odpovědět všem nebo " +"Přeposlat na nástrojové liště." -#: C/archive.page:10(title) -msgid "Delete or archive a message" -msgstr "Mazání a archivace zpráv" +#. (itstool) path: section/title +#: C/write.page:21 +msgid "Features" +msgstr "Funkce" -#: C/archive.page:12(p) +#. (itstool) path: section/p +#: C/write.page:23 msgid "" -"When you use Geary with a Gmail account, Geary lets you archive " -"messages. The Archive toolbar button archives the selected " -"conversation(s). Archived messages appear in the All Mail folder." +"Geary's email composer lets you adjust the font, size and color of text. You " +"can also insert hyperlinks into messages." msgstr "" -"Pokud používáte Geary s účtem Gmail, umožní vám Geary zprávy archivovat. Tlačítko Archivovat na nástrojové liště archivuje vybranou " -"konverzaci. Archivované zprávy se objeví ve složce Všechny zprávy." +"Při psaní e-mailů v Geary si můžete přizpůsobit písmo, velikost a barvu " +"textu. Rovněž můžete do zpráv vkládat hypertextové odkazy." -#: C/archive.page:16(p) +#. (itstool) path: section/p +#: C/write.page:25 msgid "" -"With other mail servers, you can trash or delete, but not archive, messages. " -"To move one or more conversations to the Trash folder, select " -"them and press the Trash button on the toolbar. To permanently " -"delete the conversations, hold down Shift and press the " -"Delete button that appears in place of the Trash " -"button." +"Geary can also send plain text messages. In the drop-down menu, check or " +"uncheck \"Rich Text\" to toggle between plain text and rich text mode." msgstr "" -"U ostatních poštovních serverů můžete zprávy mazat a přesouvat do koše, ale " -"ne je archivovat. Když chcete jednu nebo více konverzací přesunout do složky " -"Koš, vyberte je a zmáčkněte tlačítko Koš na nástrojové " -"liště. Abyste konverzaci smazali trvale, držte zmáčknutý Shift a " -"zmáčkněte tlačítko Smazat vyskytující se vedle tlačítka Koš." +"Zprávy je možné posílat i jako čistě textové. V rozbalovací nabídce " +"zaškrtněte nebo zrušte zaškrtnutí „Formátovaný text“, abyste se přepnuli " +"mezi režimy prostého textu a formátovaného textu." -#: C/archive.page:21(p) +#. (itstool) path: section/p +#: C/write.page:28 msgid "" -"Delete is not available from every folder, such as Search. Delete is also " -"unavailable for Gmail. For Gmail, Trash will move messages to the " -"Trash folder on the server, where the user can then manually delete them. " -"The server will automatically remove trashed messages after 30 days." -msgstr "" -"Mázání není k dispozici u každé složky, např. není u složky Hledání. Rovněž " -"není k dispozici pro Gmail. U něj tlačítko Koš přesune zprávy do " -"složky koš na serveru, kde je uživatel může smazat ručně. Případně je smaže " -"server automaticky po 30 dnech." - -#: C/accounts.page:10(title) -msgid "Accounts" -msgstr "Účty" - -#: C/accounts.page:13(title) -msgid "Adding accounts" -msgstr "Přidávání účtů" +"You can attach a file to a message you're writing in either of these ways:" +msgstr "K napsané zprávě můžete přiložit soubory a to následujícími způsoby:" -#: C/accounts.page:15(p) +#. (itstool) path: item/p +#: C/write.page:30 msgid "" -"The first time you start Geary, you will be prompted to add an email " -"account. On this screen, select if your account is Gmail, Yahoo, Outlook." -"com, or other. For other account types, you will need to enter your IMAP and " -"SMTP login settings manually." +"Press the Attach File button at the lower left of the composer " +"window, then select a file to attach." msgstr "" -"Když Geary spustíte poprvé, budete dotázáni na přidání poštovního účtu. Na " -"této obrazovce vyberte, jestli máte účet Gmail, Yahoo, Outlook.com nebo " -"jiný. U účtu jiného typu budete muset zadat své nastavení IMAP a SMTP ručně." +"Zmáčkněte tlačítko Přiložit soubor v levém dolním rohu okna " +"editoru a následně vyberte soubor, který chcete přiložit." -#: C/accounts.page:19(p) +#. (itstool) path: item/p +#: C/write.page:32 msgid "" -"Additional accounts can be added from the Accounts dialog. The " -"Accounts option is available in either Geary's application menu " -"or the gear menu in the upper-right of the toolbar. (The location depends on " -"the install desktop shell. For GNOME Shell and Unity, the application menu " -"is available near the top-left corner of the screen.) Alternately, " -"CtrlM will open the Accounts dialog. " -"To add an account, click the + button." +"Drag the file from the Nautilus file manager to the composer window, and " +"drop it either on the text fields at the top of the window or on the toolbar " +"at the bottom." msgstr "" -"Další účty můžete přidat v dialogovém okně Účty. Volbu Účty " -"najdete buď v aplikační nabídce Geary nebo v nabídce v pravém horním rohu " -"nástrojové lišty. (Přesné místo závisí na používaném grafickém shellu. V " -"GNOME Shell a Unity se aplikační nabídka nachází poblíž levého horního rohu " -"obrazovky.) Případně můžete dialogové okno Účty otevřít pomocí " -"CtrlM. Nový účet přidáte kliknutím na " -"tlačítko +." - -#: C/accounts.page:28(title) -msgid "Editing existing accounts" -msgstr "Úprava stávajících účtů" +"Přetáhněte soubor ze správce souborů Nautilus do okna editoru a upusťte jej " +"buď na textových polích v horní části okna nebo na nástrojové liště dole." -#: C/accounts.page:30(p) +#. (itstool) path: section/p +#: C/write.page:36 msgid "" -"From the Accounts dialog, select an account and click the pencil icon to " -"change various settings. Please note that Geary cannot change server " -"settings on an existing account. If you need to change your IMAP or SMTP " -"server, you will need to delete the account and re-add it." +"A number of keyboard shortcuts are available in the composer; see for details." msgstr "" -"V dialogovém okně Účty vyberte účet a po kliknutí na ikonu tužky můžete " -"měnit různá nastavení. Vezměte ale prosím na vědomí, že Geary neumí změnit u " -"existujícího účtu server. Pokud potřebujete změnit server IMAP nebo SMTP, " -"budete muset účet smazat a znovu jej přidat." +"Řada klávesových zkratek je dostupná v editoru zpráv, podrobnosti viz ." -#: C/accounts.page:34(p) +#. (itstool) path: section/p +#: C/write.page:38 msgid "" -"To change the order that accounts are displayed in the folder list, drag the " -"accounts in the Accounts dialog to the desired order." +"You may specify a signature to be inserted into the composer in the dialog." msgstr "" -"Pro změnu pořadí zobrazení účtů v seznamu složek, stačí účet v dialogovém " -"okně Účty přetáhnout na požadované místo." +"V dialogovém okně můžete zadat patičku, která se " +"bude vkládat do editoru." -#: C/accounts.page:37(p) -msgid "There are some advanced options available when editing accounts:" -msgstr "Během úprav účtu jsou k dispozici některá pokročilá nastavení:" +#. (itstool) path: section/title +#: C/write.page:43 +msgid "Drafts" +msgstr "Koncepty" -#: C/accounts.page:39(p) +#. (itstool) path: section/p +#: C/write.page:45 msgid "" -"The Save sent mail checkbox controls whether Geary will push " -"successfully sent messages up to the account's Sent Mail folder. " -"For Gmail accounts, this happens automatically. Yahoo and some other " -"accounts can be configured to do this automatically as well. For other " -"accounts, if you disable this setting, you may be unable to view messages " -"you've sent." +"For mail servers that support drafts, Geary will automatically save the " +"message as you type. If you close the composer without sending, Geary will " +"prompt you to keep the draft or to discard it." msgstr "" -"Zaškrtávací políčko Ukládat odeslanou poštu řídí, jestli se mají " -"úspěšně odeslané zprávy vkládat do složky Odeslaná pošta " -"náležející k účtu. U Gmailu se tak děje automaticky. Yahoo a některé další " -"účty lze nastavit, aby se tak dělo také automaticky. U ostatních účtů " -"nebudete při vypnutí této volby moci procházet zprávy, které jste odeslali." +"Na serverech, které podporují koncepty, ukládá Gerary zprávy automaticky " +"během psaní. Pokud zavřete okno editoru, aniž byste zprávu odeslali, Geary " +"se vás dotáže, jestli má zprávu uchovat v konceptech nebo ji zahodit." -#: C/accounts.page:45(p) +#. (itstool) path: section/p +#: C/write.page:48 msgid "" -"The Sign emails checkbox indicates whether a signature will be " -"automatically inserted when a composer is opened. You may enter the " -"signature into the box immediately below. You may use HTML tags to style the " -"text. Switch to a preview of the signature using the buttons to the right." +"To edit an existing draft, select the Drafts folder in the folder list, " +"select the message, and click \"Edit Draft\" in the message viewer." msgstr "" -"Zaškrtávací políčko Patička e-mailů říká, jestli se má při " -"otevření editoru automaticky vložit patička s podpisem. Obsah patičky můžete " -"zadat do pole níže. Můžete při tom určovat styl pomocí značek HTML. " -"Tlačítkem napravo si můžete zobrazit náhled patičky." +"Až chcete ve psaní stávajícího konceptu pokračovat, vyberte v seznamu složek " +"složku Koncepty, vyberte zprávu a klikněte v prohlížeči zpráv na „Upravit " +"koncept“ ." -#: C/accounts.page:50(p) -msgid "" -"If you leave the signature in the Accounts dialog blank, Geary will use the " -".signature file in your home directory, if it exists. This file " -"may contain either plain text or HTML markup. In the latter case, the markup " -"will be inserted directly into the composer, without any escaping." -msgstr "" -"Pokud ponecháte patičku v dialogovém okně Účty prázdnou, bude Geary používat " -"soubor .signature ve vaší domovské složce (za předpokladu, že " -"soubor existuje). Může obsahovat buď prostý text nebo text formátovaný jako " -"HTML. Ve druhém případě bude vložen do editoru tak, jak je, bez jakéhokoliv " -"ošetření speciálních znaků." +#. (itstool) path: section/p +#: C/write.page:51 +msgid "Geary deletes the draft when you send the message." +msgstr "Po odeslání zprávy Geary koncept smaže." -#: C/accounts.page:55(p) -msgid "" -"The Download mail drop-down allows you to configure how much mail " -"Geary will keep locally. Geary can only use locally available mail when " -"searching and forming conversations." -msgstr "" -"V rozbalovacím seznamu Stahnout e-mail je možné nastavit, kolik " -"zpráv bude Geary uchovávat místně. Jen v místně uložených zprávám může Geary " -"vychledávat a sestavovat z nich konverzace." +#~ msgid "F1" +#~ msgstr "F1" -#: C/accounts.page:63(title) -msgid "Removing accounts" -msgstr "Odebírání účtů" +#~ msgid "filename" +#~ msgstr "název_souboru" -#: C/accounts.page:65(p) -msgid "" -"To delete an account, open the Accounts dialog, select the account, and " -"press the - button. Geary will delete all information associated with the " -"account." -msgstr "" -"Když chcete účet smazat, otevřte dialogové okno Účty, vyberte účet a " -"zmáčkněte tlačítko -. Geary smaže všechny informace související s daným " -"účtem." +#~ msgid "attachment:" +#~ msgstr "attachment:" -#. Put one translator per line, in the form of NAME , YEAR1, YEAR2 -#: C/accounts.page:0(None) -msgid "translator-credits" -msgstr "Marek Černocký " +#~ msgid "recipient" +#~ msgstr "příjemce" + +#~ msgid "bcc:" +#~ msgstr "bcc:" + +#~ msgid "text" +#~ msgstr "text" + +#~ msgid "body:" +#~ msgstr "body:" + +#~ msgid "cc:" +#~ msgstr "cc:" + +#~ msgid "sender" +#~ msgstr "odesilatel" + +#~ msgid "from:" +#~ msgstr "from:" + +#~ msgid "is:read" +#~ msgstr "is:read" + +#~ msgid "is:starred" +#~ msgstr "is:starred" + +#~ msgid "is:unread" +#~ msgstr "is:unread" + +#~ msgid "subject:" +#~ msgstr "subject:" + +#~ msgid "to:" +#~ msgstr "to:" + +#~ msgid "Bugs" +#~ msgstr "Chyby" + +#~ msgid "Think you've found a bug?" +#~ msgstr "Myslíte, že jste našli chybu?" + +#~ msgid "" +#~ "If you suspect you've found a bug in Geary, follow these steps to report " +#~ "it:" +#~ msgstr "" +#~ "Pokud předpokládáte, že jste našli v aplikaci Geary chybu, postupujte " +#~ "následovně:" + +#~ msgid "" +#~ "Search Geary's bug database to see if someone else has reported " +#~ "the bug." +#~ msgstr "" +#~ "Podívejte se do databáze chyb aplikace Geary, jestli stejnou chybu " +#~ "nenahlásil již někdo jiný." + +#~ msgid "" +#~ "Don't see your bug listed? Congratulations! You've found a new bug. To " +#~ "create an bug report, create an account on GNOME's Bugzilla and file a " +#~ "new bug. Be as specific as you can and describe the steps to " +#~ "reproduce it. Don't forget to include details about your operating system " +#~ "and what version of Geary you're running." +#~ msgstr "" +#~ "Nenašli jste ji? Gratulujeme! Našli jste zbrusu novou chybu. Abyste ji " +#~ "mohli nahlásit, vytvořte si účet v Bugzille projektu GNOME a vyplňte údaje " +#~ "o nové chybě. Buďte co nejvíce konkrétní a popište kroky, kterými " +#~ "se dá chyba reprodukovat. Nezapomeňte zahrnout informace o svém operačním " +#~ "systému a verzi Geary." + +#~ msgid "" +#~ "For general inquiries, please join the Geary mailing list." +#~ msgstr "" +#~ "Ohledně obecných dotazů se prosím připojte do poštovní konference Geary." diff -Nru geary-0.12.4/help/de/de.po geary-3.32.0/help/de/de.po --- geary-0.12.4/help/de/de.po 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/help/de/de.po 2019-03-17 13:39:29.000000000 +0000 @@ -5,587 +5,799 @@ # Frank Schiersner , 2014. # Simon Linden , 2014. -# Mario Blättermann , 2017. +# Mario Blättermann , 2017-2019. # msgid "" msgstr "" "Project-Id-Version: Geary master\n" -"POT-Creation-Date: 2017-10-20 05:56+0000\n" -"PO-Revision-Date: 2017-10-24 22:16+0200\n" +"POT-Creation-Date: 2019-01-29 05:49+0000\n" +"PO-Revision-Date: 2019-01-29 08:28+0100\n" "Last-Translator: Mario Blättermann \n" "Language-Team: Deutsch \n" "Language: de\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Generator: Poedit 2.0.3\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Gtranslator 3.30.0\n" +"Plural-Forms: nplurals=2; plural=(n != 1)\n" -#: C/write.page:9(title) -msgid "Write a message" -msgstr "Eine Nachricht schreiben" +#. Put one translator per line, in the form NAME , YEAR1, YEAR2 +msgctxt "_" +msgid "translator-credits" +msgstr "" +"Andreas Wilhelm \n" +"Frank Schiersner \n" +"Simon Linden " -#: C/write.page:12(title) -msgid "Composing and replying" -msgstr "Verfassen und antworten" +#. (itstool) path: page/title +#: C/accounts.page:10 +msgid "Accounts" +msgstr "Konten" -#: C/write.page:13(p) +#. (itstool) path: section/title +#: C/accounts.page:13 +msgid "Adding accounts" +msgstr "Konten hinzufügen" + +#. (itstool) path: section/p +#: C/accounts.page:15 msgid "" -"To compose a new message in Geary, press the New Message button " -"on the toolbar." +"The first time you start Geary, you will be prompted to add an email " +"account. On this screen, select if your account is Gmail, Yahoo, Outlook." +"com, or other. For other account types, you will need to enter your IMAP and " +"SMTP login settings manually." msgstr "" -"Um eine neue Nachricht in Geary zu erstellen, klicken Sie auf den Knopf " -"Neue Nachricht in der Werkzeugleiste." +"Wenn Sie Geary das erste Mal starten, werden Sie gebeten, ein E-Mail-Konto " +"hinzuzufügen. In diesem Fenster wählen Sie aus, ob es sich um ein Gmail-, " +"Yahoo-, Outlook.com oder ein anderes E-Mail-Konto handelt. Für andere Konto-" +"Arten müssen Sie die IMAP- und SMTP-Angaben selbst eingetragen." -#: C/write.page:16(p) +#. (itstool) path: section/p +#: C/accounts.page:19 msgid "" -"To reply to a message, open the message menu in the upper right corner of " -"the message and choose Reply, Reply All or " -"Forward. You can also reply to the last message in a conversation " -"via the Reply, Reply All or Forward buttons " -"on the toolbar." +"Additional accounts can be added from the Accounts dialog. The " +"Accounts option is available in either Geary's application menu " +"or the gear menu in the upper-right of the toolbar. (The location depends on " +"the install desktop shell. For GNOME Shell and Unity, the application menu " +"is available near the top-left corner of the screen.) To add an account, " +"click the + button." msgstr "" -"Um auf eine Nachricht zu antworten, öffnen Sie das Nachrichten-Menü in der " -"oberen rechten Ecke der Nachricht und wählen Sie Antworten, " -"Allen Antworten oder Weiterleiten. Sie können außerdem " -"auf die letzte Nachricht einer Konversation mit Hilfe der Knöpfe " -"Antworten, Allen Antworten und Weiterleiten " -"in der Werkzeugleiste antworten." +"Weitere Konten können Sie im Konten-Dialogfenster hinzufügen. Sie erreichen " +"Konten über Gearys Programm-Menü oder das Menü in der oberen " +"rechten Ecke der Werkzeugleiste. Das hängt von Ihrer Arbeitsumgebung ab. Bei " +"der GNOME Shell und Unity befindet sich das Programmmenü nahe der oberen " +"linken Ecke des Bildschirms. Klicken Sie auf den + Knopf, um ein Konto " +"hinzuzufügen." -#: C/write.page:21(title) -msgid "Features" -msgstr "Besonderheiten" +#. (itstool) path: section/title +#: C/accounts.page:27 +msgid "Editing existing accounts" +msgstr "Konten bearbeiten" -#: C/write.page:23(p) +#. (itstool) path: section/p +#: C/accounts.page:29 msgid "" -"Geary's email composer lets you adjust the font, size and color of text. You " -"can also insert hyperlinks into messages." +"From the Accounts dialog, select an account and click the pencil icon to " +"change various settings. Please note that Geary cannot change server " +"settings on an existing account. If you need to change your IMAP or SMTP " +"server, you will need to delete the account and re-add it." msgstr "" -"Der E-Mail-Editor von Geary ermöglicht es Ihnen, die Schriftart, die " -"Schriftgröße und die Textfarbe anzupassen. Außerdem ist es möglich, Verweise " -"in Nachrichten einzufügen." +"Wählen Sie im Konten-Dialog ein Konto aus und klicken Sie auf das Stift-" +"Symbol, um die Einstellungen zu bearbeiten. Bitte beachten Sie, dass Geary " +"die Server-Einstellungen bestehender Konten nicht verändern kann. Wenn Sie " +"den IMAP- oder SMTP-Server ändern wollen, müssen Sie das bereits " +"existierende Konto löschen und neu anlegen." -#: C/write.page:25(p) +#. (itstool) path: section/p +#: C/accounts.page:33 msgid "" -"Geary can also send plain text messages. In the drop-down menu, check or " -"uncheck \"Rich Text\" to toggle between plain text and rich text mode." +"To change the order that accounts are displayed in the folder list, drag the " +"accounts in the Accounts dialog to the desired order." msgstr "" -"Geary kann auch einfache Text-Nachrichten verschicken. Aktivieren oder " -"deaktivieren Sie »Rich Text« im Auswahl-Menü, um zwischen einfachem Text- " -"und Rich-Text-Modus zu wechseln." +"Um die Reihenfolge zu ändern, in der die Konten in der Ordnerliste angezeigt " +"werden, ziehen Sie die Konten im Konten-Dialog an die gewünschte Position." -#: C/write.page:28(p) -msgid "" -"You can attach a file to a message you're writing in either of these ways:" -msgstr "" -"Sie können eine Datei an eine gerade verfasste Nachricht wie folgt anhängen:" +#. (itstool) path: section/p +#: C/accounts.page:36 +msgid "There are some advanced options available when editing accounts:" +msgstr "In den Konten-Einstellungen gibt es folgende erweiterte Möglichkeiten:" -#: C/write.page:30(p) +#. (itstool) path: item/p +#: C/accounts.page:38 msgid "" -"Press the Attach File button at the lower left of the composer " -"window, then select a file to attach." +"The Save sent mail checkbox controls whether Geary will push " +"successfully sent messages up to the account's Sent Mail folder. " +"For Gmail accounts, this happens automatically. Yahoo and some other " +"accounts can be configured to do this automatically as well. For other " +"accounts, if you disable this setting, you may be unable to view messages " +"you've sent." msgstr "" -"Klicken Sie auf den Knopf Datei anhängen in der unteren linken " -"Ecke des Editor-Fensters und wählen Sie anschließend die anzuhängende Datei." +"Das Ankreuzfeld Gesendete E-Mail speichern legt fest, ob Geary " +"erfolgreich gesendete Nachrichten in den Ordner Gesendete E-Mails " +"verschiebt. Für Gmail-Konten erfolgt das automatisch. Yahoo und einige " +"andere können so eingestellt werden, dass sie gesendete E-Mails ebenfalls " +"automatisch verschieben. Bei Konten, bei denen Sie diese Möglichkeit " +"ausgeschaltet haben, können Sie die von Ihnen gesendeten E-Mails nicht mehr " +"aufrufen." -#: C/write.page:32(p) +#. (itstool) path: item/p +#: C/accounts.page:44 msgid "" -"Drag the file from the Nautilus file manager to the composer window, and " -"drop it either on the text fields at the top of the window or on the toolbar " -"at the bottom." +"The Sign emails checkbox indicates whether a signature will be " +"automatically inserted when a composer is opened. You may enter the " +"signature into the box immediately below. You may use HTML tags to style the " +"text. Switch to a preview of the signature using the buttons to the right." msgstr "" -"Ziehen Sie die Datei aus der Nautilus-Dateiverwaltung in das Editor-Fenster " -"und legen Sie diese entweder im Textfeld oben im Fenster oder in der " -"Werkzeugleiste im unteren Teil des Fensters ab." +"Das Ankreuzfeld E-Mails unterzeichnen zeigt an ob beim öffnen des " +"E-Mail-Editors automatisch eine Signatur in die E-Mail eingefügt wird. In " +"dem Eingabefeld direkt darunter können Sie die Signatur eingeben. Um den " +"Text hervorzuheben, können Sie auch HTML-Tags verwenden. Mit den Knöpfen auf " +"der rechten Seite wechseln Sie zur Vorschau der Signatur." -#: C/write.page:36(p) +#. (itstool) path: item/p +#: C/accounts.page:49 msgid "" -"A number of keyboard shortcuts are available in the composer; see for details." +"If you leave the signature in the Accounts dialog blank, Geary will use the " +".signature file in your home directory, if it exists. This file " +"may contain either plain text or HTML markup. In the latter case, the markup " +"will be inserted directly into the composer, without any escaping." msgstr "" -"Im Editor stehen eine große Zahl von Tastaturkürzeln zur Verfügung; Details " -"unter ." +"Wenn Sie im Konten-Dialog kein Signatur einstellen, wird Geary den Inhalt " +"der Datei .signature aus Ihrem Persönlicher Ordner verwenden, " +"sofern diese vorhanden ist. Diese Datei kann sowohl reinen Text als auch " +"HTML-Textauszeichnungen enthalten. Im letzteren Fall werden die " +"Textauszeichnungen direkt und ohne Maskierung im E-Mail-Editor eingefügt." -#: C/write.page:38(p) +#. (itstool) path: item/p +#: C/accounts.page:54 msgid "" -"You may specify a signature to be inserted into the composer in the dialog." +"The Download mail drop-down allows you to configure how much mail " +"Geary will keep locally. Geary can only use locally available mail when " +"searching and forming conversations." msgstr "" -"Sie können im -Dialog eine Signatur angeben, welche " -"dann im E-Mail-Editor eingefügt wird." - -#: C/write.page:43(title) -msgid "Drafts" -msgstr "Entwürfe" +"Über das Mail herunterladen-Auswahlmenü können Sie festlegen, " +"wieviel E-Mail Geary lokal behält. Geary kann nur lokal verfügbare E-Mails " +"berücksichtigen, wenn es um das Durchsuchen oder das Bündeln von Nachrichten " +"in Konversationen geht." -#: C/write.page:45(p) -msgid "" -"For mail servers that support drafts, Geary will automatically save the " -"message as you type. If you close the composer without sending, Geary will " -"prompt you to keep the draft or to discard it." -msgstr "" -"Unterstützt ein E-Mail Server das Speichern von Entwürfen, speichert Geary " -"die Nachricht automatisch während Sie schreiben. Schließen Sie den Editor, " -"ohne die Nachricht zu senden, fragt Geary, ob die Nachricht als Entwurf " -"gespeichert oder verworfen werden soll." +#. (itstool) path: section/title +#: C/accounts.page:62 +msgid "Removing accounts" +msgstr "Konten löschen" -#: C/write.page:48(p) +#. (itstool) path: section/p +#: C/accounts.page:64 msgid "" -"To edit an existing draft, select the Drafts folder in the folder list, " -"select the message, and click \"Edit Draft\" in the message viewer." +"To delete an account, open the Accounts dialog, select the account, and " +"press the - button. Geary will delete all information associated with the " +"account." msgstr "" -"Um einen bereits existierenden Entwurf zu bearbeiten, wählen Sie den " -"Entwürfe-Ordner aus der Ordnerliste, wählen die Nachricht und wählen in der " -"Nachrichten Ansicht Entwurf bearbeiten." +"Um ein Konto zu löschen, öffnen Sie den Konten-Dialog, wählen das " +"betreffende Konto und klicken auf den - Knopf. Geary wird alle mit diesem " +"Konto verknüpften Daten löschen." -#: C/write.page:51(p) -msgid "Geary deletes the draft when you send the message." -msgstr "Geary löscht den Entwurf, wenn die Nachricht gesendet wird." +#. (itstool) path: page/title +#: C/archive.page:10 +msgid "Delete or archive a message" +msgstr "Eine Nachricht löschen oder archivieren" -#: C/star.page:10(title) -msgid "Star a message or mark it as read/unread" +#. (itstool) path: page/p +#: C/archive.page:12 +msgid "" +"When you use Geary with a Gmail account, Geary lets you archive " +"messages. The Archive toolbar button archives the selected " +"conversation(s). Archived messages appear in the All Mail folder." msgstr "" -"Eine Nachricht mit Sternchen versehen oder als gelesen/ungelesen markieren" - -#: C/star.page:12(title) -msgid "Star messages" -msgstr "Nachrichten mit Sternchen versehen " +"Wenn Sie Geary mit einem Gmail-Konto verwenden, können Sie Nachrichten " +"archivieren. Der Archivieren Knopf in der Werkzeugleiste " +"archiviert die ausgewählte(n) Konversation(en). Archivierte Nachrichten " +"erscheinen im Alle E-Mails Ordner." -#: C/star.page:13(p) +#. (itstool) path: page/p +#: C/archive.page:16 msgid "" -"You can star messages to indicate that they're important to you. To mark a " -"conversation with a star, click its star icon in the conversation list. You " -"can star an individual message by clicking the star at the upper right of " -"the message itself." +"With other mail servers, you can trash or delete, but not archive, messages. " +"To move one or more conversations to the Trash folder, select " +"them and press the Trash button on the toolbar. To permanently " +"delete the conversations, hold down Shift and press the " +"Delete button that appears in place of the Trash " +"button." msgstr "" -"Sie können Nachrichten mit Sternchen versehen, um sie als besonders wichtig " -"zu kennzeichnen. Um eine Konversation mit einem Sternchen zu versehen, " -"klicken Sie auf das Sternchen-Symbol in der Konversationsliste. Eine " -"einzelne Nachricht können Sie mit einem Sternchen versehen, indem Sie auf " -"das Sternchen-Symbol in der oberen rechten Ecke der Nachricht klicken." +"Bei anderen E-Mail-Servern können Sie Nachrichten in den Papierkorb " +"verschieben oder löschen, nicht aber archivieren. Um eine oder mehrere " +"Konversationen in den Papierkorb zu verschieben, wählen Sie diese " +"aus und klicken den Papierkorb-Knopf in der Werkzeugleiste. Um " +"die Konversationen vollständig zu löschen, halten Sie die " +"Umschalttaste gedrückt und klicken auf den Löschen-" +"Knopf, der an Stelle von Papierkorb erscheint." -#: C/star.page:15(p) +#. (itstool) path: page/p +#: C/archive.page:21 msgid "" -"With Gmail accounts, starred messages appear in the Starred folder in the " -"folder list." +"Delete is not available from every folder, such as Search. Delete is also " +"unavailable for Gmail. For Gmail, Trash will move messages to the " +"Trash folder on the server, where the user can then manually delete them. " +"The server will automatically remove trashed messages after 30 days." msgstr "" -"Bei Gmail-Konten erscheinen mit Sternchen markierte Nachrichten im " -"Markiert-Ordner in der Ordnerliste." +"Löschen ist in manchem Ordner, wie beispielsweise Suche, nicht möglich. " +"Löschen ist auch bei Gmail nicht möglich. Bei Gmail verschiebt " +"Papierkorb Nachrichten nur in den Papierkorb-Ordner des " +"Verzeichnisses auf dem Server, von wo Sie sie von Hand löschen können. Der " +"Server löscht sie automatisch nach 30 Tagen." -#: C/star.page:18(title) -msgid "Mark messages as read or unread" -msgstr "Nachrichten als gelesen oder ungelesen markieren" +#. (itstool) path: page/title +#: C/bugs.page:10 +msgid "Found a bug?" +msgstr "Einen Fehler gefunden?" -#: C/star.page:19(p) +#. (itstool) path: page/p +#: C/bugs.page:12 msgid "" -"Geary marks messages as read automatically as you read them. To manually " -"toggle a conversation as read or unread, click the circle icon in the " -"conversation list." +"If you suspect you've found a bug in Geary, please get in touch about it so it can be " +"fixed." msgstr "" -"Geary markiert Nachrichten automatisch als gelesen, während Sie sie lesen. " -"Um eine Konversation von Hand als gelesen oder ungelesen zu markieren, " -"klicken Sie auf das Kreis-Symbol in der Konversationsliste." +"Falls Sie den Verdacht haben, einen Fehler in Geary gefunden zu haben, nehmen Sie mit uns " +"Kontakt auf, damit der Fehler beseitigt werden kann." -#: C/star.page:22(p) +#. (itstool) path: page/p +#: C/bugs.page:16 msgid "" -"Alternately, the Mark as Unread in the Mark menu on " -"the toolbar can be used to toggle the read status of the selected " -"conversation(s)." +"To help diagnose the problem as fast as possible, please include the " +"following information:" msgstr "" -"Alternativ kann auch Als ungelesen markieren aus dem " -"Markierungen-Menü in der Werkzeugleiste verwendet werden, um den " -"Gelesen-Status der Konversation zu ändern." +"Sie können dabei helfen, das Problem so schnell wie möglich zu " +"diagnostizieren, indem Sie folgende Informationen übermitteln:" -#: C/star.page:25(p) -msgid "" -"To mark an individual message as read, select Mark as Read from " -"the dropdown menu." +#. (itstool) path: item/p +#: C/bugs.page:20 +msgid "Geary version and installation method (Package? Flathub? Source code?)" msgstr "" -"Um eine einzelne Nachricht als gelesen zu markieren, wählen Sie Als " -"gelesen markieren aus dem Auswahlmenü." - -#: C/shortcuts.page:11(title) -msgid "Keyboard shortcuts" -msgstr "Tastaturkürzel" - -#: C/shortcuts.page:12(p) -msgid "Geary has keyboard shortcuts for most common operations." -msgstr "Geary bietet Tastenkürzel für die meisten gängigen Operationen." - -#: C/shortcuts.page:15(p) -msgid "Compose a new message" -msgstr "Neue Nachricht erstellen" - -#: C/shortcuts.page:16(p) -msgid "CtrlN or N" -msgstr "StrgN oder N" - -#: C/shortcuts.page:19(p) -msgid "Reply to sender" -msgstr "Antwort an Absender" +"Die Version von Geary und die Installationsmethode (Paket? Flathub? " +"Quellcode?)" -#: C/shortcuts.page:20(p) -msgid "CtrlR or R" -msgstr "StrgR oder R" +#. (itstool) path: item/p +#: C/bugs.page:22 +msgid "Your desktop (GNOME? KDE? Something else?)" +msgstr "Ihre Arbeitsumgebung (GNOME? KDE? Andere?)" -#: C/shortcuts.page:23(p) -msgid "Reply to all" -msgstr "Allen antworten" - -#: C/shortcuts.page:24(p) +#. (itstool) path: item/p +#: C/bugs.page:23 msgid "" -"CtrlShiftR or " -"ShiftR" +"Your operating system and version (Ubuntu 16.04? Fedora 28? Rolled your own?)" msgstr "" -"StrgUmschalttasteR oder " -"UmschalttasteR" - -#: C/shortcuts.page:27(p) -msgid "Forward" -msgstr "Weiterleiten" +"Ihr Betriebssystem und dessen Version (Ubuntu 16.04? Fedora 28? Ihr eigenes?)" -#: C/shortcuts.page:28(p) -msgid "CtrlL or F" -msgstr "StrgL oder F" +#. (itstool) path: item/p +#: C/bugs.page:25 +msgid "Email provider (Gmail, Yahoo!, Outlook.com, or someone else?)" +msgstr "E-Mail-Dienstanbieter (Gmail, Yahoo!, Outlook.com oder etwas anderes?)" -#: C/shortcuts.page:31(p) -msgid "Archive" -msgstr "Archiv" +#. (itstool) path: item/p +#: C/bugs.page:27 +msgid "Steps to reproduce the bug" +msgstr "Schritte, um den Fehler zu reproduzieren" -#: C/shortcuts.page:32(key) -msgid "A" -msgstr "A" +#. (itstool) path: item/p +#: C/bugs.page:28 +msgid "What happened?" +msgstr "Was ist passiert?" -#: C/shortcuts.page:35(p) -msgid "Trash" -msgstr "Papierkorb" +#. (itstool) path: item/p +#: C/bugs.page:29 +msgid "What did you expect to happen?" +msgstr "Was hätte erwartungsgemäß passieren sollen?" -#: C/shortcuts.page:36(p) -msgid "Delete or Backspace" -msgstr "Entfernen oder Rücktaste" +#. (itstool) path: page/p +#: C/bugs.page:32 +msgid "Thanks for your help!" +msgstr "Danke für Ihre Hilfe!" -#: C/shortcuts.page:39(p) -msgid "Delete" -msgstr "Löschen" +#. (itstool) path: page/title +#: C/contributing.page:10 +msgid "Contribute to Geary" +msgstr "Zu Geary beitragen" -#: C/shortcuts.page:40(p) +#. (itstool) path: page/p +#: C/contributing.page:12 msgid "" -"ShiftDelete or ShiftBackspace" +"Want to help improve Geary? There are a number of ways you can contribute:" msgstr "" -"UmschalttasteEntf oder " -"UmschalttasteRücktaste" - -#: C/shortcuts.page:43(p) -msgid "Star" -msgstr "Mit Sternchen markieren" - -#: C/shortcuts.page:44(key) C/shortcuts.page:100(key) -msgid "S" -msgstr "S" +"Wollen Sie dabei helfen, Geary zu verbessern? Es gibt verschiedene " +"Möglichkeiten, etwas beizutragen:" -#: C/shortcuts.page:47(p) -msgid "Unstar" -msgstr "Sternchen-Markierung entfernen" - -#: C/shortcuts.page:48(key) C/shortcuts.page:138(key) -msgid "D" -msgstr "D" - -#: C/shortcuts.page:51(p) -msgid "Mark read" -msgstr "Als gelesen markieren" - -#: C/shortcuts.page:52(p) +#. (itstool) path: item/p +#: C/contributing.page:16 msgid "" -"CtrlI or ShiftI" +"Bug " +"reporting—report new bugs or request new features" msgstr "" -"StrgI oder " -"UmschalttasteI" +"Melden Sie " +"Fehler oder fragen Sie nach neuen Funktionsmerkmalen" -#: C/shortcuts.page:55(p) -msgid "Mark unread" -msgstr "Als ungelesen markieren" +#. (itstool) path: item/p +#: C/contributing.page:19 +msgid "" +"User Experience " +"Design—research and develop Geary’s user experience" +msgstr "" +"Design der " +"Benutzeroberfläche — verbessern Sie das Benutzererlebnis für Geary" -#: C/shortcuts.page:56(p) +#. (itstool) path: item/p +#: C/contributing.page:20 msgid "" -"CtrlU or ShiftU" +"Development—fix bugs and add new features" msgstr "" -"StrgU oder " -"UmschalttasteU" +"Entwickler — korrigieren Sie Fehler und fügen Sie neue Funktionsmerkmale hinzu" -#: C/shortcuts.page:59(p) -msgid "Move the conversation" -msgstr "Eine Konversation verschieben" +#. (itstool) path: item/p +#: C/contributing.page:21 +msgid "" +"Translating—translate Geary’s user interface and user manual into new languages" +msgstr "" +"Übersetzen Sie die Benutzeroberfläche oder das Handbuch von Geary in weitere " +"Sprachen" -#: C/shortcuts.page:60(key) -msgid "M" -msgstr "M" +#. (itstool) path: item/p +#: C/contributing.page:22 +msgid "" +"Join the " +"discussion—on the mailing list or IRC channel" +msgstr "" +"Diskutieren Sie " +"mit – auf der Mailingliste oder im IRC-Kanal" -#: C/shortcuts.page:63(p) -msgid "Label the conversation" -msgstr "Eine Konversation mit einer Beschriftung versehen" +#. (itstool) path: page/p +#: C/contributing.page:25 +msgid "Thanks for your help making Geary better!" +msgstr "Danke dass Sie mithelfen, Geary zu verbessern!" -#: C/shortcuts.page:64(key) C/shortcuts.page:162(key) -msgid "L" -msgstr "L" +#. (itstool) path: title/media +#. This is a reference to an external file such as an image or video. When +#. the file changes, the md5 hash will change to let you know you need to +#. update your localized copy. The msgstr is not used at all. Set it to +#. whatever you like once you have updated your copy of the file. +#: C/index.page:5 +msgctxt "_" +msgid "external ref='figures/geary.svg' md5='18b50c9e10fe5256ae1cb12aaa3a7600'" +msgstr "original'" -#: C/shortcuts.page:67(p) -msgid "Jump to next (older) conversation" -msgstr "Zur nächsten (älteren) Konversation wechseln" +#. (itstool) path: page/title +#: C/index.page:5 +msgid " Geary" +msgstr " Geary" -#: C/shortcuts.page:68(key) -msgid "J" -msgstr "J" +#. (itstool) path: section/title +#: C/index.page:8 +msgid "Introduction" +msgstr "Einführung" -#: C/shortcuts.page:71(p) -msgid "Jump to previous (newer) conversation" -msgstr "Zur vorhergehenden (jüngeren) Konversation wechseln" +#. (itstool) path: section/title +#: C/index.page:12 +msgid "Using Geary" +msgstr "Geary verwenden" -#: C/shortcuts.page:72(key) C/shortcuts.page:158(key) -msgid "K" -msgstr "K" +#. (itstool) path: section/title +#: C/index.page:16 +msgid "Contributing and bug reporting" +msgstr "Mitwirken und Fehler melden" -#: C/shortcuts.page:75(p) -msgid "Toggle spam" -msgstr "Unerwünscht-Markierung umschalten" +#. (itstool) path: page/title +#: C/label.page:10 +msgid "Label or move a conversation" +msgstr "Eine Konversation mit einer Beschriftung versehen oder verschieben" -#: C/shortcuts.page:76(p) -msgid "CtrlJ or !" -msgstr "StrgJ oder !" +#. (itstool) path: section/title +#: C/label.page:12 +msgid "Label a conversation" +msgstr "Eine Konversation mit einer Beschriftung versehen" -#: C/shortcuts.page:79(p) -msgid "Quit" -msgstr "Beenden" - -#: C/shortcuts.page:80(key) C/shortcuts.page:96(key) C/shortcuts.page:100(key) -#: C/shortcuts.page:104(key) C/shortcuts.page:108(key) -#: C/shortcuts.page:112(key) C/shortcuts.page:122(key) -#: C/shortcuts.page:126(key) C/shortcuts.page:130(key) -#: C/shortcuts.page:138(key) C/shortcuts.page:146(key) -#: C/shortcuts.page:150(key) C/shortcuts.page:154(key) -#: C/shortcuts.page:158(key) C/shortcuts.page:162(key) -#: C/shortcuts.page:166(key) C/shortcuts.page:181(key) -msgid "Ctrl" -msgstr "Strg" - -#: C/shortcuts.page:80(key) -msgid "Q" -msgstr "Q" - -#: C/shortcuts.page:83(p) -msgid "Zoom in" -msgstr "Ansicht vergrößern" - -#: C/shortcuts.page:84(p) -msgid "Ctrl= or =" -msgstr "Strg= oder =" - -#: C/shortcuts.page:87(p) -msgid "Zoom out" -msgstr "Ansicht verkleinern" - -#: C/shortcuts.page:88(p) -msgid "Ctrl- or -" -msgstr "Strg- oder -" +#. (itstool) path: section/p +#: C/label.page:13 +msgid "" +"Geary lets you apply one or more labels to each conversation. Geary " +"labels correspond to labels in Gmail, or ordinary folders in other mail " +"services." +msgstr "" +"Mit Geary können Sie einer Konversation eine oder mehrere " +"Beschriftungen zuweisen. Gearys Beschriftungen entsprechen Gmail-" +"Markierungen oder normalen Ordnern anderer E-Mail-Dienste." -#: C/shortcuts.page:91(p) -msgid "Reset zoom" -msgstr "Ansicht zurücksetzen" +#. (itstool) path: section/p +#: C/label.page:15 +msgid "" +"To label one or more conversations, first select the conversation(s), then " +"do either of the following:" +msgstr "" +"Um eine oder mehrere Konversationen mit einer Beschriftung zu versehen, " +"wählen Sie zuerst die Konversation(en) aus und können dann einen der " +"folgenden Wege nutzen:" -#: C/shortcuts.page:92(p) -msgid "Ctrl0 or 0" -msgstr "Strg0 oder 0" +#. (itstool) path: item/p +#: C/label.page:18 +msgid "" +"Click the Label button on the toolbar and select a label from the " +"resulting drop-down menu." +msgstr "" +"Klicken Sie den Beschriftung-Knopf in der Werkzeugleiste und " +"wählen Sie eine Beschriftung aus dem erscheinenden Auswahlmenü." -#: C/shortcuts.page:95(p) -msgid "Close composer window" -msgstr "Editor-Fenster schließen" +#. (itstool) path: item/p +#: C/label.page:20 +msgid "" +"Hold down the Ctrl key and drag the conversation(s) from the " +"conversation list to the label in the sidebar." +msgstr "" +"Drücken Sie die Strg-Taste und ziehen Sie die Konversation(en) " +"aus der Konversationsliste auf die Beschriftung in der Seitenleiste." -#: C/shortcuts.page:96(key) -msgid "W" -msgstr "W" +#. (itstool) path: section/title +#: C/label.page:25 +msgid "Move a conversation to a folder or label" +msgstr "Verschieben einer Konversation in einen Ordner oder eine Beschriftung" -#: C/shortcuts.page:99(p) -msgid "Jump to search box" -msgstr "Zur Suche springen" +#. (itstool) path: section/p +#: C/label.page:26 +msgid "" +"To move one or more conversations to a folder or label, first select the " +"conversation(s), then do either of the following:" +msgstr "" +"Um eine oder mehrere Konversationen in einen Ordner oder eine Beschriftung " +"zu verschieben, wählen Sie zunächst die Konversation(en) aus und können dann " +"einen der folgenden Wege nutzen:" -#: C/shortcuts.page:103(p) -msgid "Find in current conversation" -msgstr "Aktuelle Konversation durchsuchen" +#. (itstool) path: item/p +#: C/label.page:29 +msgid "" +"Click the Move button on the toolbar and select a folder or label " +"from the resulting drop-down menu." +msgstr "" +"Klicken Sie den Verschieben Knopf in der Werkzeugleiste und " +"wählen Sie einen Ordner oder eine Beschriftung aus dem erscheinenden " +"Auswahlmenü aus." -#: C/shortcuts.page:104(key) -msgid "F" -msgstr "F" +#. (itstool) path: item/p +#: C/label.page:31 +msgid "" +"Drag the conversation(s) from the conversation list to the folder or label " +"in the sidebar." +msgstr "" +"Ziehen Sie die Konversation(en) aus der Konversationsliste auf den Ordner " +"oder die Beschriftung in der Seitenleiste." -#: C/shortcuts.page:107(p) -msgid "Find next in current conversation" -msgstr "Nächstes in aktueller Konversation finden" +#. (itstool) path: page/title +#: C/limits.page:9 +msgid "Limitations" +msgstr "Einschränkungen" -#: C/shortcuts.page:108(key) C/shortcuts.page:112(key) -msgid "G" -msgstr "G" +#. (itstool) path: page/p +#: C/limits.page:11 +msgid "" +"Geary is still in early development. Geary supports IMAP and has been tested " +"with Gmail, Yahoo, and the free Dovecot mail server. Experimental support " +"for Outlook.com is provided. Geary may not yet work well with some IMAP " +"servers. At this time Geary is still missing numerous features including " +"offline mode." +msgstr "" +"Geary befindet sich noch in einem frühen Entwicklungsstadium. Es unterstützt " +"IMAP und wurde mit Gmail, Yahoo und dem freien Dovecot E-Mail-Server " +"getestet. Die Unterstützung von Outlook.com ist noch experimentell. Es kann " +"sein, dass Geary mit einigen IMAP-Servern noch nicht gut zusammen arbeitet. " +"Zum aktuellen Zeitpunkt fehlen Geary noch viele Leistungsmerkmale, wie " +"beispielsweise ein Offline Modus." -#: C/shortcuts.page:111(p) -msgid "Find previous in current conversation" -msgstr "Vorheriges in aktueller Konversation finden" +#. (itstool) path: page/p +#: C/limits.page:17 +msgid "" +"To learn more about the features we're working on and the future of Geary, " +"please visit Geary's wiki " +"page." +msgstr "" +"Um mehr über die angestrebten Funktionen und die Zukunft von Geary zu " +"erfahren, besuchen Sie bitte Gearys Wiki." -#: C/shortcuts.page:112(key) -msgid "Shift" -msgstr "Umschalttaste" +#. (itstool) path: page/title +#: C/overview.page:8 +msgid "Overview" +msgstr "Überblick" -#: C/shortcuts.page:117(title) -msgid "Composer shortcuts" -msgstr "Tastenkombinationen des Editors" +#. (itstool) path: page/p +#: C/overview.page:10 +msgid "" +"Geary is a lightweight email reader for the GNOME desktop. It works with mail servers that support the IMAP " +"protocol, including popular services such as Gmail, Yahoo Mail, and Outlook." +"com." +msgstr "" +"Geary ist ein schlankes E-Mail-Programm für die GNOME-Arbeitsumgebung. Es arbeitet mit allen E-Mail-Servern, die " +"das IMAP-Protokoll unterstützen, wie beispielsweise Gmail, Yahoo Mail und " +"Outlook.com." -#: C/shortcuts.page:118(p) -msgid "These shortcuts are active whenever focus is in a composer." +#. (itstool) path: page/p +#: C/overview.page:14 +msgid "" +"Geary groups mail messages into conversations. A conversation " +"contains all messages in a single thread of discussion." msgstr "" -"Diese Tastenkombinationen stehen zur Verfügung, sobald der Editor aktiv ist." +"Geary fasst E-Mail Nachrichten in Konversationen zusammen. Eine " +"Konversation beinhaltet alle Nachrichten einer einzelnen Diskussion." -#: C/shortcuts.page:121(p) -msgid "Attach file" -msgstr "Datei anhängen" +#. (itstool) path: page/p +#: C/overview.page:17 +msgid "The main Geary window is divided into several areas:" +msgstr "Das Hauptfenster von Geary ist in verschiedene Bereiche unterteilt:" -#: C/shortcuts.page:122(key) -msgid "T" -msgstr "T" +#. (itstool) path: section/title +#: C/overview.page:20 +msgid "Folder list" +msgstr "Ordnerliste" -#: C/shortcuts.page:125(p) -msgid "Quote text" -msgstr "Text zitieren" +#. (itstool) path: section/p +#: C/overview.page:21 +msgid "" +"The folder list at the left displays all folders and " +"labels in your mail account. Geary uses the term label for " +"any folder that you have created to help organize your messages. (The Gmail " +"web interface also uses this term; most other mail services do not.)" +msgstr "" +"Die Ordnerliste auf der linken Seite zeigt alle Ordner und " +"Label Ihres E-Mail-Kontos. Geary verwendet den Begriff " +"Beschriftung für alle Ordner, die Sie erstellt haben, um Ihre " +"Nachrichten zu organisiern. Die Gmail Web-Oberfläche verwendet den Begriff " +"»Markierung«; die meisten anderen E-Mail-Dienste tun das nicht." -#: C/shortcuts.page:126(key) -msgid "]" -msgstr "]" +#. (itstool) path: section/title +#: C/overview.page:28 +msgid "Conversation list" +msgstr "Konversationsliste" -#: C/shortcuts.page:129(p) -msgid "Unquote text" -msgstr "Text nicht mehr zitieren" +#. (itstool) path: section/p +#: C/overview.page:29 +msgid "" +"The conversation list displays a list of conversations in the " +"selected folder. Newer conversations appear at the top." +msgstr "" +"Die Konversationsliste zeigt eine Liste der Konversationen im " +"ausgewählten Ordner. Neuere Konversationen stehen oben." -#: C/shortcuts.page:130(key) -msgid "[" -msgstr "[" +#. (itstool) path: section/p +#: C/overview.page:31 +msgid "" +"Each sender's name appears bold if there are unread messages from that " +"sender. If a conversation has more than one message, Geary displays a count " +"of messages in the conversation." +msgstr "" +"Jeder Absendername erscheint fett, falls es ungelesene Nachrichten von " +"diesem Absender gibt. Falls eine Konversation mehr als eine Nachricht " +"enthält, zeigt Geary die Anzahl der Nachrichten in der Konversation an." -#: C/shortcuts.page:133(p) -msgid "Close composer" -msgstr "Editor schließen" +#. (itstool) path: section/p +#: C/overview.page:34 +msgid "" +"Geary does not automatically download all messages in all of your mail " +"folders. When you first visit your Inbox or any other folder, Geary " +"downloads the 50 most recent messages in that folder. To see more messages, " +"simply scroll down the conversation list and Geary will fetch more messages " +"automatically." +msgstr "" +"Geary lädt nicht automatisch alle Nachrichten aus allen E-Mail-Ordnern " +"herunter. Wenn Sie das erste Mal den Posteingang oder irgendeinen anderen " +"Ordner besuchen, lädt Geary die letzten 50 Nachrichten in diesem Ordner " +"herunter. Um mehr Nachrichten zu sehen, müssen Sie sich nur in der Liste der " +"angezeigten Nachrichten nach unten bewegen und Geary lädt automatisch " +"weitere Nachrichten nach." -#: C/shortcuts.page:134(p) -msgid "CtrlW or Esc" -msgstr "StrgW oder Esc" +#. (itstool) path: section/p +#: C/overview.page:36 +msgid "" +"Some commands in Geary can act on a group of conversations. To select " +"multiple conversations, hold down the Ctrl key and click each " +"conversation in turn in the conversation list. Alternatively, click the " +"first conversation in a range, then hold down Shift and click the " +"last conversation." +msgstr "" +"Manche Befehle in Geary können auf eine Gruppe von Konversationen angewendet " +"werden. Um mehrere Konversationen auszuwählen, halten Sie die Strg-Taste gedrückt und wählen die Konversationen durch Anklicken einzeln in " +"der Konversationsliste aus. Alternativ können Sie auch die erste " +"Konversation einer Reihe anklicken und bei gedrückter Umschalttaste-Taste die letzte Konversation." -#: C/shortcuts.page:137(p) -msgid "Detach composer" -msgstr "Editor abtrennen" +#. (itstool) path: section/title +#: C/overview.page:44 +msgid "Message area" +msgstr "Nachrichtenbereich" -#: C/shortcuts.page:142(p) -msgid "These shortcuts are only active in composers in rich text mode." +#. (itstool) path: section/p +#: C/overview.page:45 +msgid "" +"The message area displays all messages in the selected " +"conversation, with the oldest message at the top." msgstr "" -"Diese Tastenkombinationen stehen nur in Editoren mit Rich-Text-Modus zur " -"Verfügung." - -#: C/shortcuts.page:145(p) -msgid "Bold text" -msgstr "Fetter Text" +"Der Nachrichten Bereich zeigt alle Nachrichten in der ausgewählten " +"Konversation an, wobei die älteste Nachricht als erstes angezeigt wird." -#: C/shortcuts.page:146(key) C/shortcuts.page:181(key) -msgid "B" -msgstr "B" +#. (itstool) path: section/p +#: C/overview.page:47 +msgid "" +"At the upper right of each message, Geary displays a dropdown arrow that " +"lets you open the message menu with commands that operate on the " +"message." +msgstr "" +"In der oberen rechten Ecke jeder Nachricht zeigt Geary einen Ausklapp-Pfeil " +"an, um das Nachrichten-Menü zu öffnen. Es enthält Befehle, die sich " +"nur auf die jeweilige Nachricht auswirken." -#: C/shortcuts.page:149(p) -msgid "Italicize text" -msgstr "Kursiver Text" +#. (itstool) path: section/p +#: C/overview.page:49 +msgid "" +"When you view a conversation, Geary collapses messages that you've already " +"read. Click collapsed messages to expand them. Click an expanded message's " +"header to collapse it." +msgstr "" +"Wenn Sie eine Konversation ansehen, klappt Geary Nachrichten ein, die Sie " +"bereits gesehen haben. Klicken Sie auf eingeklappte Nachrichten, um sie " +"aufzuklappen. Drücken Sie auf die Kopfzeile einer aufgeklappten Nachricht, " +"um sie zu einzuklappen." -#: C/shortcuts.page:150(key) -msgid "I" -msgstr "I" +#. (itstool) path: section/p +#: C/overview.page:50 +msgid "" +"Any attachments in a message appear at the bottom of the message. You can " +"click an attachment to open it or right-click to save it." +msgstr "" +"Alle Anhänge einer Nachricht erscheinen an deren Ende. Sie können einen " +"Anhang anklicken, um ihn zu öffnen oder mit einem Rechtsklick speichern." -#: C/shortcuts.page:153(p) -msgid "Underline text" -msgstr "Text unterstreichen" +#. (itstool) path: section/p +#: C/overview.page:52 +msgid "" +"Geary uses Gravatar to " +"display an avatar for each message's sender in its header." +msgstr "" +"Geary nutzt Gravatar, um " +"ein Benutzerbild des Absenders im Kopf jeder Nachricht anzuzeigen." -#: C/shortcuts.page:154(key) -msgid "U" -msgstr "U" +#. (itstool) path: page/title +#: C/preferences.page:10 +msgid "Preferences" +msgstr "Einstellungen" -#: C/shortcuts.page:157(p) -msgid "Strike text" -msgstr "Text durchstreichen" +#. (itstool) path: page/p +#: C/preferences.page:11 +msgid "" +"The Preferences option is available in either Geary's application " +"menu or the gear menu in the upper-right of the toolbar. (The location " +"depends on the install desktop shell. For GNOME Shell and Unity, the " +"application menu is available near the top-left corner of the screen.)" +msgstr "" +"Das Fenster Einstellungen erreichen Sie über das Programm-Menü " +"oder das Menü in der oberen rechten Ecke der Werkzeugleiste. Das hängt von " +"Ihrer Arbeitsumgebung ab. Bei der GNOME Shell und Unity befindet sich das " +"Programmmenü nahe der oberen linken Ecke des Bildschirms." -#: C/shortcuts.page:161(p) -msgid "Insert a link" -msgstr "Verweis einfügen" +# There may be a better translation, but I am missing a context. +#. (itstool) path: section/title +#: C/preferences.page:17 +msgid "Reading" +msgstr "Lesen" -#: C/shortcuts.page:165(p) -msgid "Remove formatting" -msgstr "Formatierung entfernen" +#. (itstool) path: item/title +#: C/preferences.page:20 +msgid "Automatically select next message" +msgstr "Automatisch die nächste Nachricht auswählen" -#: C/shortcuts.page:166(key) C/shortcuts.page:186(key) -msgid "Space" -msgstr "Leerzeichen" +#. (itstool) path: item/p +#: C/preferences.page:21 +msgid "" +"When this option is enabled, Geary automatically selects the latest message " +"in a folder when you enter the folder. In addition, after archiving a " +"message, Geary automatically selects an adjacent message." +msgstr "" +"Ist diese Option aktiviert, wählt Geary automatisch die letzte Nachricht im " +"aktuellen Ordner aus. Wird eine Nachricht aktiviert, wählt Geary automatisch " +"eine benachbarte Nachricht aus." -#: C/shortcuts.page:172(title) -msgid "Keyboard navigation" -msgstr "Tastatursteuerung" +#. (itstool) path: item/title +#: C/preferences.page:26 +msgid "Display conversation preview" +msgstr "Konversationsvorschau anzeigen" -#: C/shortcuts.page:173(p) +#. (itstool) path: item/p +#: C/preferences.page:27 msgid "" -"These shortcuts can be used to move the keyboard focus in the main window." +"Enables message previews in the conversation list. Previews show the first " +"few lines of each message." msgstr "" -"Diese Tastenkombinationen können dazu verwendet werden, um den Tastaturfokus " -"in das Hauptfenster zu verschieben." +"Aktiviert eine Nachrichten-Vorschau in der Konversationsliste. In einer " +"Vorschau werden die ersten Zeilen einer Nachricht angezeigt." -#: C/shortcuts.page:176(p) -msgid "Move focus to the next/previous pane" -msgstr "Fokus in die nächste bzw. vorige Ansicht wechseln" +#. (itstool) path: item/title +#: C/preferences.page:31 +msgid "Use three pane view" +msgstr "Dreispaltige Ansicht verwenden" -#: C/shortcuts.page:177(p) +#. (itstool) path: item/p +#: C/preferences.page:32 msgid "" -"F6 / ShiftF6" +"Show the folder list, the conversation list, and the messages side-by-side-" +"by-side in three panes. If not selected, the folder list and conversation " +"list will be stacked vertically in a single pane." msgstr "" -"F6I oder UmschalttasteF6" +"Zeigt die Ordnerliste, die Konversationsliste und die Nachrichten " +"nebeneinander in drei Ansichten an. Falls nicht ausgewählt, werden die " +"Ordnerliste und die Konversationsliste vertikal übereinander in einer " +"Ansicht angezeigt." + +#. (itstool) path: section/title +#: C/preferences.page:40 +msgid "Notifications" +msgstr "Benachrichtigungen" -#: C/shortcuts.page:180(p) -msgid "Move focus to conversation list" -msgstr "Fokus auf Konversationsliste wechseln" +#. (itstool) path: item/title +#: C/preferences.page:43 +msgid "Play notification sounds" +msgstr "Benachrichtigungstöne abspielen" -#: C/shortcuts.page:184(p) -msgid "Move to the next message in a conversation" -msgstr "Zur nächsten Nachricht in einer Konversation wechseln" +#. (itstool) path: item/p +#: C/preferences.page:44 +msgid "When set, Geary plays a sound whenever a new message arrives." +msgstr "" +"Falls aktiviert, spielt Geary immer einen Ton wenn eine Nachricht ankommt." -#: C/shortcuts.page:190(p) -msgid "Move to the next/previous message in a conversation" -msgstr "Zur nächsten/vorigen Nachricht in einer Konversation wechseln" +#. (itstool) path: item/title +#: C/preferences.page:47 +msgid "Show notifications for new mail" +msgstr "Benachrichtigung bei neuen E-Mails anzeigen" -#: C/shortcuts.page:191(p) +#. (itstool) path: item/p +#: C/preferences.page:48 msgid "" -"CtrlDown / CtrlUp" +"When set, Geary displays a notification each time a new message " +"arrives. Notifications are displayed in a system-dependent manner. On GNOME " +"Shell, notifications appear at the bottom of the display (older versions) or " +"centered just below the top bar (newer versions). In Ubuntu Unity, " +"notifications appear at the upper right of the display." msgstr "" -"StrgRunter / StrgHoch" +"Falls aktiviert, zeigt Geary jedes Mal eine Benachrichtigung an, " +"wenn eine neue Nachricht eintrifft. Benachrichtigungen werden systemabhängig " +"angezeigt. Unter der GNOME Shell werden Benachrichtigungen am unteren " +"Bildschirmrand angezeigt. Unter Ubuntu Unity werden Benachrichtigungen in " +"der oberen rechten Ecke des Bildschirms angezeigt." -#: C/shortcuts.page:197(p) -msgid "Move to the first/last message in a conversation" -msgstr "Zur ersten/letzten Nachricht in einer Konversation wechseln" +#. (itstool) path: item/title +#: C/preferences.page:54 +msgid "Watch for new mail when closed" +msgstr "Ständig nach neuen E-Mails prüfen" -#: C/shortcuts.page:198(p) +#. (itstool) path: item/p +#: C/preferences.page:55 msgid "" -"CtrlHome / CtrlEnd" +"Geary will watch your accounts for new mail even when the main window is not " +"open. To do this, it will silently start when you log in to your computer, " +"and it will continue to run after you close all windows." msgstr "" -"StrgPos1 / StrgEnde" +"Geary wird in Ihren Konten nach neuen Nachrichten suchen, auch wenn das " +"Hauptfenster nicht geöffnet ist. Damit dies möglich wird, startet es im " +"Hintergrund, wenn Sie sich an Ihrem Rechner anmelden, und läuft auch dann " +"weiter, wenn Sie das Hauptfenster schließen." -#: C/search.page:10(title) +#. (itstool) path: page/title +#: C/search.page:10 msgid "Search" msgstr "Suche" -#: C/search.page:12(p) +#. (itstool) path: page/p +#: C/search.page:12 msgid "" "Geary supports a per-account full text search. To start a search, select a " "folder associated with the account you'd like to search against. Then click " @@ -598,7 +810,8 @@ "key>S) und beginnen zu tippen. Die Ergebnisse werden " "nach einer kurzen Verzögerung angezeigt." -#: C/search.page:16(p) +#. (itstool) path: page/p +#: C/search.page:16 msgid "" "The full text search includes email text, email addresses (to, from, and " "cc), subject lines and attachment filenames." @@ -606,7 +819,8 @@ "Die Volltextsuche umfasst den Text der E-Mail, E-Mail-Adressen (An, Von und " "Blindkopie), Betreffzeilen und Dateinamen von Anhängen." -#: C/search.page:19(p) +#. (itstool) path: page/p +#: C/search.page:19 msgid "" "Keywords that match your search are highlighted in the message view. Geary " "will match different forms of the same word, for example searching for \"walk" @@ -617,114 +831,124 @@ "eines Wortes, beispielsweise würde eine Suche nach »Eis« auch »Eisberg« und " "»Eisen« finden." -#: C/search.page:23(title) +#. (itstool) path: section/title +#: C/search.page:23 msgid "Search operators" msgstr "Such-Schalter" -#: C/search.page:24(p) +#. (itstool) path: section/p +#: C/search.page:24 msgid "Geary supports the following operators to limit the scope of searches:" msgstr "Geary bietet die folgenden Schalter, um den Suchraum zu begrenzen:" -#: C/search.page:27(var) -msgid "filename" -msgstr "Dateiname" - -#: C/search.page:27(input) -msgid "attachment:" -msgstr "attachment:" +#. (itstool) path: td/p +#: C/search.page:27 +msgid "attachment:filename" +msgstr "attachment:Dateiname" -#: C/search.page:28(p) +#. (itstool) path: td/p +#: C/search.page:28 msgid "Finds messages with attachments whose name matches filename." msgstr "" "Findet Nachrichten mit Anhängen, deren Name Dateiname entspricht." -#: C/search.page:31(var) C/search.page:39(var) C/search.page:63(var) -msgid "recipient" -msgstr "Empfänger" - -#: C/search.page:31(input) -msgid "bcc:" -msgstr "bcc:" +#. (itstool) path: td/p +#: C/search.page:31 +msgid "bcc:recipient" +msgstr "bcc:Empfänger" -#: C/search.page:32(p) +#. (itstool) path: td/p +#: C/search.page:32 msgid "Finds messages where recipient matches the BCC header." msgstr "Findet Nachrichten mit Empfänger im BCC-Feld." -#: C/search.page:35(var) C/search.page:59(var) -msgid "text" -msgstr "Text" - -#: C/search.page:35(input) -msgid "body:" -msgstr "body:" +#. (itstool) path: td/p +#: C/search.page:35 +msgid "body:text" +msgstr "body:Text" -#: C/search.page:36(p) +#. (itstool) path: td/p +#: C/search.page:36 msgid "Finds messages whose body contains text." msgstr "Findet Nachrichten deren Inhalt Text enthält." -#: C/search.page:39(input) -msgid "cc:" -msgstr "cc:" +#. (itstool) path: td/p +#: C/search.page:39 +msgid "cc:recipient" +msgstr "cc:Empfänger" -#: C/search.page:40(p) +#. (itstool) path: td/p +#: C/search.page:40 msgid "Finds messages where recipient matches the CC header." msgstr "Findet Nachrichten mit Empfänger im CC-Feld." -#: C/search.page:43(var) -msgid "sender" -msgstr "Absender" - -#: C/search.page:43(input) -msgid "from:" -msgstr "from:" +#. (itstool) path: td/p +#: C/search.page:43 +msgid "from:sender" +msgstr "from:Absender" -#: C/search.page:44(p) +#. (itstool) path: td/p +#: C/search.page:44 msgid "Finds messages where sender matches the From header." msgstr "Findet Nachrichten mit Absender im Von-Feld." -#: C/search.page:47(input) -msgid "is:read" -msgstr "is:read" +#. (itstool) path: td/p +#: C/search.page:47 +msgid "is:read" +msgstr "is:read" -#: C/search.page:48(p) +#. (itstool) path: td/p +#: C/search.page:48 msgid "Finds messages that have been marked as read." msgstr "Findet Nachrichten, die als gelesen markiert sind." -#: C/search.page:51(input) -msgid "is:starred" -msgstr "is:starred" +#. (itstool) path: td/p +#: C/search.page:51 +msgid "is:starred" +msgstr "is:starred" -#: C/search.page:52(p) +#. (itstool) path: td/p +#: C/search.page:52 msgid "Finds messages that have been marked as starred." msgstr "Findet Nachrichten, die mit einem Sternchen markiert sind." -#: C/search.page:55(input) -msgid "is:unread" -msgstr "is:unread" +#. (itstool) path: td/p +#: C/search.page:55 +msgid "is:unread" +msgstr "is:unread" -#: C/search.page:56(p) +#. (itstool) path: td/p +#: C/search.page:56 msgid "Finds messages that have been marked as not read." msgstr "Findet Nachrichten, die als ungelesen markiert sind." -#: C/search.page:59(input) -msgid "subject:" -msgstr "subject:" +#. (itstool) path: td/p +#: C/search.page:59 +msgid "subject:text" +msgstr "subject:Text" -#: C/search.page:60(p) +#. (itstool) path: td/p +#: C/search.page:60 msgid "Finds messages whose subject contains text." msgstr "Findet Nachrichten mit Text im Betreff-Feld." -#: C/search.page:63(input) -msgid "to:" -msgstr "to:" - -#: C/search.page:64(p) +#. (itstool) path: td/p +#: C/search.page:63 +msgid "to:recipient" +msgstr "to:Empfänger" + +#. (itstool) path: td/p +#: C/search.page:64 +#| msgid "" +#| "Finds messages where sender matches the To, CC, or BCC header." msgid "" -"Finds messages where sender matches the To, CC, or BCC header." +"Finds messages where recipient matches the To, CC, or BCC header." msgstr "" -"Findet Nachrichten mit Absender im An-Feld, CC-Feld oder BCC-Feld." +"Findet Nachrichten mit Empfänger im An-Feld, CC-Feld oder BCC-" +"Feld." -#: C/search.page:68(p) +#. (itstool) path: section/p +#: C/search.page:68 msgid "" "As a special case, the bcc, cc, from, and to operators support me as their " @@ -736,632 +960,261 @@ "input>. Damit wird in den genannten Feldern nach der E-Mail-Adresse des " "Kontos gesucht." -#: C/preferences.page:10(title) -msgid "Preferences" -msgstr "Einstellungen" +#. (itstool) path: page/title +#: C/shortcuts.page:10 +msgid "Keyboard shortcuts" +msgstr "Tastaturkürzel" -#: C/preferences.page:11(p) +#. (itstool) path: page/p +#: C/shortcuts.page:12 msgid "" -"The Preferences option is available in either Geary's application " -"menu or the gear menu in the upper-right of the toolbar. (The location " -"depends on the install desktop shell. For GNOME Shell and Unity, the " -"application menu is available near the top-left corner of the screen.)" +"Geary has keyboard shortcuts for most common operations. Use the built-in " +"keyboard shortcuts help in Geary to discover the full list. This can be " +"accessed via the application menu: GearyKeyboard " +"Shortcuts or using the keyboard shortcuts listed below." +msgstr "" +"Geary verfügt über Tastenkombinationen für die am häufigsten " +"verwendeten Operationen. Sie finden eine vollständige Liste in der Hilfe zu " +"Geary. Sie erreichen diese über das Anwendungsmenü: " +"GearyTastenkombinationen oder in den " +"nachfolgend aufgelisteten Tastenkombinationen." + +#. (itstool) path: page/p +#: C/shortcuts.page:18 +msgid "" +"The following keyboard shortcuts can be used to access on-line help from " +"Geary:" +msgstr "" +"Mit den folgenden Tastenkombinationen erreichen Sie die Online-Hilfe von " +"Geary:" + +#. (itstool) path: td/p +#: C/shortcuts.page:22 +msgid "Display this User Manual" +msgstr "Dieses Benutzerhandbuch anzeigen" + +#. (itstool) path: td/p +#: C/shortcuts.page:23 +msgid "F1" +msgstr "F1" + +#. (itstool) path: td/p +#: C/shortcuts.page:26 +msgid "Display all keyboard shortcuts" +msgstr "Alle Tastaturkürzel anzeigen" + +#. (itstool) path: td/p +#: C/shortcuts.page:27 +msgid "" +"Ctrl? or CtrlF1" msgstr "" -"Das Fenster Einstellungen erreichen Sie über das Programm-Menü " -"oder das Menü in der oberen rechten Ecke der Werkzeugleiste. Das hängt von " -"Ihrer Arbeitsumgebung ab. Bei der GNOME Shell und Unity befindet sich das " -"Programmmenü nahe der oberen linken Ecke des Bildschirms." - -# There may be a better translation, but I am missing a context. -#: C/preferences.page:17(title) -msgid "Reading" -msgstr "Lesen" +"Strg? oder StrgF1" -#: C/preferences.page:20(gui) -msgid "Automatically select next message" -msgstr "Automatisch die nächste Nachricht auswählen" - -#: C/preferences.page:21(p) -msgid "" -"When this option is enabled, Geary automatically selects the latest message " -"in a folder when you enter the folder. In addition, after archiving a " -"message, Geary automatically selects an adjacent message." +#. (itstool) path: page/title +#: C/star.page:10 +msgid "Star a message or mark it as read/unread" msgstr "" -"Ist diese Option aktiviert, wählt Geary automatisch die letzte Nachricht im " -"aktuellen Ordner aus. Wird eine Nachricht aktiviert, wählt Geary automatisch " -"eine benachbarte Nachricht aus." +"Eine Nachricht mit Sternchen versehen oder als gelesen/ungelesen markieren" -#: C/preferences.page:26(gui) -msgid "Display conversation preview" -msgstr "Konversationsvorschau anzeigen" +#. (itstool) path: section/title +#: C/star.page:12 +msgid "Star messages" +msgstr "Nachrichten mit Sternchen versehen " -#: C/preferences.page:27(p) +#. (itstool) path: section/p +#: C/star.page:13 msgid "" -"Enables message previews in the conversation list. Previews show the first " -"few lines of each message." +"You can star messages to indicate that they're important to you. To mark a " +"conversation with a star, click its star icon in the conversation list. You " +"can star an individual message by clicking the star at the upper right of " +"the message itself." msgstr "" -"Aktiviert eine Nachrichten-Vorschau in der Konversationsliste. In einer " -"Vorschau werden die ersten Zeilen einer Nachricht angezeigt." - -#: C/preferences.page:31(gui) -msgid "Use three pane view" -msgstr "Dreispaltige Ansicht verwenden" +"Sie können Nachrichten mit Sternchen versehen, um sie als besonders wichtig " +"zu kennzeichnen. Um eine Konversation mit einem Sternchen zu versehen, " +"klicken Sie auf das Sternchen-Symbol in der Konversationsliste. Eine " +"einzelne Nachricht können Sie mit einem Sternchen versehen, indem Sie auf " +"das Sternchen-Symbol in der oberen rechten Ecke der Nachricht klicken." -#: C/preferences.page:32(p) +#. (itstool) path: section/p +#: C/star.page:15 msgid "" -"Show the folder list, the conversation list, and the messages side-by-side-" -"by-side in three panes. If not selected, the folder list and conversation " -"list will be stacked vertically in a single pane." +"With Gmail accounts, starred messages appear in the Starred folder in the " +"folder list." msgstr "" -"Zeigt die Ordnerliste, die Konversationsliste und die Nachrichten " -"nebeneinander in drei Ansichten an. Falls nicht ausgewählt, werden die " -"Ordnerliste und die Konversationsliste vertikal übereinander in einer " -"Ansicht angezeigt." - -#: C/preferences.page:40(title) -msgid "Composer" -msgstr "Editor" +"Bei Gmail-Konten erscheinen mit Sternchen markierte Nachrichten im " +"Markiert-Ordner in der Ordnerliste." -#: C/preferences.page:43(gui) -msgid "Enable spell checking" -msgstr "Rechtschreibkorrektur aktivieren" +#. (itstool) path: section/title +#: C/star.page:18 +msgid "Mark messages as read or unread" +msgstr "Nachrichten als gelesen oder ungelesen markieren" -#: C/preferences.page:44(p) +#. (itstool) path: section/p +#: C/star.page:19 msgid "" -"When set, Geary automatically spell checks a message as you write it, " -"underlying each misspelled word in red." -msgstr "" -"Falls aktiviert, überprüft Geary eine Nachricht auf Rechtschreibfehler, noch " -"während Sie diese schreiben und unterlegt falsch geschriebene Wörter rot." - -#: C/preferences.page:51(title) -msgid "Notifications" -msgstr "Benachrichtigungen" - -#: C/preferences.page:54(gui) -msgid "Play notification sounds" -msgstr "Benachrichtigungstöne abspielen" - -#: C/preferences.page:55(p) -msgid "When set, Geary plays a sound whenever a new message arrives." +"Geary marks messages as read automatically as you read them. To manually " +"toggle a conversation as read or unread, click the circle icon in the " +"conversation list." msgstr "" -"Falls aktiviert, spielt Geary immer einen Ton wenn eine Nachricht ankommt." - -#: C/preferences.page:58(gui) -msgid "Show notifications for new mail" -msgstr "Benachrichtigung bei _neuen E-Mails anzeigen" +"Geary markiert Nachrichten automatisch als gelesen, während Sie sie lesen. " +"Um eine Konversation von Hand als gelesen oder ungelesen zu markieren, " +"klicken Sie auf das Kreis-Symbol in der Konversationsliste." -#: C/preferences.page:59(p) +#. (itstool) path: section/p +#: C/star.page:22 msgid "" -"When set, Geary displays a notification each time a new message " -"arrives. Notifications are displayed in a system-dependent manner. On GNOME " -"Shell, notifications appear at the bottom of the display (older versions) or " -"centered just below the top bar (newer versions). In Ubuntu Unity, " -"notifications appear at the upper right of the display." +"Alternately, the Mark as Unread in the Mark menu on " +"the toolbar can be used to toggle the read status of the selected " +"conversation(s)." msgstr "" -"Falls aktiviert, zeigt Geary jedes Mal eine Benachrichtigung an, " -"wenn eine neue Nachricht eintrifft. Benachrichtigungen werden systemabhängig " -"angezeigt. Unter der GNOME Shell werden Benachrichtigungen am unteren " -"Bildschirmrand angezeigt. Unter Ubuntu Unity werden Benachrichtigungen in " -"der oberen rechten Ecke des Bildschirms angezeigt." - -#: C/preferences.page:65(gui) -msgid "Always watch for new mail" -msgstr "Ständig nach neuen E-Mails prüfen" +"Alternativ kann auch Als ungelesen markieren aus dem " +"Markierungen-Menü in der Werkzeugleiste verwendet werden, um den " +"Gelesen-Status der Konversation zu ändern." -#: C/preferences.page:66(p) +#. (itstool) path: section/p +#: C/star.page:25 msgid "" -"Geary will watch your accounts for new mail even when the main window is not " -"open. To do this, it will silently start when you log in to your computer, " -"and it will continue to run after you close the main window." +"To mark an individual message as read, select Mark as Read from " +"the dropdown menu." msgstr "" -"Geary wird in Ihren Konten nach neuen Nachrichten suchen, auch wenn das " -"Hauptfenster nicht geöffnet ist. Damit dies möglich wird, startet es im " -"Hintergrund, wenn Sie sich an Ihrem Rechner anmelden, und löuft auch dann " -"weiter, wenn Sie das Hauptfenster schließen." +"Um eine einzelne Nachricht als gelesen zu markieren, wählen Sie Als " +"gelesen markieren aus dem Auswahlmenü." -#: C/overview.page:8(title) -msgid "Overview" -msgstr "Überblick" +#. (itstool) path: page/title +#: C/write.page:9 +msgid "Write a message" +msgstr "Eine Nachricht schreiben" -#: C/overview.page:10(p) -msgid "" -"Geary is a lightweight email reader for the GNOME desktop. It works with mail servers that support the IMAP " -"protocol, including popular services such as Gmail, Yahoo Mail, and Outlook." -"com." -msgstr "" -"Geary ist ein schlankes E-Mail-Programm für die GNOME-Arbeitsumgebung. Es arbeitet mit allen E-Mail-Servern, die " -"das IMAP-Protokoll unterstützen, wie beispielsweise Gmail, Yahoo Mail und " -"Outlook.com." +#. (itstool) path: section/title +#: C/write.page:12 +msgid "Composing and replying" +msgstr "Verfassen und antworten" -#: C/overview.page:14(p) +#. (itstool) path: section/p +#: C/write.page:13 msgid "" -"Geary groups mail messages into conversations. A conversation " -"contains all messages in a single thread of discussion." +"To compose a new message in Geary, press the New Message button " +"on the toolbar." msgstr "" -"Geary fasst E-Mail Nachrichten in Konversationen zusammen. Eine " -"Konversation beinhaltet alle Nachrichten einer einzelnen Diskussion." - -#: C/overview.page:17(p) -msgid "The main Geary window is divided into several areas:" -msgstr "Das Hauptfenster von Geary ist in verschiedene Bereiche unterteilt:" - -#: C/overview.page:20(title) -msgid "Folder list" -msgstr "Ordnerliste" +"Um eine neue Nachricht in Geary zu erstellen, klicken Sie auf den Knopf " +"Neue Nachricht in der Werkzeugleiste." -#: C/overview.page:21(p) +#. (itstool) path: section/p +#: C/write.page:16 msgid "" -"The folder list at the left displays all folders and " -"labels in your mail account. Geary uses the term label for " -"any folder that you have created to help organize your messages. (The Gmail " -"web interface also uses this term; most other mail services do not.)" +"To reply to a message, open the message menu in the upper right corner of " +"the message and choose Reply, Reply All or " +"Forward. You can also reply to the last message in a conversation " +"via the Reply, Reply All or Forward buttons " +"on the toolbar." msgstr "" -"Die Ordnerliste auf der linken Seite zeigt alle Ordner und " -"Label Ihres E-Mail-Kontos. Geary verwendet den Begriff " -"Beschriftung für alle Ordner, die Sie erstellt haben, um Ihre " -"Nachrichten zu organisiern. Die Gmail Web-Oberfläche verwendet den Begriff " -"»Markierung«; die meisten anderen E-Mail-Dienste tun das nicht." +"Um auf eine Nachricht zu antworten, öffnen Sie das Nachrichten-Menü in der " +"oberen rechten Ecke der Nachricht und wählen Sie Antworten, " +"Allen Antworten oder Weiterleiten. Sie können außerdem " +"auf die letzte Nachricht einer Konversation mit Hilfe der Knöpfe " +"Antworten, Allen Antworten und Weiterleiten " +"in der Werkzeugleiste antworten." -#: C/overview.page:28(title) -msgid "Conversation list" -msgstr "Konversationsliste" +#. (itstool) path: section/title +#: C/write.page:21 +msgid "Features" +msgstr "Besonderheiten" -#: C/overview.page:29(p) +#. (itstool) path: section/p +#: C/write.page:23 msgid "" -"The conversation list displays a list of conversations in the " -"selected folder. Newer conversations appear at the top." +"Geary's email composer lets you adjust the font, size and color of text. You " +"can also insert hyperlinks into messages." msgstr "" -"Die Konversationsliste zeigt eine Liste der Konversationen im " -"ausgewählten Ordner. Neuere Konversationen stehen oben." +"Der E-Mail-Editor von Geary ermöglicht es Ihnen, die Schriftart, die " +"Schriftgröße und die Textfarbe anzupassen. Außerdem ist es möglich, Verweise " +"in Nachrichten einzufügen." -#: C/overview.page:31(p) +#. (itstool) path: section/p +#: C/write.page:25 msgid "" -"Each sender's name appears bold if there are unread messages from that " -"sender. If a conversation has more than one message, Geary displays a count " -"of messages in the conversation." +"Geary can also send plain text messages. In the drop-down menu, check or " +"uncheck \"Rich Text\" to toggle between plain text and rich text mode." msgstr "" -"Jeder Absendername erscheint fett, falls es ungelesene Nachrichten von " -"diesem Absender gibt. Falls eine Konversation mehr als eine Nachricht " -"enthält, zeigt Geary die Anzahl der Nachrichten in der Konversation an." +"Geary kann auch einfache Text-Nachrichten verschicken. Aktivieren oder " +"deaktivieren Sie »Rich Text« im Auswahl-Menü, um zwischen einfachem Text- " +"und Rich-Text-Modus zu wechseln." -#: C/overview.page:34(p) +#. (itstool) path: section/p +#: C/write.page:28 msgid "" -"Geary does not automatically download all messages in all of your mail " -"folders. When you first visit your Inbox or any other folder, Geary " -"downloads the 50 most recent messages in that folder. To see more messages, " -"simply scroll down the conversation list and Geary will fetch more messages " -"automatically." +"You can attach a file to a message you're writing in either of these ways:" msgstr "" -"Geary lädt nicht automatisch alle Nachrichten aus allen E-Mail-Ordnern " -"herunter. Wenn Sie das erste Mal den Posteingang oder irgendeinen anderen " -"Ordner besuchen, lädt Geary die letzten 50 Nachrichten in diesem Ordner " -"herunter. Um mehr Nachrichten zu sehen, müssen Sie sich nur in der Liste der " -"angezeigten Nachrichten nach unten bewegen und Geary lädt automatisch " -"weitere Nachrichten nach." +"Sie können eine Datei an eine gerade verfasste Nachricht wie folgt anhängen:" -#: C/overview.page:36(p) +#. (itstool) path: item/p +#: C/write.page:30 msgid "" -"Some commands in Geary can act on a group of conversations. To select " -"multiple conversations, hold down the Ctrl key and click each " -"conversation in turn in the conversation list. Alternatively, click the " -"first conversation in a range, then hold down Shift and click the " -"last conversation." +"Press the Attach File button at the lower left of the composer " +"window, then select a file to attach." msgstr "" -"Manche Befehle in Geary können auf eine Gruppe von Konversationen angewendet " -"werden. Um mehrere Konversationen auszuwählen, halten Sie die Strg-Taste gedrückt und wählen die Konversationen durch Anklicken einzeln in " -"der Konversationsliste aus. Alternativ können Sie auch die erste " -"Konversation einer Reihe anklicken und bei gedrückter Umschalttaste-Taste die letzte Konversation." - -#: C/overview.page:44(title) -msgid "Message area" -msgstr "Nachrichtenbereich" +"Klicken Sie auf den Knopf Datei anhängen in der unteren linken " +"Ecke des Editor-Fensters und wählen Sie anschließend die anzuhängende Datei." -#: C/overview.page:45(p) +#. (itstool) path: item/p +#: C/write.page:32 msgid "" -"The message area displays all messages in the selected " -"conversation, with the oldest message at the top." +"Drag the file from the Nautilus file manager to the composer window, and " +"drop it either on the text fields at the top of the window or on the toolbar " +"at the bottom." msgstr "" -"Der Nachrichten Bereich zeigt alle Nachrichten in der ausgewählten " -"Konversation an, wobei die älteste Nachricht als erstes angezeigt wird." +"Ziehen Sie die Datei aus der Nautilus-Dateiverwaltung in das Editor-Fenster " +"und legen Sie diese entweder im Textfeld oben im Fenster oder in der " +"Werkzeugleiste im unteren Teil des Fensters ab." -#: C/overview.page:47(p) +#. (itstool) path: section/p +#: C/write.page:36 msgid "" -"At the upper right of each message, Geary displays a dropdown arrow that " -"lets you open the message menu with commands that operate on the " -"message." +"A number of keyboard shortcuts are available in the composer; see for details." msgstr "" -"In der oberen rechten Ecke jeder Nachricht zeigt Geary einen Ausklapp-Pfeil " -"an, um das Nachrichten-Menü zu öffnen. Es enthält Befehle, die sich " -"nur auf die jeweilige Nachricht auswirken." +"Im Editor stehen eine große Zahl von Tastaturkürzeln zur Verfügung; Details " +"unter ." -#: C/overview.page:49(p) +#. (itstool) path: section/p +#: C/write.page:38 msgid "" -"When you view a conversation, Geary collapses messages that you've already " -"read. Click collapsed messages to expand them. Click an expanded message's " -"header to collapse it." +"You may specify a signature to be inserted into the composer in the dialog." msgstr "" -"Wenn Sie eine Konversation ansehen, klappt Geary Nachrichten ein, die Sie " -"bereits gesehen haben. Klicken Sie auf eingeklappte Nachrichten, um sie " -"aufzuklappen. Drücken Sie auf die Kopfzeile einer aufgeklappten Nachricht, " -"um sie zu einzuklappen." +"Sie können im -Dialog eine Signatur angeben, welche " +"dann im E-Mail-Editor eingefügt wird." -#: C/overview.page:50(p) -msgid "" -"Any attachments in a message appear at the bottom of the message. You can " -"click an attachment to open it or right-click to save it." -msgstr "" -"Alle Anhänge einer Nachricht erscheinen an deren Ende. Sie können einen " -"Anhang anklicken, um ihn zu öffnen oder mit einem Rechtsklick speichern." +#. (itstool) path: section/title +#: C/write.page:43 +msgid "Drafts" +msgstr "Entwürfe" -#: C/overview.page:52(p) +#. (itstool) path: section/p +#: C/write.page:45 msgid "" -"Geary uses Gravatar to " -"display an avatar for each message's sender in its header." +"For mail servers that support drafts, Geary will automatically save the " +"message as you type. If you close the composer without sending, Geary will " +"prompt you to keep the draft or to discard it." msgstr "" -"Geary nutzt Gravatar, um " -"ein Benutzerbild des Absenders im Kopf jeder Nachricht anzuzeigen." - -#: C/limits.page:11(title) -msgid "Limitations" -msgstr "Einschränkungen" +"Unterstützt ein E-Mail Server das Speichern von Entwürfen, speichert Geary " +"die Nachricht automatisch während Sie schreiben. Schließen Sie den Editor, " +"ohne die Nachricht zu senden, fragt Geary, ob die Nachricht als Entwurf " +"gespeichert oder verworfen werden soll." -#: C/limits.page:12(p) +#. (itstool) path: section/p +#: C/write.page:48 msgid "" -"Geary is still in early development. Geary supports IMAP and has been tested " -"with Gmail, Yahoo, and the free Dovecot mail server. Experimental support " -"for Outlook.com is provided. Geary may not yet work well with some IMAP " -"servers. At this time Geary is still missing numerous features including " -"offline mode." +"To edit an existing draft, select the Drafts folder in the folder list, " +"select the message, and click \"Edit Draft\" in the message viewer." msgstr "" -"Geary befindet sich noch in einem frühen Entwicklungsstadium. Es unterstützt " -"IMAP und wurde mit Gmail, Yahoo und dem freien Dovecot E-Mail-Server " -"getestet. Die Unterstützung von Outlook.com ist noch experimentell. Es kann " -"sein, dass Geary mit einigen IMAP-Servern noch nicht gut zusammen arbeitet. " -"Zum aktuellen Zeitpunkt fehlen Geary noch viele Leistungsmerkmale, wie " -"beispielsweise ein Offline Modus." - -#: C/limits.page:14(p) -msgid "" -"To learn more about the features we're working on and the future of Geary, " -"please visit Geary's wiki " -"page." -msgstr "" -"Um mehr über die angestrebten Funktionen und die Zukunft von Geary zu " -"erfahren, besuchen Sie bitte Gearys Wiki." - -#: C/label.page:10(title) -msgid "Label or move a conversation" -msgstr "Eine Konversation mit einer Beschriftung versehen oder verschieben" - -#: C/label.page:12(title) -msgid "Label a conversation" -msgstr "Eine Konversation mit einer Beschriftung versehen" - -#: C/label.page:13(p) -msgid "" -"Geary lets you apply one or more labels to each conversation. Geary " -"labels correspond to labels in Gmail, or ordinary folders in other mail " -"services." -msgstr "" -"Mit Geary können Sie einer Konversation eine oder mehrere " -"Beschriftungen zuweisen. Gearys Beschriftungen entsprechen Gmail-" -"Markierungen oder normalen Ordnern anderer E-Mail-Dienste." - -#: C/label.page:15(p) -msgid "" -"To label one or more conversations, first select the conversation(s), then " -"do either of the following:" -msgstr "" -"Um eine oder mehrere Konversationen mit einer Beschriftung zu versehen, " -"wählen Sie zuerst die Konversation(en) aus und können dann einen der " -"folgenden Wege nutzen:" - -#: C/label.page:18(p) -msgid "" -"Click the Label button on the toolbar and select a label from the " -"resulting drop-down menu." -msgstr "" -"Klicken Sie den Beschriftung-Knopf in der Werkzeugleiste und " -"wählen Sie eine Beschriftung aus dem erscheinenden Auswahlmenü." - -#: C/label.page:20(p) -msgid "" -"Hold down the Ctrl key and drag the conversation(s) from the " -"conversation list to the label in the sidebar." -msgstr "" -"Drücken Sie die Strg-Taste und ziehen Sie die Konversation(en) " -"aus der Konversationsliste auf die Beschriftung in der Seitenleiste." - -#: C/label.page:25(title) -msgid "Move a conversation to a folder or label" -msgstr "Verschieben einer Konversation in einen Ordner oder eine Beschriftung" - -#: C/label.page:26(p) -msgid "" -"To move one or more conversations to a folder or label, first select the " -"conversation(s), then do either of the following:" -msgstr "" -"Um eine oder mehrere Konversationen in einen Ordner oder eine Beschriftung " -"zu verschieben, wählen Sie zunächst die Konversation(en) aus und können dann " -"einen der folgenden Wege nutzen:" - -#: C/label.page:29(p) -msgid "" -"Click the Move button on the toolbar and select a folder or label " -"from the resulting drop-down menu." -msgstr "" -"Klicken Sie den Verschieben Knopf in der Werkzeugleiste und " -"wählen Sie einen Ordner oder eine Beschriftung aus dem erscheinenden " -"Auswahlmenü aus." - -#: C/label.page:31(p) -msgid "" -"Drag the conversation(s) from the conversation list to the folder or label " -"in the sidebar." -msgstr "" -"Ziehen Sie die Konversation(en) aus der Konversationsliste auf den Ordner " -"oder die Beschriftung in der Seitenleiste." - -#. When image changes, this message will be marked fuzzy or untranslated for you. -#. It doesn't matter what you translate it to: it's not used at all. -#: C/index.page:5(None) -msgid "@@image: 'figures/geary.svg'; md5=18b50c9e10fe5256ae1cb12aaa3a7600" -msgstr "@@image: 'figures/geary.svg'; md5=18b50c9e10fe5256ae1cb12aaa3a7600" - -#: C/index.page:5(title) -msgid " Geary" -msgstr " Geary" - -#: C/index.page:9(title) -msgid "Introduction" -msgstr "Einführung" - -#: C/index.page:13(title) -msgid "Using Geary" -msgstr "Geary verwenden" - -#: C/index.page:17(title) -msgid "Bugs" -msgstr "Fehler" - -#: C/bugs.page:8(title) -msgid "Think you've found a bug?" -msgstr "Sie glauben einen Fehler gefunden zu haben?" - -#: C/bugs.page:9(p) -msgid "" -"If you suspect you've found a bug in Geary, follow these steps to report it:" -msgstr "" -"Wenn Sie meinen, einen Fehler in Geary gefunden zu haben, befolgen Sie die " -"folgenden Schritte um ihn zu melden:" - -#: C/bugs.page:11(p) -msgid "" -"Search Geary's bug database to see if someone else has reported the " -"bug." -msgstr "" -"Suchen Sie in Gearys Fehlerdatenbank, um zu sehen, ob jemand anderes " -"diesen Fehler bereits gemeldet hat." - -#: C/bugs.page:13(p) -msgid "" -"Don't see your bug listed? Congratulations! You've found a new bug. To " -"create an bug report, create an account on GNOME's Bugzilla and file a new bug. Be as specific as you can and describe the steps to reproduce it. " -"Don't forget to include details about your operating system and what version " -"of Geary you're running." -msgstr "" -"Ihr Fehler wird nicht aufgeführt? Herzlichen Glückwunsch! Sie haben einen " -"Fehler entdeckt. Um einen Fehlerbericht zu erstellen, legen Sie sich ein " -"Konto bei GNOMEs Bugzilla an und melden einen neuen Fehler. Seien Sie so " -"präzise wie möglich und beschreiben Sie die Schritte, um diesen Fehler zu " -"reproduzieren. Vergessen Sie nicht, Details zu Ihrem Betriebssystem und der " -"verwendeten Geary-Version anzugeben." - -#: C/bugs.page:18(p) -msgid "" -"For general inquiries, please join the Geary mailing list." -msgstr "" -"Für allgemeine Anfragen melden Sie sich bitte bei der Geary-Mailingliste " -"an." - -#: C/archive.page:10(title) -msgid "Delete or archive a message" -msgstr "Eine Nachricht löschen oder archivieren" - -#: C/archive.page:12(p) -msgid "" -"When you use Geary with a Gmail account, Geary lets you archive " -"messages. The Archive toolbar button archives the selected " -"conversation(s). Archived messages appear in the All Mail folder." -msgstr "" -"Wenn Sie Geary mit einem Gmail-Konto verwenden, können Sie Nachrichten " -"archivieren. Der Archivieren Knopf in der Werkzeugleiste " -"archiviert die ausgewählte(n) Konversation(en). Archivierte Nachrichten " -"erscheinen im Alle E-Mails Ordner." - -#: C/archive.page:16(p) -msgid "" -"With other mail servers, you can trash or delete, but not archive, messages. " -"To move one or more conversations to the Trash folder, select " -"them and press the Trash button on the toolbar. To permanently " -"delete the conversations, hold down Shift and press the " -"Delete button that appears in place of the Trash " -"button." -msgstr "" -"Bei anderen E-Mail-Servern können Sie Nachrichten in den Papierkorb " -"verschieben oder löschen, nicht aber archivieren. Um eine oder mehrere " -"Konversationen in den Papierkorb zu verschieben, wählen Sie diese " -"aus und klicken den Papierkorb-Knopf in der Werkzeugleiste. Um " -"die Konversationen vollständig zu löschen, halten Sie die " -"Umschalttaste gedrückt und klicken auf den Löschen-" -"Knopf, der an Stelle von Papierkorb erscheint." - -#: C/archive.page:21(p) -msgid "" -"Delete is not available from every folder, such as Search. Delete is also " -"unavailable for Gmail. For Gmail, Trash will move messages to the " -"Trash folder on the server, where the user can then manually delete them. " -"The server will automatically remove trashed messages after 30 days." -msgstr "" -"Löschen ist in manchem Ordner, wie beispielsweise Suche, nicht möglich. " -"Löschen ist auch bei Gmail nicht möglich. Bei Gmail verschiebt " -"Papierkorb Nachrichten nur in den Papierkorb-Ordner des " -"Verzeichnisses auf dem Server, von wo Sie sie von Hand löschen können. Der " -"Server löscht sie automatisch nach 30 Tagen." - -#: C/accounts.page:10(title) -msgid "Accounts" -msgstr "Konten" - -#: C/accounts.page:13(title) -msgid "Adding accounts" -msgstr "Konten hinzufügen" - -#: C/accounts.page:15(p) -msgid "" -"The first time you start Geary, you will be prompted to add an email " -"account. On this screen, select if your account is Gmail, Yahoo, Outlook." -"com, or other. For other account types, you will need to enter your IMAP and " -"SMTP login settings manually." -msgstr "" -"Wenn Sie Geary das erste Mal starten, werden Sie gebeten, ein E-Mail-Konto " -"hinzuzufügen. In diesem Fenster wählen Sie aus, ob es sich um ein Gmail-, " -"Yahoo-, Outlook.com oder ein anderes E-Mail-Konto handelt. Für andere Konto-" -"Arten müssen Sie die IMAP- und SMTP-Angaben selbst eingetragen." - -#: C/accounts.page:19(p) -msgid "" -"Additional accounts can be added from the Accounts dialog. The " -"Accounts option is available in either Geary's application menu " -"or the gear menu in the upper-right of the toolbar. (The location depends on " -"the install desktop shell. For GNOME Shell and Unity, the application menu " -"is available near the top-left corner of the screen.) Alternately, " -"CtrlM will open the Accounts dialog. " -"To add an account, click the + button." -msgstr "" -"Weitere Konten können Sie im Konten-Dialogfenster hinzufügen. Sie erreichen " -"Konten über Gearys Programm-Menü oder das Menü in der oberen " -"rechten Ecke der Werkzeugleiste. Das hängt von Ihrer Arbeitsumgebung ab. Bei " -"der GNOME Shell und Unity befindet sich das Programmmenü nahe der oberen " -"linken Ecke des Bildschirms. Alternativ öffnen Sie das Konten-Dialogfenster " -"mit der Tastenkombination StrgM. " -"Klicken Sie auf den + Knopf, um ein Konto hinzuzufügen." - -#: C/accounts.page:28(title) -msgid "Editing existing accounts" -msgstr "Konten bearbeiten" - -#: C/accounts.page:30(p) -msgid "" -"From the Accounts dialog, select an account and click the pencil icon to " -"change various settings. Please note that Geary cannot change server " -"settings on an existing account. If you need to change your IMAP or SMTP " -"server, you will need to delete the account and re-add it." -msgstr "" -"Wählen Sie im Konten-Dialog ein Konto aus und klicken Sie auf das Stift-" -"Symbol, um die Einstellungen zu bearbeiten. Bitte beachten Sie, dass Geary " -"die Server-Einstellungen bestehender Konten nicht verändern kann. Wenn Sie " -"den IMAP- oder SMTP-Server ändern wollen, müssen Sie das bereits " -"existierende Konto löschen und neu anlegen." - -#: C/accounts.page:34(p) -msgid "" -"To change the order that accounts are displayed in the folder list, drag the " -"accounts in the Accounts dialog to the desired order." -msgstr "" -"Um die Reihenfolge zu ändern, in der die Konten in der Ordnerliste angezeigt " -"werden, ziehen Sie die Konten im Konten-Dialog an die gewünschte Position." - -#: C/accounts.page:37(p) -msgid "There are some advanced options available when editing accounts:" -msgstr "In den Konten-Einstellungen gibt es folgende erweiterte Möglichkeiten:" - -#: C/accounts.page:39(p) -msgid "" -"The Save sent mail checkbox controls whether Geary will push " -"successfully sent messages up to the account's Sent Mail folder. " -"For Gmail accounts, this happens automatically. Yahoo and some other " -"accounts can be configured to do this automatically as well. For other " -"accounts, if you disable this setting, you may be unable to view messages " -"you've sent." -msgstr "" -"Das Ankreuzfeld Gesendete E-Mail speichern legt fest, ob Geary " -"erfolgreich gesendete Nachrichten in den Ordner Gesendete E-Mails " -"verschiebt. Für Gmail-Konten erfolgt das automatisch. Yahoo und einige " -"andere können so eingestellt werden, dass sie gesendete E-Mails ebenfalls " -"automatisch verschieben. Bei Konten, bei denen Sie diese Möglichkeit " -"ausgeschaltet haben, können Sie die von Ihnen gesendeten E-Mails nicht mehr " -"aufrufen." - -#: C/accounts.page:45(p) -msgid "" -"The Sign emails checkbox indicates whether a signature will be " -"automatically inserted when a composer is opened. You may enter the " -"signature into the box immediately below. You may use HTML tags to style the " -"text. Switch to a preview of the signature using the buttons to the right." -msgstr "" -"Das Ankreuzfeld E-Mails unterzeichnen zeigt an ob beim öffnen des " -"E-Mail-Editors automatisch eine Signatur in die E-Mail eingefügt wird. In " -"dem Eingabefeld direkt darunter können Sie die Signatur eingeben. Um den " -"Text hervorzuheben, können Sie auch HTML-Tags verwenden. Mit den Knöpfen auf " -"der rechten Seite wechseln Sie zur Vorschau der Signatur." - -#: C/accounts.page:50(p) -msgid "" -"If you leave the signature in the Accounts dialog blank, Geary will use the " -".signature file in your home directory, if it exists. This file " -"may contain either plain text or HTML markup. In the latter case, the markup " -"will be inserted directly into the composer, without any escaping." -msgstr "" -"Wenn Sie im Konten-Dialog kein Signatur einstellen, wird Geary den Inhalt " -"der Datei .signature aus Ihrem Persönlicher Ordner verwenden, " -"sofern diese vorhanden ist. Diese Datei kann sowohl reinen Text als auch " -"HTML-Textauszeichnungen enthalten. Im letzteren Fall werden die " -"Textauszeichnungen direkt und ohne Maskierung im E-Mail-Editor eingefügt." - -#: C/accounts.page:55(p) -msgid "" -"The Download mail drop-down allows you to configure how much mail " -"Geary will keep locally. Geary can only use locally available mail when " -"searching and forming conversations." -msgstr "" -"Über das Mail herunterladen-Auswahlmenü können Sie festlegen, " -"wieviel E-Mail Geary lokal behält. Geary kann nur lokal verfügbare E-Mails " -"berücksichtigen, wenn es um das Durchsuchen oder das Bündeln von Nachrichten " -"in Konversationen geht." - -#: C/accounts.page:63(title) -msgid "Removing accounts" -msgstr "Konten löschen" - -#: C/accounts.page:65(p) -msgid "" -"To delete an account, open the Accounts dialog, select the account, and " -"press the - button. Geary will delete all information associated with the " -"account." -msgstr "" -"Um ein Konto zu löschen, öffnen Sie den Konten-Dialog, wählen das " -"betreffende Konto und klicken auf den - Knopf. Geary wird alle mit diesem " -"Konto verknüpften Daten löschen." +"Um einen bereits existierenden Entwurf zu bearbeiten, wählen Sie den " +"Entwürfe-Ordner aus der Ordnerliste, wählen die Nachricht und wählen in der " +"Nachrichten Ansicht Entwurf bearbeiten." -#. Put one translator per line, in the form of NAME , YEAR1, YEAR2 -#: C/accounts.page:0(None) -msgid "translator-credits" -msgstr "" -"Andreas Wilhelm , 2014\n" -"Frank Schiersner , 2014\n" -"Simon Linden , 2017" +#. (itstool) path: section/p +#: C/write.page:51 +msgid "Geary deletes the draft when you send the message." +msgstr "Geary löscht den Entwurf, wenn die Nachricht gesendet wird." diff -Nru geary-0.12.4/help/it/it.po geary-3.32.0/help/it/it.po --- geary-0.12.4/help/it/it.po 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/help/it/it.po 2019-03-17 13:39:29.000000000 +0000 @@ -1,13 +1,13 @@ # Italian translation for geary. # Copyright (C) 2014 geary's COPYRIGHT HOLDER # This file is distributed under the same license as the geary package. -# Federico Bruni , 2014, 2015, 2016, 2017. +# Federico Bruni , 2014, 2015, 2016, 2017, 2018. # msgid "" msgstr "" "Project-Id-Version: geary help master\n" -"POT-Creation-Date: 2017-02-23 03:08+0000\n" -"PO-Revision-Date: 2017-03-03 13:18+0100\n" +"POT-Creation-Date: 2018-06-14 02:33+0000\n" +"PO-Revision-Date: 2018-06-29 12:41+0200\n" "Last-Translator: Federico Bruni \n" "Language-Team: Italiano <>\n" "Language: it\n" @@ -17,11 +17,397 @@ "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Gtranslator 2.91.7\n" -#: C/write.page:9(title) +#. Put one translator per line, in the form NAME , YEAR1, YEAR2 +#| msgid "translator-credits" +msgctxt "_" +msgid "translator-credits" +msgstr "Federico Bruni , 2015, 2016, 2017, 2018" + +#. (itstool) path: page/title +#: C/overview.page:8 +msgid "Overview" +msgstr "Panoramica" + +#. (itstool) path: page/p +#: C/overview.page:10 +msgid "" +"Geary is a lightweight email reader for the GNOME desktop. It works with mail servers that support the IMAP " +"protocol, including popular services such as Gmail, Yahoo Mail, and Outlook." +"com." +msgstr "" +"Geary è un lettore email leggero per il desktop GNOME. Funziona con i server mail che supportano il protocollo " +"IMAP, compresi popolari servizi come Gmail, Yahoo Mail e Outlook.com." + +#. (itstool) path: page/p +#: C/overview.page:14 +msgid "" +"Geary groups mail messages into conversations. A conversation " +"contains all messages in a single thread of discussion." +msgstr "" +"Geary raggruppa i messaggi di posta in conversazioni. Una " +"conversazione contiene tutti i messaggi di una singola discussione." + +#. (itstool) path: page/p +#: C/overview.page:17 +msgid "The main Geary window is divided into several areas:" +msgstr "La finestra principale di Geary è divisa in varie aree:" + +#. (itstool) path: section/title +#: C/overview.page:20 +msgid "Folder list" +msgstr "Elenco delle cartelle" + +#. (itstool) path: section/p +#: C/overview.page:21 +msgid "" +"The folder list at the left displays all folders and " +"labels in your mail account. Geary uses the term label for " +"any folder that you have created to help organize your messages. (The Gmail " +"web interface also uses this term; most other mail services do not.)" +msgstr "" +"L'elenco delle cartelle a sinistra mostra tutte le cartelle e le etichette del proprio account di posta. Geary usa il " +"termine etichetta per indicare qualsiasi cartella creata per " +"organizzare i propri messaggi. (Anche l'interfaccia web di Gmail usa questo " +"termine; la maggior parte degli altri servizi mail non lo usa.)" + +#. (itstool) path: section/title +#: C/overview.page:28 +msgid "Conversation list" +msgstr "Elenco delle conversazioni" + +#. (itstool) path: section/p +#: C/overview.page:29 +msgid "" +"The conversation list displays a list of conversations in the " +"selected folder. Newer conversations appear at the top." +msgstr "" +"L'elenco delle conversazioni mostra le conversazioni presenti nella " +"cartella selezionata. Le conversazioni più recenti appaiono in cima." + +#. (itstool) path: section/p +#: C/overview.page:31 +msgid "" +"Each sender's name appears bold if there are unread messages from that " +"sender. If a conversation has more than one message, Geary displays a count " +"of messages in the conversation." +msgstr "" +"Il nome del mittente appare in grassetto se ci sono dei messaggi non letti " +"inviati da quel mittente. Se una conversazione ha più di un messaggio, Geary " +"indica il numero di messaggi nella conversazione." + +#. (itstool) path: section/p +#: C/overview.page:34 +msgid "" +"Geary does not automatically download all messages in all of your mail " +"folders. When you first visit your Inbox or any other folder, Geary " +"downloads the 50 most recent messages in that folder. To see more messages, " +"simply scroll down the conversation list and Geary will fetch more messages " +"automatically." +msgstr "" +"Geary non scarica automaticamente tutti i messaggi di tutte le cartelle di " +"posta. Quando si apre la prima volta la posta in arrivo o qualsiasi altra " +"cartella, Geary scarica i 50 messaggi più recenti di quella cartella. Per " +"vedere più messaggi, scorrere in giù l'elenco delle conversazioni: Geary " +"recupererà automaticamente altri messaggi." + +#. (itstool) path: section/p +#: C/overview.page:36 +msgid "" +"Some commands in Geary can act on a group of conversations. To select " +"multiple conversations, hold down the Ctrl key and click each " +"conversation in turn in the conversation list. Alternatively, click the " +"first conversation in a range, then hold down Shift and click the " +"last conversation." +msgstr "" +"Alcuni comandi in Geary possono agire su un gruppo di conversazioni. Per " +"selezionare più di una conversazione, tenere premuto il tasto Ctrl e fare clic su ogni conversazione nell'elenco delle conversazioni. " +"Oppure fare clic sulla prima conversazione di una serie consecutiva, tenere " +"premuto il tasto Maiusc e fare clic sull'ultima conversazione." + +#. (itstool) path: section/title +#: C/overview.page:44 +msgid "Message area" +msgstr "Area dei messaggi" + +#. (itstool) path: section/p +#: C/overview.page:45 +msgid "" +"The message area displays all messages in the selected " +"conversation, with the oldest message at the top." +msgstr "" +"L'area dei messaggi mostra tutti i messaggi della conversazione " +"selezionata, col messaggio più vecchio in cima." + +#. (itstool) path: section/p +#: C/overview.page:47 +msgid "" +"At the upper right of each message, Geary displays a dropdown arrow that " +"lets you open the message menu with commands that operate on the " +"message." +msgstr "" +"Nell'angolo superiore destro di ogni messaggio, c'è una freccia a discesa " +"che permette di aprire il menù dei messaggi contenente i comandi " +"che agiscono sul messaggio." + +#. (itstool) path: section/p +#: C/overview.page:49 +msgid "" +"When you view a conversation, Geary collapses messages that you've already " +"read. Click collapsed messages to expand them. Click an expanded message's " +"header to collapse it." +msgstr "" +"Quando si legge una conversazione, Geary contrae i messaggi già letti. " +"Cliccare sui messaggi compressi per espanderli; fare clic sull'intestazione " +"di un messaggio espanso per contrarlo." + +#. (itstool) path: section/p +#: C/overview.page:50 +msgid "" +"Any attachments in a message appear at the bottom of the message. You can " +"click an attachment to open it or right-click to save it." +msgstr "" +"Gli allegati di un messaggio appaiono in fondo al messaggio. È possibile " +"fare clic sull'allegato per aprirlo o usare il clic destro per salvarlo." + +#. (itstool) path: section/p +#: C/overview.page:52 +msgid "" +"Geary uses Gravatar to " +"display an avatar for each message's sender in its header." +msgstr "" +"Geary usa Gravatar per " +"mostrare nell'intestazione un avatar del mittente di ogni messaggio." + +#. (itstool) path: page/title +#: C/archive.page:10 +msgid "Delete or archive a message" +msgstr "Eliminare o archiviare un messaggio" + +#. (itstool) path: page/p +#: C/archive.page:12 +msgid "" +"When you use Geary with a Gmail account, Geary lets you archive " +"messages. The Archive toolbar button archives the selected " +"conversation(s). Archived messages appear in the All Mail folder." +msgstr "" +"Se si usa Geary con un account Gmail, Geary permette di archiviare " +"i messaggi. Il pulsante Archivia della barra degli strumenti " +"archivia le conversazioni selezionate. I messaggi archiviati appaiono nella " +"cartella Tutta la posta." + +#. (itstool) path: page/p +#: C/archive.page:16 +msgid "" +"With other mail servers, you can trash or delete, but not archive, messages. " +"To move one or more conversations to the Trash folder, select " +"them and press the Trash button on the toolbar. To permanently " +"delete the conversations, hold down Shift and press the " +"Delete button that appears in place of the Trash " +"button." +msgstr "" +"Se si usano altri server mail, è possibile spostare nel cestino o eliminare " +"i messaggi, ma non archiviarli. Per spostare una o più conversazioni nella " +"cartella Cestino, selezionarle e premere il pulsante " +"Cestina della barra degli strumenti. Per eliminare " +"permanentemente le conversazioni, tenere premuto Maiusc e premere " +"il pulsante Elimina che appare al posto del pulsante " +"Cestina." + +#. (itstool) path: page/p +#: C/archive.page:21 +msgid "" +"Delete is not available from every folder, such as Search. Delete is also " +"unavailable for Gmail. For Gmail, Trash will move messages to the " +"Trash folder on the server, where the user can then manually delete them. " +"The server will automatically remove trashed messages after 30 days." +msgstr "" +"Il pulsante Elimina non è disponibile in tutte le cartelle, come ad esempio " +"nella ricerca. È del tutto assente negli account Gmail. Questi hanno a " +"disposizione solo il pulsante Cestina, che sposta i messaggi " +"nella cartella Cestino sul server, dove l'utente può poi eliminarli " +"manualmente. Il server eliminerà automaticamente i messaggi cestinati dopo " +"30 giorni." + +#. (itstool) path: page/title +#: C/contributing.page:5 +msgid "Contribute to Geary" +msgstr "Contribuisci a Geary" + +#. (itstool) path: page/p +#: C/contributing.page:12 +msgid "" +"Want to help improve Geary? There are a number of ways you can contribute:" +msgstr "" +"Vuoi aiutare a migliorare Geary? Ci sono vari modi in cui puoi dare un " +"contributo:" + +#. (itstool) path: item/p +#: C/contributing.page:16 +msgid "" +"Bug " +"reporting—report new bugs or request new features" +msgstr "" +"Segnalazione " +"bug—segnala nuovi bug o richiedi nuove funzionalità" + +#. (itstool) path: item/p +#: C/contributing.page:19 +msgid "" +"User Experience " +"Design—research and develop Geary’s user experience" +msgstr "" +"Progettazione " +"dell'interfaccia utente—ricerca e sviluppo dell'interfaccia utente di " +"Geary" + +#. (itstool) path: item/p +#: C/contributing.page:20 +msgid "" +"Development—fix bugs and add new features" +msgstr "" +"Sviluppo—" +"correggi i bug e aggiungi nuove funzionalità" + +#. (itstool) path: item/p +#: C/contributing.page:21 +msgid "" +"Translating—translate Geary’s user interface and user manual into new languages" +msgstr "" +"Traduzione—traduci l'interfaccia utente e il manuale di Geary in nuove lingue" + +#. (itstool) path: item/p +#: C/contributing.page:22 +msgid "" +"Join the " +"discussion—on the mailing list or IRC channel" +msgstr "" +"Partecipa alla " +"discussione—sulla mailing list o sul canale IRC" + +#. (itstool) path: page/p +#: C/contributing.page:25 +msgid "Thanks for your help making Geary better!" +msgstr "Grazie per aiutarci a rendere Geary migliore!" + +#. (itstool) path: page/title +#: C/limits.page:5 +msgid "Limitations" +msgstr "Limiti" + +#. (itstool) path: page/p +#: C/limits.page:11 +msgid "" +"Geary is still in early development. Geary supports IMAP and has been tested " +"with Gmail, Yahoo, and the free Dovecot mail server. Experimental support " +"for Outlook.com is provided. Geary may not yet work well with some IMAP " +"servers. At this time Geary is still missing numerous features including " +"offline mode." +msgstr "" +"Geary è ancora in fase iniziale di sviluppo. Supporta il protocollo IMAP e è " +"stato testato con Gmail, Yahoo e il server mail libero Dovecot. È fornito " +"anche un supporto sperimentale a Outlook.com. Geary potrebbe non funzionare " +"ancora correttamente con alcuni server IMAP. Per il momento a Geary mancano " +"varie funzionalità, tra cui la modalità offline." + +#. (itstool) path: page/p +#: C/limits.page:17 +msgid "" +"To learn more about the features we're working on and the future of Geary, " +"please visit Geary's wiki " +"page." +msgstr "" +"Per avere informazioni sulle funzionalità a cui stiamo lavorando e sul " +"futuro di Geary, visita la pagina wiki di Geary." + +#. (itstool) path: page/title +#: C/bugs.page:5 +msgid "Found a bug?" +msgstr "Trovato un bug?" + +#. (itstool) path: page/p +#: C/bugs.page:12 +msgid "" +"If you suspect you've found a bug in Geary, please get in touch about it so it can be " +"fixed." +msgstr "" +"Se credi di aver trovato un bug in Geary, contattaci in modo che possa essere risolto." + +#. (itstool) path: page/p +#: C/bugs.page:16 +msgid "" +"To help diagnose the problem as fast as possible, please include the " +"following information:" +msgstr "" +"Per aiutarci a fare la diagnosi del problema il più rapidamente possibile, " +"includi le seguenti informazioni:" + +#. (itstool) path: item/p +#: C/bugs.page:20 +msgid "Geary version and installation method (Package? Flathub? Source code?)" +msgstr "" +"Versione di Geary e metodo di installazione (pacchetto? Flathub? Codice " +"sorgente?)" + +#. (itstool) path: item/p +#: C/bugs.page:22 +msgid "Your desktop (GNOME? KDE? Something else?)" +msgstr "Il desktop che stai usando (GNOME? KDE? Qualcos'altro?)" + +#. (itstool) path: item/p +#: C/bugs.page:23 +msgid "" +"Your operating system and version (Ubuntu 16.04? Fedora 28? Rolled your own?)" +msgstr "" +"Il sistema operativo in uso e la versione (Ubuntu 16.04? Fedora 28? Una tua " +"versione personalizzata?)" + +#. (itstool) path: item/p +#: C/bugs.page:25 +msgid "Email provider (Gmail, Yahoo!, Outlook.com, or someone else?)" +msgstr "Il fornitore di posta (Gmail, Yahoo!, Outlook.com o qualcos'altro?)" + +#. (itstool) path: item/p +#: C/bugs.page:27 +msgid "Steps to reproduce the bug" +msgstr "Passi per riprodurre il bug" + +#. (itstool) path: item/p +#: C/bugs.page:28 +msgid "What happened?" +msgstr "Cosa è successo?" + +#. (itstool) path: item/p +#: C/bugs.page:29 +msgid "What did you expect to happen?" +msgstr "Cosa ti aspettavi che succedesse?" + +#. (itstool) path: page/p +#: C/bugs.page:32 +msgid "Thanks for your help!" +msgstr "Grazie per il tuo aiuto!" + +#. (itstool) path: page/title +#: C/write.page:9 msgid "Write a message" msgstr "Scrivere un messaggio" -#: C/write.page:12(p) +#. (itstool) path: section/title +#: C/write.page:12 +msgid "Composing and replying" +msgstr "Composizione e risposta" + +#. (itstool) path: section/p +#: C/write.page:13 msgid "" "To compose a new message in Geary, press the New Message button " "on the toolbar." @@ -29,7 +415,8 @@ "Per comporre un nuovo messaggio in Geary, premere il pulsante Nuovo " "messaggio della barra degli strumenti." -#: C/write.page:15(p) +#. (itstool) path: section/p +#: C/write.page:16 msgid "" "To reply to a message, open the message menu in the upper right corner of " "the message and choose Reply, Reply All or " @@ -44,11 +431,13 @@ "Rispondi, Rispondi a tutti o Inoltra della " "barra degli strumenti." -#: C/write.page:20(title) +#. (itstool) path: section/title +#: C/write.page:21 msgid "Features" msgstr "Funzionalità" -#: C/write.page:22(p) +#. (itstool) path: section/p +#: C/write.page:23 msgid "" "Geary's email composer lets you adjust the font, size and color of text. You " "can also insert hyperlinks into messages." @@ -57,7 +446,8 @@ "dimensione e il colore del testo. Si possono anche inserire dei collegamenti " "ipertestuali all'interno dei messaggi." -#: C/write.page:24(p) +#. (itstool) path: section/p +#: C/write.page:25 msgid "" "Geary can also send plain text messages. In the drop-down menu, check or " "uncheck \"Rich Text\" to toggle between plain text and rich text mode." @@ -66,12 +456,14 @@ "aggiungere o togliere la spunta a \"Testo formattato\" per scegliere tra le " "modalità testo semplice e testo formattato." -#: C/write.page:27(p) +#. (itstool) path: section/p +#: C/write.page:28 msgid "" "You can attach a file to a message you're writing in either of these ways:" msgstr "È possibile allegare un file a un messaggio in uno di questi modi:" -#: C/write.page:29(p) +#. (itstool) path: item/p +#: C/write.page:30 msgid "" "Press the Attach File button at the lower left of the composer " "window, then select a file to attach." @@ -79,7 +471,8 @@ "Premere il pulsante File allegato in basso a sinistra della " "finestra di composizione, quindi selezionare un file da allegare." -#: C/write.page:31(p) +#. (itstool) path: item/p +#: C/write.page:32 msgid "" "Drag the file from the Nautilus file manager to the composer window, and " "drop it either on the text fields at the top of the window or on the toolbar " @@ -89,16 +482,20 @@ "rilasciarlo sui campi testuali in cima alla finestra o sulla barra degli " "strumenti in basso." -#: C/write.page:35(p) +#. (itstool) path: section/p +#: C/write.page:36 +#| msgid "" +#| "A number of keyboard shortcuts are available in the composer; see for details." msgid "" "A number of keyboard shortcuts are available in the composer; see for details." +"\"shortcuts\"/> for details." msgstr "" "Sono disponibili varie scorciatoie da tastiera nella finestra di " -"composizione, come è spiegato dettagliatamente in ." +"composizione, come è spiegato dettagliatamente in ." -#: C/write.page:37(p) +#. (itstool) path: section/p +#: C/write.page:38 msgid "" "You may specify a signature to be inserted into the composer in the dialog." @@ -106,11 +503,13 @@ "Si può indicare una firma da inserire nella finestra di composizione nelle " "opzioni ." -#: C/write.page:42(title) +#. (itstool) path: section/title +#: C/write.page:43 msgid "Drafts" msgstr "Bozze" -#: C/write.page:44(p) +#. (itstool) path: section/p +#: C/write.page:45 msgid "" "For mail servers that support drafts, Geary will automatically save the " "message as you type. If you close the composer without sending, Geary will " @@ -120,7 +519,8 @@ "automaticamente il messaggio mentre lo si scrive. Se si chiude il " "compositore senza inviare, Geary chiede se conservare la bozza o scartarla." -#: C/write.page:47(p) +#. (itstool) path: section/p +#: C/write.page:48 msgid "" "To edit an existing draft, select the Drafts folder in the folder list, " "select the message, and click \"Edit Draft\" in the message viewer." @@ -129,470 +529,23 @@ "delle cartelle, selezionare il messaggio e fare clic su \"Modifica bozza\" " "nel visualizzatore del messaggio." -#: C/write.page:50(p) +#. (itstool) path: section/p +#: C/write.page:51 msgid "Geary deletes the draft when you send the message." msgstr "Geary elimina la bozza quando il messaggio viene inviato." -#: C/star.page:10(title) -msgid "Star a message or mark it as read/unread" -msgstr "Segnare un messaggio come speciale o come letto/non letto" - -#: C/star.page:12(title) -msgid "Star messages" -msgstr "Segnare un messaggio come speciale" +#. (itstool) path: page/title +#: C/search.page:10 +msgid "Search" +msgstr "Ricerca" -#: C/star.page:13(p) +#. (itstool) path: page/p +#: C/search.page:12 msgid "" -"You can star messages to indicate that they're important to you. To mark a " -"conversation with a star, click its star icon in the conversation list. You " -"can star an individual message by clicking the star at the upper right of " -"the message itself." -msgstr "" -"È possibile segnare un messaggio come speciale per indicare che è " -"importante. Per contrassegnare una conversazione con una stella, si clicca " -"sull'icona della stella nell'elenco delle conversazioni. È possibile anche " -"segnare come speciale un singolo messaggio cliccando la stella sull'angolo " -"superiore destro del messaggio." - -#: C/star.page:15(p) -msgid "" -"With Gmail accounts, starred messages appear in the Starred folder in the " -"folder list." -msgstr "" -"Negli account Gmail i messaggi speciali appaiono nella cartella Speciali " -"dell'elenco delle cartelle." - -#: C/star.page:18(title) -msgid "Mark messages as read or unread" -msgstr "Segnare i messaggi come letti o non letti" - -#: C/star.page:19(p) -msgid "" -"Geary marks messages as read automatically as you read them. To manually " -"toggle a conversation as read or unread, click the circle icon in the " -"conversation list." -msgstr "" -"Geary contrassegna automaticamente i messaggi come letti quando vengono " -"letti. Per segnare manualmente una conversazione come letta o non letta fare " -"clic sull'icona del cerchio nell'elenco delle conversazioni." - -#: C/star.page:22(p) -msgid "" -"Alternately, the Mark as Unread in the Mark menu on " -"the toolbar can be used to toggle the read status of the selected " -"conversation(s)." -msgstr "" -"Oppure si può usare l'opzione Segna come non letto del menù " -"Segna della barra degli strumenti per modificare lo stato di " -"lettura delle conversazioni selezionate." - -#: C/star.page:25(p) -msgid "" -"To mark an individual message as read, select Mark as Read from " -"the dropdown menu." -msgstr "" -"Per contrassegnare un singolo messaggio come letto, selezionare Segna " -"come letto dal menù a discesa." - -#: C/shortcuts.page:11(title) -msgid "Keyboard shortcuts" -msgstr "Scorciatoie da tastiera" - -#: C/shortcuts.page:12(p) -msgid "Geary has keyboard shortcuts for most common operations." -msgstr "Geary ha delle scorciatoie da tastiera per le operazioni più comuni." - -#: C/shortcuts.page:15(p) -msgid "Compose a new message" -msgstr "Componi un nuovo messaggio" - -#: C/shortcuts.page:16(p) -msgid "CtrlN or N" -msgstr "CtrlN o N" - -#: C/shortcuts.page:19(p) -msgid "Reply to sender" -msgstr "Rispondi al mittente" - -#: C/shortcuts.page:20(p) -msgid "CtrlR or R" -msgstr "CtrlR o R" - -#: C/shortcuts.page:23(p) -msgid "Reply to all" -msgstr "Rispondi a tutti" - -#: C/shortcuts.page:24(p) -msgid "" -"CtrlShiftR or " -"ShiftR" -msgstr "" -"CtrlMaiuscR o " -"MaiuscR" - -#: C/shortcuts.page:27(p) -msgid "Forward" -msgstr "Inoltra" - -#: C/shortcuts.page:28(p) -msgid "CtrlL or F" -msgstr "CtrlL o F" - -#: C/shortcuts.page:31(p) -msgid "Archive" -msgstr "Archivia" - -#: C/shortcuts.page:32(key) -msgid "A" -msgstr "A" - -#: C/shortcuts.page:35(p) -msgid "Trash" -msgstr "Cestina" - -#: C/shortcuts.page:36(p) -msgid "Delete or Backspace" -msgstr "Canc o Backspace" - -#: C/shortcuts.page:39(p) -msgid "Delete" -msgstr "Elimina" - -#: C/shortcuts.page:40(p) -msgid "" -"ShiftDelete or ShiftBackspace" -msgstr "" -"MaiuscCanc o MaiuscBackspace" - -#: C/shortcuts.page:43(p) -msgid "Star" -msgstr "Speciale" - -#: C/shortcuts.page:44(key) C/shortcuts.page:100(key) -msgid "S" -msgstr "S" - -#: C/shortcuts.page:47(p) -msgid "Unstar" -msgstr "Non speciale" - -#: C/shortcuts.page:48(key) C/shortcuts.page:138(key) -msgid "D" -msgstr "D" - -#: C/shortcuts.page:51(p) -msgid "Mark read" -msgstr "Segna come letto" - -#: C/shortcuts.page:52(p) -msgid "" -"CtrlI or ShiftI" -msgstr "" -"CtrlI o MaiuscI" - -#: C/shortcuts.page:55(p) -msgid "Mark unread" -msgstr "Segna come non letto" - -#: C/shortcuts.page:56(p) -msgid "" -"CtrlU or ShiftU" -msgstr "" -"CtrlU o MaiuscU" - -#: C/shortcuts.page:59(p) -#| msgid "Label a conversation" -msgid "Move the conversation" -msgstr "Sposta la conversazione" - -#: C/shortcuts.page:60(key) -msgid "M" -msgstr "M" - -#: C/shortcuts.page:63(p) -#| msgid "Label a conversation" -msgid "Label the conversation" -msgstr "Etichettare la conversazione" - -#: C/shortcuts.page:64(key) C/shortcuts.page:162(key) -msgid "L" -msgstr "L" - -#: C/shortcuts.page:67(p) -msgid "Jump to next (older) conversation" -msgstr "Passa alla conversazione successiva (più vecchia)" - -#: C/shortcuts.page:68(key) -msgid "J" -msgstr "J" - -#: C/shortcuts.page:71(p) -msgid "Jump to previous (newer) conversation" -msgstr "Passa alla conversazione precedente (più recente)" - -#: C/shortcuts.page:72(key) C/shortcuts.page:158(key) -msgid "K" -msgstr "K" - -#: C/shortcuts.page:75(p) -msgid "Toggle spam" -msgstr "Segna come indesiderata" - -#: C/shortcuts.page:76(p) -msgid "CtrlJ or !" -msgstr "CtrlJ o !" - -#: C/shortcuts.page:79(p) -msgid "Quit" -msgstr "Esci" - -#: C/shortcuts.page:80(key) C/shortcuts.page:96(key) C/shortcuts.page:100(key) -#: C/shortcuts.page:104(key) C/shortcuts.page:108(key) -#: C/shortcuts.page:112(key) C/shortcuts.page:122(key) -#: C/shortcuts.page:126(key) C/shortcuts.page:130(key) -#: C/shortcuts.page:138(key) C/shortcuts.page:146(key) -#: C/shortcuts.page:150(key) C/shortcuts.page:154(key) -#: C/shortcuts.page:158(key) C/shortcuts.page:162(key) -#: C/shortcuts.page:166(key) C/shortcuts.page:181(key) -msgid "Ctrl" -msgstr "Ctrl" - -#: C/shortcuts.page:80(key) -msgid "Q" -msgstr "Q" - -#: C/shortcuts.page:83(p) -msgid "Zoom in" -msgstr "Aumenta ingrandimento" - -#: C/shortcuts.page:84(p) -msgid "Ctrl= or =" -msgstr "Ctrl= o =" - -#: C/shortcuts.page:87(p) -msgid "Zoom out" -msgstr "Diminuisci ingrandimento" - -#: C/shortcuts.page:88(p) -msgid "Ctrl- or -" -msgstr "Ctrl- o -" - -#: C/shortcuts.page:91(p) -msgid "Reset zoom" -msgstr "Ripristina ingrandimento" - -#: C/shortcuts.page:92(p) -msgid "Ctrl0 or 0" -msgstr "Ctrl0 o 0" - -#: C/shortcuts.page:95(p) -msgid "Close composer window" -msgstr "Chiudi la finestra di composizione" - -#: C/shortcuts.page:96(key) -msgid "W" -msgstr "W" - -#: C/shortcuts.page:99(p) -msgid "Jump to search box" -msgstr "Passa alla casella di ricerca" - -#: C/shortcuts.page:103(p) -msgid "Find in current conversation" -msgstr "Trova nella conversazione corrente" - -#: C/shortcuts.page:104(key) -msgid "F" -msgstr "F" - -#: C/shortcuts.page:107(p) -msgid "Find next in current conversation" -msgstr "Trova successivo nella conversazione corrente" - -#: C/shortcuts.page:108(key) C/shortcuts.page:112(key) -msgid "G" -msgstr "G" - -#: C/shortcuts.page:111(p) -msgid "Find previous in current conversation" -msgstr "Trova precedente nella conversazione corrente" - -#: C/shortcuts.page:112(key) -msgid "Shift" -msgstr "Maiusc" - -#: C/shortcuts.page:117(title) -msgid "Composer shortcuts" -msgstr "Scorciatoie della finestra di composizione" - -#: C/shortcuts.page:118(p) -msgid "These shortcuts are active whenever focus is in a composer." -msgstr "" -"Queste scorciatoie da tastiera sono attive quando si lavora nella finestra " -"di composizione." - -#: C/shortcuts.page:121(p) -msgid "Attach file" -msgstr "Allega file" - -#: C/shortcuts.page:122(key) -msgid "T" -msgstr "T" - -#: C/shortcuts.page:125(p) -msgid "Quote text" -msgstr "Cita testo" - -#: C/shortcuts.page:126(key) -msgid "]" -msgstr "]" - -#: C/shortcuts.page:129(p) -msgid "Unquote text" -msgstr "Togli citazione" - -#: C/shortcuts.page:130(key) -msgid "[" -msgstr "[" - -#: C/shortcuts.page:133(p) -msgid "Close composer" -msgstr "Chiudi la finestra di composizione" - -#: C/shortcuts.page:134(p) -msgid "CtrlW or Esc" -msgstr "CtrlW o Esc" - -#: C/shortcuts.page:137(p) -msgid "Detach composer" -msgstr "Stacca la finestra di composizione" - -#: C/shortcuts.page:142(p) -msgid "These shortcuts are only active in composers in rich text mode." -msgstr "" -"Queste scorciatoie sono attive solo se nella finestra di composizione è " -"spuntata la modalità di testo formattato." - -#: C/shortcuts.page:145(p) -msgid "Bold text" -msgstr "Testo in grassetto" - -#: C/shortcuts.page:146(key) C/shortcuts.page:181(key) -msgid "B" -msgstr "B" - -#: C/shortcuts.page:149(p) -msgid "Italicize text" -msgstr "Testo in corsivo" - -#: C/shortcuts.page:150(key) -msgid "I" -msgstr "I" - -#: C/shortcuts.page:153(p) -msgid "Underline text" -msgstr "Testo sottolineato" - -#: C/shortcuts.page:154(key) -msgid "U" -msgstr "U" - -#: C/shortcuts.page:157(p) -msgid "Strike text" -msgstr "Testo barrato" - -#: C/shortcuts.page:161(p) -msgid "Insert a link" -msgstr "Inserisci un collegamento" - -#: C/shortcuts.page:165(p) -msgid "Remove formatting" -msgstr "Togli formattazione" - -#: C/shortcuts.page:166(key) C/shortcuts.page:186(key) -msgid "Space" -msgstr "Spazio" - -#: C/shortcuts.page:172(title) -msgid "Keyboard navigation" -msgstr "Navigazione con la tastiera" - -#: C/shortcuts.page:173(p) -#| msgid "These shortcuts are active whenever focus is in a composer." -msgid "" -"These shortcuts can be used to move the keyboard focus in the main window." -msgstr "" -"Queste scorciatoie possono essere usate per spostare lo stato attivo della " -"tastiera nella finestra principale." - -#: C/shortcuts.page:176(p) -msgid "Move focus to the next/previous pane" -msgstr "Sposta lo stato attivo al riquadro successivo/precedente" - -#: C/shortcuts.page:177(p) -msgid "" -"F6 / ShiftF6" -msgstr "" -"F6 / MaiuscF6" - -#: C/shortcuts.page:180(p) -msgid "Move focus to conversation list" -msgstr "Sposta lo stato attivo sull'elenco delle conversazioni" - -#: C/shortcuts.page:184(p) -#| msgid "Jump to next (older) conversation" -msgid "Move to the next message in a conversation" -msgstr "Passa al messaggio successivo in una conversazione" - -#: C/shortcuts.page:190(p) -#| msgid "Move focus to the next/previous pane" -msgid "Move to the next/previous message in a conversation" -msgstr "Passa al messaggio successivo/precedente in una conversazione" - -#: C/shortcuts.page:191(p) -#| msgid "" -#| "CtrlU or ShiftU" -msgid "" -"CtrlDown / CtrlUp" -msgstr "" -"CtrlGiù / Ctrl" - -#: C/shortcuts.page:197(p) -msgid "Move to the first/last message in a conversation" -msgstr "Passa al primo/ultimo messaggio in una conversazione" - -#: C/shortcuts.page:198(p) -#| msgid "" -#| "CtrlI or ShiftI" -msgid "" -"CtrlHome / CtrlEnd" -msgstr "" -"CtrlInizio / CtrlFine" - -#: C/search.page:10(title) -msgid "Search" -msgstr "Ricerca" - -#: C/search.page:12(p) -msgid "" -"Geary supports a per-account full text search. To start a search, select a " -"folder associated with the account you'd like to search against. Then click " -"the search box in the toolbar (or press CtrlS) and start typing. Results will appear after a brief delay." +"Geary supports a per-account full text search. To start a search, select a " +"folder associated with the account you'd like to search against. Then click " +"the search box in the toolbar (or press CtrlS) and start typing. Results will appear after a brief delay." msgstr "" "Geary supporta una ricerca «full text» per account. Per avviare una ricerca, " "selezionare una cartella associata all'account in cui si vuole fare la " @@ -600,7 +553,8 @@ "premere CtrlS) e iniziare a scrivere. " "I risultati appariranno dopo un breve ritardo." -#: C/search.page:16(p) +#. (itstool) path: page/p +#: C/search.page:16 msgid "" "The full text search includes email text, email addresses (to, from, and " "cc), subject lines and attachment filenames." @@ -608,7 +562,8 @@ "La ricerca «full text» include il corpo del messaggio, gli indirizzi email " "(A, Da e Cc), l'oggetto e i nomi dei file allegati." -#: C/search.page:19(p) +#. (itstool) path: page/p +#: C/search.page:19 msgid "" "Keywords that match your search are highlighted in the message view. Geary " "will match different forms of the same word, for example searching for \"walk" @@ -619,123 +574,130 @@ "della stessa parola, per esempio la ricerca di \"canta\" troverà come " "corrispondenti le parole \"cantare\" e \"cantante\"." -#: C/search.page:23(title) +#. (itstool) path: section/title +#: C/search.page:23 msgid "Search operators" msgstr "Operatori di ricerca" -#: C/search.page:24(p) +#. (itstool) path: section/p +#: C/search.page:24 msgid "Geary supports the following operators to limit the scope of searches:" msgstr "" "Geary supporta i seguenti operatori che permettono di limitare l'estensione " "delle ricerche:" -#: C/search.page:27(var) -msgid "filename" -msgstr "nomefile" - -#: C/search.page:27(input) -msgid "attachment:" -msgstr "allegato:" +#. (itstool) path: td/p +#: C/search.page:27 +msgid "attachment:filename" +msgstr "allegato:nomefile" -#: C/search.page:28(p) +#. (itstool) path: td/p +#: C/search.page:28 msgid "Finds messages with attachments whose name matches filename." msgstr "" "Trova i messaggi con allegati il cui nome corrisponda a nomefile." -#: C/search.page:31(var) C/search.page:39(var) C/search.page:63(var) -msgid "recipient" -msgstr "destinatario" - -#: C/search.page:31(input) -msgid "bcc:" -msgstr "ccn:" +#. (itstool) path: td/p +#: C/search.page:31 +msgid "bcc:recipient" +msgstr "ccn:destinatario" -#: C/search.page:32(p) +#. (itstool) path: td/p +#: C/search.page:32 msgid "Finds messages where recipient matches the BCC header." msgstr "" "Trova i messaggi in cui destinatario corrisponde " "nell'intestazione Ccn." -#: C/search.page:35(var) C/search.page:59(var) -msgid "text" -msgstr "testo" - -#: C/search.page:35(input) -msgid "body:" -msgstr "corpo:" +#. (itstool) path: td/p +#: C/search.page:35 +msgid "body:text" +msgstr "corpo:testo" -#: C/search.page:36(p) +#. (itstool) path: td/p +#: C/search.page:36 msgid "Finds messages whose body contains text." msgstr "Trova i messaggi il cui corpo contiene testo." -#: C/search.page:39(input) -msgid "cc:" -msgstr "cc:" +#. (itstool) path: td/p +#: C/search.page:39 +msgid "cc:recipient" +msgstr "cc:destinatario" -#: C/search.page:40(p) +#. (itstool) path: td/p +#: C/search.page:40 msgid "Finds messages where recipient matches the CC header." msgstr "" "Trova i messaggi in cui destinatario corrisponde " "nell'intestazione Cc." -#: C/search.page:43(var) -msgid "sender" -msgstr "mittente" - -#: C/search.page:43(input) -msgid "from:" -msgstr "da:" +#. (itstool) path: td/p +#: C/search.page:43 +msgid "from:sender" +msgstr "da:mittente" -#: C/search.page:44(p) +#. (itstool) path: td/p +#: C/search.page:44 msgid "Finds messages where sender matches the From header." msgstr "" "Trova i messaggi in cui mittente corrisponde nell'intestazione Da " "(From in inglese)." -#: C/search.page:47(input) -msgid "is:read" -msgstr "è:letto" +#. (itstool) path: td/p +#: C/search.page:47 +msgid "is:read" +msgstr "è:letto" -#: C/search.page:48(p) +#. (itstool) path: td/p +#: C/search.page:48 msgid "Finds messages that have been marked as read." msgstr "Trova i messaggi contrassegnati come letti." -#: C/search.page:51(input) -msgid "is:starred" -msgstr "è:speciale" +#. (itstool) path: td/p +#: C/search.page:51 +msgid "is:starred" +msgstr "è:speciale" -#: C/search.page:52(p) +#. (itstool) path: td/p +#: C/search.page:52 msgid "Finds messages that have been marked as starred." msgstr "Trova i messaggi contrassegnati come speciali." -#: C/search.page:55(input) -msgid "is:unread" -msgstr "è:nonletto" +#. (itstool) path: td/p +#: C/search.page:55 +msgid "is:unread" +msgstr "è:nonletto" -#: C/search.page:56(p) +#. (itstool) path: td/p +#: C/search.page:56 msgid "Finds messages that have been marked as not read." msgstr "Trova i messaggi contrassegnati come non letti." -#: C/search.page:59(input) -msgid "subject:" -msgstr "oggetto:" +#. (itstool) path: td/p +#: C/search.page:59 +msgid "subject:text" +msgstr "oggetto:testo" -#: C/search.page:60(p) +#. (itstool) path: td/p +#: C/search.page:60 msgid "Finds messages whose subject contains text." msgstr "Trova i messaggi il cui oggetto contiene testo." -#: C/search.page:63(input) -msgid "to:" -msgstr "a:" +#. (itstool) path: td/p +#: C/search.page:63 +msgid "to:recipient" +msgstr "a:destinatario" -#: C/search.page:64(p) +#. (itstool) path: td/p +#: C/search.page:64 msgid "" "Finds messages where sender matches the To, CC, or BCC header." msgstr "" "Trova i messaggi in cui mittente corrisponde all'intestazione A, " "Cc o Ccn." -#: C/search.page:68(p) +#. (itstool) path: section/p +#: C/search.page:68 msgid "" "As a special case, the bcc, cc, from, and to operators support me as their " @@ -747,493 +709,251 @@ "argomento, abilitando la ricerca dell'indirizzo email del proprio account " "nel contesto appropriato." -#: C/preferences.page:10(title) -msgid "Preferences" -msgstr "Preferenze" - -#: C/preferences.page:11(p) -msgid "" -"The Preferences option is available in either Geary's application " -"menu or the gear menu in the upper-right of the toolbar. (The location " -"depends on the install desktop shell. For GNOME Shell and Unity, the " -"application menu is available near the top-left corner of the screen.)" -msgstr "" -"L'opzione Preferenze è disponibile nel menù dell'applicazione o " -"nel menù dell'icona con la ruota dentata in alto a destra (la posizione " -"dipende dall'ambiente grafico utilizzato: in GNOME Shell e Unity il menù " -"dell'applicazione si trova vicino all'angolo superiore sinistro dello " -"schermo)." - -#: C/preferences.page:17(title) -msgid "Reading" -msgstr "Lettura" - -#: C/preferences.page:20(gui) -msgid "Automatically select next message" -msgstr "Seleziona automaticamente il messaggio successivo" - -#: C/preferences.page:21(p) -msgid "" -"When this option is enabled, Geary automatically selects the latest message " -"in a folder when you enter the folder. In addition, after archiving a " -"message, Geary automatically selects an adjacent message." +#. (itstool) path: title/media +#. This is a reference to an external file such as an image or video. When +#. the file changes, the md5 hash will change to let you know you need to +#. update your localized copy. The msgstr is not used at all. Set it to +#. whatever you like once you have updated your copy of the file. +#: C/index.page:5 +#| msgid "@@image: 'figures/geary.svg'; md5=18b50c9e10fe5256ae1cb12aaa3a7600" +msgctxt "_" +msgid "external ref='figures/geary.svg' md5='18b50c9e10fe5256ae1cb12aaa3a7600'" msgstr "" -"Quando questa opzione è abilitata, Geary seleziona automaticamente l'ultimo " -"messaggio di una cartella quando si è in quella cartella. Inoltre, dopo " -"l'archiviazione di un messaggio, Geary seleziona automaticamente un " -"messaggio vicino." +"external ref='figures/geary.svg' md5='18b50c9e10fe5256ae1cb12aaa3a7600'" -#: C/preferences.page:26(gui) -msgid "Display conversation preview" -msgstr "Visualizza l'anteprima della conversazione" - -#: C/preferences.page:27(p) -msgid "" -"Enables message previews in the conversation list. Previews show the first " -"few lines of each message." -msgstr "" -"Abilita le anteprime dei messaggi nell'elenco delle conversazioni. Le " -"anteprime mostrano le prime righe di ogni messaggio." - -#: C/preferences.page:31(gui) -msgid "Use three pane view" -msgstr "Usa la visuale a tre riquadri" - -#: C/preferences.page:32(p) -msgid "" -"Show the folder list, the conversation list, and the messages side-by-side-" -"by-side in three panes. If not selected, the folder list and conversation " -"list will be stacked vertically in a single pane." -msgstr "" -"Mostra l'elenco delle cartelle, quello delle conversazioni e i messaggi uno " -"accanto all'altro in tre riquadri. Se non selezionato, l'elenco delle " -"cartelle e quello delle conversazioni saranno impilati verticalmente in un " -"unico riquadro." - -#: C/preferences.page:40(title) -msgid "Composer" -msgstr "Componitore" - -#: C/preferences.page:43(gui) -msgid "Enable spell checking" -msgstr "Abilita controllo ortografico" - -#: C/preferences.page:44(p) -msgid "" -"When set, Geary automatically spell checks a message as you write it, " -"underlying each misspelled word in red." -msgstr "" -"Se impostato, Geary esegue automaticamente il controllo ortografico di un " -"messaggio mentre lo si scrive, sottolineando le parole sbagliate in rosso." - -#: C/preferences.page:51(title) -msgid "Notifications" -msgstr "Notifiche" - -#: C/preferences.page:54(gui) -msgid "Play notification sounds" -msgstr "Attiva avvisi sonori" - -#: C/preferences.page:55(p) -msgid "When set, Geary plays a sound whenever a new message arrives." -msgstr "Se impostato, Geary emette un suono quando arriva un nuovo messaggio." - -#: C/preferences.page:58(gui) -msgid "Show notifications for new mail" -msgstr "Mostra notifiche per i nuovi messaggi" - -#: C/preferences.page:59(p) -msgid "" -"When set, Geary displays a notification each time a new message " -"arrives. Notifications are displayed in a system-dependent manner. On GNOME " -"Shell, notifications appear at the bottom of the display (older versions) or " -"centered just below the top bar (newer versions). In Ubuntu Unity, " -"notifications appear at the upper right of the display." -msgstr "" -"Se impostato, Geary mostra una notifica ogni volta che arriva un " -"nuovo messaggio. Il modo in cui le notifiche appaiono dipende dal sistema. " -"In GNOME Shell le notifiche appaiono in fondo allo schermo (nelle versioni " -"più vecchie) o centrate proprio sotto la barra superiore (nelle versioni " -"recenti). In Ubuntu Unity appaiono in alto a destra." - -#: C/preferences.page:65(gui) -msgid "Always watch for new mail" -msgstr "Controlla sempre la nuova posta" - -#: C/preferences.page:66(p) -msgid "" -"Geary will watch your accounts for new mail even when the main window is not " -"open. To do this, it will silently start when you log in to your computer, " -"and it will continue to run after you close the main window." -msgstr "" -"Geary controllerà la presenza di nuova posta anche quando la finestra " -"principale non è aperta. Per far ciò, si avvierà silenziosamente al login e " -"continuerà a girare dopo la chiusura della finestra principale." - -#: C/overview.page:8(title) -msgid "Overview" -msgstr "Panoramica" - -#: C/overview.page:10(p) -msgid "" -"Geary is a lightweight email reader for the GNOME desktop. It works with mail servers that support the IMAP " -"protocol, including popular services such as Gmail, Yahoo Mail, and Outlook." -"com." -msgstr "" -"Geary è un lettore email leggero per il desktop GNOME. Funziona con i server mail che supportano il protocollo " -"IMAP, compresi popolari servizi come Gmail, Yahoo Mail e Outlook.com." - -#: C/overview.page:14(p) -msgid "" -"Geary groups mail messages into conversations. A conversation " -"contains all messages in a single thread of discussion." -msgstr "" -"Geary raggruppa i messaggi di posta in conversazioni. Una " -"conversazione contiene tutti i messaggi di una singola discussione." - -#: C/overview.page:17(p) -msgid "The main Geary window is divided into several areas:" -msgstr "La finestra principale di Geary è divisa in varie aree:" - -#: C/overview.page:20(title) -msgid "Folder list" -msgstr "Elenco delle cartelle" - -#: C/overview.page:21(p) -msgid "" -"The folder list at the left displays all folders and " -"labels in your mail account. Geary uses the term label for " -"any folder that you have created to help organize your messages. (The Gmail " -"web interface also uses this term; most other mail services do not.)" -msgstr "" -"L'elenco delle cartelle a sinistra mostra tutte le cartelle e le etichette del proprio account di posta. Geary usa il " -"termine etichetta per indicare qualsiasi cartella creata per " -"organizzare i propri messaggi. (Anche l'interfaccia web di Gmail usa questo " -"termine; la maggior parte degli altri servizi mail non lo usa.)" - -#: C/overview.page:28(title) -msgid "Conversation list" -msgstr "Elenco delle conversazioni" - -#: C/overview.page:29(p) -msgid "" -"The conversation list displays a list of conversations in the " -"selected folder. Newer conversations appear at the top." -msgstr "" -"L'elenco delle conversazioni mostra le conversazioni presenti nella " -"cartella selezionata. Le conversazioni più recenti appaiono in cima." - -#: C/overview.page:31(p) -msgid "" -"Each sender's name appears bold if there are unread messages from that " -"sender. If a conversation has more than one message, Geary displays a count " -"of messages in the conversation." -msgstr "" -"Il nome del mittente appare in grassetto se ci sono dei messaggi non letti " -"inviati da quel mittente. Se una conversazione ha più di un messaggio, Geary " -"indica il numero di messaggi nella conversazione." - -#: C/overview.page:34(p) -msgid "" -"Geary does not automatically download all messages in all of your mail " -"folders. When you first visit your Inbox or any other folder, Geary " -"downloads the 50 most recent messages in that folder. To see more messages, " -"simply scroll down the conversation list and Geary will fetch more messages " -"automatically." -msgstr "" -"Geary non scarica automaticamente tutti i messaggi di tutte le cartelle di " -"posta. Quando si apre la prima volta la posta in arrivo o qualsiasi altra " -"cartella, Geary scarica i 50 messaggi più recenti di quella cartella. Per " -"vedere più messaggi, scorrere in giù l'elenco delle conversazioni: Geary " -"recupererà automaticamente altri messaggi." - -#: C/overview.page:36(p) -msgid "" -"Some commands in Geary can act on a group of conversations. To select " -"multiple conversations, hold down the Ctrl key and click each " -"conversation in turn in the conversation list. Alternatively, click the " -"first conversation in a range, then hold down Shift and click the " -"last conversation." -msgstr "" -"Alcuni comandi in Geary possono agire su un gruppo di conversazioni. Per " -"selezionare più di una conversazione, tenere premuto il tasto Ctrl e fare clic su ogni conversazione nell'elenco delle conversazioni. " -"Oppure fare clic sulla prima conversazione di una serie consecutiva, tenere " -"premuto il tasto Maiusc e fare clic sull'ultima conversazione." - -#: C/overview.page:44(title) -msgid "Message area" -msgstr "Area dei messaggi" - -#: C/overview.page:45(p) -msgid "" -"The message area displays all messages in the selected " -"conversation, with the oldest message at the top." -msgstr "" -"L'area dei messaggi mostra tutti i messaggi della conversazione " -"selezionata, col messaggio più vecchio in cima." - -#: C/overview.page:47(p) -msgid "" -"At the upper right of each message, Geary displays a dropdown arrow that " -"lets you open the message menu with commands that operate on the " -"message." -msgstr "" -"Nell'angolo superiore destro di ogni messaggio, c'è una freccia a discesa " -"che permette di aprire il menù dei messaggi contenente i comandi " -"che agiscono sul messaggio." +#. (itstool) path: page/title +#: C/index.page:5 +msgid " Geary" +msgstr " Geary" -#: C/overview.page:49(p) -msgid "" -"When you view a conversation, Geary collapses messages that you've already " -"read. Click collapsed messages to expand them. Click an expanded message's " -"header to collapse it." -msgstr "" -"Quando si legge una conversazione, Geary contrae i messaggi già letti. " -"Cliccare sui messaggi compressi per espanderli; fare clic sull'intestazione " -"di un messaggio espanso per contrarlo." +#. (itstool) path: section/title +#: C/index.page:8 +msgid "Introduction" +msgstr "Introduzione" -#: C/overview.page:50(p) -msgid "" -"Any attachments in a message appear at the bottom of the message. You can " -"click an attachment to open it or right-click to save it." -msgstr "" -"Gli allegati di un messaggio appaiono in fondo al messaggio. È possibile " -"fare clic sull'allegato per aprirlo o usare il clic destro per salvarlo." +#. (itstool) path: section/title +#: C/index.page:12 +msgid "Using Geary" +msgstr "Uso di Geary" -#: C/overview.page:52(p) -msgid "" -"Geary uses Gravatar to " -"display an avatar for each message's sender in its header." -msgstr "" -"Geary usa Gravatar per " -"mostrare nell'intestazione un avatar del mittente di ogni messaggio." +#. (itstool) path: section/title +#: C/index.page:16 +msgid "Contributing and bug reporting" +msgstr "Contribuire e segnalazione bug" -#: C/limits.page:11(title) -msgid "Limitations" -msgstr "Limiti" +#. (itstool) path: page/title +#: C/star.page:10 +msgid "Star a message or mark it as read/unread" +msgstr "Segnare un messaggio come speciale o come letto/non letto" -#: C/limits.page:12(p) -msgid "" -"Geary is still in early development. Geary supports IMAP and has been tested " -"with Gmail, Yahoo, and the free Dovecot mail server. Experimental support " -"for Outlook.com is provided. Geary may not yet work well with some IMAP " -"servers. At this time Geary is still missing numerous features including " -"offline mode." -msgstr "" -"Geary è ancora in fase iniziale di sviluppo. Supporta il protocollo IMAP e è " -"stato testato con Gmail, Yahoo e il server mail libero Dovecot. È fornito " -"anche un supporto sperimentale a Outlook.com. Geary potrebbe non funzionare " -"ancora correttamente con alcuni server IMAP. Per il momento a Geary mancano " -"varie funzionalità, tra cui la modalità offline." +#. (itstool) path: section/title +#: C/star.page:12 +msgid "Star messages" +msgstr "Segnare un messaggio come speciale" -#: C/limits.page:14(p) +#. (itstool) path: section/p +#: C/star.page:13 msgid "" -"To learn more about the features we're working on and the future of Geary, " -"please visit Geary's wiki " -"page." +"You can star messages to indicate that they're important to you. To mark a " +"conversation with a star, click its star icon in the conversation list. You " +"can star an individual message by clicking the star at the upper right of " +"the message itself." msgstr "" -"Per avere informazioni sulle funzionalità a cui stiamo lavorando e sul " -"futuro di Geary, visita la pagina wiki di Geary." - -#: C/label.page:10(title) -msgid "Label or move a conversation" -msgstr "Etichetta o sposta una conversazione" - -#: C/label.page:12(title) -msgid "Label a conversation" -msgstr "Etichettare una conversazione" +"È possibile segnare un messaggio come speciale per indicare che è " +"importante. Per contrassegnare una conversazione con una stella, si clicca " +"sull'icona della stella nell'elenco delle conversazioni. È possibile anche " +"segnare come speciale un singolo messaggio cliccando la stella sull'angolo " +"superiore destro del messaggio." -#: C/label.page:13(p) +#. (itstool) path: section/p +#: C/star.page:15 msgid "" -"Geary lets you apply one or more labels to each conversation. Geary " -"labels correspond to labels in Gmail, or ordinary folders in other mail " -"services." +"With Gmail accounts, starred messages appear in the Starred folder in the " +"folder list." msgstr "" -"Geary permette di applicare una o più etichette a una " -"conversazione. Le etichette in Geary corrispondono alle etichette in Gmail e " -"alle normali cartelle in altri servizi di posta." +"Negli account Gmail i messaggi speciali appaiono nella cartella Speciali " +"dell'elenco delle cartelle." -#: C/label.page:15(p) -msgid "" -"To label one or more conversations, first select the conversation(s), then " -"do either of the following:" -msgstr "" -"Per etichettare una o più conversazioni, selezionare la conversazione/le " -"conversazioni e poi scegliere una delle seguenti azioni:" +#. (itstool) path: section/title +#: C/star.page:18 +msgid "Mark messages as read or unread" +msgstr "Segnare i messaggi come letti o non letti" -#: C/label.page:18(p) +#. (itstool) path: section/p +#: C/star.page:19 msgid "" -"Click the Label button on the toolbar and select a label from the " -"resulting drop-down menu." +"Geary marks messages as read automatically as you read them. To manually " +"toggle a conversation as read or unread, click the circle icon in the " +"conversation list." msgstr "" -"Cliccare il pulsante Etichetta della barra degli strumenti e " -"selezionare un'etichetta dal menù a tendina." +"Geary contrassegna automaticamente i messaggi come letti quando vengono " +"letti. Per segnare manualmente una conversazione come letta o non letta fare " +"clic sull'icona del cerchio nell'elenco delle conversazioni." -#: C/label.page:20(p) +#. (itstool) path: section/p +#: C/star.page:22 msgid "" -"Hold down the Ctrl key and drag the conversation(s) from the " -"conversation list to the label in the sidebar." +"Alternately, the Mark as Unread in the Mark menu on " +"the toolbar can be used to toggle the read status of the selected " +"conversation(s)." msgstr "" -"Tenere premuto il tasto Ctrl e trascinare la conversazione/le " -"conversazioni dall'elenco delle conversazioni all'etichetta della barra " -"laterale." - -#: C/label.page:25(title) -msgid "Move a conversation to a folder or label" -msgstr "Spostare una conversazione in una cartella o etichetta" +"Oppure si può usare l'opzione Segna come non letto del menù " +"Segna della barra degli strumenti per modificare lo stato di " +"lettura delle conversazioni selezionate." -#: C/label.page:26(p) +#. (itstool) path: section/p +#: C/star.page:25 msgid "" -"To move one or more conversations to a folder or label, first select the " -"conversation(s), then do either of the following:" +"To mark an individual message as read, select Mark as Read from " +"the dropdown menu." msgstr "" -"Per spostare una o più conversazioni in una cartella o etichetta, " -"selezionare la conversazione/le conversazioni e poi scegliere una delle " -"seguenti azioni:" +"Per contrassegnare un singolo messaggio come letto, selezionare Segna " +"come letto dal menù a discesa." -#: C/label.page:29(p) -msgid "" -"Click the Move button on the toolbar and select a folder or label " -"from the resulting drop-down menu." -msgstr "" -"Cliccare il pulsante Sposta della barra degli strumenti e " -"selezionare una cartella o etichetta dal menù a discesa." +#. (itstool) path: page/title +#: C/preferences.page:10 +msgid "Preferences" +msgstr "Preferenze" -#: C/label.page:31(p) +#. (itstool) path: page/p +#: C/preferences.page:11 msgid "" -"Drag the conversation(s) from the conversation list to the folder or label " -"in the sidebar." +"The Preferences option is available in either Geary's application " +"menu or the gear menu in the upper-right of the toolbar. (The location " +"depends on the install desktop shell. For GNOME Shell and Unity, the " +"application menu is available near the top-left corner of the screen.)" msgstr "" -"Trascinare la conversazione/le conversazioni dall'elenco delle conversazioni " -"alla cartella o etichetta della barra laterale." - -#. When image changes, this message will be marked fuzzy or untranslated for you. -#. It doesn't matter what you translate it to: it's not used at all. -#: C/index.page:5(None) -msgid "@@image: 'figures/geary.svg'; md5=18b50c9e10fe5256ae1cb12aaa3a7600" -msgstr "@@image: 'figures/geary.svg'; md5=18b50c9e10fe5256ae1cb12aaa3a7600" - -#: C/index.page:5(title) -msgid " Geary" -msgstr " Geary" - -#: C/index.page:9(title) -msgid "Introduction" -msgstr "Introduzione" - -#: C/index.page:13(title) -msgid "Using Geary" -msgstr "Uso di Geary" +"L'opzione Preferenze è disponibile nel menù dell'applicazione o " +"nel menù dell'icona con la ruota dentata in alto a destra (la posizione " +"dipende dall'ambiente grafico utilizzato: in GNOME Shell e Unity il menù " +"dell'applicazione si trova vicino all'angolo superiore sinistro dello " +"schermo)." -#: C/index.page:17(title) -msgid "Bugs" -msgstr "Bug" +#. (itstool) path: section/title +#: C/preferences.page:17 +msgid "Reading" +msgstr "Lettura" -#: C/bugs.page:8(title) -msgid "Think you've found a bug?" -msgstr "Pensi di aver trovato un bug?" +#. (itstool) path: item/title +#: C/preferences.page:20 +#| msgid "Automatically select next message" +msgid "Automatically select next message" +msgstr "Seleziona automaticamente il messaggio successivo" -#: C/bugs.page:9(p) +#. (itstool) path: item/p +#: C/preferences.page:21 msgid "" -"If you suspect you've found a bug in Geary, follow these steps to report it:" +"When this option is enabled, Geary automatically selects the latest message " +"in a folder when you enter the folder. In addition, after archiving a " +"message, Geary automatically selects an adjacent message." msgstr "" -"Se pensi di aver trovato un bug in Geary, segui queste istruzioni per " -"segnalarlo:" +"Quando questa opzione è abilitata, Geary seleziona automaticamente l'ultimo " +"messaggio di una cartella quando si è in quella cartella. Inoltre, dopo " +"l'archiviazione di un messaggio, Geary seleziona automaticamente un " +"messaggio vicino." -#: C/bugs.page:11(p) -msgid "" -"Search Geary's bug database to see if someone else has reported the " -"bug." -msgstr "" -"Cerca nel database dei bug di Geary per vedere se qualcun altro ha già " -"segnalato il bug." +#. (itstool) path: item/title +#: C/preferences.page:26 +#| msgid "Display conversation preview" +msgid "Display conversation preview" +msgstr "Visualizza l'anteprima della conversazione" -#: C/bugs.page:13(p) +#. (itstool) path: item/p +#: C/preferences.page:27 msgid "" -"Don't see your bug listed? Congratulations! You've found a new bug. To " -"create an bug report, create an account on GNOME's Bugzilla and file a new bug. Be as specific as you can and describe the steps to reproduce it. " -"Don't forget to include details about your operating system and what version " -"of Geary you're running." +"Enables message previews in the conversation list. Previews show the first " +"few lines of each message." msgstr "" -"Non vedi elencato il tuo bug? Congratulazioni! Hai trovato un nuovo bug. Per " -"creare una segnalazione bug, crea un account su Bugzilla di GNOME e apri un " -"nuovo bug. Cerca di dare più particolari possibili e descrivi i passi " -"necessari per riprodurlo. Non dimenticare di includere dettagli sul tuo " -"sistema operativo e sulla versione di Geary che stai usando." +"Abilita le anteprime dei messaggi nell'elenco delle conversazioni. Le " +"anteprime mostrano le prime righe di ogni messaggio." + +#. (itstool) path: item/title +#: C/preferences.page:31 +#| msgid "Use three pane view" +msgid "Use three pane view" +msgstr "Usa la visuale a tre riquadri" -#: C/bugs.page:18(p) +#. (itstool) path: item/p +#: C/preferences.page:32 msgid "" -"For general inquiries, please join the Geary mailing list." +"Show the folder list, the conversation list, and the messages side-by-side-" +"by-side in three panes. If not selected, the folder list and conversation " +"list will be stacked vertically in a single pane." msgstr "" -"Per richieste generiche, iscriviti alla mailing list di Geary." +"Mostra l'elenco delle cartelle, quello delle conversazioni e i messaggi uno " +"accanto all'altro in tre riquadri. Se non selezionato, l'elenco delle " +"cartelle e quello delle conversazioni saranno impilati verticalmente in un " +"unico riquadro." -#: C/archive.page:10(title) -msgid "Delete or archive a message" -msgstr "Eliminare o archiviare un messaggio" +#. (itstool) path: section/title +#: C/preferences.page:40 +msgid "Notifications" +msgstr "Notifiche" -#: C/archive.page:12(p) -msgid "" -"When you use Geary with a Gmail account, Geary lets you archive " -"messages. The Archive toolbar button archives the selected " -"conversation(s). Archived messages appear in the All Mail folder." -msgstr "" -"Se si usa Geary con un account Gmail, Geary permette di archiviare " -"i messaggi. Il pulsante Archivia della barra degli strumenti " -"archivia le conversazioni selezionate. I messaggi archiviati appaiono nella " -"cartella Tutta la posta." +#. (itstool) path: item/title +#: C/preferences.page:43 +#| msgid "Play notification sounds" +msgid "Play notification sounds" +msgstr "Attiva avvisi sonori" + +#. (itstool) path: item/p +#: C/preferences.page:44 +msgid "When set, Geary plays a sound whenever a new message arrives." +msgstr "Se impostato, Geary emette un suono quando arriva un nuovo messaggio." + +#. (itstool) path: item/title +#: C/preferences.page:47 +#| msgid "Show notifications for new mail" +msgid "Show notifications for new mail" +msgstr "Mostra notifiche per i nuovi messaggi" -#: C/archive.page:16(p) +#. (itstool) path: item/p +#: C/preferences.page:48 msgid "" -"With other mail servers, you can trash or delete, but not archive, messages. " -"To move one or more conversations to the Trash folder, select " -"them and press the Trash button on the toolbar. To permanently " -"delete the conversations, hold down Shift and press the " -"Delete button that appears in place of the Trash " -"button." +"When set, Geary displays a notification each time a new message " +"arrives. Notifications are displayed in a system-dependent manner. On GNOME " +"Shell, notifications appear at the bottom of the display (older versions) or " +"centered just below the top bar (newer versions). In Ubuntu Unity, " +"notifications appear at the upper right of the display." msgstr "" -"Se si usano altri server mail, è possibile spostare nel cestino o eliminare " -"i messaggi, ma non archiviarli. Per spostare una o più conversazioni nella " -"cartella Cestino, selezionarle e premere il pulsante " -"Cestina della barra degli strumenti. Per eliminare " -"permanentemente le conversazioni, tenere premuto Maiusc e premere " -"il pulsante Elimina che appare al posto del pulsante " -"Cestina." +"Se impostato, Geary mostra una notifica ogni volta che arriva un " +"nuovo messaggio. Il modo in cui le notifiche appaiono dipende dal sistema. " +"In GNOME Shell le notifiche appaiono in fondo allo schermo (nelle versioni " +"più vecchie) o centrate proprio sotto la barra superiore (nelle versioni " +"recenti). In Ubuntu Unity appaiono in alto a destra." + +#. (itstool) path: item/title +#: C/preferences.page:54 +msgid "Watch for new mail when closed" +msgstr "Controllare la nuova posta dopo la chiusura" -#: C/archive.page:21(p) +#. (itstool) path: item/p +#: C/preferences.page:55 +#| msgid "" +#| "Geary will watch your accounts for new mail even when the main window is " +#| "not open. To do this, it will silently start when you log in to your " +#| "computer, and it will continue to run after you close the main window." msgid "" -"Delete is not available from every folder, such as Search. Delete is also " -"unavailable for Gmail. For Gmail, Trash will move messages to the " -"Trash folder on the server, where the user can then manually delete them. " -"The server will automatically remove trashed messages after 30 days." +"Geary will watch your accounts for new mail even when the main window is not " +"open. To do this, it will silently start when you log in to your computer, " +"and it will continue to run after you close all windows." msgstr "" -"Il pulsante Elimina non è disponibile in tutte le cartelle, come ad esempio " -"nella ricerca. È del tutto assente negli account Gmail. Questi hanno a " -"disposizione solo il pulsante Cestina, che sposta i messaggi " -"nella cartella Cestino sul server, dove l'utente può poi eliminarli " -"manualmente. Il server eliminerà automaticamente i messaggi cestinati dopo " -"30 giorni." +"Geary controllerà la presenza di nuova posta per i tuoi account anche quando " +"la finestra principale non è aperta. Per far ciò, si avvierà silenziosamente " +"al login e continuerà a girare dopo la chiusura di tutte le finestre." -#: C/accounts.page:10(title) +#. (itstool) path: page/title +#: C/accounts.page:10 msgid "Accounts" msgstr "Account" -#: C/accounts.page:13(title) +#. (itstool) path: section/title +#: C/accounts.page:13 msgid "Adding accounts" msgstr "Aggiungere account" -#: C/accounts.page:15(p) +#. (itstool) path: section/p +#: C/accounts.page:15 msgid "" "The first time you start Geary, you will be prompted to add an email " "account. On this screen, select if your account is Gmail, Yahoo, Outlook." @@ -1245,30 +965,38 @@ "com o altro. Per altri tipi di account è necessario inserire manualmente le " "impostazioni di login per IMAP e SMTP." -#: C/accounts.page:19(p) +#. (itstool) path: section/p +#: C/accounts.page:19 +#| msgid "" +#| "Additional accounts can be added from the Accounts dialog. The " +#| "Accounts option is available in either Geary's application " +#| "menu or the gear menu in the upper-right of the toolbar. (The location " +#| "depends on the install desktop shell. For GNOME Shell and Unity, the " +#| "application menu is available near the top-left corner of the screen.) " +#| "Alternately, CtrlM will open the " +#| "Accounts dialog. To add an account, click the + button." msgid "" "Additional accounts can be added from the Accounts dialog. The " "Accounts option is available in either Geary's application menu " "or the gear menu in the upper-right of the toolbar. (The location depends on " "the install desktop shell. For GNOME Shell and Unity, the application menu " -"is available near the top-left corner of the screen.) Alternately, " -"CtrlM will open the Accounts dialog. " -"To add an account, click the + button." +"is available near the top-left corner of the screen.) To add an account, " +"click the + button." msgstr "" "Si possono aggiungere ulteriori account tramite la finestra di dialogo degli " "Account. L'opzione Account è disponibile nel menù " "dell'applicazione o nel menù dell'icona con la ruota dentata in alto a " "destra (la posizione dipende dall'ambiente grafico utilizzato: in GNOME " "Shell e Unity il menù dell'applicazione si trova vicino all'angolo superiore " -"sinistro dello schermo). Altrimenti, premere CtrlM per aprire la finestra di dialogo degli account. Per " -"aggiungere un account fare clic sul pulsante +." +"sinistro dello schermo). Per aggiungere un account fare clic sul pulsante +." -#: C/accounts.page:28(title) +#. (itstool) path: section/title +#: C/accounts.page:27 msgid "Editing existing accounts" msgstr "Modificare account esistenti" -#: C/accounts.page:30(p) +#. (itstool) path: section/p +#: C/accounts.page:29 msgid "" "From the Accounts dialog, select an account and click the pencil icon to " "change various settings. Please note that Geary cannot change server " @@ -1281,7 +1009,8 @@ "deve modificare il proprio server IMAP o SMTP, bisogna eliminare l'account e " "riaggiungerlo." -#: C/accounts.page:34(p) +#. (itstool) path: section/p +#: C/accounts.page:33 msgid "" "To change the order that accounts are displayed in the folder list, drag the " "accounts in the Accounts dialog to the desired order." @@ -1290,11 +1019,13 @@ "cartelle, trascinare gli account nel dialogo degli Account in modo da " "ottenere l'ordine desiderato." -#: C/accounts.page:37(p) +#. (itstool) path: section/p +#: C/accounts.page:36 msgid "There are some advanced options available when editing accounts:" msgstr "Nella modifica degli account sono disponibili alcune opzioni avanzate:" -#: C/accounts.page:39(p) +#. (itstool) path: item/p +#: C/accounts.page:38 msgid "" "The Save sent mail checkbox controls whether Geary will push " "successfully sent messages up to the account's Sent Mail folder. " @@ -1310,7 +1041,8 @@ "altri account, se questa impostazione viene disabilitata, potrebbe non " "essere possibile vedere i messaggi inviati." -#: C/accounts.page:45(p) +#. (itstool) path: item/p +#: C/accounts.page:44 msgid "" "The Sign emails checkbox indicates whether a signature will be " "automatically inserted when a composer is opened. You may enter the " @@ -1323,7 +1055,8 @@ "formattare il testo. Usare i pulsanti a destra per passare a un'anteprima " "della firma." -#: C/accounts.page:50(p) +#. (itstool) path: item/p +#: C/accounts.page:49 msgid "" "If you leave the signature in the Accounts dialog blank, Geary will use the " ".signature file in your home directory, if it exists. This file " @@ -1336,7 +1069,8 @@ "caso i marcatori saranno inseriti direttamente nel compositore, senza alcun " "carattere di escape." -#: C/accounts.page:55(p) +#. (itstool) path: item/p +#: C/accounts.page:54 msgid "" "The Download mail drop-down allows you to configure how much mail " "Geary will keep locally. Geary can only use locally available mail when " @@ -1347,11 +1081,13 @@ "disponibile localmente quando si fa una ricerca o si formano le " "conversazioni." -#: C/accounts.page:63(title) +#. (itstool) path: section/title +#: C/accounts.page:62 msgid "Removing accounts" msgstr "Eliminare account" -#: C/accounts.page:65(p) +#. (itstool) path: section/p +#: C/accounts.page:64 msgid "" "To delete an account, open the Accounts dialog, select the account, and " "press the - button. Geary will delete all information associated with the " @@ -1361,10 +1097,530 @@ "premere il pulsante -. Geary eliminerà tutte le informazioni associate con " "quell'account." -#. Put one translator per line, in the form of NAME , YEAR1, YEAR2 -#: C/accounts.page:0(None) -msgid "translator-credits" -msgstr "Federico Bruni , 2015, 2016" +#. (itstool) path: page/title +#: C/label.page:10 +msgid "Label or move a conversation" +msgstr "Etichetta o sposta una conversazione" + +#. (itstool) path: section/title +#: C/label.page:12 +msgid "Label a conversation" +msgstr "Etichettare una conversazione" + +#. (itstool) path: section/p +#: C/label.page:13 +msgid "" +"Geary lets you apply one or more labels to each conversation. Geary " +"labels correspond to labels in Gmail, or ordinary folders in other mail " +"services." +msgstr "" +"Geary permette di applicare una o più etichette a una " +"conversazione. Le etichette in Geary corrispondono alle etichette in Gmail e " +"alle normali cartelle in altri servizi di posta." + +#. (itstool) path: section/p +#: C/label.page:15 +msgid "" +"To label one or more conversations, first select the conversation(s), then " +"do either of the following:" +msgstr "" +"Per etichettare una o più conversazioni, selezionare la conversazione/le " +"conversazioni e poi scegliere una delle seguenti azioni:" + +#. (itstool) path: item/p +#: C/label.page:18 +msgid "" +"Click the Label button on the toolbar and select a label from the " +"resulting drop-down menu." +msgstr "" +"Cliccare il pulsante Etichetta della barra degli strumenti e " +"selezionare un'etichetta dal menù a tendina." + +#. (itstool) path: item/p +#: C/label.page:20 +msgid "" +"Hold down the Ctrl key and drag the conversation(s) from the " +"conversation list to the label in the sidebar." +msgstr "" +"Tenere premuto il tasto Ctrl e trascinare la conversazione/le " +"conversazioni dall'elenco delle conversazioni all'etichetta della barra " +"laterale." + +#. (itstool) path: section/title +#: C/label.page:25 +msgid "Move a conversation to a folder or label" +msgstr "Spostare una conversazione in una cartella o etichetta" + +#. (itstool) path: section/p +#: C/label.page:26 +msgid "" +"To move one or more conversations to a folder or label, first select the " +"conversation(s), then do either of the following:" +msgstr "" +"Per spostare una o più conversazioni in una cartella o etichetta, " +"selezionare la conversazione/le conversazioni e poi scegliere una delle " +"seguenti azioni:" + +#. (itstool) path: item/p +#: C/label.page:29 +msgid "" +"Click the Move button on the toolbar and select a folder or label " +"from the resulting drop-down menu." +msgstr "" +"Cliccare il pulsante Sposta della barra degli strumenti e " +"selezionare una cartella o etichetta dal menù a discesa." + +#. (itstool) path: item/p +#: C/label.page:31 +msgid "" +"Drag the conversation(s) from the conversation list to the folder or label " +"in the sidebar." +msgstr "" +"Trascinare la conversazione/le conversazioni dall'elenco delle conversazioni " +"alla cartella o etichetta della barra laterale." + +#. (itstool) path: page/title +#: C/shortcuts.page:10 +msgid "Keyboard shortcuts" +msgstr "Scorciatoie da tastiera" + +#. (itstool) path: page/p +#: C/shortcuts.page:12 +msgid "" +"Geary has keyboard shortcuts for most common operations. Use the built-in " +"keyboard shortcuts help in Geary to discover the full list. This can be " +"accessed via the application menu: GearyKeyboard " +"Shortcuts or using the keyboard shortcuts listed below." +msgstr "" +"Geary ha scorciatoie da tastiera per le operazioni più comuni. Usa l'aiuto " +"integrato delle scorciatoie da tastiera per scoprire l'elenco completo. Si " +"può accedervi dal menu dell'applicazione: GearyScorciatoie da tastiera oppure usando le scorciatoie " +"elencate sotto." + +#. (itstool) path: page/p +#: C/shortcuts.page:18 +msgid "" +"The following keyboard shortcuts can be used to access on-line help from " +"Geary:" +msgstr "" +"Si possono usare le seguenti scorciatoie da tastiera per accedere al manuale " +"in linea dall'applicazione:" + +#. (itstool) path: td/p +#: C/shortcuts.page:22 +msgid "Display this User Manual" +msgstr "Mostra questo manuale utente" + +#. (itstool) path: td/p +#: C/shortcuts.page:23 +#| msgid "CtrlL or F" +msgid "F1" +msgstr "F1" + +#. (itstool) path: td/p +#: C/shortcuts.page:26 +#| msgid "Keyboard shortcuts" +msgid "Display all keyboard shortcuts" +msgstr "Mostra tutte le scorciatoie da tastiera" + +#. (itstool) path: td/p +#: C/shortcuts.page:27 +#| msgid "" +#| "CtrlDown / CtrlUp" +msgid "" +"Ctrl? or CtrlF1" +msgstr "" +"Ctrl? o CtrlF1" + +#~ msgid "Geary has keyboard shortcuts for most common operations." +#~ msgstr "" +#~ "Geary ha delle scorciatoie da tastiera per le operazioni più comuni." + +#~ msgid "Compose a new message" +#~ msgstr "Componi un nuovo messaggio" + +#~ msgid "CtrlN or N" +#~ msgstr "CtrlN o N" + +#~ msgid "Reply to sender" +#~ msgstr "Rispondi al mittente" + +#~ msgid "CtrlR or R" +#~ msgstr "CtrlR o R" + +#~ msgid "Reply to all" +#~ msgstr "Rispondi a tutti" + +#~ msgid "" +#~ "CtrlShiftR or " +#~ "ShiftR" +#~ msgstr "" +#~ "CtrlMaiuscR o " +#~ "MaiuscR" + +#~ msgid "Forward" +#~ msgstr "Inoltra" + +#~ msgid "Archive" +#~ msgstr "Archivia" + +#~ msgid "A" +#~ msgstr "A" + +#~ msgid "Trash" +#~ msgstr "Cestina" + +#~ msgid "Delete or Backspace" +#~ msgstr "Canc o Backspace" + +#~ msgid "Delete" +#~ msgstr "Elimina" + +#~ msgid "" +#~ "ShiftDelete or ShiftBackspace" +#~ msgstr "" +#~ "MaiuscCanc o MaiuscBackspace" + +#~ msgid "Star" +#~ msgstr "Speciale" + +#~ msgid "S" +#~ msgstr "S" + +#~ msgid "Unstar" +#~ msgstr "Non speciale" + +#~ msgid "D" +#~ msgstr "D" + +#~ msgid "Mark read" +#~ msgstr "Segna come letto" + +#~ msgid "" +#~ "CtrlI or ShiftI" +#~ msgstr "" +#~ "CtrlI o MaiuscI" + +#~ msgid "Mark unread" +#~ msgstr "Segna come non letto" + +#~ msgid "" +#~ "CtrlU or ShiftU" +#~ msgstr "" +#~ "CtrlU o MaiuscU" + +#~| msgid "Label a conversation" +#~ msgid "Move the conversation" +#~ msgstr "Sposta la conversazione" + +#~ msgid "M" +#~ msgstr "M" + +#~| msgid "Label a conversation" +#~ msgid "Label the conversation" +#~ msgstr "Etichettare la conversazione" + +#~ msgid "L" +#~ msgstr "L" + +#~ msgid "Jump to next (older) conversation" +#~ msgstr "Passa alla conversazione successiva (più vecchia)" + +#~ msgid "J" +#~ msgstr "J" + +#~ msgid "Jump to previous (newer) conversation" +#~ msgstr "Passa alla conversazione precedente (più recente)" + +#~ msgid "K" +#~ msgstr "K" + +#~ msgid "Toggle spam" +#~ msgstr "Segna come indesiderata" + +#~ msgid "CtrlJ or !" +#~ msgstr "CtrlJ o !" + +#~ msgid "Quit" +#~ msgstr "Esci" + +#~ msgid "Ctrl" +#~ msgstr "Ctrl" + +#~ msgid "Q" +#~ msgstr "Q" + +#~ msgid "Zoom in" +#~ msgstr "Aumenta ingrandimento" + +#~ msgid "Ctrl= or =" +#~ msgstr "Ctrl= o =" + +#~ msgid "Zoom out" +#~ msgstr "Diminuisci ingrandimento" + +#~ msgid "Ctrl- or -" +#~ msgstr "Ctrl- o -" + +#~ msgid "Reset zoom" +#~ msgstr "Ripristina ingrandimento" + +#~ msgid "Ctrl0 or 0" +#~ msgstr "Ctrl0 o 0" + +#~ msgid "Close composer window" +#~ msgstr "Chiudi la finestra di composizione" + +#~ msgid "W" +#~ msgstr "W" + +#~ msgid "Jump to search box" +#~ msgstr "Passa alla casella di ricerca" + +#~ msgid "Find in current conversation" +#~ msgstr "Trova nella conversazione corrente" + +#~ msgid "F" +#~ msgstr "F" + +#~ msgid "Find next in current conversation" +#~ msgstr "Trova successivo nella conversazione corrente" + +#~ msgid "G" +#~ msgstr "G" + +#~ msgid "Find previous in current conversation" +#~ msgstr "Trova precedente nella conversazione corrente" + +#~ msgid "Shift" +#~ msgstr "Maiusc" + +#~ msgid "Composer shortcuts" +#~ msgstr "Scorciatoie della finestra di composizione" + +#~ msgid "These shortcuts are active whenever focus is in a composer." +#~ msgstr "" +#~ "Queste scorciatoie da tastiera sono attive quando si lavora nella " +#~ "finestra di composizione." + +#~ msgid "Attach file" +#~ msgstr "Allega file" + +#~ msgid "T" +#~ msgstr "T" + +#~ msgid "Quote text" +#~ msgstr "Cita testo" + +#~ msgid "]" +#~ msgstr "]" + +#~ msgid "Unquote text" +#~ msgstr "Togli citazione" + +#~ msgid "[" +#~ msgstr "[" + +#~ msgid "Close composer" +#~ msgstr "Chiudi la finestra di composizione" + +#~ msgid "CtrlW or Esc" +#~ msgstr "CtrlW o Esc" + +#~ msgid "Detach composer" +#~ msgstr "Stacca la finestra di composizione" + +#~ msgid "These shortcuts are only active in composers in rich text mode." +#~ msgstr "" +#~ "Queste scorciatoie sono attive solo se nella finestra di composizione è " +#~ "spuntata la modalità di testo formattato." + +#~ msgid "Bold text" +#~ msgstr "Testo in grassetto" + +#~ msgid "B" +#~ msgstr "B" + +#~ msgid "Italicize text" +#~ msgstr "Testo in corsivo" + +#~ msgid "I" +#~ msgstr "I" + +#~ msgid "Underline text" +#~ msgstr "Testo sottolineato" + +#~ msgid "U" +#~ msgstr "U" + +#~ msgid "Strike text" +#~ msgstr "Testo barrato" + +#~ msgid "Insert a link" +#~ msgstr "Inserisci un collegamento" + +#~ msgid "Remove formatting" +#~ msgstr "Togli formattazione" + +#~ msgid "Space" +#~ msgstr "Spazio" + +#~ msgid "Keyboard navigation" +#~ msgstr "Navigazione con la tastiera" + +#~| msgid "These shortcuts are active whenever focus is in a composer." +#~ msgid "" +#~ "These shortcuts can be used to move the keyboard focus in the main window." +#~ msgstr "" +#~ "Queste scorciatoie possono essere usate per spostare lo stato attivo " +#~ "della tastiera nella finestra principale." + +#~ msgid "Move focus to the next/previous pane" +#~ msgstr "Sposta lo stato attivo al riquadro successivo/precedente" + +#~ msgid "" +#~ "F6 / ShiftF6" +#~ msgstr "" +#~ "F6 / MaiuscF6" + +#~ msgid "Move focus to conversation list" +#~ msgstr "Sposta lo stato attivo sull'elenco delle conversazioni" + +#~| msgid "Jump to next (older) conversation" +#~ msgid "Move to the next message in a conversation" +#~ msgstr "Passa al messaggio successivo in una conversazione" + +#~| msgid "Move focus to the next/previous pane" +#~ msgid "Move to the next/previous message in a conversation" +#~ msgstr "Passa al messaggio successivo/precedente in una conversazione" + +#~ msgid "Move to the first/last message in a conversation" +#~ msgstr "Passa al primo/ultimo messaggio in una conversazione" + +#~| msgid "" +#~| "CtrlI or ShiftI" +#~ msgid "" +#~ "CtrlHome / CtrlEnd" +#~ msgstr "" +#~ "CtrlInizio / CtrlFine" + +#~ msgid "filename" +#~ msgstr "nomefile" + +#~ msgid "attachment:" +#~ msgstr "allegato:" + +#~ msgid "recipient" +#~ msgstr "destinatario" + +#~ msgid "bcc:" +#~ msgstr "ccn:" + +#~ msgid "text" +#~ msgstr "testo" + +#~ msgid "body:" +#~ msgstr "corpo:" + +#~ msgid "cc:" +#~ msgstr "cc:" + +#~ msgid "sender" +#~ msgstr "mittente" + +#~ msgid "from:" +#~ msgstr "da:" + +#~ msgid "is:read" +#~ msgstr "è:letto" + +#~ msgid "is:starred" +#~ msgstr "è:speciale" + +#~ msgid "is:unread" +#~ msgstr "è:nonletto" + +#~ msgid "subject:" +#~ msgstr "oggetto:" + +#~ msgid "to:" +#~ msgstr "a:" + +#~ msgid "Composer" +#~ msgstr "Componitore" + +#~ msgid "Enable spell checking" +#~ msgstr "Abilita controllo ortografico" + +#~ msgid "" +#~ "When set, Geary automatically spell checks a message as you write it, " +#~ "underlying each misspelled word in red." +#~ msgstr "" +#~ "Se impostato, Geary esegue automaticamente il controllo ortografico di un " +#~ "messaggio mentre lo si scrive, sottolineando le parole sbagliate in rosso." + +#~ msgid "Always watch for new mail" +#~ msgstr "Controlla sempre la nuova posta" + +#~ msgid "Bugs" +#~ msgstr "Bug" + +#~ msgid "Think you've found a bug?" +#~ msgstr "Pensi di aver trovato un bug?" + +#~ msgid "" +#~ "If you suspect you've found a bug in Geary, follow these steps to report " +#~ "it:" +#~ msgstr "" +#~ "Se pensi di aver trovato un bug in Geary, segui queste istruzioni per " +#~ "segnalarlo:" + +#~ msgid "" +#~ "Search Geary's bug database to see if someone else has reported " +#~ "the bug." +#~ msgstr "" +#~ "Cerca nel database dei bug di Geary per vedere se qualcun altro ha già " +#~ "segnalato il bug." + +#~ msgid "" +#~ "Don't see your bug listed? Congratulations! You've found a new bug. To " +#~ "create an bug report, create an account on GNOME's Bugzilla and file a " +#~ "new bug. Be as specific as you can and describe the steps to " +#~ "reproduce it. Don't forget to include details about your operating system " +#~ "and what version of Geary you're running." +#~ msgstr "" +#~ "Non vedi elencato il tuo bug? Congratulazioni! Hai trovato un nuovo bug. " +#~ "Per creare una segnalazione bug, crea un account su Bugzilla di GNOME e " +#~ "apri un nuovo bug. Cerca di dare più particolari possibili e " +#~ "descrivi i passi necessari per riprodurlo. Non dimenticare di includere " +#~ "dettagli sul tuo sistema operativo e sulla versione di Geary che stai " +#~ "usando." + +#~ msgid "" +#~ "For general inquiries, please join the Geary mailing list." +#~ msgstr "" +#~ "Per richieste generiche, iscriviti alla mailing list di Geary." #~| msgid "Label a conversation" #~ msgid "Open the Label Conversation menu" diff -Nru geary-0.12.4/help/LINGUAS geary-3.32.0/help/LINGUAS --- geary-0.12.4/help/LINGUAS 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/help/LINGUAS 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,12 @@ +# please keep this list sorted alphabetically +# +cs +de +el +es +fr +it +pl +pt_BR +sv +tr diff -Nru geary-0.12.4/help/Makefile.am geary-3.32.0/help/Makefile.am --- geary-0.12.4/help/Makefile.am 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/help/Makefile.am 1970-01-01 00:00:00.000000000 +0000 @@ -1,20 +0,0 @@ -# The unique purpose of this file is for GNOME Damned Lies integration - -DOC_ID = geary - -DOC_PAGES = \ - accounts.page \ - archive.page \ - bugs.page \ - index.page \ - label.page \ - limits.page \ - overview.page \ - preferences.page \ - search.page \ - shortcuts.page \ - star.page \ - write.page - -# NOTE: If you add a language here it *must* also be added to help/CMakeLists.txt! -DOC_LINGUAS = cs de el es fr it pl pt_BR sv diff -Nru geary-0.12.4/help/meson.build geary-3.32.0/help/meson.build --- geary-0.12.4/help/meson.build 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/help/meson.build 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,24 @@ +geary_help_pages = [ + 'accounts.page', + 'archive.page', + 'bugs.page', + 'contributing.page', + 'index.page', + 'label.page', + 'limits.page', + 'overview.page', + 'preferences.page', + 'search.page', + 'shortcuts.page', + 'star.page', + 'write.page', +] + +geary_help_media = [ + 'figures/geary.svg' +] + +gnome.yelp(meson.project_name(), + sources: geary_help_pages, + media: geary_help_media +) diff -Nru geary-0.12.4/help/pl/pl.po geary-3.32.0/help/pl/pl.po --- geary-0.12.4/help/pl/pl.po 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/help/pl/pl.po 2019-03-17 13:39:29.000000000 +0000 @@ -1,14 +1,14 @@ # Polish translation for geary help. -# Copyright © 2016-2017 the geary authors. -# This file is distributed under the same license as the geary package. -# Piotr Drąg , 2016-2017. -# Aviary.pl , 2016-2017. +# Copyright © 2016-2019 the geary authors. +# This file is distributed under the same license as the geary help. +# Piotr Drąg , 2016-2019. +# Aviary.pl , 2016-2019. # msgid "" msgstr "" "Project-Id-Version: geary-help\n" -"POT-Creation-Date: 2017-09-25 16:02+0000\n" -"PO-Revision-Date: 2017-09-26 16:45+0200\n" +"POT-Creation-Date: 2019-01-29 16:59+0000\n" +"PO-Revision-Date: 2019-01-30 17:50+0100\n" "Last-Translator: Piotr Drąg \n" "Language-Team: Polish \n" "Language: pl\n" @@ -18,560 +18,750 @@ "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " "|| n%100>=20) ? 1 : 2);\n" -#: C/write.page:9(title) -msgid "Write a message" -msgstr "Pisanie wiadomości" +#. Put one translator per line, in the form NAME , YEAR1, YEAR2 +msgctxt "_" +msgid "translator-credits" +msgstr "" +"Piotr Drąg , 2016-2019\n" +"Aviary.pl , 2016-2019" -#: C/write.page:12(title) -msgid "Composing and replying" -msgstr "Tworzenie wiadomości i odpowiadanie" +#. (itstool) path: page/title +#: C/accounts.page:10 +msgid "Accounts" +msgstr "Konta" -#: C/write.page:13(p) +#. (itstool) path: section/title +#: C/accounts.page:13 +msgid "Adding accounts" +msgstr "Dodawanie kont" + +#. (itstool) path: section/p +#: C/accounts.page:15 msgid "" -"To compose a new message in Geary, press the New Message button " -"on the toolbar." +"The first time you start Geary, you will be prompted to add an email " +"account. On this screen, select if your account is Gmail, Yahoo, Outlook." +"com, or other. For other account types, you will need to enter your IMAP and " +"SMTP login settings manually." msgstr "" -"Aby napisać nową wiadomość, kliknij przycisk Nowa wiadomość na " -"pasku narzędziowym." +"Po pierwszym uruchomieniu zostanie wyświetlony ekran dodawania konta e-mail. " +"Wybierz, czy to konto serwisu Gmail, Yahoo, Outlook.com lub inne. " +"W przypadku innych typów konta należy ręcznie wprowadzić dane logowania IMAP " +"i SMTP." -#: C/write.page:16(p) +#. (itstool) path: section/p +#: C/accounts.page:19 msgid "" -"To reply to a message, open the message menu in the upper right corner of " -"the message and choose Reply, Reply All or " -"Forward. You can also reply to the last message in a conversation " -"via the Reply, Reply All or Forward buttons " -"on the toolbar." +"Additional accounts can be added from the Accounts dialog. The " +"Accounts option is available in either Geary's application menu " +"or the gear menu in the upper-right of the toolbar. (The location depends on " +"the install desktop shell. For GNOME Shell and Unity, the application menu " +"is available near the top-left corner of the screen.) To add an account, " +"click the + button." msgstr "" -"Aby odpowiedzieć na wiadomość, otwórz menu wiadomości w górnym prawym rogu " -"wiadomości i kliknij Odpowiedz, Odpowiedz wszystkim " -"lub Przekaż. Można także odpowiadać na ostatnią wiadomość w wątku " -"za pomocą przycisków Odpowiedz, Odpowiedz wszystkim " -"i Przekaż na pasku narzędziowym." +"W oknie Konta można dodawać dodatkowe konta. Konta są dostępne " +"w menu programu lub menu z kołem zębatym w górnym prawym rogu paska " +"narzędziowego, zależnie od zainstalowanego środowiska. W środowiskach GNOME " +"i Unity menu programu znajduje się obok górnego lewego rogu ekranu. Aby " +"dodać konto, kliknij przycisk „+”." -#: C/write.page:21(title) -msgid "Features" -msgstr "Funkcje" +#. (itstool) path: section/title +#: C/accounts.page:27 +msgid "Editing existing accounts" +msgstr "Modyfikowanie istniejących kont" -#: C/write.page:23(p) +#. (itstool) path: section/p +#: C/accounts.page:29 msgid "" -"Geary's email composer lets you adjust the font, size and color of text. You " -"can also insert hyperlinks into messages." +"From the Accounts dialog, select an account and click the pencil icon to " +"change various settings. Please note that Geary cannot change server " +"settings on an existing account. If you need to change your IMAP or SMTP " +"server, you will need to delete the account and re-add it." msgstr "" -"Okno tworzenia wiadomości umożliwia dostosowanie czcionki, rozmiaru i koloru " -"tekstu. Można także wstawiać odnośniki do wiadomości." +"W oknie Konta wybierz konto i kliknij ikonę z ołówkiem, aby zmienić różne " +"ustawienia. Proszę zauważyć, że program Geary nie może zmieniać ustawień " +"serwera istniejącego konta. Aby zmienić serwer IMAP lub SMTP, należy usunąć " +"konto i dodać je ponownie." -#: C/write.page:25(p) +#. (itstool) path: section/p +#: C/accounts.page:33 msgid "" -"Geary can also send plain text messages. In the drop-down menu, check or " -"uncheck \"Rich Text\" to toggle between plain text and rich text mode." +"To change the order that accounts are displayed in the folder list, drag the " +"accounts in the Accounts dialog to the desired order." msgstr "" -"Można także wysyłać wiadomości w zwykłym tekście. Zaznacz lub odznacz „Tekst " -"sformatowany” w rozwijanym menu, aby przełączyć między zwykłym " -"a sformatowanym tekstem." - -#: C/write.page:28(p) -msgid "" -"You can attach a file to a message you're writing in either of these ways:" -msgstr "Można dołączyć plik do pisanej wiadomości na trzy różne sposoby:" +"Aby zmienić kolejność kont na liście katalogów, przeciągnij je w oknie Konta." -#: C/write.page:30(p) -msgid "" -"Press the Attach File button at the lower left of the composer " -"window, then select a file to attach." +#. (itstool) path: section/p +#: C/accounts.page:36 +msgid "There are some advanced options available when editing accounts:" msgstr "" -"Kliknij przycisk Załącz plik w dolnym lewym rogu okna tworzenia " -"wiadomości, a następnie wybierz plik do dołączenia." +"Podczas modyfikowania kont dostępnych jest kilka zaawansowanych funkcji:" -#: C/write.page:32(p) +#. (itstool) path: item/p +#: C/accounts.page:38 msgid "" -"Drag the file from the Nautilus file manager to the composer window, and " -"drop it either on the text fields at the top of the window or on the toolbar " -"at the bottom." +"The Save sent mail checkbox controls whether Geary will push " +"successfully sent messages up to the account's Sent Mail folder. " +"For Gmail accounts, this happens automatically. Yahoo and some other " +"accounts can be configured to do this automatically as well. For other " +"accounts, if you disable this setting, you may be unable to view messages " +"you've sent." msgstr "" -"Przeciągnij plik z menedżera plików Nautilus do pól tekstowych na górze okna " -"tworzenia wiadomości lub do paska narzędziowego na dole." +"Zapisywanie wysłanych wiadomości określa, czy pomyślnie wysłane " +"wiadomości są przenoszone do katalogu Wysłane konta. W przypadku " +"kont Gmail dzieje się to automatycznie. Yahoo i kilka innych kont także może " +"zostać skonfigurowanych w ten sposób. Wyłączenie tego ustawienia w przypadku " +"pozostałych kont może spowodować, że wysłane wiadomości będą niewidoczne." -#: C/write.page:36(p) +#. (itstool) path: item/p +#: C/accounts.page:44 msgid "" -"A number of keyboard shortcuts are available in the composer; see for details." +"The Sign emails checkbox indicates whether a signature will be " +"automatically inserted when a composer is opened. You may enter the " +"signature into the box immediately below. You may use HTML tags to style the " +"text. Switch to a preview of the signature using the buttons to the right." msgstr "" -"Okno tworzenia wiadomości obsługuje wiele skrótów klawiszowych. zawiera więcej informacji." +"Podpisywanie wiadomości określa, czy podpis jest automatycznie " +"wstawiany po otwarciu okna tworzenia wiadomości. Można wprowadzić podpis " +"w polu poniżej. Można używać znaczników HTML, aby sformatować tekst. " +"Przełącz na podgląd podpisu za pomocą przycisków po prawej." -#: C/write.page:38(p) +#. (itstool) path: item/p +#: C/accounts.page:49 msgid "" -"You may specify a signature to be inserted into the composer in the dialog." +"If you leave the signature in the Accounts dialog blank, Geary will use the " +".signature file in your home directory, if it exists. This file " +"may contain either plain text or HTML markup. In the latter case, the markup " +"will be inserted directly into the composer, without any escaping." msgstr "" -"Można określić podpis do wstawiania do okna tworzenia wiadomości w oknie " -"." - -#: C/write.page:43(title) -msgid "Drafts" -msgstr "Szkice" +"Pozostawienie pustego pola w oknie Konta spowoduje użycie pliku ." +"signature z katalogu domowego (jeśli istnieje). Ten plik może " +"zawierać zwykły tekst lub tekst ze znacznikami HTML. W tym drugim przypadku " +"znaczniki będą wstawiane bezpośrednio do okna tworzenia wiadomości." -#: C/write.page:45(p) +#. (itstool) path: item/p +#: C/accounts.page:54 msgid "" -"For mail servers that support drafts, Geary will automatically save the " -"message as you type. If you close the composer without sending, Geary will " -"prompt you to keep the draft or to discard it." +"The Download mail drop-down allows you to configure how much mail " +"Geary will keep locally. Geary can only use locally available mail when " +"searching and forming conversations." msgstr "" -"W przypadku serwerów pocztowych obsługujących szkice wiadomość jest " -"automatycznie zapisywana w trakcie jej pisania. Jeśli okno tworzenia " -"wiadomości jest zamykane bez wysłania, zostanie wyświetlone pytanie, czy " -"zachować lub odrzucić szkic." +"Rozwijane menu Pobieranie wiadomości umożliwia skonfigurowanie, " +"ile wiadomości przechowywać lokalnie. Lokalnie dostępna poczta jest używana " +"tylko do wyszukiwania i formowania wątków." + +#. (itstool) path: section/title +#: C/accounts.page:62 +msgid "Removing accounts" +msgstr "Usuwanie kont" -#: C/write.page:48(p) +#. (itstool) path: section/p +#: C/accounts.page:64 msgid "" -"To edit an existing draft, select the Drafts folder in the folder list, " -"select the message, and click \"Edit Draft\" in the message viewer." +"To delete an account, open the Accounts dialog, select the account, and " +"press the - button. Geary will delete all information associated with the " +"account." msgstr "" -"Aby zmodyfikować istniejący szkic, wybierz katalog Szkice z listy katalogów, " -"wybierz wiadomość i kliknij „Modyfikuj szkic” w przeglądarce wiadomości." +"Aby usunąć konto, otwórz okno Konta, zaznacz konto i kliknij przycisk „-”. " +"Wszystkie informacje powiązane z tym kontem zostaną usunięte." -#: C/write.page:51(p) -msgid "Geary deletes the draft when you send the message." -msgstr "Szkic jest usuwany po wysłaniu wiadomości." +#. (itstool) path: page/title +#: C/archive.page:10 +msgid "Delete or archive a message" +msgstr "Usuwanie i archiwizowanie wiadomości" -#: C/star.page:10(title) -msgid "Star a message or mark it as read/unread" +#. (itstool) path: page/p +#: C/archive.page:12 +msgid "" +"When you use Geary with a Gmail account, Geary lets you archive " +"messages. The Archive toolbar button archives the selected " +"conversation(s). Archived messages appear in the All Mail folder." msgstr "" -"Wyróżnianie wiadomości i oznaczanie ich jako przeczytane/nieprzeczytane" - -#: C/star.page:12(title) -msgid "Star messages" -msgstr "Wyróżnianie wiadomości" +"Jeśli używane jest konto serwisu Gmail, to można archiwizować " +"wiadomości. Przycisk Archiwizuj archiwizuje zaznaczone " +"wiadomości. Zarchiwizowane wiadomości są widoczne w katalogu Wszystkie." -#: C/star.page:13(p) +#. (itstool) path: page/p +#: C/archive.page:16 msgid "" -"You can star messages to indicate that they're important to you. To mark a " -"conversation with a star, click its star icon in the conversation list. You " -"can star an individual message by clicking the star at the upper right of " -"the message itself." +"With other mail servers, you can trash or delete, but not archive, messages. " +"To move one or more conversations to the Trash folder, select " +"them and press the Trash button on the toolbar. To permanently " +"delete the conversations, hold down Shift and press the " +"Delete button that appears in place of the Trash " +"button." msgstr "" -"Można wyróżniać wiadomości, aby wskazać, że są ważne. Aby to zrobić, kliknij " -"ikonę gwiazdki na liście wątków. Można wyróżniać pojedyncze wiadomości " -"klikając gwiazdkę w górnym prawym rogu wiadomości." +"Za pomocą innych serwerów poczty można przenosić wiadomości do kosza i je " +"usuwać, ale nie można ich archiwizować. Aby przenieść wątki to katalogu " +"Kosz, najpierw je zaznacz, a następnie kliknij przycisk " +"Kosz na pasku narzędziowym. Aby trwale usunąć wątki, przytrzymaj " +"klawisz Shift i kliknij przycisk Usuń, który pojawi " +"się zamiast przycisku Kosz." -#: C/star.page:15(p) +#. (itstool) path: page/p +#: C/archive.page:21 msgid "" -"With Gmail accounts, starred messages appear in the Starred folder in the " -"folder list." +"Delete is not available from every folder, such as Search. Delete is also " +"unavailable for Gmail. For Gmail, Trash will move messages to the " +"Trash folder on the server, where the user can then manually delete them. " +"The server will automatically remove trashed messages after 30 days." msgstr "" -"W kontach Gmail wyróżnione wiadomości są widoczne w katalogu Wyróżnione na " -"liście katalogów." +"Usuwanie nie jest dostępne w każdym katalogu (np. Wyszukiwanie). Nie można " +"też usuwać w serwisie Gmail: przycisk Kosz przenosi wiadomości do " +"katalogu Kosz na serwerze, gdzie użytkownik może je usuwać. Serwer " +"automatycznie usuwa wiadomości z kosza po 30 dniach." -#: C/star.page:18(title) -msgid "Mark messages as read or unread" -msgstr "Oznaczanie wiadomości jako przeczytane i nieprzeczytane" +#. (itstool) path: page/title +#: C/bugs.page:10 +msgid "Found a bug?" +msgstr "Wystąpił błąd?" -#: C/star.page:19(p) +#. (itstool) path: page/p +#: C/bugs.page:12 msgid "" -"Geary marks messages as read automatically as you read them. To manually " -"toggle a conversation as read or unread, click the circle icon in the " -"conversation list." +"If you suspect you've found a bug in Geary, please get in touch about it so it can be " +"fixed." msgstr "" -"Wiadomości są automatycznie oznaczane jako przeczytane w czasie ich " -"czytania. Aby ręcznie przełączyć przeczytanie lub nieprzeczytanie " -"wiadomości, kliknij ikonę kółka na liście wątków." +"Jeśli w programie Geary wystąpił błąd, to prosimy skontaktować się z nami, abyśmy mogli " +"go naprawić." -#: C/star.page:22(p) +#. (itstool) path: page/p +#: C/bugs.page:16 msgid "" -"Alternately, the Mark as Unread in the Mark menu on " -"the toolbar can be used to toggle the read status of the selected " -"conversation(s)." +"To help diagnose the problem as fast as possible, please include the " +"following information:" msgstr "" -"Można także używać Oznacz jako nieprzeczytane w menu Oznacz na pasku narzędziowym, aby przełączyć stan przeczytania zaznaczonych " -"wątków." +"Aby pomóc jak najszybciej zdiagnozować problem, prosimy dołączyć te " +"informacje (w języku angielskim):" -#: C/star.page:25(p) -msgid "" -"To mark an individual message as read, select Mark as Read from " -"the dropdown menu." +#. (itstool) path: item/p +#: C/bugs.page:20 +msgid "Geary version and installation method (Package? Flathub? Source code?)" msgstr "" -"Aby oznaczać pojedyncze wiadomości jako przeczytane, wybierz Oznacz " -"jako przeczytane z rozwijanego menu." - -#: C/shortcuts.page:11(title) -msgid "Keyboard shortcuts" -msgstr "Skróty klawiszowe" - -#: C/shortcuts.page:12(p) -msgid "Geary has keyboard shortcuts for most common operations." -msgstr "Dostępne są skróty klawiszowe dla najczęściej wykonywanych działań." - -#: C/shortcuts.page:15(p) -msgid "Compose a new message" -msgstr "Napisanie nowej wiadomości" - -#: C/shortcuts.page:16(p) -msgid "CtrlN or N" -msgstr "CtrlN lub N" - -#: C/shortcuts.page:19(p) -msgid "Reply to sender" -msgstr "Odpowiedź do nadawcy" +"Wersja programu Geary i metoda instalacji (pakiet? Flathub? kod źródłowy?)" -#: C/shortcuts.page:20(p) -msgid "CtrlR or R" -msgstr "CtrlR lub R" +#. (itstool) path: item/p +#: C/bugs.page:22 +msgid "Your desktop (GNOME? KDE? Something else?)" +msgstr "Używane środowisko (GNOME? KDE? inne?)" -#: C/shortcuts.page:23(p) -msgid "Reply to all" -msgstr "Odpowiedź do wszystkich" - -#: C/shortcuts.page:24(p) +#. (itstool) path: item/p +#: C/bugs.page:23 msgid "" -"CtrlShiftR or " -"ShiftR" +"Your operating system and version (Ubuntu 16.04? Fedora 28? Rolled your own?)" msgstr "" -"CtrlShiftR lub " -"ShiftR" +"Używany system operacyjny i jego wersja (Ubuntu 16.04? Fedora 28? własnego " +"autorstwa?)" -#: C/shortcuts.page:27(p) -msgid "Forward" -msgstr "Przekazanie" +#. (itstool) path: item/p +#: C/bugs.page:25 +msgid "Email provider (Gmail, Yahoo!, Outlook.com, or someone else?)" +msgstr "Dostawca poczty (Gmail, Yahoo!, Outlook.com czy jakiś inny?)" -#: C/shortcuts.page:28(p) -msgid "CtrlL or F" -msgstr "CtrlL lub F" +#. (itstool) path: item/p +#: C/bugs.page:27 +msgid "Steps to reproduce the bug" +msgstr "Kroki potrzebne, aby powtórzyć błąd" -#: C/shortcuts.page:31(p) -msgid "Archive" -msgstr "Archiwizacja" +#. (itstool) path: item/p +#: C/bugs.page:28 +msgid "What happened?" +msgstr "Co się stało?" -#: C/shortcuts.page:32(key) -msgid "A" -msgstr "A" +#. (itstool) path: item/p +#: C/bugs.page:29 +msgid "What did you expect to happen?" +msgstr "Co powinno było się stać?" -#: C/shortcuts.page:35(p) -msgid "Trash" -msgstr "Przeniesienie do kosza" +#. (itstool) path: page/p +#: C/bugs.page:32 +msgid "Thanks for your help!" +msgstr "Dziękujemy za pomoc!" -#: C/shortcuts.page:36(p) -msgid "Delete or Backspace" -msgstr "Delete lub Backspace" +#. (itstool) path: page/title +#: C/contributing.page:10 +msgid "Contribute to Geary" +msgstr "Pomoc w tworzeniu programu Geary" -#: C/shortcuts.page:39(p) -msgid "Delete" -msgstr "Usunięcie" +#. (itstool) path: page/p +#: C/contributing.page:12 +msgid "" +"Want to help improve Geary? There are a number of ways you can contribute:" +msgstr "Chcesz pomóc ulepszyć program Geary? Jest na to wiele sposobów:" -#: C/shortcuts.page:40(p) +#. (itstool) path: item/p +#: C/contributing.page:16 msgid "" -"ShiftDelete or ShiftBackspace" +"Bug " +"reporting—report new bugs or request new features" msgstr "" -"ShiftDelete lub ShiftBackspace" - -#: C/shortcuts.page:43(p) -msgid "Star" -msgstr "Wyróżnienie" - -#: C/shortcuts.page:44(key) C/shortcuts.page:100(key) -msgid "S" -msgstr "S" +"Zgłaszanie " +"błędów — zgłaszanie błędów i próśb o nowe funkcje" -#: C/shortcuts.page:47(p) -msgid "Unstar" -msgstr "Usunięcie wyróżnienia" - -#: C/shortcuts.page:48(key) C/shortcuts.page:138(key) -msgid "D" -msgstr "D" - -#: C/shortcuts.page:51(p) -msgid "Mark read" -msgstr "Oznaczenie jako przeczytane" +#. (itstool) path: item/p +#: C/contributing.page:19 +msgid "" +"User Experience " +"Design—research and develop Geary’s user experience" +msgstr "" +"Projektowanie " +"interfejsu użytkownika — badanie i tworzenie interfejsu użytkownika " +"programu Geary" -#: C/shortcuts.page:52(p) +#. (itstool) path: item/p +#: C/contributing.page:20 msgid "" -"CtrlI or ShiftI" +"Development—fix bugs and add new features" msgstr "" -"CtrlI lub ShiftI" +"Programowanie — naprawianie błędów i dodawanie nowych funkcji" -#: C/shortcuts.page:55(p) -msgid "Mark unread" -msgstr "Oznaczenie jako nieprzeczytane" +#. (itstool) path: item/p +#: C/contributing.page:21 +msgid "" +"Translating—translate Geary’s user interface and user manual into new languages" +msgstr "" +"Tłumaczenie — tłumaczenie interfejsu i podręcznika programu Geary na nowe języki" -#: C/shortcuts.page:56(p) +#. (itstool) path: item/p +#: C/contributing.page:22 msgid "" -"CtrlU or ShiftU" +"Join the " +"discussion—on the mailing list or IRC channel" msgstr "" -"CtrlU lub ShiftU" +"Dyskusje — " +"na liście dyskusyjnej i kanale IRC (w języku angielskim)" -#: C/shortcuts.page:59(p) -msgid "Move the conversation" -msgstr "Przeniesienie wątku" +#. (itstool) path: page/p +#: C/contributing.page:25 +msgid "Thanks for your help making Geary better!" +msgstr "Dziękujemy za pomoc w ulepszaniu programu Geary!" -#: C/shortcuts.page:60(key) -msgid "M" -msgstr "M" +#. (itstool) path: title/media +#. This is a reference to an external file such as an image or video. When +#. the file changes, the md5 hash will change to let you know you need to +#. update your localized copy. The msgstr is not used at all. Set it to +#. whatever you like once you have updated your copy of the file. +#: C/index.page:5 +msgctxt "_" +msgid "external ref='figures/geary.svg' md5='18b50c9e10fe5256ae1cb12aaa3a7600'" +msgstr "" +"external ref='figures/geary.svg' md5='18b50c9e10fe5256ae1cb12aaa3a7600'" -#: C/shortcuts.page:63(p) -msgid "Label the conversation" -msgstr "Nadanie etykiety wątkowi" +#. (itstool) path: page/title +#: C/index.page:5 +msgid " Geary" +msgstr " Geary" -#: C/shortcuts.page:64(key) C/shortcuts.page:162(key) -msgid "L" -msgstr "L" +#. (itstool) path: section/title +#: C/index.page:8 +msgid "Introduction" +msgstr "Wprowadzenie" -#: C/shortcuts.page:67(p) -msgid "Jump to next (older) conversation" -msgstr "Przejście do następnego (starszego) wątku" +#. (itstool) path: section/title +#: C/index.page:12 +msgid "Using Geary" +msgstr "Używanie programu Geary" -#: C/shortcuts.page:68(key) -msgid "J" -msgstr "J" +#. (itstool) path: section/title +#: C/index.page:16 +msgid "Contributing and bug reporting" +msgstr "Pomoc w tworzeniu programu i zgłaszanie błędów" -#: C/shortcuts.page:71(p) -msgid "Jump to previous (newer) conversation" -msgstr "Przejście do poprzedniego (nowszego) wątku" +#. (itstool) path: page/title +#: C/label.page:10 +msgid "Label or move a conversation" +msgstr "Nadawanie etykiet i przenoszenie wątków" -#: C/shortcuts.page:72(key) C/shortcuts.page:158(key) -msgid "K" -msgstr "K" +#. (itstool) path: section/title +#: C/label.page:12 +msgid "Label a conversation" +msgstr "Nadawanie etykiet wątkom" -#: C/shortcuts.page:75(p) -msgid "Toggle spam" -msgstr "Oznaczenie jako niechciane" +#. (itstool) path: section/p +#: C/label.page:13 +msgid "" +"Geary lets you apply one or more labels to each conversation. Geary " +"labels correspond to labels in Gmail, or ordinary folders in other mail " +"services." +msgstr "" +"Do każdego wątku można zastosować jedną lub więcej etykiet. " +"Etykiety programu Geary odpowiadają etykietom w serwisie Gmail lub zwykłym " +"katalogom w innych serwisach pocztowych." -#: C/shortcuts.page:76(p) -msgid "CtrlJ or !" -msgstr "CtrlJ lub !" +#. (itstool) path: section/p +#: C/label.page:15 +msgid "" +"To label one or more conversations, first select the conversation(s), then " +"do either of the following:" +msgstr "" +"Aby nadać etykiety wątkom, najpierw je zaznacz, a następnie wykonaj jedno z:" -#: C/shortcuts.page:79(p) -msgid "Quit" -msgstr "Zakończenie działania" +#. (itstool) path: item/p +#: C/label.page:18 +msgid "" +"Click the Label button on the toolbar and select a label from the " +"resulting drop-down menu." +msgstr "" +"Kliknij przycisk Etykieta na pasku narzędziowym i wybierz " +"etykietę z rozwijanego menu." -#: C/shortcuts.page:80(key) C/shortcuts.page:96(key) C/shortcuts.page:100(key) -#: C/shortcuts.page:104(key) C/shortcuts.page:108(key) -#: C/shortcuts.page:112(key) C/shortcuts.page:122(key) -#: C/shortcuts.page:126(key) C/shortcuts.page:130(key) -#: C/shortcuts.page:138(key) C/shortcuts.page:146(key) -#: C/shortcuts.page:150(key) C/shortcuts.page:154(key) -#: C/shortcuts.page:158(key) C/shortcuts.page:162(key) -#: C/shortcuts.page:166(key) C/shortcuts.page:181(key) -msgid "Ctrl" -msgstr "Ctrl" - -#: C/shortcuts.page:80(key) -msgid "Q" -msgstr "Q" - -#: C/shortcuts.page:83(p) -msgid "Zoom in" -msgstr "Przybliżenie" - -#: C/shortcuts.page:84(p) -msgid "Ctrl= or =" -msgstr "Ctrl= lub =" - -#: C/shortcuts.page:87(p) -msgid "Zoom out" -msgstr "Oddalenie" - -#: C/shortcuts.page:88(p) -msgid "Ctrl- or -" -msgstr "Ctrl- lub -" - -#: C/shortcuts.page:91(p) -msgid "Reset zoom" -msgstr "Przywrócenie domyślnego poziomu przybliżenia" - -#: C/shortcuts.page:92(p) -msgid "Ctrl0 or 0" -msgstr "Ctrl0 lub 0" - -#: C/shortcuts.page:95(p) -msgid "Close composer window" -msgstr "Zamknięcie okna pisania wiadomości" +#. (itstool) path: item/p +#: C/label.page:20 +msgid "" +"Hold down the Ctrl key and drag the conversation(s) from the " +"conversation list to the label in the sidebar." +msgstr "" +"Przytrzymaj klawisz Ctrl i przeciągnij wątki z listy wątków do " +"etykiety na panelu bocznym." -#: C/shortcuts.page:96(key) -msgid "W" -msgstr "W" +#. (itstool) path: section/title +#: C/label.page:25 +msgid "Move a conversation to a folder or label" +msgstr "Przenoszenie wątku do katalogu lub etykiety" -#: C/shortcuts.page:99(p) -msgid "Jump to search box" -msgstr "Przejście do wyszukiwania" +#. (itstool) path: section/p +#: C/label.page:26 +msgid "" +"To move one or more conversations to a folder or label, first select the " +"conversation(s), then do either of the following:" +msgstr "" +"Aby przenieść wątki do katalogu lub etykiety, najpierw je zaznacz, " +"a następnie wykonaj jedno z:" -#: C/shortcuts.page:103(p) -msgid "Find in current conversation" -msgstr "Wyszukiwanie w obecnym wątku" +#. (itstool) path: item/p +#: C/label.page:29 +msgid "" +"Click the Move button on the toolbar and select a folder or label " +"from the resulting drop-down menu." +msgstr "" +"Kliknij przycisk Przenieś na pasku narzędziowym i wybierz katalog " +"lub etykietę z rozwijanego menu." -#: C/shortcuts.page:104(key) -msgid "F" -msgstr "F" +#. (itstool) path: item/p +#: C/label.page:31 +msgid "" +"Drag the conversation(s) from the conversation list to the folder or label " +"in the sidebar." +msgstr "" +"Przeciągnij wątki z listy wątków do katalogu lub etykiety na panelu bocznym." -#: C/shortcuts.page:107(p) -msgid "Find next in current conversation" -msgstr "Wyszukiwanie następnego wystąpienia w obecnym wątku" +#. (itstool) path: page/title +#: C/limits.page:9 +msgid "Limitations" +msgstr "Ograniczenia" -#: C/shortcuts.page:108(key) C/shortcuts.page:112(key) -msgid "G" -msgstr "G" +#. (itstool) path: page/p +#: C/limits.page:11 +msgid "" +"Geary is still in early development. Geary supports IMAP and has been tested " +"with Gmail, Yahoo, and the free Dovecot mail server. Experimental support " +"for Outlook.com is provided. Geary may not yet work well with some IMAP " +"servers. At this time Geary is still missing numerous features including " +"offline mode." +msgstr "" +"Geary jest na wczesnym etapie rozwoju. Obsługuje protokół IMAP i został " +"przetestowany z serwisami Gmail, Yahoo i wolnym serwerem poczty Dovecot. " +"Obsługa serwisu Outlook.com jest eksperymentalna. Geary może nie działać " +"poprawnie z niektórymi serwerami IMAP. W tej chwili brakuje wielu funkcji, " +"w tym trybu offline." -#: C/shortcuts.page:111(p) -msgid "Find previous in current conversation" -msgstr "Wyszukiwanie poprzedniego wystąpienia w obecnym wątku" +#. (itstool) path: page/p +#: C/limits.page:17 +msgid "" +"To learn more about the features we're working on and the future of Geary, " +"please visit Geary's wiki " +"page." +msgstr "" +"Strona wiki programu " +"Geary zawiera więcej informacji o dodawanych i planowanych funkcjach." -#: C/shortcuts.page:112(key) -msgid "Shift" -msgstr "Shift" +#. (itstool) path: page/title +#: C/overview.page:8 +msgid "Overview" +msgstr "Przegląd" -#: C/shortcuts.page:117(title) -msgid "Composer shortcuts" -msgstr "Skróty okna pisania wiadomości" +#. (itstool) path: page/p +#: C/overview.page:10 +msgid "" +"Geary is a lightweight email reader for the GNOME desktop. It works with mail servers that support the IMAP " +"protocol, including popular services such as Gmail, Yahoo Mail, and Outlook." +"com." +msgstr "" +"Geary to lekki klient poczty dla środowiska GNOME. Działa z serwerami poczty obsługującymi protokół IMAP, " +"w tym popularnymi serwisami Gmail, Yahoo Mail i Outlook." -#: C/shortcuts.page:118(p) -msgid "These shortcuts are active whenever focus is in a composer." -msgstr "Poniższe skróty są aktywne w oknie pisania wiadomości." +#. (itstool) path: page/p +#: C/overview.page:14 +msgid "" +"Geary groups mail messages into conversations. A conversation " +"contains all messages in a single thread of discussion." +msgstr "" +"Wiadomości są grupowane w wątki. Wątek zawiera całą dyskusję " +"w jednym miejscu." -#: C/shortcuts.page:121(p) -msgid "Attach file" -msgstr "Załączenie pliku" +#. (itstool) path: page/p +#: C/overview.page:17 +msgid "The main Geary window is divided into several areas:" +msgstr "Główne okno programu jest podzielone na kilka obszarów:" -#: C/shortcuts.page:122(key) -msgid "T" -msgstr "T" +#. (itstool) path: section/title +#: C/overview.page:20 +msgid "Folder list" +msgstr "Lista katalogów" -#: C/shortcuts.page:125(p) -msgid "Quote text" -msgstr "Cytowanie tekstu" +#. (itstool) path: section/p +#: C/overview.page:21 +msgid "" +"The folder list at the left displays all folders and " +"labels in your mail account. Geary uses the term label for " +"any folder that you have created to help organize your messages. (The Gmail " +"web interface also uses this term; most other mail services do not.)" +msgstr "" +"Lista katalogów po lewej wyświetla wszystkie katalogi " +"i etykiety na koncie poczty. Geary używa terminu etykieta " +"dla wszystkich katalogów utworzonych do organizowania wiadomości (podobnie " +"jak serwis Gmail, ale inaczej niż większość innych serwisów)." -#: C/shortcuts.page:126(key) -msgid "]" -msgstr "]" +#. (itstool) path: section/title +#: C/overview.page:28 +msgid "Conversation list" +msgstr "Lista wątków" -#: C/shortcuts.page:129(p) -msgid "Unquote text" -msgstr "Zakończenie cytowania tekstu" +#. (itstool) path: section/p +#: C/overview.page:29 +msgid "" +"The conversation list displays a list of conversations in the " +"selected folder. Newer conversations appear at the top." +msgstr "" +"Lista wątków wyświetla listę wątków w wybranym katalogu. Nowsze " +"wątki pojawiają się na górze." -#: C/shortcuts.page:130(key) -msgid "[" -msgstr "[" +#. (itstool) path: section/p +#: C/overview.page:31 +msgid "" +"Each sender's name appears bold if there are unread messages from that " +"sender. If a conversation has more than one message, Geary displays a count " +"of messages in the conversation." +msgstr "" +"Nazwa nadawcy jest pogrubiona, jeśli są od niego nieprzeczytane wiadomości. " +"Jeśli wątek zawiera więcej niż jedną wiadomość, to wyświetlana jest ich " +"liczba." -#: C/shortcuts.page:133(p) -msgid "Close composer" -msgstr "Zamknięcie okna pisania wiadomości" +#. (itstool) path: section/p +#: C/overview.page:34 +msgid "" +"Geary does not automatically download all messages in all of your mail " +"folders. When you first visit your Inbox or any other folder, Geary " +"downloads the 50 most recent messages in that folder. To see more messages, " +"simply scroll down the conversation list and Geary will fetch more messages " +"automatically." +msgstr "" +"Wszystkie wiadomości ze wszystkich katalogów nie są automatycznie pobierane. " +"Po pierwszym wejściu do Odebranych lub innego katalogu pobierane jest 50 " +"najnowszych wiadomości. Aby zobaczyć więcej wiadomości, po prostu przewiń " +"listę wątków w dół, a więcej wiadomości zostanie automatycznie pobranych." -#: C/shortcuts.page:134(p) -msgid "CtrlW or Esc" -msgstr "CtrlW lub Esc" +#. (itstool) path: section/p +#: C/overview.page:36 +msgid "" +"Some commands in Geary can act on a group of conversations. To select " +"multiple conversations, hold down the Ctrl key and click each " +"conversation in turn in the conversation list. Alternatively, click the " +"first conversation in a range, then hold down Shift and click the " +"last conversation." +msgstr "" +"Niektóre polecenia mogą być używane na grupach wątków. Aby zaznaczyć kilka " +"wątków, przytrzymaj klawisz Ctrl i kliknij każdy wątek na liście " +"wątków. Można też kliknąć pierwszy wątek w żądanym zakresie, a następnie " +"przytrzymać klawisz Shift i kliknąć ostatni." -#: C/shortcuts.page:137(p) -msgid "Detach composer" -msgstr "Odłączenie okna pisania wiadomości" +#. (itstool) path: section/title +#: C/overview.page:44 +msgid "Message area" +msgstr "Obszar wiadomości" -#: C/shortcuts.page:142(p) -msgid "These shortcuts are only active in composers in rich text mode." +#. (itstool) path: section/p +#: C/overview.page:45 +msgid "" +"The message area displays all messages in the selected " +"conversation, with the oldest message at the top." msgstr "" -"Poniższe skróty są aktywne tylko, gdy okno pisania wiadomości jest w trybie " -"tekstu sformatowanego." - -#: C/shortcuts.page:145(p) -msgid "Bold text" -msgstr "Pogrubienie tekstu" +"Obszar wiadomości wyświetla wszystkie wiadomości w wybranym wątku, " +"zaczynając od najstarszych." -#: C/shortcuts.page:146(key) C/shortcuts.page:181(key) -msgid "B" -msgstr "B" +#. (itstool) path: section/p +#: C/overview.page:47 +msgid "" +"At the upper right of each message, Geary displays a dropdown arrow that " +"lets you open the message menu with commands that operate on the " +"message." +msgstr "" +"W dolnym prawym rogu każdej wiadomości wyświetlana jest strzałka w dół, " +"umożliwiająca otwarcie menu wiadomości zawierającego polecenia " +"używane na wiadomości." -#: C/shortcuts.page:149(p) -msgid "Italicize text" -msgstr "Pochylenie tekstu" +#. (itstool) path: section/p +#: C/overview.page:49 +msgid "" +"When you view a conversation, Geary collapses messages that you've already " +"read. Click collapsed messages to expand them. Click an expanded message's " +"header to collapse it." +msgstr "" +"Po wyświetleniu wątku już przeczytane wiadomości są zwinięte. Kliknij " +"zwinięte wiadomości, aby je rozwinąć. Kliknij nagłówek rozwiniętej " +"wiadomości, aby ją zwinąć." -#: C/shortcuts.page:150(key) -msgid "I" -msgstr "I" +#. (itstool) path: section/p +#: C/overview.page:50 +msgid "" +"Any attachments in a message appear at the bottom of the message. You can " +"click an attachment to open it or right-click to save it." +msgstr "" +"Załączniki są widoczne na dole wiadomości. Można kliknąć załącznik, aby " +"otworzyć lub kliknąć prawym przyciskiem myszy, aby go zapisać." -#: C/shortcuts.page:153(p) -msgid "Underline text" -msgstr "Podkreślenie tekstu" +#. (itstool) path: section/p +#: C/overview.page:52 +msgid "" +"Geary uses Gravatar to " +"display an avatar for each message's sender in its header." +msgstr "" +"Serwis Gravatar jest " +"używany do wyświetlania awatarów nadawców wiadomości w nagłówku." -#: C/shortcuts.page:154(key) -msgid "U" -msgstr "U" +#. (itstool) path: page/title +#: C/preferences.page:10 +msgid "Preferences" +msgstr "Preferencje" -#: C/shortcuts.page:157(p) -msgid "Strike text" -msgstr "Przekreślenie tekstu" +#. (itstool) path: page/p +#: C/preferences.page:11 +msgid "" +"The Preferences option is available in either Geary's application " +"menu or the gear menu in the upper-right of the toolbar. (The location " +"depends on the install desktop shell. For GNOME Shell and Unity, the " +"application menu is available near the top-left corner of the screen.)" +msgstr "" +"Preferencje są dostępne w menu programu lub menu z kołem zębatym " +"w górnym prawym rogu paska narzędziowego, zależnie od zainstalowanego " +"środowiska. W środowiskach GNOME i Unity menu programu znajduje się obok " +"górnego lewego rogu ekranu." -#: C/shortcuts.page:161(p) -msgid "Insert a link" -msgstr "Wstawienie odnośnika" +#. (itstool) path: section/title +#: C/preferences.page:17 +msgid "Reading" +msgstr "Czytanie" -#: C/shortcuts.page:165(p) -msgid "Remove formatting" -msgstr "Usunięcie formatowania" +#. (itstool) path: item/title +#: C/preferences.page:20 +msgid "Automatically select next message" +msgstr "Automatyczne wybieranie następnej wiadomości" -#: C/shortcuts.page:166(key) C/shortcuts.page:186(key) -msgid "Space" -msgstr "Spacja" +#. (itstool) path: item/p +#: C/preferences.page:21 +msgid "" +"When this option is enabled, Geary automatically selects the latest message " +"in a folder when you enter the folder. In addition, after archiving a " +"message, Geary automatically selects an adjacent message." +msgstr "" +"Po włączeniu tej opcji następna wiadomość jest automatycznie wybierana po " +"wejściu do katalogu. Oprócz tego sąsiednia wiadomość jest automatycznie " +"wybierana po zarchiwizowaniu wiadomości." -#: C/shortcuts.page:172(title) -msgid "Keyboard navigation" -msgstr "Nawigacja za pomocą klawiatury" +#. (itstool) path: item/title +#: C/preferences.page:26 +msgid "Display conversation preview" +msgstr "Podgląd wątku" -#: C/shortcuts.page:173(p) +#. (itstool) path: item/p +#: C/preferences.page:27 msgid "" -"These shortcuts can be used to move the keyboard focus in the main window." -msgstr "Poniższe skróty mogą być używane do nawigacji w głównym oknie." +"Enables message previews in the conversation list. Previews show the first " +"few lines of each message." +msgstr "" +"Włącza podgląd wiadomości na liście wątków. Podgląd wyświetla kilka " +"pierwszych wierszy każdej wiadomości." -#: C/shortcuts.page:176(p) -msgid "Move focus to the next/previous pane" -msgstr "Przejście do następnego/poprzedniego panelu" +#. (itstool) path: item/title +#: C/preferences.page:31 +msgid "Use three pane view" +msgstr "Widok trzech paneli" -#: C/shortcuts.page:177(p) +#. (itstool) path: item/p +#: C/preferences.page:32 msgid "" -"F6 / ShiftF6" +"Show the folder list, the conversation list, and the messages side-by-side-" +"by-side in three panes. If not selected, the folder list and conversation " +"list will be stacked vertically in a single pane." msgstr "" -"F6/ShiftF6" +"Wyświetla listę katalogów, listę wątków i wiadomości obok siebie w trzech " +"panelach. Jeśli wyłączono tę opcję, lista katalogów i lista wątków są " +"umieszczone pionowo w jednym panelu." + +#. (itstool) path: section/title +#: C/preferences.page:40 +msgid "Notifications" +msgstr "Powiadomienia" -#: C/shortcuts.page:180(p) -msgid "Move focus to conversation list" -msgstr "Przejście do listy wątków" +#. (itstool) path: item/title +#: C/preferences.page:43 +msgid "Play notification sounds" +msgstr "Dźwięki powiadomień" -#: C/shortcuts.page:184(p) -msgid "Move to the next message in a conversation" -msgstr "Przejście do następnej wiadomości w wątku" +#. (itstool) path: item/p +#: C/preferences.page:44 +msgid "When set, Geary plays a sound whenever a new message arrives." +msgstr "Po włączeniu przy nadejściu wiadomości odtwarzany jest dźwięk." -#: C/shortcuts.page:190(p) -msgid "Move to the next/previous message in a conversation" -msgstr "Przejście do następnej/poprzedniej wiadomości w wątku" +#. (itstool) path: item/title +#: C/preferences.page:47 +msgid "Show notifications for new mail" +msgstr "Powiadomienia o nowych wiadomościach" -#: C/shortcuts.page:191(p) +#. (itstool) path: item/p +#: C/preferences.page:48 msgid "" -"CtrlDown / CtrlUp" +"When set, Geary displays a notification each time a new message " +"arrives. Notifications are displayed in a system-dependent manner. On GNOME " +"Shell, notifications appear at the bottom of the display (older versions) or " +"centered just below the top bar (newer versions). In Ubuntu Unity, " +"notifications appear at the upper right of the display." msgstr "" -"CtrlW dół/CtrlW górę" +"Po włączeniu przy nadejściu wiadomości wyświetlane jest powiadomienie. Powiadomienia są różnie wyświetlane w zależności od środowiska. GNOME " +"wyświetla je pod górnym paskiem, a Unity w górnym prawym rogu ekranu." -#: C/shortcuts.page:197(p) -msgid "Move to the first/last message in a conversation" -msgstr "Przejście do pierwszej/ostatniej wiadomości w wątku" +#. (itstool) path: item/title +#: C/preferences.page:54 +msgid "Watch for new mail when closed" +msgstr "Monitorowanie nowych wiadomości po zamknięciu" -#: C/shortcuts.page:198(p) +#. (itstool) path: item/p +#: C/preferences.page:55 msgid "" -"CtrlHome / CtrlEnd" +"Geary will watch your accounts for new mail even when the main window is not " +"open. To do this, it will silently start when you log in to your computer, " +"and it will continue to run after you close all windows." msgstr "" -"CtrlHome/CtrlEnd" +"Nowe wiadomości będą monitorowane nawet po zamknięciu głównego okna. W tym " +"celu program Geary jest uruchamiany w tle po zalogowaniu się do komputera " +"i kontynuuje działanie po zamknięciu wszystkich okien." -#: C/search.page:10(title) +#. (itstool) path: page/title +#: C/search.page:10 msgid "Search" msgstr "Wyszukiwanie" -#: C/search.page:12(p) +#. (itstool) path: page/p +#: C/search.page:12 msgid "" "Geary supports a per-account full text search. To start a search, select a " "folder associated with the account you'd like to search against. Then click " @@ -584,7 +774,8 @@ "CtrlS) i zacząć pisać. Wyniki pojawią " "się po krótkiej chwili." -#: C/search.page:16(p) +#. (itstool) path: page/p +#: C/search.page:16 msgid "" "The full text search includes email text, email addresses (to, from, and " "cc), subject lines and attachment filenames." @@ -592,7 +783,8 @@ "Wyszukiwanie obejmuje treść e-maila, adresy (do, z i DW), temat i nazwy " "załączników." -#: C/search.page:19(p) +#. (itstool) path: page/p +#: C/search.page:19 msgid "" "Keywords that match your search are highlighted in the message view. Geary " "will match different forms of the same word, for example searching for \"walk" @@ -602,122 +794,129 @@ "Dopasowane zostaną różne formy tego samego słowa, na przykład szukanie " "„spacer” znajdzie też „spaceruję” i „spacerowałam”." -#: C/search.page:23(title) +#. (itstool) path: section/title +#: C/search.page:23 msgid "Search operators" msgstr "Operatory wyszukiwania" -#: C/search.page:24(p) +#. (itstool) path: section/p +#: C/search.page:24 msgid "Geary supports the following operators to limit the scope of searches:" msgstr "" "Obsługiwane są następujące operatory ograniczające zakres wyszukiwania:" -#: C/search.page:27(var) -msgid "filename" -msgstr "nazwa pliku" - -#: C/search.page:27(input) -msgid "attachment:" -msgstr "załącznik:" +#. (itstool) path: td/p +#: C/search.page:27 +msgid "attachment:filename" +msgstr "załącznik:nazwa pliku" -#: C/search.page:28(p) +#. (itstool) path: td/p +#: C/search.page:28 msgid "Finds messages with attachments whose name matches filename." msgstr "" "Odnajduje wiadomości z załącznikami, których nazwy pasują do nazwa " "pliku." -#: C/search.page:31(var) C/search.page:39(var) C/search.page:63(var) -msgid "recipient" -msgstr "odbiorca" - -#: C/search.page:31(input) -msgid "bcc:" -msgstr "udw:" +#. (itstool) path: td/p +#: C/search.page:31 +msgid "bcc:recipient" +msgstr "udw:odbiorca" -#: C/search.page:32(p) +#. (itstool) path: td/p +#: C/search.page:32 msgid "Finds messages where recipient matches the BCC header." msgstr "" "Odnajduje wiadomości, w których odbiorca pasuje do nagłówka " "„Ukryty do wiadomości”." -#: C/search.page:35(var) C/search.page:59(var) -msgid "text" -msgstr "tekst" - -#: C/search.page:35(input) -msgid "body:" -msgstr "treść:" +#. (itstool) path: td/p +#: C/search.page:35 +msgid "body:text" +msgstr "treść:tekst" -#: C/search.page:36(p) +#. (itstool) path: td/p +#: C/search.page:36 msgid "Finds messages whose body contains text." msgstr "Odnajduje wiadomości, których treść zawiera tekst." -#: C/search.page:39(input) -msgid "cc:" -msgstr "dw:" +#. (itstool) path: td/p +#: C/search.page:39 +msgid "cc:recipient" +msgstr "dw:odbiorca" -#: C/search.page:40(p) +#. (itstool) path: td/p +#: C/search.page:40 msgid "Finds messages where recipient matches the CC header." msgstr "" "Odnajduje wiadomości, w których odbiorca pasuje do nagłówka „Do " "wiadomości”." -#: C/search.page:43(var) -msgid "sender" -msgstr "nadawca" - -#: C/search.page:43(input) -msgid "from:" -msgstr "od:" +#. (itstool) path: td/p +#: C/search.page:43 +msgid "from:sender" +msgstr "od:nadawca" -#: C/search.page:44(p) +#. (itstool) path: td/p +#: C/search.page:44 msgid "Finds messages where sender matches the From header." msgstr "" "Odnajduje wiadomości, w których nadawca pasuje do nagłówka „Od”." -#: C/search.page:47(input) -msgid "is:read" -msgstr "jest:przeczytane" +#. (itstool) path: td/p +#: C/search.page:47 +msgid "is:read" +msgstr "jest:przeczytane" -#: C/search.page:48(p) +#. (itstool) path: td/p +#: C/search.page:48 msgid "Finds messages that have been marked as read." msgstr "Odnajduje wiadomości oznaczone jako przeczytane." -#: C/search.page:51(input) -msgid "is:starred" -msgstr "jest:wyróżnione" +#. (itstool) path: td/p +#: C/search.page:51 +msgid "is:starred" +msgstr "jest:wyróżnione" -#: C/search.page:52(p) +#. (itstool) path: td/p +#: C/search.page:52 msgid "Finds messages that have been marked as starred." msgstr "Odnajduje wiadomości oznaczone jako wyróżnione." -#: C/search.page:55(input) -msgid "is:unread" -msgstr "jest:nieprzeczytane" +#. (itstool) path: td/p +#: C/search.page:55 +msgid "is:unread" +msgstr "jest:nieprzeczytane" -#: C/search.page:56(p) +#. (itstool) path: td/p +#: C/search.page:56 msgid "Finds messages that have been marked as not read." msgstr "Odnajduje wiadomości oznaczone jako nieprzeczytane." -#: C/search.page:59(input) -msgid "subject:" -msgstr "temat:" +#. (itstool) path: td/p +#: C/search.page:59 +msgid "subject:text" +msgstr "temat:tekst" -#: C/search.page:60(p) +#. (itstool) path: td/p +#: C/search.page:60 msgid "Finds messages whose subject contains text." msgstr "Odnajduje wiadomości, których temat zawiera tekst." -#: C/search.page:63(input) -msgid "to:" -msgstr "do:" +#. (itstool) path: td/p +#: C/search.page:63 +msgid "to:recipient" +msgstr "do:odbiorca" -#: C/search.page:64(p) +#. (itstool) path: td/p +#: C/search.page:64 msgid "" -"Finds messages where sender matches the To, CC, or BCC header." +"Finds messages where recipient matches the To, CC, or BCC header." msgstr "" -"Odnajduje wiadomości, w których nadawca pasuje do nagłówka „Do”, " +"Odnajduje wiadomości, w których odbiorca pasuje do nagłówka „Do”, " "„Do wiadomości” lub „Ukryty do wiadomości”." -#: C/search.page:68(p) +#. (itstool) path: section/p +#: C/search.page:68 msgid "" "As a special case, the bcc, cc, from, and to operators support me as their " @@ -728,597 +927,253 @@ "i do obsługują specjalny parametr ja, który " "wyszukuje własny adres e-mail użytkownika w odpowiednim kontekście." -#: C/preferences.page:10(title) -msgid "Preferences" -msgstr "Preferencje" +#. (itstool) path: page/title +#: C/shortcuts.page:10 +msgid "Keyboard shortcuts" +msgstr "Skróty klawiszowe" -#: C/preferences.page:11(p) +#. (itstool) path: page/p +#: C/shortcuts.page:12 msgid "" -"The Preferences option is available in either Geary's application " -"menu or the gear menu in the upper-right of the toolbar. (The location " -"depends on the install desktop shell. For GNOME Shell and Unity, the " -"application menu is available near the top-left corner of the screen.)" +"Geary has keyboard shortcuts for most common operations. Use the built-in " +"keyboard shortcuts help in Geary to discover the full list. This can be " +"accessed via the application menu: GearyKeyboard " +"Shortcuts or using the keyboard shortcuts listed below." +msgstr "" +"Dostępne są skróty klawiszowe dla najczęściej wykonywanych działań. " +"Wbudowana pomoc skrótów klawiszowych zawiera pełną listę. Można ją otworzyć " +"przez menu programu: GearySkróty klawiszowe lub za pomocą poniższych skrótów." + +#. (itstool) path: page/p +#: C/shortcuts.page:18 +msgid "" +"The following keyboard shortcuts can be used to access on-line help from " +"Geary:" +msgstr "" +"Można używać tych skrótów klawiszowych do otwierania pomocy w programie " +"Geary:" + +#. (itstool) path: td/p +#: C/shortcuts.page:22 +msgid "Display this User Manual" +msgstr "Wyświetlenie tego podręcznika użytkownika" + +#. (itstool) path: td/p +#: C/shortcuts.page:23 +msgid "F1" +msgstr "F1" + +#. (itstool) path: td/p +#: C/shortcuts.page:26 +msgid "Display all keyboard shortcuts" +msgstr "Wyświetlenie wszystkich skrótów klawiszowych" + +#. (itstool) path: td/p +#: C/shortcuts.page:27 +msgid "" +"Ctrl? or CtrlF1" msgstr "" -"Preferencje są dostępne w menu programu lub menu z kołem zębatym " -"w górnym prawym rogu paska narzędziowego, zależnie od zainstalowanego " -"środowiska. W środowiskach GNOME i Unity menu programu znajduje się obok " -"górnego lewego rogu ekranu." +"Ctrl? lub CtrlF1" -#: C/preferences.page:17(title) -msgid "Reading" -msgstr "Czytanie" +#. (itstool) path: page/title +#: C/star.page:10 +msgid "Star a message or mark it as read/unread" +msgstr "" +"Wyróżnianie wiadomości i oznaczanie ich jako przeczytane/nieprzeczytane" -#: C/preferences.page:20(gui) -msgid "Automatically select next message" -msgstr "Automatyczne wybieranie następnej wiadomości" +#. (itstool) path: section/title +#: C/star.page:12 +msgid "Star messages" +msgstr "Wyróżnianie wiadomości" -#: C/preferences.page:21(p) +#. (itstool) path: section/p +#: C/star.page:13 msgid "" -"When this option is enabled, Geary automatically selects the latest message " -"in a folder when you enter the folder. In addition, after archiving a " -"message, Geary automatically selects an adjacent message." +"You can star messages to indicate that they're important to you. To mark a " +"conversation with a star, click its star icon in the conversation list. You " +"can star an individual message by clicking the star at the upper right of " +"the message itself." msgstr "" -"Po włączeniu tej opcji następna wiadomość jest automatycznie wybierana po " -"wejściu do katalogu. Oprócz tego sąsiednia wiadomość jest automatycznie " -"wybierana po zarchiwizowaniu wiadomości." - -#: C/preferences.page:26(gui) -msgid "Display conversation preview" -msgstr "Podgląd wątku" +"Można wyróżniać wiadomości, aby wskazać, że są ważne. Aby to zrobić, kliknij " +"ikonę gwiazdki na liście wątków. Można wyróżniać pojedyncze wiadomości " +"klikając gwiazdkę w górnym prawym rogu wiadomości." -#: C/preferences.page:27(p) +#. (itstool) path: section/p +#: C/star.page:15 msgid "" -"Enables message previews in the conversation list. Previews show the first " -"few lines of each message." +"With Gmail accounts, starred messages appear in the Starred folder in the " +"folder list." msgstr "" -"Włącza podgląd wiadomości na liście wątków. Podgląd wyświetla kilka " -"pierwszych wierszy każdej wiadomości." +"W kontach Gmail wyróżnione wiadomości są widoczne w katalogu Wyróżnione na " +"liście katalogów." -#: C/preferences.page:31(gui) -msgid "Use three pane view" -msgstr "Widok trzech paneli" +#. (itstool) path: section/title +#: C/star.page:18 +msgid "Mark messages as read or unread" +msgstr "Oznaczanie wiadomości jako przeczytane i nieprzeczytane" -#: C/preferences.page:32(p) +#. (itstool) path: section/p +#: C/star.page:19 msgid "" -"Show the folder list, the conversation list, and the messages side-by-side-" -"by-side in three panes. If not selected, the folder list and conversation " -"list will be stacked vertically in a single pane." +"Geary marks messages as read automatically as you read them. To manually " +"toggle a conversation as read or unread, click the circle icon in the " +"conversation list." msgstr "" -"Wyświetla listę katalogów, listę wątków i wiadomości obok siebie w trzech " -"panelach. Jeśli wyłączono tę opcję, lista katalogów i lista wątków są " -"umieszczone pionowo w jednym panelu." - -#: C/preferences.page:40(title) -msgid "Composer" -msgstr "Okno tworzenia wiadomości" - -#: C/preferences.page:43(gui) -msgid "Enable spell checking" -msgstr "Sprawdzanie pisowni" - -#: C/preferences.page:44(p) -msgid "" -"When set, Geary automatically spell checks a message as you write it, " -"underlying each misspelled word in red." -msgstr "" -"Po włączeniu pisownia jest automatycznie sprawdzania w trakcie pisania " -"wiadomości, podkreślając słowa z błędami na czerwono." - -#: C/preferences.page:51(title) -msgid "Notifications" -msgstr "Powiadomienia" - -#: C/preferences.page:54(gui) -msgid "Play notification sounds" -msgstr "Dźwięki powiadomień" - -#: C/preferences.page:55(p) -msgid "When set, Geary plays a sound whenever a new message arrives." -msgstr "Po włączeniu przy nadejściu wiadomości odtwarzany jest dźwięk." - -#: C/preferences.page:58(gui) -msgid "Show notifications for new mail" -msgstr "Powiadomienia o nowych wiadomościach" - -#: C/preferences.page:59(p) -msgid "" -"When set, Geary displays a notification each time a new message " -"arrives. Notifications are displayed in a system-dependent manner. On GNOME " -"Shell, notifications appear at the bottom of the display (older versions) or " -"centered just below the top bar (newer versions). In Ubuntu Unity, " -"notifications appear at the upper right of the display." -msgstr "" -"Po włączeniu przy nadejściu wiadomości wyświetlane jest powiadomienie. Powiadomienia są różnie wyświetlane w zależności od środowiska. GNOME " -"wyświetla je pod górnym paskiem, a Unity w górnym prawym rogu ekranu." - -#: C/preferences.page:65(gui) -msgid "Always watch for new mail" -msgstr "Monitorowanie nowych wiadomości" - -#: C/preferences.page:66(p) -msgid "" -"Geary will watch your accounts for new mail even when the main window is not " -"open. To do this, it will silently start when you log in to your computer, " -"and it will continue to run after you close the main window." -msgstr "" -"Nowe wiadomości będą monitorowane nawet po zamknięciu głównego okna. W tym " -"celu program Geary jest uruchamiany w tle po zalogowaniu się do komputera " -"i kontynuuje działanie po zamknięciu głównego okna." - -#: C/overview.page:8(title) -msgid "Overview" -msgstr "Przegląd" - -#: C/overview.page:10(p) -msgid "" -"Geary is a lightweight email reader for the GNOME desktop. It works with mail servers that support the IMAP " -"protocol, including popular services such as Gmail, Yahoo Mail, and Outlook." -"com." -msgstr "" -"Geary to lekki klient poczty dla środowiska GNOME. Działa z serwerami poczty obsługującymi protokół IMAP, " -"w tym popularnymi serwisami Gmail, Yahoo Mail i Outlook." - -#: C/overview.page:14(p) -msgid "" -"Geary groups mail messages into conversations. A conversation " -"contains all messages in a single thread of discussion." -msgstr "" -"Wiadomości są grupowane w wątki. Wątek zawiera całą dyskusję " -"w jednym miejscu." - -#: C/overview.page:17(p) -msgid "The main Geary window is divided into several areas:" -msgstr "Główne okno programu jest podzielone na kilka obszarów:" - -#: C/overview.page:20(title) -msgid "Folder list" -msgstr "Lista katalogów" - -#: C/overview.page:21(p) -msgid "" -"The folder list at the left displays all folders and " -"labels in your mail account. Geary uses the term label for " -"any folder that you have created to help organize your messages. (The Gmail " -"web interface also uses this term; most other mail services do not.)" -msgstr "" -"Lista katalogów po lewej wyświetla wszystkie katalogi " -"i etykiety na koncie poczty. Geary używa terminu etykieta " -"dla wszystkich katalogów utworzonych do organizowania wiadomości (podobnie " -"jak serwis Gmail, ale inaczej niż większość innych serwisów)." - -#: C/overview.page:28(title) -msgid "Conversation list" -msgstr "Lista wątków" - -#: C/overview.page:29(p) -msgid "" -"The conversation list displays a list of conversations in the " -"selected folder. Newer conversations appear at the top." -msgstr "" -"Lista wątków wyświetla listę wątków w wybranym katalogu. Nowsze " -"wątki pojawiają się na górze." - -#: C/overview.page:31(p) -msgid "" -"Each sender's name appears bold if there are unread messages from that " -"sender. If a conversation has more than one message, Geary displays a count " -"of messages in the conversation." -msgstr "" -"Nazwa nadawcy jest pogrubiona, jeśli są od niego nieprzeczytane wiadomości. " -"Jeśli wątek zawiera więcej niż jedną wiadomość, to wyświetlana jest ich " -"liczba." - -#: C/overview.page:34(p) -msgid "" -"Geary does not automatically download all messages in all of your mail " -"folders. When you first visit your Inbox or any other folder, Geary " -"downloads the 50 most recent messages in that folder. To see more messages, " -"simply scroll down the conversation list and Geary will fetch more messages " -"automatically." -msgstr "" -"Wszystkie wiadomości ze wszystkich katalogów nie są automatycznie pobierane. " -"Po pierwszym wejściu do Odebranych lub innego katalogu pobierane jest 50 " -"najnowszych wiadomości. Aby zobaczyć więcej wiadomości, po prostu przewiń " -"listę wątków w dół, a więcej wiadomości zostanie automatycznie pobranych." - -#: C/overview.page:36(p) -msgid "" -"Some commands in Geary can act on a group of conversations. To select " -"multiple conversations, hold down the Ctrl key and click each " -"conversation in turn in the conversation list. Alternatively, click the " -"first conversation in a range, then hold down Shift and click the " -"last conversation." -msgstr "" -"Niektóre polecenia mogą być używane na grupach wątków. Aby zaznaczyć kilka " -"wątków, przytrzymaj klawisz Ctrl i kliknij każdy wątek na liście " -"wątków. Można też kliknąć pierwszy wątek w żądanym zakresie, a następnie " -"przytrzymać klawisz Shift i kliknąć ostatni." - -#: C/overview.page:44(title) -msgid "Message area" -msgstr "Obszar wiadomości" - -#: C/overview.page:45(p) -msgid "" -"The message area displays all messages in the selected " -"conversation, with the oldest message at the top." -msgstr "" -"Obszar wiadomości wyświetla wszystkie wiadomości w wybranym wątku, " -"zaczynając od najstarszych." - -#: C/overview.page:47(p) -msgid "" -"At the upper right of each message, Geary displays a dropdown arrow that " -"lets you open the message menu with commands that operate on the " -"message." -msgstr "" -"W dolnym prawym rogu każdej wiadomości wyświetlana jest strzałka w dół, " -"umożliwiająca otwarcie menu wiadomości zawierającego polecenia " -"używane na wiadomości." - -#: C/overview.page:49(p) -msgid "" -"When you view a conversation, Geary collapses messages that you've already " -"read. Click collapsed messages to expand them. Click an expanded message's " -"header to collapse it." -msgstr "" -"Po wyświetleniu wątku już przeczytane wiadomości są zwinięte. Kliknij " -"zwinięte wiadomości, aby je rozwinąć. Kliknij nagłówek rozwiniętej " -"wiadomości, aby ją zwinąć." - -#: C/overview.page:50(p) -msgid "" -"Any attachments in a message appear at the bottom of the message. You can " -"click an attachment to open it or right-click to save it." -msgstr "" -"Załączniki są widoczne na dole wiadomości. Można kliknąć załącznik, aby " -"otworzyć lub kliknąć prawym przyciskiem myszy, aby go zapisać." - -#: C/overview.page:52(p) -msgid "" -"Geary uses Gravatar to " -"display an avatar for each message's sender in its header." -msgstr "" -"Serwis Gravatar jest " -"używany do wyświetlania awatarów nadawców wiadomości w nagłówku." - -#: C/limits.page:11(title) -msgid "Limitations" -msgstr "Ograniczenia" - -#: C/limits.page:12(p) -msgid "" -"Geary is still in early development. Geary supports IMAP and has been tested " -"with Gmail, Yahoo, and the free Dovecot mail server. Experimental support " -"for Outlook.com is provided. Geary may not yet work well with some IMAP " -"servers. At this time Geary is still missing numerous features including " -"offline mode." -msgstr "" -"Program Geary jest na wczesnym etapie rozwoju. Obsługuje on protokół IMAP " -"i został przetestowany z serwisami Gmail, Yahoo i wolnym serwerem poczty " -"Dovecot. Obsługa serwisu Outlook.com jest eksperymentalna. Program Geary " -"może nie działać poprawnie z niektórymi serwerami IMAP. W tej chwili brakuje " -"wielu funkcji, w tym trybu offline." - -#: C/limits.page:14(p) -msgid "" -"To learn more about the features we're working on and the future of Geary, " -"please visit Geary's wiki " -"page." -msgstr "" -"Strona wiki programu " -"Geary zawiera więcej informacji o dodawanych i planowanych funkcjach." - -#: C/label.page:10(title) -msgid "Label or move a conversation" -msgstr "Nadawanie etykiet i przenoszenie wątków" - -#: C/label.page:12(title) -msgid "Label a conversation" -msgstr "Nadawanie etykiet wątkom" - -#: C/label.page:13(p) -msgid "" -"Geary lets you apply one or more labels to each conversation. Geary " -"labels correspond to labels in Gmail, or ordinary folders in other mail " -"services." -msgstr "" -"Do każdego wątku można zastosować jedną lub więcej etykiet. " -"Etykiety programu Geary odpowiadają etykietom w serwisie Gmail lub zwykłym " -"katalogom w innych serwisach pocztowych." - -#: C/label.page:15(p) -msgid "" -"To label one or more conversations, first select the conversation(s), then " -"do either of the following:" -msgstr "" -"Aby nadać etykiety wątkom, najpierw je zaznacz, a następnie wykonaj jedno z:" - -#: C/label.page:18(p) -msgid "" -"Click the Label button on the toolbar and select a label from the " -"resulting drop-down menu." -msgstr "" -"Kliknij przycisk Etykieta na pasku narzędziowym i wybierz " -"etykietę z rozwijanego menu." - -#: C/label.page:20(p) -msgid "" -"Hold down the Ctrl key and drag the conversation(s) from the " -"conversation list to the label in the sidebar." -msgstr "" -"Przytrzymaj klawisz Ctrl i przeciągnij wątki z listy wątków do " -"etykiety na panelu bocznym." - -#: C/label.page:25(title) -msgid "Move a conversation to a folder or label" -msgstr "Przenoszenie wątku do katalogu lub etykiety" - -#: C/label.page:26(p) -msgid "" -"To move one or more conversations to a folder or label, first select the " -"conversation(s), then do either of the following:" -msgstr "" -"Aby przenieść wątki do katalogu lub etykiety, najpierw je zaznacz, " -"a następnie wykonaj jedno z:" +"Wiadomości są automatycznie oznaczane jako przeczytane w czasie ich " +"czytania. Aby ręcznie przełączyć przeczytanie lub nieprzeczytanie " +"wiadomości, kliknij ikonę kółka na liście wątków." -#: C/label.page:29(p) +#. (itstool) path: section/p +#: C/star.page:22 msgid "" -"Click the Move button on the toolbar and select a folder or label " -"from the resulting drop-down menu." +"Alternately, the Mark as Unread in the Mark menu on " +"the toolbar can be used to toggle the read status of the selected " +"conversation(s)." msgstr "" -"Kliknij przycisk Przenieś na pasku narzędziowym i wybierz katalog " -"lub etykietę z rozwijanego menu." +"Można także używać Oznacz jako nieprzeczytane w menu Oznacz na pasku narzędziowym, aby przełączyć stan przeczytania zaznaczonych " +"wątków." -#: C/label.page:31(p) +#. (itstool) path: section/p +#: C/star.page:25 msgid "" -"Drag the conversation(s) from the conversation list to the folder or label " -"in the sidebar." +"To mark an individual message as read, select Mark as Read from " +"the dropdown menu." msgstr "" -"Przeciągnij wątki z listy wątków do katalogu lub etykiety na panelu bocznym." - -#. When image changes, this message will be marked fuzzy or untranslated for you. -#. It doesn't matter what you translate it to: it's not used at all. -#: C/index.page:5(None) -msgid "@@image: 'figures/geary.svg'; md5=18b50c9e10fe5256ae1cb12aaa3a7600" -msgstr "@@image: 'figures/geary.svg'; md5=18b50c9e10fe5256ae1cb12aaa3a7600" - -#: C/index.page:5(title) -msgid " Geary" -msgstr " Geary" - -#: C/index.page:9(title) -msgid "Introduction" -msgstr "Wprowadzenie" - -#: C/index.page:13(title) -msgid "Using Geary" -msgstr "Używanie programu Geary" - -#: C/index.page:17(title) -msgid "Bugs" -msgstr "Błędy" - -#: C/bugs.page:8(title) -msgid "Think you've found a bug?" -msgstr "Wystąpił błąd?" - -#: C/bugs.page:9(p) -msgid "" -"If you suspect you've found a bug in Geary, follow these steps to report it:" -msgstr "Jeśli w programie Geary wystąpił błąd, to prosimy go zgłosić:" +"Aby oznaczać pojedyncze wiadomości jako przeczytane, wybierz Oznacz " +"jako przeczytane z rozwijanego menu." -#: C/bugs.page:11(p) -msgid "" -"Search Geary's bug database to see if someone else has reported the " -"bug." -msgstr "" -"Przeszukaj bazę błędów programu Geary, aby upewnić się, że nikt go jeszcze " -"nie zgłosił." +#. (itstool) path: page/title +#: C/write.page:9 +msgid "Write a message" +msgstr "Pisanie wiadomości" -#: C/bugs.page:13(p) -msgid "" -"Don't see your bug listed? Congratulations! You've found a new bug. To " -"create an bug report, create an account on GNOME's Bugzilla and file a new bug. Be as specific as you can and describe the steps to reproduce it. " -"Don't forget to include details about your operating system and what version " -"of Geary you're running." -msgstr "" -"Nie ma tego błędu na liście? Gratulacje! Wystąpił zupełnie nowy błąd. Aby " -"wysłać zgłoszenie błędu, utwórz konto w serwisie Bugzilla projektu GNOME " -"i zgłoś nowy błąd. Prosimy pisać po angielsku, a także o konkrety " -"i kroki potrzebne, aby powtórzyć błąd. Należy dołączyć też informacje " -"o używanym systemie operacyjnym i wersji programu Geary." +#. (itstool) path: section/title +#: C/write.page:12 +msgid "Composing and replying" +msgstr "Tworzenie wiadomości i odpowiadanie" -#: C/bugs.page:18(p) +#. (itstool) path: section/p +#: C/write.page:13 msgid "" -"For general inquiries, please join the Geary mailing list." +"To compose a new message in Geary, press the New Message button " +"on the toolbar." msgstr "" -"Na liście " -"dyskusyjnej programu Geary można zadawać inne pytania (w języku " -"angielskim)." - -#: C/archive.page:10(title) -msgid "Delete or archive a message" -msgstr "Usuwanie i archiwizowanie wiadomości" +"Aby napisać nową wiadomość, kliknij przycisk Nowa wiadomość na " +"pasku narzędziowym." -#: C/archive.page:12(p) +#. (itstool) path: section/p +#: C/write.page:16 msgid "" -"When you use Geary with a Gmail account, Geary lets you archive " -"messages. The Archive toolbar button archives the selected " -"conversation(s). Archived messages appear in the All Mail folder." +"To reply to a message, open the message menu in the upper right corner of " +"the message and choose Reply, Reply All or " +"Forward. You can also reply to the last message in a conversation " +"via the Reply, Reply All or Forward buttons " +"on the toolbar." msgstr "" -"Jeśli używane jest konto serwisu Gmail, to można archiwizować " -"wiadomości. Przycisk Archiwizuj archiwizuje zaznaczone " -"wiadomości. Zarchiwizowane wiadomości są widoczne w katalogu Wszystkie." +"Aby odpowiedzieć na wiadomość, otwórz menu wiadomości w górnym prawym rogu " +"wiadomości i kliknij Odpowiedz, Odpowiedz wszystkim " +"lub Przekaż. Można także odpowiadać na ostatnią wiadomość w wątku " +"za pomocą przycisków Odpowiedz, Odpowiedz wszystkim " +"i Przekaż na pasku narzędziowym." -#: C/archive.page:16(p) -msgid "" -"With other mail servers, you can trash or delete, but not archive, messages. " -"To move one or more conversations to the Trash folder, select " -"them and press the Trash button on the toolbar. To permanently " -"delete the conversations, hold down Shift and press the " -"Delete button that appears in place of the Trash " -"button." -msgstr "" -"Za pomocą innych serwerów poczty można przenosić wiadomości do kosza i je " -"usuwać, ale nie można ich archiwizować. Aby przenieść wątki to katalogu " -"Kosz, najpierw je zaznacz, a następnie kliknij przycisk " -"Kosz na pasku narzędziowym. Aby trwale usunąć wątki, przytrzymaj " -"klawisz Shift i kliknij przycisk Usuń, który pojawi " -"się zamiast przycisku Kosz." +#. (itstool) path: section/title +#: C/write.page:21 +msgid "Features" +msgstr "Funkcje" -#: C/archive.page:21(p) +#. (itstool) path: section/p +#: C/write.page:23 msgid "" -"Delete is not available from every folder, such as Search. Delete is also " -"unavailable for Gmail. For Gmail, Trash will move messages to the " -"Trash folder on the server, where the user can then manually delete them. " -"The server will automatically remove trashed messages after 30 days." +"Geary's email composer lets you adjust the font, size and color of text. You " +"can also insert hyperlinks into messages." msgstr "" -"Usuwanie nie jest dostępne w każdym katalogu (np. Wyszukiwanie). Nie można " -"też usuwać w serwisie Gmail: przycisk Kosz przenosi wiadomości do " -"katalogu Kosz na serwerze, gdzie użytkownik może je usuwać. Serwer " -"automatycznie usuwa wiadomości z kosza po 30 dniach." - -#: C/accounts.page:10(title) -msgid "Accounts" -msgstr "Konta" - -#: C/accounts.page:13(title) -msgid "Adding accounts" -msgstr "Dodawanie kont" +"Okno tworzenia wiadomości umożliwia dostosowanie czcionki, rozmiaru i koloru " +"tekstu. Można także wstawiać odnośniki do wiadomości." -#: C/accounts.page:15(p) +#. (itstool) path: section/p +#: C/write.page:25 msgid "" -"The first time you start Geary, you will be prompted to add an email " -"account. On this screen, select if your account is Gmail, Yahoo, Outlook." -"com, or other. For other account types, you will need to enter your IMAP and " -"SMTP login settings manually." +"Geary can also send plain text messages. In the drop-down menu, check or " +"uncheck \"Rich Text\" to toggle between plain text and rich text mode." msgstr "" -"Po pierwszym uruchomieniu zostanie wyświetlony ekran dodawania konta e-mail. " -"Wybierz, czy to konto serwisu Gmail, Yahoo, Outlook.com lub inne. " -"W przypadku innych typów konta należy ręcznie wprowadzić dane logowania IMAP " -"i SMTP." +"Można także wysyłać wiadomości w zwykłym tekście. Zaznacz lub odznacz „Tekst " +"sformatowany” w rozwijanym menu, aby przełączyć między zwykłym " +"a sformatowanym tekstem." -#: C/accounts.page:19(p) +#. (itstool) path: section/p +#: C/write.page:28 msgid "" -"Additional accounts can be added from the Accounts dialog. The " -"Accounts option is available in either Geary's application menu " -"or the gear menu in the upper-right of the toolbar. (The location depends on " -"the install desktop shell. For GNOME Shell and Unity, the application menu " -"is available near the top-left corner of the screen.) Alternately, " -"CtrlM will open the Accounts dialog. " -"To add an account, click the + button." -msgstr "" -"W oknie Konta można dodawać dodatkowe konta. Konta są dostępne " -"w menu programu lub menu z kołem zębatym w górnym prawym rogu paska " -"narzędziowego, zależnie od zainstalowanego środowiska. W środowiskach GNOME " -"i Unity menu programu znajduje się obok górnego lewego rogu ekranu. Można " -"także nacisnąć klawisze CtrlM, aby " -"otworzyć to okno. Aby dodać konto, kliknij przycisk „+”." - -#: C/accounts.page:28(title) -msgid "Editing existing accounts" -msgstr "Modyfikowanie istniejących kont" +"You can attach a file to a message you're writing in either of these ways:" +msgstr "Można dołączyć plik do pisanej wiadomości na trzy różne sposoby:" -#: C/accounts.page:30(p) +#. (itstool) path: item/p +#: C/write.page:30 msgid "" -"From the Accounts dialog, select an account and click the pencil icon to " -"change various settings. Please note that Geary cannot change server " -"settings on an existing account. If you need to change your IMAP or SMTP " -"server, you will need to delete the account and re-add it." +"Press the Attach File button at the lower left of the composer " +"window, then select a file to attach." msgstr "" -"W oknie Konta wybierz konto i kliknij ikonę z ołówkiem, aby zmienić różne " -"ustawienia. Proszę zauważyć, że program Geary nie może zmieniać ustawień " -"serwera istniejącego konta. Aby zmienić serwer IMAP lub SMTP, należy usunąć " -"konto i dodać je ponownie." +"Kliknij przycisk Załącz plik w dolnym lewym rogu okna tworzenia " +"wiadomości, a następnie wybierz plik do dołączenia." -#: C/accounts.page:34(p) +#. (itstool) path: item/p +#: C/write.page:32 msgid "" -"To change the order that accounts are displayed in the folder list, drag the " -"accounts in the Accounts dialog to the desired order." -msgstr "" -"Aby zmienić kolejność kont na liście katalogów, przeciągnij je w oknie Konta." - -#: C/accounts.page:37(p) -msgid "There are some advanced options available when editing accounts:" +"Drag the file from the Nautilus file manager to the composer window, and " +"drop it either on the text fields at the top of the window or on the toolbar " +"at the bottom." msgstr "" -"Podczas modyfikowania kont dostępnych jest kilka zaawansowanych funkcji:" +"Przeciągnij plik z menedżera plików Nautilus do pól tekstowych na górze okna " +"tworzenia wiadomości lub do paska narzędziowego na dole." -#: C/accounts.page:39(p) +#. (itstool) path: section/p +#: C/write.page:36 msgid "" -"The Save sent mail checkbox controls whether Geary will push " -"successfully sent messages up to the account's Sent Mail folder. " -"For Gmail accounts, this happens automatically. Yahoo and some other " -"accounts can be configured to do this automatically as well. For other " -"accounts, if you disable this setting, you may be unable to view messages " -"you've sent." +"A number of keyboard shortcuts are available in the composer; see for details." msgstr "" -"Zapisywanie wysłanych wiadomości określa, czy pomyślnie wysłane " -"wiadomości są przenoszone do katalogu Wysłane konta. W przypadku " -"kont Gmail dzieje się to automatycznie. Yahoo i kilka innych kont także może " -"zostać skonfigurowanych w ten sposób. Wyłączenie tego ustawienia w przypadku " -"pozostałych kont może spowodować, że wysłane wiadomości będą niewidoczne." +"Okno tworzenia wiadomości obsługuje wiele skrótów klawiszowych. zawiera więcej informacji." -#: C/accounts.page:45(p) +#. (itstool) path: section/p +#: C/write.page:38 msgid "" -"The Sign emails checkbox indicates whether a signature will be " -"automatically inserted when a composer is opened. You may enter the " -"signature into the box immediately below. You may use HTML tags to style the " -"text. Switch to a preview of the signature using the buttons to the right." +"You may specify a signature to be inserted into the composer in the dialog." msgstr "" -"Podpisywanie wiadomości określa, czy podpis jest automatycznie " -"wstawiany po otwarciu okna tworzenia wiadomości. Można wprowadzić podpis " -"w polu poniżej. Można używać znaczników HTML, aby sformatować tekst. " -"Przełącz na podgląd podpisu za pomocą przycisków po prawej." +"Można określić podpis do wstawiania do okna tworzenia wiadomości w oknie " +"." -#: C/accounts.page:50(p) -msgid "" -"If you leave the signature in the Accounts dialog blank, Geary will use the " -".signature file in your home directory, if it exists. This file " -"may contain either plain text or HTML markup. In the latter case, the markup " -"will be inserted directly into the composer, without any escaping." -msgstr "" -"Pozostawienie pustego pola w oknie Konta spowoduje użycie pliku ." -"signature z katalogu domowego (jeśli istnieje). Ten plik może " -"zawierać zwykły tekst lub tekst ze znacznikami HTML. W tym drugim przypadku " -"znaczniki będą wstawiane bezpośrednio do okna tworzenia wiadomości." +#. (itstool) path: section/title +#: C/write.page:43 +msgid "Drafts" +msgstr "Szkice" -#: C/accounts.page:55(p) +#. (itstool) path: section/p +#: C/write.page:45 msgid "" -"The Download mail drop-down allows you to configure how much mail " -"Geary will keep locally. Geary can only use locally available mail when " -"searching and forming conversations." +"For mail servers that support drafts, Geary will automatically save the " +"message as you type. If you close the composer without sending, Geary will " +"prompt you to keep the draft or to discard it." msgstr "" -"Rozwijane menu Pobieranie wiadomości umożliwia skonfigurowanie, " -"ile wiadomości przechowywać lokalnie. Lokalnie dostępna poczta jest używana " -"tylko do wyszukiwania i formowania wątków." - -#: C/accounts.page:63(title) -msgid "Removing accounts" -msgstr "Usuwanie kont" +"W przypadku serwerów pocztowych obsługujących szkice wiadomość jest " +"automatycznie zapisywana w trakcie jej pisania. Jeśli okno tworzenia " +"wiadomości jest zamykane bez wysłania, zostanie wyświetlone pytanie, czy " +"zachować lub odrzucić szkic." -#: C/accounts.page:65(p) +#. (itstool) path: section/p +#: C/write.page:48 msgid "" -"To delete an account, open the Accounts dialog, select the account, and " -"press the - button. Geary will delete all information associated with the " -"account." +"To edit an existing draft, select the Drafts folder in the folder list, " +"select the message, and click \"Edit Draft\" in the message viewer." msgstr "" -"Aby usunąć konto, otwórz okno Konta, zaznacz konto i kliknij przycisk „-”. " -"Wszystkie informacje powiązane z tym kontem zostaną usunięte." +"Aby zmodyfikować istniejący szkic, wybierz katalog Szkice z listy katalogów, " +"wybierz wiadomość i kliknij „Modyfikuj szkic” w przeglądarce wiadomości." -#. Put one translator per line, in the form of NAME , YEAR1, YEAR2 -#: C/accounts.page:0(None) -msgid "translator-credits" -msgstr "" -"Piotr Drąg , 2016-2017\n" -"Aviary.pl , 2016-2017" +#. (itstool) path: section/p +#: C/write.page:51 +msgid "Geary deletes the draft when you send the message." +msgstr "Szkic jest usuwany po wysłaniu wiadomości." diff -Nru geary-0.12.4/help/pt_BR/pt_BR.po geary-3.32.0/help/pt_BR/pt_BR.po --- geary-0.12.4/help/pt_BR/pt_BR.po 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/help/pt_BR/pt_BR.po 2019-03-17 13:39:29.000000000 +0000 @@ -1,14 +1,15 @@ # Brazilian Portuguese translation for geary. -# Copyright 2017 Software Freedom Conservancy Inc. +# Copyright 2016 Software Freedom Conservancy Inc. +# Copyright 2016-2019 Geary Development Team. # This file is distributed under the same license as the geary package. -# Rafael Fontenelle , 2016, 2017. +# Rafael Fontenelle , 2016-2019. msgid "" msgstr "" "Project-Id-Version: geary master\n" -"POT-Creation-Date: 2017-09-25 16:02+0000\n" -"PO-Revision-Date: 2017-09-26 19:29-0200\n" +"POT-Creation-Date: 2019-01-29 05:49+0000\n" +"PO-Revision-Date: 2019-01-31 08:13-0200\n" "Last-Translator: Rafael Fontenelle \n" -"Language-Team: Brazilian Portuguese \n" +"Language-Team: Portuguese - Brazil \n" "Language: pt_BR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -16,873 +17,496 @@ "Plural-Forms: nplurals=2; plural=(n > 1);\n" "X-Generator: Virtaal 1.0.0-beta1\n" -#: C/write.page:9(title) -msgid "Write a message" -msgstr "Escrevendo uma mensagem" +#. Put one translator per line, in the form NAME , YEAR1, YEAR2 +msgctxt "_" +msgid "translator-credits" +msgstr "Rafael Fontenelle , 2016-2019" -#: C/write.page:12(title) -msgid "Composing and replying" -msgstr "Composição e resposta" +#. (itstool) path: page/title +#: C/accounts.page:10 +msgid "Accounts" +msgstr "Contas" + +#. (itstool) path: section/title +#: C/accounts.page:13 +msgid "Adding accounts" +msgstr "Adicionando contas" -#: C/write.page:13(p) +#. (itstool) path: section/p +#: C/accounts.page:15 msgid "" -"To compose a new message in Geary, press the New Message button " -"on the toolbar." +"The first time you start Geary, you will be prompted to add an email " +"account. On this screen, select if your account is Gmail, Yahoo, Outlook." +"com, or other. For other account types, you will need to enter your IMAP and " +"SMTP login settings manually." msgstr "" -"Para compor uma nova mensagem no Geary, pressione o botão Nova " -"mensagem na barra de ferramentas." +"A primeira vez que você inicia o Geary, será solicitado adicionar uma conta " +"de e-mail. Nesta tela, selecione se sua conta é Gmail, Yahoo, Outlook.com ou " +"outra. Para outros tipos de conta, você precisará informar as configurações " +"de credencial IMAP e SMTP." -#: C/write.page:16(p) +#. (itstool) path: section/p +#: C/accounts.page:19 msgid "" -"To reply to a message, open the message menu in the upper right corner of " -"the message and choose Reply, Reply All or " -"Forward. You can also reply to the last message in a conversation " -"via the Reply, Reply All or Forward buttons " -"on the toolbar." +"Additional accounts can be added from the Accounts dialog. The " +"Accounts option is available in either Geary's application menu " +"or the gear menu in the upper-right of the toolbar. (The location depends on " +"the install desktop shell. For GNOME Shell and Unity, the application menu " +"is available near the top-left corner of the screen.) To add an account, " +"click the + button." msgstr "" -"Para responder a uma mensagem, abra o menu de mensagem no canto direito " -"superior da mensagem e escolha Responder, Responder todos ou Encaminhar. Você também pode responder à última mensagem " -"em uma conversa via os botões Responder, Responder todos ou Encaminhar na barra de ferramentas." +"Contas adicionais podem ser adicionadas a partir do diálogo de Contas. A " +"opção Contas está disponível tanto no menu de aplicativo do Geary " +"quanto no menu de engrenagens no canto direito superior da barra de " +"ferramentas. (A localização depende do shell de ambiente instalado. Para o " +"GNOME Shell e Unity, o menu de aplicativo está disponível próximo ao canto " +"superior esquerdo da tela.) Para adicionar uma conta, clique no botão +." -#: C/write.page:21(title) -msgid "Features" -msgstr "Recursos" +#. (itstool) path: section/title +#: C/accounts.page:27 +msgid "Editing existing accounts" +msgstr "Editando contas existente" -#: C/write.page:23(p) +#. (itstool) path: section/p +#: C/accounts.page:29 msgid "" -"Geary's email composer lets you adjust the font, size and color of text. You " -"can also insert hyperlinks into messages." +"From the Accounts dialog, select an account and click the pencil icon to " +"change various settings. Please note that Geary cannot change server " +"settings on an existing account. If you need to change your IMAP or SMTP " +"server, you will need to delete the account and re-add it." msgstr "" -"O compositor de e-mail do Geary permite que você ajuste a fonte, tamanho e " -"cor do texto. Você também pode inserir hyperlinks nas mensagens." +"A partir do diálogo de Contas, selecione uma conta ou clique no ícone de " +"lápis para alterar uma gama de configurações. Por favor, note que o Geary " +"não pode alterar as configurações de servidor em uma conta existente. Se " +"você precisar alterar seu servidor IMAP ou SMTP, será necessário excluir a " +"conta e re-adicioná-la." -#: C/write.page:25(p) +#. (itstool) path: section/p +#: C/accounts.page:33 msgid "" -"Geary can also send plain text messages. In the drop-down menu, check or " -"uncheck \"Rich Text\" to toggle between plain text and rich text mode." +"To change the order that accounts are displayed in the folder list, drag the " +"accounts in the Accounts dialog to the desired order." msgstr "" -"Geary também pode enviar mensagens de texto simples. No menu suspenso, " -"marque ou desmarque \"Texto rico\" para alternar entre modos texto simples " -"ou texto rico." +"Para alterar a ordem que as contas são exibidas na lista de pasta, arraste " +"as contas no diálogo de Contas para a ordem desejada." -#: C/write.page:28(p) -msgid "" -"You can attach a file to a message you're writing in either of these ways:" -msgstr "" -"Você pode anexar um arquivo a uma mensagem que você está escrever de duas " -"formas:" +#. (itstool) path: section/p +#: C/accounts.page:36 +msgid "There are some advanced options available when editing accounts:" +msgstr "Há algumas opções avançadas disponíveis na edição de contas:" -#: C/write.page:30(p) +#. (itstool) path: item/p +#: C/accounts.page:38 msgid "" -"Press the Attach File button at the lower left of the composer " -"window, then select a file to attach." +"The Save sent mail checkbox controls whether Geary will push " +"successfully sent messages up to the account's Sent Mail folder. " +"For Gmail accounts, this happens automatically. Yahoo and some other " +"accounts can be configured to do this automatically as well. For other " +"accounts, if you disable this setting, you may be unable to view messages " +"you've sent." msgstr "" -"Pressione o botão Anexar arquivo na esquerda inferior da janela " -"do compositor e, então, selecione um arquivo para anexar." +"A caixa de seleção Salvar e-mails enviados controla se o Geary " +"vai enviar mensagens enviadas para a pasta E-mails enviados da " +"conta. Para contas do Gmail, isso acontece automaticamente. Yahoo e outras " +"contas pode ser configuradas para fazer isso automaticamente também. Para " +"outras contas, se você desabilitar essa configuração. você pode não " +"conseguir ver mensagens que você enviou." -#: C/write.page:32(p) +#. (itstool) path: item/p +#: C/accounts.page:44 msgid "" -"Drag the file from the Nautilus file manager to the composer window, and " -"drop it either on the text fields at the top of the window or on the toolbar " -"at the bottom." +"The Sign emails checkbox indicates whether a signature will be " +"automatically inserted when a composer is opened. You may enter the " +"signature into the box immediately below. You may use HTML tags to style the " +"text. Switch to a preview of the signature using the buttons to the right." msgstr "" -"Arraste o arquivo do gerenciador de arquivos para a janela do compositor e, " -"então, solte-o nos campos de texto no topo da janela ou na barra de " -"ferramentas em baixo." +"A caixa de seleção Assinar e-mails indica se uma assinatura será " +"inserida automaticamente quando o compositor for aberto. Você pode inserir a " +"assinatura em uma caixa imediatamente abaixo. Você pode usar tags HTML para " +"estilizar o texto. Mude para a visualização da assinatura usando os botões à " +"direita." -#: C/write.page:36(p) +#. (itstool) path: item/p +#: C/accounts.page:49 msgid "" -"A number of keyboard shortcuts are available in the composer; see for details." +"If you leave the signature in the Accounts dialog blank, Geary will use the " +".signature file in your home directory, if it exists. This file " +"may contain either plain text or HTML markup. In the latter case, the markup " +"will be inserted directly into the composer, without any escaping." msgstr "" -"Uma quantidade de teclas de atalho estão disponíveis no compositor; veja " -" para detalhes." +"Se você deixar a assinatura em branco no diálogo de Contas, Geary vai usar o " +"arquivo .signature em seu diretório Home, se ele existir. Esse " +"arquivo pode conter um texto simples ou marcação HTML. No último caso, a " +"marcação será inserida diretamente no compositor, sem qualquer escape." -#: C/write.page:38(p) +#. (itstool) path: item/p +#: C/accounts.page:54 msgid "" -"You may specify a signature to be inserted into the composer in the dialog." +"The Download mail drop-down allows you to configure how much mail " +"Geary will keep locally. Geary can only use locally available mail when " +"searching and forming conversations." msgstr "" -"Você pode especificar uma assinatura a ser inserida no compositor no diálogo " -"." - -#: C/write.page:43(title) -msgid "Drafts" -msgstr "Rascunhos" +"A menu suspenso de Baixar e-mail permite que você configure " +"quantos e-mails o Geary manterá localmente. Geary só pode usar e-mails " +"disponíveis localmente ao pesquisar e formar conversas." -#: C/write.page:45(p) -msgid "" -"For mail servers that support drafts, Geary will automatically save the " -"message as you type. If you close the composer without sending, Geary will " -"prompt you to keep the draft or to discard it." -msgstr "" -"Para servidores de e-mails que oferecem suporte a rascunhos, Geary vai " -"salvar automaticamente a mensagem que você digitar. Se você fechar o " -"compositor sem enviar, Geary vai perguntar se deseja manter o rascunho ou " -"descartá-lo." +#. (itstool) path: section/title +#: C/accounts.page:62 +msgid "Removing accounts" +msgstr "Removendo contas" -#: C/write.page:48(p) +#. (itstool) path: section/p +#: C/accounts.page:64 msgid "" -"To edit an existing draft, select the Drafts folder in the folder list, " -"select the message, and click \"Edit Draft\" in the message viewer." +"To delete an account, open the Accounts dialog, select the account, and " +"press the - button. Geary will delete all information associated with the " +"account." msgstr "" -"Para editar um rascunho existente, selecione a pasta Rascunhos na lista de " -"pastas, selecione a mensagem e, então, clique em \"Editar rascunho\" no " -"visualizador de mensagem." - -#: C/write.page:51(p) -msgid "Geary deletes the draft when you send the message." -msgstr "Geary exclui o rascunho quando você envia a mensagem." - -#: C/star.page:10(title) -msgid "Star a message or mark it as read/unread" -msgstr "Marcando estrela uma mensagem ou marcando como lida/não lida" +"Para excluir uma conta, abra o diálogo de Contas, selecione a conta e " +"pressione o botão -. Geary vai excluir toda informação associada com a conta." -#: C/star.page:12(title) -msgid "Star messages" -msgstr "Marcando estrela nas mensagens" +#. (itstool) path: page/title +#: C/archive.page:10 +msgid "Delete or archive a message" +msgstr "Excluindo ou arquivando uma mensagem" -#: C/star.page:13(p) +#. (itstool) path: page/p +#: C/archive.page:12 msgid "" -"You can star messages to indicate that they're important to you. To mark a " -"conversation with a star, click its star icon in the conversation list. You " -"can star an individual message by clicking the star at the upper right of " -"the message itself." +"When you use Geary with a Gmail account, Geary lets you archive " +"messages. The Archive toolbar button archives the selected " +"conversation(s). Archived messages appear in the All Mail folder." msgstr "" -"Você pode marcar estrela nas mensagens para indicar que elas são importantes " -"para você. Para isto, clique em seu ícone de estrela na lista de conversa. " -"Você pode marcar com estrela uma mensagem individual clicando na estrela no " -"canto direito superior da própria mensagem." +"Quando você usa o Geary com uma conta do Gmail, Geary permite arquivar mensagens. O botão Arquivar da barra de ferramentas arquiva a " +"conversa selecionada. Mensagens arquivadas aparecem na pasta Todos os e-" +"mails." -#: C/star.page:15(p) +#. (itstool) path: page/p +#: C/archive.page:16 msgid "" -"With Gmail accounts, starred messages appear in the Starred folder in the " -"folder list." +"With other mail servers, you can trash or delete, but not archive, messages. " +"To move one or more conversations to the Trash folder, select " +"them and press the Trash button on the toolbar. To permanently " +"delete the conversations, hold down Shift and press the " +"Delete button that appears in place of the Trash " +"button." msgstr "" -"Com contas de Gmail, mensagens marcadas com estrela aparecem na pasta " -"Importante na lista de pastas." - -#: C/star.page:18(title) -msgid "Mark messages as read or unread" -msgstr "Marcando mensagens como lidas ou não lidas" +"Com outros servidores de e-mail, você pode mover para a lixeira ou excluir, " +"mas não arquivar, mensagens. Para mover uma ou mais conversas para a pasta " +"Lixeira, selecione elas e pressione o botão Lixeira na " +"barra de ferramentas. Para excluir permanentemente as conversas, segure " +"Shift e pressione o botão Excluir que aparecerá no " +"lugar do botão Lixeira." -#: C/star.page:19(p) +#. (itstool) path: page/p +#: C/archive.page:21 msgid "" -"Geary marks messages as read automatically as you read them. To manually " -"toggle a conversation as read or unread, click the circle icon in the " -"conversation list." +"Delete is not available from every folder, such as Search. Delete is also " +"unavailable for Gmail. For Gmail, Trash will move messages to the " +"Trash folder on the server, where the user can then manually delete them. " +"The server will automatically remove trashed messages after 30 days." msgstr "" -"Geary marca mensagens como lidas automaticamente na medida em que você as " -"lê. Para alternar manualmente uma conversa como lida ou não lida, clique no " -"ícone de círculo na lista de conversa." +"Excluir não está disponível em toda pasta, como em Pesquisar. Excluir também " +"não está disponível para Gmail. Para o Gmail, Lixeira vai mover " +"mensagens para a pasta Lixeira do servidor, onde o usuário pode manualmente " +"exclui-las. O servidor vai remover automaticamente mensagens da lixeira após " +"30 dias." + +#. (itstool) path: page/title +#: C/bugs.page:10 +msgid "Found a bug?" +msgstr "Encontrou um erro?" -#: C/star.page:22(p) +#. (itstool) path: page/p +#: C/bugs.page:12 msgid "" -"Alternately, the Mark as Unread in the Mark menu on " -"the toolbar can be used to toggle the read status of the selected " -"conversation(s)." +"If you suspect you've found a bug in Geary, please get in touch about it so it can be " +"fixed." msgstr "" -"Alternativamente, Marcar como não lida no menu Marcar " -"na barra de ferramentas pode ser usado para alternar o status de leitura das " -"conversas selecionadas." +"Se você suspeita que encontrou um erro no Geary, por favor, entre em contato para " +"que possa ser corrigido." -#: C/star.page:25(p) +#. (itstool) path: page/p +#: C/bugs.page:16 msgid "" -"To mark an individual message as read, select Mark as Read from " -"the dropdown menu." +"To help diagnose the problem as fast as possible, please include the " +"following information:" msgstr "" -"Para marcar uma mensagem individual como lida, selecione Marcar como " -"lida a partir do menu suspenso." +"Para ajudar a diagnosticar o problema o mais rápido possível, inclua as " +"seguintes informações:" -#: C/shortcuts.page:11(title) -msgid "Keyboard shortcuts" -msgstr "Atalhos de teclado" - -#: C/shortcuts.page:12(p) -msgid "Geary has keyboard shortcuts for most common operations." -msgstr "Geary possui atalhos de teclado para a maioria das operações." - -#: C/shortcuts.page:15(p) -msgid "Compose a new message" -msgstr "Compor uma nova mensagem" +#. (itstool) path: item/p +#: C/bugs.page:20 +msgid "Geary version and installation method (Package? Flathub? Source code?)" +msgstr "Versão Geary e método de instalação (Pacote? Flathub? Código-fonte?)" -#: C/shortcuts.page:16(p) -msgid "CtrlN or N" -msgstr "CtrlN ou N" +#. (itstool) path: item/p +#: C/bugs.page:22 +msgid "Your desktop (GNOME? KDE? Something else?)" +msgstr "Seu ambiente gráfico (GNOME? KDE? Outra coisa?)" -#: C/shortcuts.page:19(p) -msgid "Reply to sender" -msgstr "Responder ao remetente" - -#: C/shortcuts.page:20(p) -msgid "CtrlR or R" -msgstr "CtrlR ou R" - -#: C/shortcuts.page:23(p) -msgid "Reply to all" -msgstr "Responder a todos" - -#: C/shortcuts.page:24(p) +#. (itstool) path: item/p +#: C/bugs.page:23 msgid "" -"CtrlShiftR or " -"ShiftR" +"Your operating system and version (Ubuntu 16.04? Fedora 28? Rolled your own?)" msgstr "" -"CtrlShiftR ou " -"ShiftR" - -#: C/shortcuts.page:27(p) -msgid "Forward" -msgstr "Encaminhar" +"Seu sistema operacional e versão (Ubuntu 16.04? Fedora 28? Seu próprio?)" -#: C/shortcuts.page:28(p) -msgid "CtrlL or F" -msgstr "CtrlL ou F" +#. (itstool) path: item/p +#: C/bugs.page:25 +msgid "Email provider (Gmail, Yahoo!, Outlook.com, or someone else?)" +msgstr "Provedor de e-mail (Gmail, Yahoo!, Outlook.com ou outra pessoa?)" -#: C/shortcuts.page:31(p) -msgid "Archive" -msgstr "Arquivar" +#. (itstool) path: item/p +#: C/bugs.page:27 +msgid "Steps to reproduce the bug" +msgstr "Passos para reproduzir o erro" -#: C/shortcuts.page:32(key) -msgid "A" -msgstr "A" +#. (itstool) path: item/p +#: C/bugs.page:28 +msgid "What happened?" +msgstr "O que aconteceu?" -#: C/shortcuts.page:35(p) -msgid "Trash" -msgstr "Lixeira" +#. (itstool) path: item/p +#: C/bugs.page:29 +msgid "What did you expect to happen?" +msgstr "O que você esperava acontecer?" -#: C/shortcuts.page:36(p) -msgid "Delete or Backspace" -msgstr "Delete ou Backspace" +#. (itstool) path: page/p +#: C/bugs.page:32 +msgid "Thanks for your help!" +msgstr "Obrigado pela sua ajuda!" -#: C/shortcuts.page:39(p) -msgid "Delete" -msgstr "Excluir" +#. (itstool) path: page/title +#: C/contributing.page:10 +msgid "Contribute to Geary" +msgstr "Contribuindo para o Geary" -#: C/shortcuts.page:40(p) +#. (itstool) path: page/p +#: C/contributing.page:12 msgid "" -"ShiftDelete or ShiftBackspace" +"Want to help improve Geary? There are a number of ways you can contribute:" msgstr "" -"ShiftDelete ou ShiftBackspace" +"Quer ajudar a melhorar o Geary? Existem várias maneiras pelas quais você " +"pode contribuir:" -#: C/shortcuts.page:43(p) -msgid "Star" -msgstr "Marcar estrela" - -#: C/shortcuts.page:44(key) C/shortcuts.page:100(key) -msgid "S" -msgstr "S" - -#: C/shortcuts.page:47(p) -msgid "Unstar" -msgstr "Desmarcar estrela" - -#: C/shortcuts.page:48(key) C/shortcuts.page:138(key) -msgid "D" -msgstr "D" - -#: C/shortcuts.page:51(p) -msgid "Mark read" -msgstr "Marcar como lida" - -#: C/shortcuts.page:52(p) +#. (itstool) path: item/p +#: C/contributing.page:16 msgid "" -"CtrlI or ShiftI" +"Bug " +"reporting—report new bugs or request new features" msgstr "" -"CtrlI ou ShiftI" - -#: C/shortcuts.page:55(p) -msgid "Mark unread" -msgstr "Marcar como não lida" +"Relatório de " +"erros – informe novos erros ou solicite novos recursos" -#: C/shortcuts.page:56(p) +#. (itstool) path: item/p +#: C/contributing.page:19 msgid "" -"CtrlU or ShiftU" +"User Experience " +"Design—research and develop Geary’s user experience" msgstr "" -"CtrlU ou ShiftU" +"Design de " +"experiência do usuário – pesquise e desenvolva a experiência do " +"usuário da Geary" -#: C/shortcuts.page:59(p) -msgid "Move the conversation" -msgstr "Mover a conversação" - -#: C/shortcuts.page:60(key) -msgid "M" -msgstr "M" +#. (itstool) path: item/p +#: C/contributing.page:20 +msgid "" +"Development—fix bugs and add new features" +msgstr "" +"Desenvolvimento – corrija erros e adicionar novos recursos" -#: C/shortcuts.page:63(p) -msgid "Label the conversation" -msgstr "Rotular a conversa" +#. (itstool) path: item/p +#: C/contributing.page:21 +msgid "" +"Translating—translate Geary’s user interface and user manual into new languages" +msgstr "" +"Tradução " +"– traduza a interface do usuário e o manual do usuário do Geary em novos " +"idiomas" -#: C/shortcuts.page:64(key) C/shortcuts.page:162(key) -msgid "L" -msgstr "L" +#. (itstool) path: item/p +#: C/contributing.page:22 +msgid "" +"Join the " +"discussion—on the mailing list or IRC channel" +msgstr "" +"Participe da " +"discussão – na lista de discussão ou no canal de IRC" -#: C/shortcuts.page:67(p) -msgid "Jump to next (older) conversation" -msgstr "Saltar para a próxima (mais antiga) conversa" +#. (itstool) path: page/p +#: C/contributing.page:25 +msgid "Thanks for your help making Geary better!" +msgstr "Obrigado pela sua ajuda para tornar a Geary melhor!" -#: C/shortcuts.page:68(key) -msgid "J" -msgstr "J" +#. (itstool) path: title/media +#. This is a reference to an external file such as an image or video. When +#. the file changes, the md5 hash will change to let you know you need to +#. update your localized copy. The msgstr is not used at all. Set it to +#. whatever you like once you have updated your copy of the file. +#: C/index.page:5 +msgctxt "_" +msgid "external ref='figures/geary.svg' md5='18b50c9e10fe5256ae1cb12aaa3a7600'" +msgstr "" +"external ref='figures/geary.svg' md5='18b50c9e10fe5256ae1cb12aaa3a7600'" -#: C/shortcuts.page:71(p) -msgid "Jump to previous (newer) conversation" -msgstr "Saltar para a conversa anterior (mais nova)" +#. (itstool) path: page/title +#: C/index.page:5 +msgid " Geary" +msgstr " Geary" -#: C/shortcuts.page:72(key) C/shortcuts.page:158(key) -msgid "K" -msgstr "K" +#. (itstool) path: section/title +#: C/index.page:8 +msgid "Introduction" +msgstr "Introdução" -#: C/shortcuts.page:75(p) -msgid "Toggle spam" -msgstr "Ativar spam" +#. (itstool) path: section/title +#: C/index.page:12 +msgid "Using Geary" +msgstr "Usando o Geary" -#: C/shortcuts.page:76(p) -msgid "CtrlJ or !" -msgstr "CtrlJ ou !" +#. (itstool) path: section/title +#: C/index.page:16 +msgid "Contributing and bug reporting" +msgstr "Contribuindo e relatando erros" -#: C/shortcuts.page:79(p) -msgid "Quit" -msgstr "Sair" +#. (itstool) path: page/title +#: C/label.page:10 +msgid "Label or move a conversation" +msgstr "Rotulando ou movendo uma conversa" -#: C/shortcuts.page:80(key) C/shortcuts.page:96(key) C/shortcuts.page:100(key) -#: C/shortcuts.page:104(key) C/shortcuts.page:108(key) -#: C/shortcuts.page:112(key) C/shortcuts.page:122(key) -#: C/shortcuts.page:126(key) C/shortcuts.page:130(key) -#: C/shortcuts.page:138(key) C/shortcuts.page:146(key) -#: C/shortcuts.page:150(key) C/shortcuts.page:154(key) -#: C/shortcuts.page:158(key) C/shortcuts.page:162(key) -#: C/shortcuts.page:166(key) C/shortcuts.page:181(key) -msgid "Ctrl" -msgstr "Ctrl" +#. (itstool) path: section/title +#: C/label.page:12 +msgid "Label a conversation" +msgstr "Rotulando uma conversa" -#: C/shortcuts.page:80(key) -msgid "Q" -msgstr "Q" +#. (itstool) path: section/p +#: C/label.page:13 +msgid "" +"Geary lets you apply one or more labels to each conversation. Geary " +"labels correspond to labels in Gmail, or ordinary folders in other mail " +"services." +msgstr "" +"Geary permite que você aplique um ou mais rótulos para cada " +"conversa. Os rótulos do Geary correspondem aos rótulos do Gmail ou a pastas " +"comuns em outros serviços de e-mail." -#: C/shortcuts.page:83(p) -msgid "Zoom in" -msgstr "Aumentar o zoom" - -#: C/shortcuts.page:84(p) -msgid "Ctrl= or =" -msgstr "Ctrl= ou =" - -#: C/shortcuts.page:87(p) -msgid "Zoom out" -msgstr "Diminuir o zoom" - -#: C/shortcuts.page:88(p) -msgid "Ctrl- or -" -msgstr "Ctrl- ou -" - -#: C/shortcuts.page:91(p) -msgid "Reset zoom" -msgstr "Redefinir o zoom" - -#: C/shortcuts.page:92(p) -msgid "Ctrl0 or 0" -msgstr "Ctrl0 ou 0" - -#: C/shortcuts.page:95(p) -msgid "Close composer window" -msgstr "Fechar a janela do compositor" - -#: C/shortcuts.page:96(key) -msgid "W" -msgstr "W" - -#: C/shortcuts.page:99(p) -msgid "Jump to search box" -msgstr "Saltar para a caixa de pesquisa" - -#: C/shortcuts.page:103(p) -msgid "Find in current conversation" -msgstr "Localizar na conversa atual" - -#: C/shortcuts.page:104(key) -msgid "F" -msgstr "F" - -#: C/shortcuts.page:107(p) -msgid "Find next in current conversation" -msgstr "Localizar próximo na conversa atual" - -#: C/shortcuts.page:108(key) C/shortcuts.page:112(key) -msgid "G" -msgstr "G" - -#: C/shortcuts.page:111(p) -msgid "Find previous in current conversation" -msgstr "Localizar anterior na conversa atual" - -#: C/shortcuts.page:112(key) -msgid "Shift" -msgstr "Shift" - -#: C/shortcuts.page:117(title) -msgid "Composer shortcuts" -msgstr "Atalhos do compositor" - -#: C/shortcuts.page:118(p) -msgid "These shortcuts are active whenever focus is in a composer." -msgstr "Esses atalhos estão ativos sem que o foco estiver em um compositor." - -#: C/shortcuts.page:121(p) -msgid "Attach file" -msgstr "Anexar arquivo" - -#: C/shortcuts.page:122(key) -msgid "T" -msgstr "T" - -#: C/shortcuts.page:125(p) -msgid "Quote text" -msgstr "Texto citado" - -#: C/shortcuts.page:126(key) -msgid "]" -msgstr "]" - -#: C/shortcuts.page:129(p) -msgid "Unquote text" -msgstr "Texto não-citado" - -#: C/shortcuts.page:130(key) -msgid "[" -msgstr "[" - -#: C/shortcuts.page:133(p) -msgid "Close composer" -msgstr "Fechar o compositor" - -#: C/shortcuts.page:134(p) -msgid "CtrlW or Esc" -msgstr "CtrlW ou Esc" - -#: C/shortcuts.page:137(p) -msgid "Detach composer" -msgstr "Desanexar o compositor" - -#: C/shortcuts.page:142(p) -msgid "These shortcuts are only active in composers in rich text mode." -msgstr "Esses atalhos estão ativos apenas em compositores em modo texto rico." - -#: C/shortcuts.page:145(p) -msgid "Bold text" -msgstr "Texto em negrito" - -#: C/shortcuts.page:146(key) C/shortcuts.page:181(key) -msgid "B" -msgstr "B" - -#: C/shortcuts.page:149(p) -msgid "Italicize text" -msgstr "Texto em itálico" - -#: C/shortcuts.page:150(key) -msgid "I" -msgstr "I" - -#: C/shortcuts.page:153(p) -msgid "Underline text" -msgstr "Sublinhar o texto" - -#: C/shortcuts.page:154(key) -msgid "U" -msgstr "U" - -#: C/shortcuts.page:157(p) -msgid "Strike text" -msgstr "Riscar o texto" - -#: C/shortcuts.page:161(p) -msgid "Insert a link" -msgstr "Inserir um link" - -#: C/shortcuts.page:165(p) -msgid "Remove formatting" -msgstr "Remover formatação" - -#: C/shortcuts.page:166(key) C/shortcuts.page:186(key) -msgid "Space" -msgstr "Espaço" - -#: C/shortcuts.page:172(title) -msgid "Keyboard navigation" -msgstr "Navegação pelo teclado" - -#: C/shortcuts.page:173(p) -#| msgid "These shortcuts are active whenever focus is in a composer." -msgid "" -"These shortcuts can be used to move the keyboard focus in the main window." -msgstr "" -"Esses atalhos pode ser usados para mover o foco do teclado na janela " -"principal." - -#: C/shortcuts.page:176(p) -msgid "Move focus to the next/previous pane" -msgstr "Mover o foco ao próximo painel ou ao anterior" - -#: C/shortcuts.page:177(p) -msgid "" -"F6 / ShiftF6" -msgstr "" -"F6 / " -"ShiftF6" - -#: C/shortcuts.page:180(p) -msgid "Move focus to conversation list" -msgstr "Mover foco para a lista de conversa" - -#: C/shortcuts.page:184(p) -#| msgid "Move the conversation" -msgid "Move to the next message in a conversation" -msgstr "Mover para a próxima mensagem em uma conversa" - -#: C/shortcuts.page:190(p) -#| msgid "Move focus to the next/previous pane" -msgid "Move to the next/previous message in a conversation" -msgstr "Mover para a mensagem seguinte/anterior em uma conversa" - -#: C/shortcuts.page:191(p) -#| msgid "" -#| "CtrlU or ShiftU" -msgid "" -"CtrlDown / CtrlUp" -msgstr "" -"CtrlDown / " -"CtrlUp" - -#: C/shortcuts.page:197(p) -#| msgid "Move the conversation" -msgid "Move to the first/last message in a conversation" -msgstr "Mover para a primeira/última mensagem em uma conversa" - -#: C/shortcuts.page:198(p) -#| msgid "" -#| "CtrlI or ShiftI" -msgid "" -"CtrlHome / CtrlEnd" -msgstr "" -"CtrlHome / " -"CtrlEnd" - -#: C/search.page:10(title) -msgid "Search" -msgstr "Pesquisando" - -#: C/search.page:12(p) -msgid "" -"Geary supports a per-account full text search. To start a search, select a " -"folder associated with the account you'd like to search against. Then click " -"the search box in the toolbar (or press CtrlS) and start typing. Results will appear after a brief delay." -msgstr "" -"Geary oferece suporte a pesquisa completa de texto por conta. Para iniciar " -"uma pesquisa, selecione uma pasta associada com a conta na qual você " -"gostaria de pesquisar. Então, clique na caixa de pesquisa na barra de " -"ferramentas (ou pressioneCtrlS) e " -"comece a digitar. Resultados aparecerão após uma breve atraso." - -#: C/search.page:16(p) -msgid "" -"The full text search includes email text, email addresses (to, from, and " -"cc), subject lines and attachment filenames." -msgstr "" -"A pesquisa completa de texto inclui texto de e-mail, endereços de e-mail " -"(para, de e cc), linhas de assunto e nomes de arquivos de anexos." - -#: C/search.page:19(p) -msgid "" -"Keywords that match your search are highlighted in the message view. Geary " -"will match different forms of the same word, for example searching for \"walk" -"\" will also match \"walking\" and \"walked.\"" -msgstr "" -"Palavras-chave que correspondem à sua pesquisa são destacadas na visão da " -"mensagem. Geary vai corresponder formas diferentes da mesma mensagem; por " -"exemplo, pesquisar por \"caminha\" também retornará \"caminhando\" e " -"\"caminhada.\"" - -#: C/search.page:23(title) -msgid "Search operators" -msgstr "Operadores de pesquisa" - -#: C/search.page:24(p) -msgid "Geary supports the following operators to limit the scope of searches:" -msgstr "" -"Geary oferece suporte aos seguintes operadores para limitar o escopo das " -"pesquisa:" - -#: C/search.page:27(var) -msgid "filename" -msgstr "nome de arquivo" - -#: C/search.page:27(input) -msgid "attachment:" -msgstr "anexo:" - -#: C/search.page:28(p) -msgid "Finds messages with attachments whose name matches filename." -msgstr "" -"Localiza mensagens com anexos cujo nome corresponda a nome de arquivo." - -#: C/search.page:31(var) C/search.page:39(var) C/search.page:63(var) -msgid "recipient" -msgstr "destinatário" - -#: C/search.page:31(input) -msgid "bcc:" -msgstr "cco:" - -#: C/search.page:32(p) -msgid "Finds messages where recipient matches the BCC header." -msgstr "" -"Localiza mensagens cujo destinatário corresponda ao cabeçalho CCO." - -#: C/search.page:35(var) C/search.page:59(var) -msgid "text" -msgstr "texto" - -#: C/search.page:35(input) -msgid "body:" -msgstr "corpo:" - -#: C/search.page:36(p) -msgid "Finds messages whose body contains text." -msgstr "Localiza mensagens cujo corpo contém texto." - -#: C/search.page:39(input) -msgid "cc:" -msgstr "cc:" - -#: C/search.page:40(p) -msgid "Finds messages where recipient matches the CC header." -msgstr "" -"Localiza mensagens cujo destinatário corresponda ao cabeçalho CC." - -#: C/search.page:43(var) -msgid "sender" -msgstr "remetente" - -#: C/search.page:43(input) -msgid "from:" -msgstr "de:" - -#: C/search.page:44(p) -msgid "Finds messages where sender matches the From header." -msgstr "" -"Localiza mensagens cujo remetente corresponda ao cabeçalho De." - -#: C/search.page:47(input) -msgid "is:read" -msgstr "está:lido" - -#: C/search.page:48(p) -msgid "Finds messages that have been marked as read." -msgstr "Localiza mensagens que foram marcadas como lidas." - -#: C/search.page:51(input) -msgid "is:starred" -msgstr "está:com-estrela" - -#: C/search.page:52(p) -msgid "Finds messages that have been marked as starred." -msgstr "Localiza mensagens que foram marcadas com estrela." - -#: C/search.page:55(input) -msgid "is:unread" -msgstr "está:não-lido" - -#: C/search.page:56(p) -msgid "Finds messages that have been marked as not read." -msgstr "Localiza mensagens que foram marcadas como não lidas." - -#: C/search.page:59(input) -msgid "subject:" -msgstr "assunto:" - -#: C/search.page:60(p) -msgid "Finds messages whose subject contains text." -msgstr "Localiza mensagens cujo assunto contém texto." - -#: C/search.page:63(input) -msgid "to:" -msgstr "para:" - -#: C/search.page:64(p) -msgid "" -"Finds messages where sender matches the To, CC, or BCC header." -msgstr "" -"Localiza mensagens cujo remetente corresponda ao cabeçalho Para, " -"CC ou CCO." - -#: C/search.page:68(p) +#. (itstool) path: section/p +#: C/label.page:15 msgid "" -"As a special case, the bcc, cc, from, and to operators support me as their " -"argument, which searches for the account's email address in the appropriate " -"context." +"To label one or more conversations, first select the conversation(s), then " +"do either of the following:" msgstr "" -"Como um caso especial, os operadores cco, cc, " -"de e para oferecem suporte a me " -"como seu argumento, o qual pesquisa pelo endereço de e-mail da conta no " -"contexto apropriado." - -#: C/preferences.page:10(title) -msgid "Preferences" -msgstr "Preferências" +"Para rotular uma ou mais conversas, primeiro selecione a conversa e, então, " +"faça uma dentre as opções abaixo:" -#: C/preferences.page:11(p) +#. (itstool) path: item/p +#: C/label.page:18 msgid "" -"The Preferences option is available in either Geary's application " -"menu or the gear menu in the upper-right of the toolbar. (The location " -"depends on the install desktop shell. For GNOME Shell and Unity, the " -"application menu is available near the top-left corner of the screen.)" +"Click the Label button on the toolbar and select a label from the " +"resulting drop-down menu." msgstr "" -"A opção Preferências está disponível em tanto o menu de " -"aplicativo do Geary quanto no menu de engrenagem no canto superior direito " -"na barra de ferramentas. (A localização depende do shell de ambiente " -"instalado. Para o GNOME Shell e Unity, o menu de aplicativo está disponível " -"próximo ao canto superior esquerdo da tela.)" - -#: C/preferences.page:17(title) -msgid "Reading" -msgstr "Lendo" - -#: C/preferences.page:20(gui) -msgid "Automatically select next message" -msgstr "Selecionar automaticamente a próxima mensagem" +"Clique no botão Rótulo na barra de ferramentas e selecione um " +"rótulo do menu suspenso resultante." -#: C/preferences.page:21(p) +#. (itstool) path: item/p +#: C/label.page:20 msgid "" -"When this option is enabled, Geary automatically selects the latest message " -"in a folder when you enter the folder. In addition, after archiving a " -"message, Geary automatically selects an adjacent message." +"Hold down the Ctrl key and drag the conversation(s) from the " +"conversation list to the label in the sidebar." msgstr "" -"Quando esta opção estiver habilitada, Geary seleciona automaticamente a " -"mensagem mais recente em uma pasta quando você entrar nela. Além disso, após " -"arquivar uma mensagem, Geary seleciona automaticamente uma mensagem ajacente." +"Segure a tecla Ctrl e arraste a conversa da lista de conversa " +"para o rótulo na barra lateral." -#: C/preferences.page:26(gui) -msgid "Display conversation preview" -msgstr "Exibir visualização de conversa" +#. (itstool) path: section/title +#: C/label.page:25 +msgid "Move a conversation to a folder or label" +msgstr "Movendo uma conversa para uma pasta ou um rótulo" -#: C/preferences.page:27(p) +#. (itstool) path: section/p +#: C/label.page:26 msgid "" -"Enables message previews in the conversation list. Previews show the first " -"few lines of each message." +"To move one or more conversations to a folder or label, first select the " +"conversation(s), then do either of the following:" msgstr "" -"Habilita visualizações de mensagem na lista de conversa. Visualizações " -"mostram as primeiras poucas linhas de cada mensagem." - -#: C/preferences.page:31(gui) -msgid "Use three pane view" -msgstr "Usar visão de três painéis" +"Para mover uma ou mais conversas para uma pasta ou rótulo, primeiro " +"selecione a conversa e, então, faça uma dentre as opções abaixo:" -#: C/preferences.page:32(p) +#. (itstool) path: item/p +#: C/label.page:29 msgid "" -"Show the folder list, the conversation list, and the messages side-by-side-" -"by-side in three panes. If not selected, the folder list and conversation " -"list will be stacked vertically in a single pane." +"Click the Move button on the toolbar and select a folder or label " +"from the resulting drop-down menu." msgstr "" -"Mostra a lista de pastas, a lista de conversa e as mensagens lado-a-lada-a-" -"lado em três painéis. Se não selecionado, a lista de pastas e a lista de " -"conversa serão empilhadas verticalmente em um único painel." - -#: C/preferences.page:40(title) -msgid "Composer" -msgstr "Compositor" - -#: C/preferences.page:43(gui) -msgid "Enable spell checking" -msgstr "Habilitar verificação ortográfica" +"Clique no botão Mover na barra de ferramentas e selecione uma " +"pasta ou um rótulo do menu suspenso resultante." -#: C/preferences.page:44(p) +#. (itstool) path: item/p +#: C/label.page:31 msgid "" -"When set, Geary automatically spell checks a message as you write it, " -"underlying each misspelled word in red." -msgstr "" -"Quando definido, Geary automaticamente verifica a ortografia de uma mensagem " -"na medida em que você a escreve, sublinhando de vermelho cada palavra " -"contendo erro de escrita." - -#: C/preferences.page:51(title) -msgid "Notifications" -msgstr "Notificações" - -#: C/preferences.page:54(gui) -msgid "Play notification sounds" -msgstr "Tocar sons de notificação" - -#: C/preferences.page:55(p) -msgid "When set, Geary plays a sound whenever a new message arrives." +"Drag the conversation(s) from the conversation list to the folder or label " +"in the sidebar." msgstr "" -"Quando definido, Geary toca um som sempre que uma nova mensagem chegar." +"Arraste uma conversa da lista de conversa para a pasta ou rótulo na barra " +"lateral." -#: C/preferences.page:58(gui) -msgid "Show notifications for new mail" -msgstr "Exibir notificações para novo e-mail" +#. (itstool) path: page/title +#: C/limits.page:9 +msgid "Limitations" +msgstr "Limitações" -#: C/preferences.page:59(p) +#. (itstool) path: page/p +#: C/limits.page:11 msgid "" -"When set, Geary displays a notification each time a new message " -"arrives. Notifications are displayed in a system-dependent manner. On GNOME " -"Shell, notifications appear at the bottom of the display (older versions) or " -"centered just below the top bar (newer versions). In Ubuntu Unity, " -"notifications appear at the upper right of the display." +"Geary is still in early development. Geary supports IMAP and has been tested " +"with Gmail, Yahoo, and the free Dovecot mail server. Experimental support " +"for Outlook.com is provided. Geary may not yet work well with some IMAP " +"servers. At this time Geary is still missing numerous features including " +"offline mode." msgstr "" -"Quando definido, Geary exibe uma notificação a cada vez que uma " -"nova mensagem chegar. Notificações são exibidas de acordo com o sistema. No " -"GNOME Shell, notificações aparecem no canto inferior da tela (versões " -"antigas) ou centralizada logo abaixo da barra superior (novas versões). No " -"Ubuntu Unity, notificações aparecem no canto direto superior da tela." - -#: C/preferences.page:65(gui) -msgid "Always watch for new mail" -msgstr "Sempre monitorar por novo e-mail" +"Geary ainda está em fase de desenvolvimento. Geary oferece suporte a IMAP e " +"foi testado com servidores de e-mail Gmail, Yahoo e o Dovecot livre. Suporte " +"experimental para Outlook.com é fornecido. Geary pode não funcionar bem " +"ainda com alguns servidores IMAP. Neste momento, o Geary ainda não possui " +"vários recursos, incluindo modo desconectado." -#: C/preferences.page:66(p) +#. (itstool) path: page/p +#: C/limits.page:17 msgid "" -"Geary will watch your accounts for new mail even when the main window is not " -"open. To do this, it will silently start when you log in to your computer, " -"and it will continue to run after you close the main window." +"To learn more about the features we're working on and the future of Geary, " +"please visit Geary's wiki " +"page." msgstr "" -"Geary vai monitorar suas contas por novo e-mail mesmo quando a janela " -"principal não estiver aberta. Para fazer isso, ele será iniciado " -"silenciosamente quando você iniciar sua sessão e continuará a executar após " -"você fechar a janela principal." +"Para saber mais sobre os recursos nos quais estamos trabalhando e o futuro " +"do Geary, por favor visite a página wiki do Geary." -#: C/overview.page:8(title) +#. (itstool) path: page/title +#: C/overview.page:8 msgid "Overview" msgstr "Visão geral" -#: C/overview.page:10(p) +#. (itstool) path: page/p +#: C/overview.page:10 msgid "" "Geary is a lightweight email reader for the GNOME desktop. It works with mail servers that support the IMAP " "protocol, including popular services such as Gmail, Yahoo Mail, and Outlook." "com." msgstr "" -"Geary é um leitor de e-mails leve para ambiente do GNOME. Ele funciona com servidores de e-mail que oferecem " "suporte ao protocolo IMAP, incluindo serviços populares como o Gmail, Yahoo " "Mail e Outlook.com." -#: C/overview.page:14(p) +#. (itstool) path: page/p +#: C/overview.page:14 msgid "" "Geary groups mail messages into conversations. A conversation " "contains all messages in a single thread of discussion." @@ -890,15 +514,18 @@ "Geary agrupa mensagens de e-mail em conversas. Uma conversa contém " "todas as mensagens em um único fluxo de discussão." -#: C/overview.page:17(p) +#. (itstool) path: page/p +#: C/overview.page:17 msgid "The main Geary window is divided into several areas:" msgstr "A janela principal do Geary é dividida em várias áreas:" -#: C/overview.page:20(title) +#. (itstool) path: section/title +#: C/overview.page:20 msgid "Folder list" msgstr "Lista de pastas" -#: C/overview.page:21(p) +#. (itstool) path: section/p +#: C/overview.page:21 msgid "" "The folder list at the left displays all folders and " "labels in your mail account. Geary uses the term label for " @@ -911,11 +538,13 @@ "mensagens. (a interface web do Gmail também usa esse termo, ao contrário da " "maioria de outros serviços de e-mail)" -#: C/overview.page:28(title) +#. (itstool) path: section/title +#: C/overview.page:28 msgid "Conversation list" msgstr "Lista de conversa" -#: C/overview.page:29(p) +#. (itstool) path: section/p +#: C/overview.page:29 msgid "" "The conversation list displays a list of conversations in the " "selected folder. Newer conversations appear at the top." @@ -923,7 +552,8 @@ "A lista de conversa exibe uma lista de conversas na pasta " "selecionada. Conversas mais novas aparecem no topo." -#: C/overview.page:31(p) +#. (itstool) path: section/p +#: C/overview.page:31 msgid "" "Each sender's name appears bold if there are unread messages from that " "sender. If a conversation has more than one message, Geary displays a count " @@ -933,7 +563,8 @@ "daquele remetente. Se uma conversa possui mais de uma mensagem, Geary exibe " "uma quantidade de mensagens na conversa." -#: C/overview.page:34(p) +#. (itstool) path: section/p +#: C/overview.page:34 msgid "" "Geary does not automatically download all messages in all of your mail " "folders. When you first visit your Inbox or any other folder, Geary " @@ -947,7 +578,8 @@ "mais mensagens, basta descer na lista de conversa e o Geary vai obter mais " "mensagens automaticamente." -#: C/overview.page:36(p) +#. (itstool) path: section/p +#: C/overview.page:36 msgid "" "Some commands in Geary can act on a group of conversations. To select " "multiple conversations, hold down the Ctrl key and click each " @@ -961,11 +593,13 @@ "conversa em um intervalo e, então, segure Shift e clique na " "última conversa." -#: C/overview.page:44(title) +#. (itstool) path: section/title +#: C/overview.page:44 msgid "Message area" msgstr "Área de mensagem" -#: C/overview.page:45(p) +#. (itstool) path: section/p +#: C/overview.page:45 msgid "" "The message area displays all messages in the selected " "conversation, with the oldest message at the top." @@ -973,7 +607,8 @@ "A área de mensagem exibe todas as mensagens na conversa " "selecionada, com a mensagem mais antiga no topo." -#: C/overview.page:47(p) +#. (itstool) path: section/p +#: C/overview.page:47 msgid "" "At the upper right of each message, Geary displays a dropdown arrow that " "lets you open the message menu with commands that operate on the " @@ -982,7 +617,8 @@ "No canto superior direito de cada mensagem, Geary exibe uma seta que permite " "abrir o menu de mensagem com comandos que operam na mensagem." -#: C/overview.page:49(p) +#. (itstool) path: section/p +#: C/overview.page:49 msgid "" "When you view a conversation, Geary collapses messages that you've already " "read. Click collapsed messages to expand them. Click an expanded message's " @@ -992,7 +628,8 @@ "nas mensagens recolhidas para expandi-las. Clique no cabeçalho das mensagens " "expandidas para recolhê-las." -#: C/overview.page:50(p) +#. (itstool) path: section/p +#: C/overview.page:50 msgid "" "Any attachments in a message appear at the bottom of the message. You can " "click an attachment to open it or right-click to save it." @@ -1001,7 +638,8 @@ "pode clicar em um anexo para abri-lo ou com clicar com botão direito do " "mouse para salvá-lo." -#: C/overview.page:52(p) +#. (itstool) path: section/p +#: C/overview.page:52 msgid "" "Geary uses Gravatar to " "display an avatar for each message's sender in its header." @@ -1009,351 +647,939 @@ "Geary usa Gravatar para " "exibir um avatar para cada remetente da mensagem em seu cabeçalho." -#: C/limits.page:11(title) -msgid "Limitations" -msgstr "Limitações" +#. (itstool) path: page/title +#: C/preferences.page:10 +msgid "Preferences" +msgstr "Preferências" -#: C/limits.page:12(p) +#. (itstool) path: page/p +#: C/preferences.page:11 msgid "" -"Geary is still in early development. Geary supports IMAP and has been tested " -"with Gmail, Yahoo, and the free Dovecot mail server. Experimental support " -"for Outlook.com is provided. Geary may not yet work well with some IMAP " -"servers. At this time Geary is still missing numerous features including " -"offline mode." +"The Preferences option is available in either Geary's application " +"menu or the gear menu in the upper-right of the toolbar. (The location " +"depends on the install desktop shell. For GNOME Shell and Unity, the " +"application menu is available near the top-left corner of the screen.)" msgstr "" -"Geary ainda está em fase de desenvolvimento. Geary oferece suporte a IMAP e " -"foi testado com servidores de e-mail Gmail, Yahoo e o Dovecot livre. Suporte " -"experimental para Outlook.com é fornecido. Geary pode não funcionar bem " -"ainda com alguns servidores IMAP. Neste momento, o Geary ainda não possui " -"vários recursos, incluindo modo desconectado." +"A opção Preferências está disponível em tanto o menu de " +"aplicativo do Geary quanto no menu de engrenagem no canto superior direito " +"na barra de ferramentas. (A localização depende do shell de ambiente " +"instalado. Para o GNOME Shell e Unity, o menu de aplicativo está disponível " +"próximo ao canto superior esquerdo da tela.)" + +#. (itstool) path: section/title +#: C/preferences.page:17 +msgid "Reading" +msgstr "Leitura" -#: C/limits.page:14(p) +#. (itstool) path: item/title +#: C/preferences.page:20 +msgid "Automatically select next message" +msgstr "Selecionar automaticamente a próxima mensagem" + +#. (itstool) path: item/p +#: C/preferences.page:21 msgid "" -"To learn more about the features we're working on and the future of Geary, " -"please visit Geary's wiki " -"page." +"When this option is enabled, Geary automatically selects the latest message " +"in a folder when you enter the folder. In addition, after archiving a " +"message, Geary automatically selects an adjacent message." msgstr "" -"Para saber mais sobre os recursos nos quais estamos trabalhando e o futuro " -"do Geary, por favor visite a página wiki do Geary." +"Quando esta opção estiver habilitada, Geary seleciona automaticamente a " +"mensagem mais recente em uma pasta quando você entrar nela. Além disso, após " +"arquivar uma mensagem, Geary seleciona automaticamente uma mensagem ajacente." -#: C/label.page:10(title) -msgid "Label or move a conversation" -msgstr "Rotulando ou movendo uma conversa" +#. (itstool) path: item/title +#: C/preferences.page:26 +msgid "Display conversation preview" +msgstr "Exibir visualização de conversa" -#: C/label.page:12(title) -msgid "Label a conversation" -msgstr "Rotulando uma conversa" - -#: C/label.page:13(p) +#. (itstool) path: item/p +#: C/preferences.page:27 msgid "" -"Geary lets you apply one or more labels to each conversation. Geary " -"labels correspond to labels in Gmail, or ordinary folders in other mail " -"services." +"Enables message previews in the conversation list. Previews show the first " +"few lines of each message." msgstr "" -"Geary permite que você aplique um ou mais rótulos para cada " -"conversa. Os rótulos do Geary correspondem aos rótulos do Gmail ou a pastas " -"comuns em outros serviços de e-mail." +"Habilita visualizações de mensagem na lista de conversa. Visualizações " +"mostram as primeiras poucas linhas de cada mensagem." -#: C/label.page:15(p) +#. (itstool) path: item/title +#: C/preferences.page:31 +msgid "Use three pane view" +msgstr "Usar visão de três painéis" + +#. (itstool) path: item/p +#: C/preferences.page:32 msgid "" -"To label one or more conversations, first select the conversation(s), then " -"do either of the following:" +"Show the folder list, the conversation list, and the messages side-by-side-" +"by-side in three panes. If not selected, the folder list and conversation " +"list will be stacked vertically in a single pane." msgstr "" -"Para rotular uma ou mais conversas, primeiro selecione a conversa e, então, " -"faça uma dentre as opções abaixo:" +"Mostra a lista de pastas, a lista de conversa e as mensagens lado-a-lada-a-" +"lado em três painéis. Se não selecionado, a lista de pastas e a lista de " +"conversa serão empilhadas verticalmente em um único painel." + +#. (itstool) path: section/title +#: C/preferences.page:40 +msgid "Notifications" +msgstr "Notificações" + +#. (itstool) path: item/title +#: C/preferences.page:43 +msgid "Play notification sounds" +msgstr "Tocar sons de notificação" + +#. (itstool) path: item/p +#: C/preferences.page:44 +msgid "When set, Geary plays a sound whenever a new message arrives." +msgstr "" +"Quando definido, Geary toca um som sempre que uma nova mensagem chegar." -#: C/label.page:18(p) +#. (itstool) path: item/title +#: C/preferences.page:47 +msgid "Show notifications for new mail" +msgstr "Exibir notificações para novo e-mail" + +#. (itstool) path: item/p +#: C/preferences.page:48 msgid "" -"Click the Label button on the toolbar and select a label from the " -"resulting drop-down menu." +"When set, Geary displays a notification each time a new message " +"arrives. Notifications are displayed in a system-dependent manner. On GNOME " +"Shell, notifications appear at the bottom of the display (older versions) or " +"centered just below the top bar (newer versions). In Ubuntu Unity, " +"notifications appear at the upper right of the display." msgstr "" -"Clique no botão Rótulo na barra de ferramentas e selecione um " -"rótulo do menu suspenso resultante." +"Quando definido, Geary exibe uma notificação a cada vez que uma " +"nova mensagem chegar. Notificações são exibidas de acordo com o sistema. No " +"GNOME Shell, notificações aparecem no canto inferior da tela (versões " +"antigas) ou centralizada logo abaixo da barra superior (novas versões). No " +"Ubuntu Unity, notificações aparecem no canto direto superior da tela." + +#. (itstool) path: item/title +#: C/preferences.page:54 +msgid "Watch for new mail when closed" +msgstr "Monitorar por um novo e-mail quando fechado" -#: C/label.page:20(p) +#. (itstool) path: item/p +#: C/preferences.page:55 msgid "" -"Hold down the Ctrl key and drag the conversation(s) from the " -"conversation list to the label in the sidebar." +"Geary will watch your accounts for new mail even when the main window is not " +"open. To do this, it will silently start when you log in to your computer, " +"and it will continue to run after you close all windows." msgstr "" -"Segure a tecla Ctrl e arraste a conversa da lista de conversa " -"para o rótulo na barra lateral." +"Geary vai monitorar suas contas por novo e-mail mesmo quando a janela " +"principal não estiver aberta. Para fazer isso, ele será iniciado " +"silenciosamente quando você iniciar sua sessão e continuará a executar após " +"você fechar todas as janelas." -#: C/label.page:25(title) -msgid "Move a conversation to a folder or label" -msgstr "Movendo uma conversa para uma pasta ou um rótulo" +#. (itstool) path: page/title +#: C/search.page:10 +msgid "Search" +msgstr "Pesquisando" -#: C/label.page:26(p) +#. (itstool) path: page/p +#: C/search.page:12 msgid "" -"To move one or more conversations to a folder or label, first select the " -"conversation(s), then do either of the following:" +"Geary supports a per-account full text search. To start a search, select a " +"folder associated with the account you'd like to search against. Then click " +"the search box in the toolbar (or press CtrlS) and start typing. Results will appear after a brief delay." msgstr "" -"Para mover uma ou mais conversas para uma pasta ou rótulo, primeiro " -"selecione a conversa e, então, faça uma dentre as opções abaixo:" +"Geary oferece suporte a pesquisa completa de texto por conta. Para iniciar " +"uma pesquisa, selecione uma pasta associada com a conta na qual você " +"gostaria de pesquisar. Então, clique na caixa de pesquisa na barra de " +"ferramentas (ou pressioneCtrlS) e " +"comece a digitar. Resultados aparecerão após uma breve atraso." -#: C/label.page:29(p) +#. (itstool) path: page/p +#: C/search.page:16 msgid "" -"Click the Move button on the toolbar and select a folder or label " -"from the resulting drop-down menu." +"The full text search includes email text, email addresses (to, from, and " +"cc), subject lines and attachment filenames." msgstr "" -"Clique no botão Mover na barra de ferramentas e selecione uma " -"pasta ou um rótulo do menu suspenso resultante." +"A pesquisa completa de texto inclui texto de e-mail, endereços de e-mail " +"(para, de e cc), linhas de assunto e nomes de arquivos de anexos." -#: C/label.page:31(p) +#. (itstool) path: page/p +#: C/search.page:19 msgid "" -"Drag the conversation(s) from the conversation list to the folder or label " -"in the sidebar." +"Keywords that match your search are highlighted in the message view. Geary " +"will match different forms of the same word, for example searching for \"walk" +"\" will also match \"walking\" and \"walked.\"" msgstr "" -"Arraste uma conversa da lista de conversa para a pasta ou rótulo na barra " -"lateral." +"Palavras-chave que correspondem à sua pesquisa são destacadas na visão da " +"mensagem. Geary vai corresponder formas diferentes da mesma mensagem; por " +"exemplo, pesquisar por “caminha” também retornará “caminhando” e “caminhada”." -#. When image changes, this message will be marked fuzzy or untranslated for you. -#. It doesn't matter what you translate it to: it's not used at all. -#: C/index.page:5(None) -msgid "@@image: 'figures/geary.svg'; md5=18b50c9e10fe5256ae1cb12aaa3a7600" -msgstr "@@image: 'figures/geary.svg'; md5=18b50c9e10fe5256ae1cb12aaa3a7600" +#. (itstool) path: section/title +#: C/search.page:23 +msgid "Search operators" +msgstr "Operadores de pesquisa" -#: C/index.page:5(title) -msgid " Geary" -msgstr " Geary" +#. (itstool) path: section/p +#: C/search.page:24 +msgid "Geary supports the following operators to limit the scope of searches:" +msgstr "" +"Geary oferece suporte aos seguintes operadores para limitar o escopo das " +"pesquisa:" -#: C/index.page:9(title) -msgid "Introduction" -msgstr "Introdução" +#. (itstool) path: td/p +#: C/search.page:27 +msgid "attachment:filename" +msgstr "attachment:nome-de-arquivo" -#: C/index.page:13(title) -msgid "Using Geary" -msgstr "Usando o Geary" +#. (itstool) path: td/p +#: C/search.page:28 +msgid "Finds messages with attachments whose name matches filename." +msgstr "" +"Localiza mensagens com anexos cujo nome corresponda a nome-de-arquivo." + +#. (itstool) path: td/p +#: C/search.page:31 +msgid "bcc:recipient" +msgstr "bcc:destinatário" + +#. (itstool) path: td/p +#: C/search.page:32 +msgid "Finds messages where recipient matches the BCC header." +msgstr "" +"Localiza mensagens cujo destinatário corresponda ao cabeçalho CCO." -#: C/index.page:17(title) -msgid "Bugs" -msgstr "Erros" +#. (itstool) path: td/p +#: C/search.page:35 +msgid "body:text" +msgstr "body:texto" -#: C/bugs.page:8(title) -msgid "Think you've found a bug?" -msgstr "Você acha que encontrou um erro?" +#. (itstool) path: td/p +#: C/search.page:36 +msgid "Finds messages whose body contains text." +msgstr "Localiza mensagens cujo corpo contém texto." + +#. (itstool) path: td/p +#: C/search.page:39 +msgid "cc:recipient" +msgstr "cc:destinatário" + +#. (itstool) path: td/p +#: C/search.page:40 +msgid "Finds messages where recipient matches the CC header." +msgstr "" +"Localiza mensagens cujo destinatário corresponda ao cabeçalho CC." + +#. (itstool) path: td/p +#: C/search.page:43 +msgid "from:sender" +msgstr "from:remetente" + +#. (itstool) path: td/p +#: C/search.page:44 +msgid "Finds messages where sender matches the From header." +msgstr "" +"Localiza mensagens cujo remetente corresponda ao cabeçalho De." + +#. (itstool) path: td/p +#: C/search.page:47 +msgid "is:read" +msgstr "is:lida" + +#. (itstool) path: td/p +#: C/search.page:48 +msgid "Finds messages that have been marked as read." +msgstr "Localiza mensagens que foram marcadas como lidas." -#: C/bugs.page:9(p) +#. (itstool) path: td/p +#: C/search.page:51 +msgid "is:starred" +msgstr "is:com-estrela" + +#. (itstool) path: td/p +#: C/search.page:52 +msgid "Finds messages that have been marked as starred." +msgstr "Localiza mensagens que foram marcadas com estrela." + +#. (itstool) path: td/p +#: C/search.page:55 +msgid "is:unread" +msgstr "is:não-lida" + +#. (itstool) path: td/p +#: C/search.page:56 +msgid "Finds messages that have been marked as not read." +msgstr "Localiza mensagens que foram marcadas como não lidas." + +#. (itstool) path: td/p +#: C/search.page:59 +msgid "subject:text" +msgstr "subject:texto" + +#. (itstool) path: td/p +#: C/search.page:60 +msgid "Finds messages whose subject contains text." +msgstr "Localiza mensagens cujo assunto contém texto." + +#. (itstool) path: td/p +#: C/search.page:63 +msgid "to:recipient" +msgstr "to:destinatário" + +#. (itstool) path: td/p +#: C/search.page:64 msgid "" -"If you suspect you've found a bug in Geary, follow these steps to report it:" +"Finds messages where recipient matches the To, CC, or BCC header." msgstr "" -"Se você suspeita ter encontrado um erro no Geary, por favor siga os passos " -"para relatá-lo:" +"Localiza mensagens cujo destinatário corresponda ao cabeçalho " +"Para, CC ou CCO." -#: C/bugs.page:11(p) +#. (itstool) path: section/p +#: C/search.page:68 msgid "" -"Search Geary's bug database to see if someone else has reported the " -"bug." +"As a special case, the bcc, cc, from, and to operators support me as their " +"argument, which searches for the account's email address in the appropriate " +"context." msgstr "" -"Pesquise o banco de dados de erros do Geary para ver se alguém já relatou o " -"error." +"Como um caso especial, os operadores cco, cc, " +"de e para oferecem suporte a me " +"como seu argumento, o qual pesquisa pelo endereço de e-mail da conta no " +"contexto apropriado." + +#. (itstool) path: page/title +#: C/shortcuts.page:10 +msgid "Keyboard shortcuts" +msgstr "Atalhos de teclado" + +#. (itstool) path: page/p +#: C/shortcuts.page:12 +msgid "" +"Geary has keyboard shortcuts for most common operations. Use the built-in " +"keyboard shortcuts help in Geary to discover the full list. This can be " +"accessed via the application menu: GearyKeyboard " +"Shortcuts or using the keyboard shortcuts listed below." +msgstr "" +"A Geary possui atalhos de teclado para as operações mais comuns. Use os " +"atalhos de teclado integrados em Geary para descobrir a lista completa. Isso " +"pode ser acessado através do menu do aplicativo: GearyAtalhos de teclado ou usando os atalhos de teclado " +"listados abaixo." + +#. (itstool) path: page/p +#: C/shortcuts.page:18 +msgid "" +"The following keyboard shortcuts can be used to access on-line help from " +"Geary:" +msgstr "" +"Os seguintes atalhos de teclado podem ser usados para acessar a ajuda on-" +"line da Geary:" + +#. (itstool) path: td/p +#: C/shortcuts.page:22 +msgid "Display this User Manual" +msgstr "Exibindo essa manual de usuário" + +#. (itstool) path: td/p +#: C/shortcuts.page:23 +msgid "F1" +msgstr "F1" + +#. (itstool) path: td/p +#: C/shortcuts.page:26 +msgid "Display all keyboard shortcuts" +msgstr "Exibindo todos os atalhos de teclado" + +#. (itstool) path: td/p +#: C/shortcuts.page:27 +msgid "" +"Ctrl? or CtrlF1" +msgstr "" +"Ctrl? ou CtrlF1" + +#. (itstool) path: page/title +#: C/star.page:10 +msgid "Star a message or mark it as read/unread" +msgstr "Marcando estrela uma mensagem ou marcando como lida/não lida" -#: C/bugs.page:13(p) +#. (itstool) path: section/title +#: C/star.page:12 +msgid "Star messages" +msgstr "Marcando estrela nas mensagens" + +#. (itstool) path: section/p +#: C/star.page:13 msgid "" -"Don't see your bug listed? Congratulations! You've found a new bug. To " -"create an bug report, create an account on GNOME's Bugzilla and file a new bug. Be as specific as you can and describe the steps to reproduce it. " -"Don't forget to include details about your operating system and what version " -"of Geary you're running." +"You can star messages to indicate that they're important to you. To mark a " +"conversation with a star, click its star icon in the conversation list. You " +"can star an individual message by clicking the star at the upper right of " +"the message itself." msgstr "" -"Não encontrou o erro listado? Meus parabéns! Você encontrou um novo erro. " -"Para criar um relatório de erro, crie uma conta no Bugzilla do GNOME e preencha um " -"relatório de erro. Seja o mais especifico possível e descreva os " -"passos para reproduzi-lo. Não se esqueça de incluir detalhes sobre seu " -"sistema operacional e qual versão do Geary você está usando." +"Você pode marcar estrela nas mensagens para indicar que elas são importantes " +"para você. Para isto, clique em seu ícone de estrela na lista de conversa. " +"Você pode marcar com estrela uma mensagem individual clicando na estrela no " +"canto direito superior da própria mensagem." -#: C/bugs.page:18(p) +#. (itstool) path: section/p +#: C/star.page:15 msgid "" -"For general inquiries, please join the Geary mailing list." +"With Gmail accounts, starred messages appear in the Starred folder in the " +"folder list." msgstr "" -"Para questões genéricas, por favor entre na lista de discussão do Geary." +"Com contas de Gmail, mensagens marcadas com estrela aparecem na pasta " +"Importante na lista de pastas." -#: C/archive.page:10(title) -msgid "Delete or archive a message" -msgstr "Excluindo ou arquivando uma mensagem" +#. (itstool) path: section/title +#: C/star.page:18 +msgid "Mark messages as read or unread" +msgstr "Marcando mensagens como lidas ou não lidas" -#: C/archive.page:12(p) +#. (itstool) path: section/p +#: C/star.page:19 msgid "" -"When you use Geary with a Gmail account, Geary lets you archive " -"messages. The Archive toolbar button archives the selected " -"conversation(s). Archived messages appear in the All Mail folder." +"Geary marks messages as read automatically as you read them. To manually " +"toggle a conversation as read or unread, click the circle icon in the " +"conversation list." msgstr "" -"Quando você usa o Geary com uma conta do Gmail, Geary permite arquivar mensagens. O botão Arquivar da barra de ferramentas arquiva a " -"conversa selecionada. Mensagens arquivadas aparecem na pasta Todos os e-" -"mails." +"Geary marca mensagens como lidas automaticamente na medida em que você as " +"lê. Para alternar manualmente uma conversa como lida ou não lida, clique no " +"ícone de círculo na lista de conversa." -#: C/archive.page:16(p) +#. (itstool) path: section/p +#: C/star.page:22 msgid "" -"With other mail servers, you can trash or delete, but not archive, messages. " -"To move one or more conversations to the Trash folder, select " -"them and press the Trash button on the toolbar. To permanently " -"delete the conversations, hold down Shift and press the " -"Delete button that appears in place of the Trash " -"button." +"Alternately, the Mark as Unread in the Mark menu on " +"the toolbar can be used to toggle the read status of the selected " +"conversation(s)." msgstr "" -"Com outros servidores de e-mail, você pode mover para a lixeira ou excluir, " -"mas não arquivar, mensagens. Para mover uma ou mais conversas para a pasta " -"Lixeira, selecione elas e pressione o botão Lixeira na " -"barra de ferramentas. Para excluir permanentemente as conversas, segure " -"Shift e pressione o botão Excluir que aparecerá no " -"lugar do botão Lixeira." +"Alternativamente, Marcar como não lida no menu Marcar " +"na barra de ferramentas pode ser usado para alternar o status de leitura das " +"conversas selecionadas." -#: C/archive.page:21(p) +#. (itstool) path: section/p +#: C/star.page:25 msgid "" -"Delete is not available from every folder, such as Search. Delete is also " -"unavailable for Gmail. For Gmail, Trash will move messages to the " -"Trash folder on the server, where the user can then manually delete them. " -"The server will automatically remove trashed messages after 30 days." +"To mark an individual message as read, select Mark as Read from " +"the dropdown menu." msgstr "" -"Excluir não está disponível em toda pasta, como em Pesquisar. Excluir também " -"não está disponível para Gmail. Para o Gmail, Lixeira vai mover " -"mensagens para a pasta Lixeira do servidor, onde o usuário pode manualmente " -"exclui-las. O servidor vai remover automaticamente mensagens da lixeira após " -"30 dias." +"Para marcar uma mensagem individual como lida, selecione Marcar como " +"lida a partir do menu suspenso." -#: C/accounts.page:10(title) -msgid "Accounts" -msgstr "Contas" +#. (itstool) path: page/title +#: C/write.page:9 +msgid "Write a message" +msgstr "Escrevendo uma mensagem" -#: C/accounts.page:13(title) -msgid "Adding accounts" -msgstr "Adicionando contas" +#. (itstool) path: section/title +#: C/write.page:12 +msgid "Composing and replying" +msgstr "Composição e resposta" -#: C/accounts.page:15(p) +#. (itstool) path: section/p +#: C/write.page:13 msgid "" -"The first time you start Geary, you will be prompted to add an email " -"account. On this screen, select if your account is Gmail, Yahoo, Outlook." -"com, or other. For other account types, you will need to enter your IMAP and " -"SMTP login settings manually." +"To compose a new message in Geary, press the New Message button " +"on the toolbar." msgstr "" -"A primeira vez que você inicia o Geary, será solicitado adicionar uma conta " -"de e-mail. Nesta tela, selecione se sua conta é Gmail, Yahoo, Outlook.com ou " -"outra. Para outros tipos de conta, você precisará informar as configurações " -"de credencial IMAP e SMTP." +"Para compor uma nova mensagem no Geary, pressione o botão Nova " +"mensagem na barra de ferramentas." -#: C/accounts.page:19(p) +#. (itstool) path: section/p +#: C/write.page:16 msgid "" -"Additional accounts can be added from the Accounts dialog. The " -"Accounts option is available in either Geary's application menu " -"or the gear menu in the upper-right of the toolbar. (The location depends on " -"the install desktop shell. For GNOME Shell and Unity, the application menu " -"is available near the top-left corner of the screen.) Alternately, " -"CtrlM will open the Accounts dialog. " -"To add an account, click the + button." +"To reply to a message, open the message menu in the upper right corner of " +"the message and choose Reply, Reply All or " +"Forward. You can also reply to the last message in a conversation " +"via the Reply, Reply All or Forward buttons " +"on the toolbar." msgstr "" -"Contas adicionais podem ser adicionadas a partir do diálogo de Contas. A " -"opção Contas está disponível tanto no menu de aplicativo do Geary " -"quanto no menu de engrenagens no canto direito superior da barra de " -"ferramentas. (A localização depende do shell de ambiente instalado. Para o " -"GNOME Shell e Unity, o menu de aplicativo está disponível próximo ao canto " -"superior esquerdo da tela.) Alternativamente, CtrlM vai abrir o diálogo de Contas. Para adicionar uma conta, " -"clique no botão +." +"Para responder a uma mensagem, abra o menu de mensagem no canto direito " +"superior da mensagem e escolha Responder, Responder todos ou Encaminhar. Você também pode responder à última mensagem " +"em uma conversa via os botões Responder, Responder todos ou Encaminhar na barra de ferramentas." -#: C/accounts.page:28(title) -msgid "Editing existing accounts" -msgstr "Editando contas existente" +#. (itstool) path: section/title +#: C/write.page:21 +msgid "Features" +msgstr "Recursos" -#: C/accounts.page:30(p) +#. (itstool) path: section/p +#: C/write.page:23 msgid "" -"From the Accounts dialog, select an account and click the pencil icon to " -"change various settings. Please note that Geary cannot change server " -"settings on an existing account. If you need to change your IMAP or SMTP " -"server, you will need to delete the account and re-add it." +"Geary's email composer lets you adjust the font, size and color of text. You " +"can also insert hyperlinks into messages." msgstr "" -"A partir do diálogo de Contas, selecione uma conta ou clique no ícone de " -"lápis para alterar uma gama de configurações. Por favor, note que o Geary " -"não pode alterar as configurações de servidor em uma conta existente. Se " -"você precisar alterar seu servidor IMAP ou SMTP, será necessário excluir a " -"conta e re-adicioná-la." +"O compositor de e-mail do Geary permite que você ajuste a fonte, tamanho e " +"cor do texto. Você também pode inserir hyperlinks nas mensagens." -#: C/accounts.page:34(p) +#. (itstool) path: section/p +#: C/write.page:25 msgid "" -"To change the order that accounts are displayed in the folder list, drag the " -"accounts in the Accounts dialog to the desired order." +"Geary can also send plain text messages. In the drop-down menu, check or " +"uncheck \"Rich Text\" to toggle between plain text and rich text mode." msgstr "" -"Para alterar a ordem que as contas são exibidas na lista de pasta, arraste " -"as contas no diálogo de Contas para a ordem desejada." +"Geary também pode enviar mensagens de texto simples. No menu suspenso, " +"marque ou desmarque “Texto rico” para alternar entre modos texto simples ou " +"texto rico." -#: C/accounts.page:37(p) -msgid "There are some advanced options available when editing accounts:" -msgstr "Há algumas opções avançadas disponíveis na edição de contas:" +#. (itstool) path: section/p +#: C/write.page:28 +msgid "" +"You can attach a file to a message you're writing in either of these ways:" +msgstr "" +"Você pode anexar um arquivo a uma mensagem que você está escrever de duas " +"formas:" -#: C/accounts.page:39(p) +#. (itstool) path: item/p +#: C/write.page:30 msgid "" -"The Save sent mail checkbox controls whether Geary will push " -"successfully sent messages up to the account's Sent Mail folder. " -"For Gmail accounts, this happens automatically. Yahoo and some other " -"accounts can be configured to do this automatically as well. For other " -"accounts, if you disable this setting, you may be unable to view messages " -"you've sent." +"Press the Attach File button at the lower left of the composer " +"window, then select a file to attach." msgstr "" -"A caixa de seleção Salvar e-mails enviados controla se o Geary " -"vai enviar mensagens enviadas para a pasta E-mails enviados da " -"conta. Para contas do Gmail, isso acontece automaticamente. Yahoo e outras " -"contas pode ser configuradas para fazer isso automaticamente também. Para " -"outras contas, se você desabilitar essa configuração. você pode não " -"conseguir ver mensagens que você enviou." +"Pressione o botão Anexar arquivo na esquerda inferior da janela " +"do compositor e, então, selecione um arquivo para anexar." -#: C/accounts.page:45(p) +#. (itstool) path: item/p +#: C/write.page:32 msgid "" -"The Sign emails checkbox indicates whether a signature will be " -"automatically inserted when a composer is opened. You may enter the " -"signature into the box immediately below. You may use HTML tags to style the " -"text. Switch to a preview of the signature using the buttons to the right." +"Drag the file from the Nautilus file manager to the composer window, and " +"drop it either on the text fields at the top of the window or on the toolbar " +"at the bottom." msgstr "" -"A caixa de seleção Assinar e-mails indica se uma assinatura será " -"inserida automaticamente quando o compositor for aberto. Você pode inserir a " -"assinatura em uma caixa imediatamente abaixo. Você pode usar tags HTML para " -"estilizar o texto. Mude para a visualização da assinatura usando os botões à " -"direita." +"Arraste o arquivo do gerenciador de arquivos para a janela do compositor e, " +"então, solte-o nos campos de texto no topo da janela ou na barra de " +"ferramentas em baixo." -#: C/accounts.page:50(p) +#. (itstool) path: section/p +#: C/write.page:36 msgid "" -"If you leave the signature in the Accounts dialog blank, Geary will use the " -".signature file in your home directory, if it exists. This file " -"may contain either plain text or HTML markup. In the latter case, the markup " -"will be inserted directly into the composer, without any escaping." +"A number of keyboard shortcuts are available in the composer; see for details." msgstr "" -"Se você deixar a assinatura em branco no diálogo de Contas, Geary vai usar o " -"arquivo .signature em seu diretório Home, se ele existir. Esse " -"arquivo pode conter um texto simples ou marcação HTML. No último caso, a " -"marcação será inserida diretamente no compositor, sem qualquer escape." +"Uma quantidade de teclas de atalho estão disponíveis no compositor; veja " +" para detalhes." -#: C/accounts.page:55(p) +#. (itstool) path: section/p +#: C/write.page:38 msgid "" -"The Download mail drop-down allows you to configure how much mail " -"Geary will keep locally. Geary can only use locally available mail when " -"searching and forming conversations." +"You may specify a signature to be inserted into the composer in the dialog." msgstr "" -"A menu suspenso de Baixar e-mail permite que você configure " -"quantos e-mails o Geary manterá localmente. Geary só pode usar e-mails " -"disponíveis localmente ao pesquisar e formar conversas." +"Você pode especificar uma assinatura a ser inserida no compositor no diálogo " +"." -#: C/accounts.page:63(title) -msgid "Removing accounts" -msgstr "Removendo contas" +#. (itstool) path: section/title +#: C/write.page:43 +msgid "Drafts" +msgstr "Rascunhos" -#: C/accounts.page:65(p) +#. (itstool) path: section/p +#: C/write.page:45 msgid "" -"To delete an account, open the Accounts dialog, select the account, and " -"press the - button. Geary will delete all information associated with the " -"account." +"For mail servers that support drafts, Geary will automatically save the " +"message as you type. If you close the composer without sending, Geary will " +"prompt you to keep the draft or to discard it." msgstr "" -"Para excluir uma conta, abra o diálogo de Contas, selecione a conta e " -"pressione o botão -. Geary vai excluir toda informação associada com a conta." +"Para servidores de e-mails que oferecem suporte a rascunhos, Geary vai " +"salvar automaticamente a mensagem que você digitar. Se você fechar o " +"compositor sem enviar, Geary vai perguntar se deseja manter o rascunho ou " +"descartá-lo." -#. Put one translator per line, in the form of NAME , YEAR1, YEAR2 -#: C/accounts.page:0(None) -msgid "translator-credits" -msgstr "Rafael Fontenelle , 2016, 2017" +#. (itstool) path: section/p +#: C/write.page:48 +msgid "" +"To edit an existing draft, select the Drafts folder in the folder list, " +"select the message, and click \"Edit Draft\" in the message viewer." +msgstr "" +"Para editar um rascunho existente, selecione a pasta Rascunhos na lista de " +"pastas, selecione a mensagem e, então, clique em “Editar rascunho” no " +"visualizador de mensagem." + +#. (itstool) path: section/p +#: C/write.page:51 +msgid "Geary deletes the draft when you send the message." +msgstr "Geary exclui o rascunho quando você envia a mensagem." + +#~ msgid "Geary has keyboard shortcuts for most common operations." +#~ msgstr "Geary possui atalhos de teclado para a maioria das operações." + +#~ msgid "Compose a new message" +#~ msgstr "Compor uma nova mensagem" + +#~ msgid "CtrlN or N" +#~ msgstr "CtrlN ou N" + +#~ msgid "Reply to sender" +#~ msgstr "Responder ao remetente" + +#~ msgid "CtrlR or R" +#~ msgstr "CtrlR ou R" + +#~ msgid "Reply to all" +#~ msgstr "Responder a todos" + +#~ msgid "" +#~ "CtrlShiftR or " +#~ "ShiftR" +#~ msgstr "" +#~ "CtrlShiftR ou " +#~ "ShiftR" + +#~ msgid "Forward" +#~ msgstr "Encaminhar" + +#~ msgid "Archive" +#~ msgstr "Arquivar" + +#~ msgid "A" +#~ msgstr "A" + +#~ msgid "Trash" +#~ msgstr "Lixeira" + +#~ msgid "Delete or Backspace" +#~ msgstr "Delete ou Backspace" + +#~ msgid "Delete" +#~ msgstr "Excluir" + +#~ msgid "" +#~ "ShiftDelete or ShiftBackspace" +#~ msgstr "" +#~ "ShiftDelete ou ShiftBackspace" + +#~ msgid "Star" +#~ msgstr "Marcar estrela" + +#~ msgid "S" +#~ msgstr "S" + +#~ msgid "Unstar" +#~ msgstr "Desmarcar estrela" + +#~ msgid "D" +#~ msgstr "D" + +#~ msgid "Mark read" +#~ msgstr "Marcar como lida" + +#~ msgid "" +#~ "CtrlI or ShiftI" +#~ msgstr "" +#~ "CtrlI ou ShiftI" + +#~ msgid "Mark unread" +#~ msgstr "Marcar como não lida" + +#~ msgid "" +#~ "CtrlU or ShiftU" +#~ msgstr "" +#~ "CtrlU ou ShiftU" + +#~ msgid "Move the conversation" +#~ msgstr "Mover a conversação" + +#~ msgid "M" +#~ msgstr "M" + +#~ msgid "Label the conversation" +#~ msgstr "Rotular a conversa" + +#~ msgid "L" +#~ msgstr "L" + +#~ msgid "Jump to next (older) conversation" +#~ msgstr "Saltar para a próxima (mais antiga) conversa" + +#~ msgid "J" +#~ msgstr "J" + +#~ msgid "Jump to previous (newer) conversation" +#~ msgstr "Saltar para a conversa anterior (mais nova)" + +#~ msgid "K" +#~ msgstr "K" + +#~ msgid "Toggle spam" +#~ msgstr "Ativar spam" + +#~ msgid "CtrlJ or !" +#~ msgstr "CtrlJ ou !" + +#~ msgid "Quit" +#~ msgstr "Sair" + +#~ msgid "Ctrl" +#~ msgstr "Ctrl" + +#~ msgid "Q" +#~ msgstr "Q" + +#~ msgid "Zoom in" +#~ msgstr "Aumentar o zoom" + +#~ msgid "Ctrl= or =" +#~ msgstr "Ctrl= ou =" + +#~ msgid "Zoom out" +#~ msgstr "Diminuir o zoom" + +#~ msgid "Ctrl- or -" +#~ msgstr "Ctrl- ou -" + +#~ msgid "Reset zoom" +#~ msgstr "Redefinir o zoom" + +#~ msgid "Ctrl0 or 0" +#~ msgstr "Ctrl0 ou 0" + +#~ msgid "Close composer window" +#~ msgstr "Fechar a janela do compositor" + +#~ msgid "W" +#~ msgstr "W" + +#~ msgid "Jump to search box" +#~ msgstr "Saltar para a caixa de pesquisa" + +#~ msgid "Find in current conversation" +#~ msgstr "Localizar na conversa atual" + +#~ msgid "F" +#~ msgstr "F" + +#~ msgid "Find next in current conversation" +#~ msgstr "Localizar próximo na conversa atual" + +#~ msgid "G" +#~ msgstr "G" + +#~ msgid "Find previous in current conversation" +#~ msgstr "Localizar anterior na conversa atual" + +#~ msgid "Shift" +#~ msgstr "Shift" + +#~ msgid "Composer shortcuts" +#~ msgstr "Atalhos do compositor" + +#~ msgid "These shortcuts are active whenever focus is in a composer." +#~ msgstr "Esses atalhos estão ativos sem que o foco estiver em um compositor." + +#~ msgid "Attach file" +#~ msgstr "Anexar arquivo" + +#~ msgid "T" +#~ msgstr "T" + +#~ msgid "Quote text" +#~ msgstr "Texto citado" + +#~ msgid "]" +#~ msgstr "]" + +#~ msgid "Unquote text" +#~ msgstr "Texto não-citado" + +#~ msgid "[" +#~ msgstr "[" + +#~ msgid "Close composer" +#~ msgstr "Fechar o compositor" + +#~ msgid "CtrlW or Esc" +#~ msgstr "CtrlW ou Esc" + +#~ msgid "Detach composer" +#~ msgstr "Desanexar o compositor" + +#~ msgid "These shortcuts are only active in composers in rich text mode." +#~ msgstr "" +#~ "Esses atalhos estão ativos apenas em compositores em modo texto rico." + +#~ msgid "Bold text" +#~ msgstr "Texto em negrito" + +#~ msgid "B" +#~ msgstr "B" + +#~ msgid "Italicize text" +#~ msgstr "Texto em itálico" + +#~ msgid "I" +#~ msgstr "I" + +#~ msgid "Underline text" +#~ msgstr "Sublinhar o texto" + +#~ msgid "U" +#~ msgstr "U" + +#~ msgid "Strike text" +#~ msgstr "Riscar o texto" + +#~ msgid "Insert a link" +#~ msgstr "Inserir um link" + +#~ msgid "Remove formatting" +#~ msgstr "Remover formatação" + +#~ msgid "Space" +#~ msgstr "Espaço" + +#~ msgid "Keyboard navigation" +#~ msgstr "Navegação pelo teclado" + +#~| msgid "These shortcuts are active whenever focus is in a composer." +#~ msgid "" +#~ "These shortcuts can be used to move the keyboard focus in the main window." +#~ msgstr "" +#~ "Esses atalhos pode ser usados para mover o foco do teclado na janela " +#~ "principal." + +#~ msgid "Move focus to the next/previous pane" +#~ msgstr "Mover o foco ao próximo painel ou ao anterior" + +#~ msgid "" +#~ "F6 / ShiftF6" +#~ msgstr "" +#~ "F6 / ShiftF6" + +#~ msgid "Move focus to conversation list" +#~ msgstr "Mover foco para a lista de conversa" + +#~| msgid "Move the conversation" +#~ msgid "Move to the next message in a conversation" +#~ msgstr "Mover para a próxima mensagem em uma conversa" + +#~| msgid "Move focus to the next/previous pane" +#~ msgid "Move to the next/previous message in a conversation" +#~ msgstr "Mover para a mensagem seguinte/anterior em uma conversa" + +#~| msgid "Move the conversation" +#~ msgid "Move to the first/last message in a conversation" +#~ msgstr "Mover para a primeira/última mensagem em uma conversa" + +#~| msgid "" +#~| "CtrlI or ShiftI" +#~ msgid "" +#~ "CtrlHome / CtrlEnd" +#~ msgstr "" +#~ "CtrlHome / CtrlEnd" + +#~ msgid "filename" +#~ msgstr "nome de arquivo" + +#~ msgid "attachment:" +#~ msgstr "anexo:" + +#~ msgid "recipient" +#~ msgstr "destinatário" + +#~ msgid "bcc:" +#~ msgstr "cco:" + +#~ msgid "text" +#~ msgstr "texto" + +#~ msgid "body:" +#~ msgstr "corpo:" + +#~ msgid "cc:" +#~ msgstr "cc:" + +#~ msgid "sender" +#~ msgstr "remetente" + +#~ msgid "from:" +#~ msgstr "de:" + +#~ msgid "is:read" +#~ msgstr "está:lido" + +#~ msgid "is:starred" +#~ msgstr "está:com-estrela" + +#~ msgid "is:unread" +#~ msgstr "está:não-lido" + +#~ msgid "subject:" +#~ msgstr "assunto:" + +#~ msgid "to:" +#~ msgstr "para:" + +#~ msgid "Composer" +#~ msgstr "Compositor" + +#~ msgid "Enable spell checking" +#~ msgstr "Habilitar verificação ortográfica" + +#~ msgid "" +#~ "When set, Geary automatically spell checks a message as you write it, " +#~ "underlying each misspelled word in red." +#~ msgstr "" +#~ "Quando definido, Geary automaticamente verifica a ortografia de uma " +#~ "mensagem na medida em que você a escreve, sublinhando de vermelho cada " +#~ "palavra contendo erro de escrita." + +#~ msgid "Always watch for new mail" +#~ msgstr "Sempre monitorar por novo e-mail" + +#~ msgid "Bugs" +#~ msgstr "Erros" + +#~ msgid "Think you've found a bug?" +#~ msgstr "Você acha que encontrou um erro?" + +#~ msgid "" +#~ "If you suspect you've found a bug in Geary, follow these steps to report " +#~ "it:" +#~ msgstr "" +#~ "Se você suspeita ter encontrado um erro no Geary, por favor siga os " +#~ "passos para relatá-lo:" + +#~ msgid "" +#~ "Search Geary's bug database to see if someone else has reported " +#~ "the bug." +#~ msgstr "" +#~ "Pesquise o banco de dados de erros do Geary para ver se " +#~ "alguém já relatou o error." + +#~ msgid "" +#~ "Don't see your bug listed? Congratulations! You've found a new bug. To " +#~ "create an bug report, create an account on GNOME's Bugzilla and file a " +#~ "new bug. Be as specific as you can and describe the steps to " +#~ "reproduce it. Don't forget to include details about your operating system " +#~ "and what version of Geary you're running." +#~ msgstr "" +#~ "Não encontrou o erro listado? Meus parabéns! Você encontrou um novo erro. " +#~ "Para criar um relatório de erro, crie uma conta no Bugzilla do GNOME e " +#~ "preencha um relatório de erro. Seja o mais especifico possível " +#~ "e descreva os passos para reproduzi-lo. Não se esqueça de incluir " +#~ "detalhes sobre seu sistema operacional e qual versão do Geary você está " +#~ "usando." + +#~ msgid "" +#~ "For general inquiries, please join the Geary mailing list." +#~ msgstr "" +#~ "Para questões genéricas, por favor entre na lista de discussão do Geary." #~ msgid "Open the Label Conversation menu" #~ msgstr "Abrir o menu de conversa de rótulo" diff -Nru geary-0.12.4/help/sv/sv.po geary-3.32.0/help/sv/sv.po --- geary-0.12.4/help/sv/sv.po 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/help/sv/sv.po 2019-03-17 13:39:29.000000000 +0000 @@ -1,13 +1,14 @@ # Swedish translation for geary help. -# Copyright © 2017 geary's COPYRIGHT HOLDER +# Copyright © 2017-2019 geary's COPYRIGHT HOLDER # This file is distributed under the same license as the geary package. # Josef Andersson , 2017. +# Anders Jonsson , 2019. # msgid "" msgstr "" "Project-Id-Version: geary master\n" -"POT-Creation-Date: 2017-09-28 17:54+0000\n" -"PO-Revision-Date: 2017-09-29 10:55+0200\n" +"POT-Creation-Date: 2019-01-29 05:49+0000\n" +"PO-Revision-Date: 2019-01-29 17:58+0100\n" "Last-Translator: Anders Jonsson \n" "Language-Team: Swedish \n" "Language: sv\n" @@ -15,835 +16,483 @@ "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Poedit 2.0.3\n" +"X-Generator: Poedit 2.2.1\n" -#: C/write.page:9(title) -msgid "Write a message" -msgstr "Skriv ett meddelande" +#. Put one translator per line, in the form NAME , YEAR1, YEAR2 +msgctxt "_" +msgid "translator-credits" +msgstr "" +"Josef Andersson , 2017\n" +"Anders Jonsson , 2019" -#: C/write.page:12(title) -msgid "Composing and replying" -msgstr "Skriva meddelanden och svara" +#. (itstool) path: page/title +#: C/accounts.page:10 +msgid "Accounts" +msgstr "Konton" -#: C/write.page:13(p) +#. (itstool) path: section/title +#: C/accounts.page:13 +msgid "Adding accounts" +msgstr "Lägga till konton" + +#. (itstool) path: section/p +#: C/accounts.page:15 msgid "" -"To compose a new message in Geary, press the New Message button " -"on the toolbar." +"The first time you start Geary, you will be prompted to add an email " +"account. On this screen, select if your account is Gmail, Yahoo, Outlook." +"com, or other. For other account types, you will need to enter your IMAP and " +"SMTP login settings manually." msgstr "" -"För att skapa ett nytt meddelande i Geary, tryck ned knappen Nytt " -"meddelande i verktygsfältet." +"Den första gången du startar Geary kommer du att frågas om att lägga till " +"ett e-postkonto. Vid denna dialog, markera om ditt konto är Gmail, Yahoo, " +"Outlook.com eller ett annat. För andra kontotyper behöver du ange dina IMAP- " +"och SMTP-inställningar manuellt." -#: C/write.page:16(p) +#. (itstool) path: section/p +#: C/accounts.page:19 msgid "" -"To reply to a message, open the message menu in the upper right corner of " -"the message and choose Reply, Reply All or " -"Forward. You can also reply to the last message in a conversation " -"via the Reply, Reply All or Forward buttons " -"on the toolbar." +"Additional accounts can be added from the Accounts dialog. The " +"Accounts option is available in either Geary's application menu " +"or the gear menu in the upper-right of the toolbar. (The location depends on " +"the install desktop shell. For GNOME Shell and Unity, the application menu " +"is available near the top-left corner of the screen.) To add an account, " +"click the + button." msgstr "" -"För att svara på ett meddelande, öppna meddelandemenyn i övre högra hörnet " -"av meddelandet och välj Svara, Svara alla eller " -"Vidarebefordra. Du kan också svara på det senaste meddelandet i " -"en konversation med knapparna Svara, Svara alla eller " -"Vidarebefordra i verktygsfältet." +"Ytterligare konton kan läggas till från kontodialogen. Alternativet " +"Konton är tillgängligt i antingen Gearys programmeny eller " +"kugghjulsmenyn i det över högra hörnet av verktygsfältet. (Platsen beror på " +"det installerade skrivbordsskalet. I GNOME Shell och Unity finns " +"programmenyn i det övre vänstra hörnet av skärmen.) För att lägga till ett " +"konto, klicka på ”+”-knappen." -#: C/write.page:21(title) -msgid "Features" -msgstr "Egenskaper" +#. (itstool) path: section/title +#: C/accounts.page:27 +msgid "Editing existing accounts" +msgstr "Redigera existerande konton" -#: C/write.page:23(p) +#. (itstool) path: section/p +#: C/accounts.page:29 msgid "" -"Geary's email composer lets you adjust the font, size and color of text. You " -"can also insert hyperlinks into messages." +"From the Accounts dialog, select an account and click the pencil icon to " +"change various settings. Please note that Geary cannot change server " +"settings on an existing account. If you need to change your IMAP or SMTP " +"server, you will need to delete the account and re-add it." msgstr "" -"Gearys e-postredigerare låter dig justera en texts typsnitt, storlek och " -"färg. Du kan också infoga länkar i meddelanden." +"Från kontodialogen, välj ett konto och klicka på pennikonen för att ändra " +"olika inställningar. Observera att Geary inte kan ändra server-inställningar " +"för ett existerande konto. Om du behöver ändra din IMAP eller SMTP-server " +"måste du ta bort kontot och lägga till det igen." -#: C/write.page:25(p) +#. (itstool) path: section/p +#: C/accounts.page:33 msgid "" -"Geary can also send plain text messages. In the drop-down menu, check or " -"uncheck \"Rich Text\" to toggle between plain text and rich text mode." +"To change the order that accounts are displayed in the folder list, drag the " +"accounts in the Accounts dialog to the desired order." msgstr "" -"Geary kan också skicka vanliga textmeddelanden. I rullgardinsmenyn, kryssa i " -"eller av ”Rich text” för att växla mellan lägena vanlig text och rich text." +"För att ändra ordningen på de konton som visas i mapplistan, dra kontona i " +"kontodialogen till önskad ordning." -#: C/write.page:28(p) -msgid "" -"You can attach a file to a message you're writing in either of these ways:" +#. (itstool) path: section/p +#: C/accounts.page:36 +msgid "There are some advanced options available when editing accounts:" msgstr "" -"Du kan bifoga en fil med ett meddelande du skriver på ett av dessa sätt:" +"Det finns några avancerade alternativ tillgängliga vid redigering av konton:" -#: C/write.page:30(p) +#. (itstool) path: item/p +#: C/accounts.page:38 msgid "" -"Press the Attach File button at the lower left of the composer " -"window, then select a file to attach." +"The Save sent mail checkbox controls whether Geary will push " +"successfully sent messages up to the account's Sent Mail folder. " +"For Gmail accounts, this happens automatically. Yahoo and some other " +"accounts can be configured to do this automatically as well. For other " +"accounts, if you disable this setting, you may be unable to view messages " +"you've sent." msgstr "" -"Tryck ned knappen Bifoga fil längst ned till vänster på " -"redigerarfönstret och välj sedan fil att bifoga." +"Kryssrutan Spara skickad e-post kontrollerar huruvida Geary " +"kommer att skicka vidare e-post som skickats till kontots mapp för " +"Skickad e-post. För Gmail-konton sker detta automatiskt. Yahoo " +"och några andra konton kan också konfigureras till att göra detta " +"automatiskt. För övriga konton gäller att om du inaktiverar denna " +"inställning så kan du inte se de meddelanden du skickat." -#: C/write.page:32(p) +#. (itstool) path: item/p +#: C/accounts.page:44 msgid "" -"Drag the file from the Nautilus file manager to the composer window, and " -"drop it either on the text fields at the top of the window or on the toolbar " -"at the bottom." +"The Sign emails checkbox indicates whether a signature will be " +"automatically inserted when a composer is opened. You may enter the " +"signature into the box immediately below. You may use HTML tags to style the " +"text. Switch to a preview of the signature using the buttons to the right." msgstr "" -"Dra filen från Nautilus filhanterare till redigerarfönstret och släpp den, " -"antingen på textfälten överst i fönstret eller på verktygsfältet längst ned." +"Kryssrutan Signera e-post visar huruvida en signatur automatiskt " +"infogas när ett redigerarfönster öppnas. Du kan ange signaturen i rutan " +"direkt under. Du kan använda HTML-taggar för att formge texten. Växla till " +"en förhandsvisning av signaturen genom att använda knapparna till höger." -#: C/write.page:36(p) +#. (itstool) path: item/p +#: C/accounts.page:49 msgid "" -"A number of keyboard shortcuts are available in the composer; see for details." +"If you leave the signature in the Accounts dialog blank, Geary will use the " +".signature file in your home directory, if it exists. This file " +"may contain either plain text or HTML markup. In the latter case, the markup " +"will be inserted directly into the composer, without any escaping." msgstr "" -"Ett antal tangentbordsgenvägar är tillgängliga i redigeraren; se för detaljer." +"Om du lämnar signaturen i kontodialogen tom kommer Geary att använda filen " +".signature i din hemkatalog om den existerar. Denna fil kan " +"innehålla vanlig text eller HTML-markup. I det senare fallet kommer markupen " +"att infogas direkt i redigeraren utan kontrollsekvenser." -#: C/write.page:38(p) +#. (itstool) path: item/p +#: C/accounts.page:54 msgid "" -"You may specify a signature to be inserted into the composer in the dialog." +"The Download mail drop-down allows you to configure how much mail " +"Geary will keep locally. Geary can only use locally available mail when " +"searching and forming conversations." msgstr "" -"Du kan ange en signatur att infoga i redigeraren i dialogen ." - -#: C/write.page:43(title) -msgid "Drafts" -msgstr "Utkast" +"Rullgardinsmenyn Hämta e-post låter dig konfigurera hur mycket e-" +"post Geary har lokalt. Geary kan endast använda lokalt tillgänglig e-post " +"vid sökning och skapandet av konversationer." -#: C/write.page:45(p) -msgid "" -"For mail servers that support drafts, Geary will automatically save the " -"message as you type. If you close the composer without sending, Geary will " -"prompt you to keep the draft or to discard it." -msgstr "" -"För e-postservrar som stöder utkast kommer Geary automatiskt att spara " -"meddelandet allt eftersom du skriver. Om du stänger redigeraren utan att " -"skicka det kommer Geary att fråga om utkastet ska behållas eller kastas." +#. (itstool) path: section/title +#: C/accounts.page:62 +msgid "Removing accounts" +msgstr "Ta bort konton" -#: C/write.page:48(p) +#. (itstool) path: section/p +#: C/accounts.page:64 msgid "" -"To edit an existing draft, select the Drafts folder in the folder list, " -"select the message, and click \"Edit Draft\" in the message viewer." +"To delete an account, open the Accounts dialog, select the account, and " +"press the - button. Geary will delete all information associated with the " +"account." msgstr "" -"För att redigera ett existerande utkast, välj mappen Utkast i mapplistan, " -"välj meddelanden och klicka på ”Redigera utkast” i meddelandevisaren." - -#: C/write.page:51(p) -msgid "Geary deletes the draft when you send the message." -msgstr "Geary tar bort utkastet när du skickar meddelandet." - -#: C/star.page:10(title) -msgid "Star a message or mark it as read/unread" -msgstr "Stjärnmärk ett meddelande eller markera det som läst/ej läst" +"För att ta bort ett konto, öppna konto-dialogen, välj konto och tryck ned -. " +"Geary kommer att ta bort all information associerad med kontot." -#: C/star.page:12(title) -msgid "Star messages" -msgstr "Stjärnmärk meddelanden" +#. (itstool) path: page/title +#: C/archive.page:10 +msgid "Delete or archive a message" +msgstr "Ta bort eller arkivera ett meddelande" -#: C/star.page:13(p) +#. (itstool) path: page/p +#: C/archive.page:12 msgid "" -"You can star messages to indicate that they're important to you. To mark a " -"conversation with a star, click its star icon in the conversation list. You " -"can star an individual message by clicking the star at the upper right of " -"the message itself." +"When you use Geary with a Gmail account, Geary lets you archive " +"messages. The Archive toolbar button archives the selected " +"conversation(s). Archived messages appear in the All Mail folder." msgstr "" -"Du kan stjärnmärka meddelanden för att indikera att de är viktiga för dig. " -"För att markera en konversation med en stjärna, klicka på dess stjärnikon i " -"konversationslistan. Du kan stjärnmärka ett individuellt meddelande genom " -"att klicka på stjärnan längst upp till höger i meddelandet." +"När du använder Geary med ett Gmail-konto låter Geary dig arkivera " +"meddelanden. Knappen Arkivera i verktygsfältet arkiverar de " +"markerade konversationerna. Arkiverade meddelanden visas i mappen All e-" +"post." -#: C/star.page:15(p) +#. (itstool) path: page/p +#: C/archive.page:16 msgid "" -"With Gmail accounts, starred messages appear in the Starred folder in the " -"folder list." +"With other mail servers, you can trash or delete, but not archive, messages. " +"To move one or more conversations to the Trash folder, select " +"them and press the Trash button on the toolbar. To permanently " +"delete the conversations, hold down Shift and press the " +"Delete button that appears in place of the Trash " +"button." msgstr "" -"Vad gäller Gmail-konton så kommer stjärnmärkta meddelanden att visas i " -"mappen Stjärnmärkt." - -#: C/star.page:18(title) -msgid "Mark messages as read or unread" -msgstr "Markera meddelanden som lästa eller olästa" +"Med andra e-postservar kan du ta bort eller flytta till papperskorgen, men " +"inte arkivera meddelanden. För att flytta en eller flera konversation till " +"Papperskorgen, markera dem och tryck ned Papperskorg i " +"verktygsfältet. För att permanent ta bort konversationerna , håll nere " +"Skift och tryck ned Ta bort som visas istället för " +"knappen Papperskorg." -#: C/star.page:19(p) +#. (itstool) path: page/p +#: C/archive.page:21 msgid "" -"Geary marks messages as read automatically as you read them. To manually " -"toggle a conversation as read or unread, click the circle icon in the " -"conversation list." +"Delete is not available from every folder, such as Search. Delete is also " +"unavailable for Gmail. For Gmail, Trash will move messages to the " +"Trash folder on the server, where the user can then manually delete them. " +"The server will automatically remove trashed messages after 30 days." msgstr "" -"Geary markerar meddelanden som automatiskt lästa allt eftersom du läser dem. " -"För att manuellt växla en konversation som läst eller oläst, klicka på " -"cirkelikonen i konversationslistan." +"Ta bort är inte tillgängligt från varje mapp som till exempel Sök. Ta bort " +"är inte heller tillgängligt för Gmail. För Gmail kommer Papperskorg att flytta meddelanden till Skräpkorgen på servern där användaren " +"manuellt kan ta bort dem. Servern tar automatiskt bort meddelanden i " +"papperskorgen efter 30 dagar." -#: C/star.page:22(p) +#. (itstool) path: page/title +#: C/bugs.page:10 +msgid "Found a bug?" +msgstr "Har du hittat ett fel?" + +#. (itstool) path: page/p +#: C/bugs.page:12 msgid "" -"Alternately, the Mark as Unread in the Mark menu on " -"the toolbar can be used to toggle the read status of the selected " -"conversation(s)." +"If you suspect you've found a bug in Geary, please get in touch about it so it can be " +"fixed." msgstr "" -"Alternativt kan Markera som oläst i menyn Markera i " -"verktygsfältet användas för att växla lässtatusen för en eller flera " -"markerade konversationer." +"Om du misstänker att du hittat ett fel i Geary, kontakta oss om det så att det kan " +"fixas." -#: C/star.page:25(p) +#. (itstool) path: page/p +#: C/bugs.page:16 msgid "" -"To mark an individual message as read, select Mark as Read from " -"the dropdown menu." +"To help diagnose the problem as fast as possible, please include the " +"following information:" msgstr "" -"För att markera ett individuellt meddelande som läst, markera Markera " -"som läst från rullgardinsmenyn." - -#: C/shortcuts.page:11(title) -msgid "Keyboard shortcuts" -msgstr "Tangentbordsgenvägar" - -#: C/shortcuts.page:12(p) -msgid "Geary has keyboard shortcuts for most common operations." -msgstr "Geary har tangentbordsgenvägar för de vanligaste åtgärderna." - -#: C/shortcuts.page:15(p) -msgid "Compose a new message" -msgstr "Skapa ett nytt meddelande" +"För att hjälpa till att diagnosticera felet så fort som möjligt, inkludera " +"följande information:" -#: C/shortcuts.page:16(p) -msgid "CtrlN or N" -msgstr "CtrlN eller N" +#. (itstool) path: item/p +#: C/bugs.page:20 +msgid "Geary version and installation method (Package? Flathub? Source code?)" +msgstr "Geary-version och installationsmetod (Paket? Flathub? Källkod?)" -#: C/shortcuts.page:19(p) -msgid "Reply to sender" -msgstr "Svara avsändare" +#. (itstool) path: item/p +#: C/bugs.page:22 +msgid "Your desktop (GNOME? KDE? Something else?)" +msgstr "Din skrivbordsmiljö (GNOME? KDE? Något annat?)" -#: C/shortcuts.page:20(p) -msgid "CtrlR or R" -msgstr "CtrlR eller R" - -#: C/shortcuts.page:23(p) -msgid "Reply to all" -msgstr "Svara alla" - -#: C/shortcuts.page:24(p) +#. (itstool) path: item/p +#: C/bugs.page:23 msgid "" -"CtrlShiftR or " -"ShiftR" +"Your operating system and version (Ubuntu 16.04? Fedora 28? Rolled your own?)" msgstr "" -"CtrlSkiftR eller " -"SkiftR" +"Ditt operativsystem och version (Ubuntu 16.04? Fedora 28? Gjort ett eget?)" -#: C/shortcuts.page:27(p) -msgid "Forward" -msgstr "Vidarebefordra" +#. (itstool) path: item/p +#: C/bugs.page:25 +msgid "Email provider (Gmail, Yahoo!, Outlook.com, or someone else?)" +msgstr "E-postleverantör (Gmail, Yahoo!, Outlook.com, eller någon annan?)" -#: C/shortcuts.page:28(p) -msgid "CtrlL or F" -msgstr "CtrlL eller F" +#. (itstool) path: item/p +#: C/bugs.page:27 +msgid "Steps to reproduce the bug" +msgstr "Steg för att reproducera felet" -#: C/shortcuts.page:31(p) -msgid "Archive" -msgstr "Arkivera" +#. (itstool) path: item/p +#: C/bugs.page:28 +msgid "What happened?" +msgstr "Vad hände?" -#: C/shortcuts.page:32(key) -msgid "A" -msgstr "A" +#. (itstool) path: item/p +#: C/bugs.page:29 +msgid "What did you expect to happen?" +msgstr "Vad förväntade du dig skulle hända?" -#: C/shortcuts.page:35(p) -msgid "Trash" -msgstr "Flytta till Papperskorg" +#. (itstool) path: page/p +#: C/bugs.page:32 +msgid "Thanks for your help!" +msgstr "Tack för hjälpen!" -#: C/shortcuts.page:36(p) -msgid "Delete or Backspace" -msgstr "Delete eller Backsteg" +#. (itstool) path: page/title +#: C/contributing.page:10 +msgid "Contribute to Geary" +msgstr "Bidra till Geary" -#: C/shortcuts.page:39(p) -msgid "Delete" -msgstr "Ta bort" - -#: C/shortcuts.page:40(p) +#. (itstool) path: page/p +#: C/contributing.page:12 msgid "" -"ShiftDelete or ShiftBackspace" +"Want to help improve Geary? There are a number of ways you can contribute:" msgstr "" -"SkiftDelete eller SkiftBacksteg" - -#: C/shortcuts.page:43(p) -msgid "Star" -msgstr "Stjärnmärk" - -#: C/shortcuts.page:44(key) C/shortcuts.page:100(key) -msgid "S" -msgstr "S" - -#: C/shortcuts.page:47(p) -msgid "Unstar" -msgstr "Ta bort stjärnmärkning" - -#: C/shortcuts.page:48(key) C/shortcuts.page:138(key) -msgid "D" -msgstr "D" - -#: C/shortcuts.page:51(p) -msgid "Mark read" -msgstr "Markera som läst" +"Vill du hjälpa till att förbättra Geary? Det finns ett antal sätt som du kan " +"bidra på:" -#: C/shortcuts.page:52(p) +#. (itstool) path: item/p +#: C/contributing.page:16 msgid "" -"CtrlI or ShiftI" +"Bug " +"reporting—report new bugs or request new features" msgstr "" -"CtrlI eller SkiftI" +"Felrapportering—rapportera nya fel eller efterfråga nya funktioner" -#: C/shortcuts.page:55(p) -msgid "Mark unread" -msgstr "Markera som oläst" - -#: C/shortcuts.page:56(p) +#. (itstool) path: item/p +#: C/contributing.page:19 msgid "" -"CtrlU or ShiftU" +"User Experience " +"Design—research and develop Geary’s user experience" msgstr "" -"CtrlU eller SkiftU" +"Design och " +"användarupplevelse—utforska och utveckla Gearys användarupplevelse" -#: C/shortcuts.page:59(p) -msgid "Move the conversation" -msgstr "Flytta konversationen" +#. (itstool) path: item/p +#: C/contributing.page:20 +msgid "" +"Development—fix bugs and add new features" +msgstr "" +"Utveckling—fixa fel och lägg till nya funktioner" -#: C/shortcuts.page:60(key) -msgid "M" -msgstr "M" +#. (itstool) path: item/p +#: C/contributing.page:21 +msgid "" +"Translating—translate Geary’s user interface and user manual into new languages" +msgstr "" +"Översättning—översätt Gearys användargränssnitt och användarhandbok till nya språk" -#: C/shortcuts.page:63(p) -msgid "Label the conversation" -msgstr "Lägg till en etikett till konversationen" +#. (itstool) path: item/p +#: C/contributing.page:22 +msgid "" +"Join the " +"discussion—on the mailing list or IRC channel" +msgstr "" +"Gå med i " +"diskussionen—på sändlistan eller IRC-kanalen" -#: C/shortcuts.page:64(key) C/shortcuts.page:162(key) -msgid "L" -msgstr "L" +#. (itstool) path: page/p +#: C/contributing.page:25 +msgid "Thanks for your help making Geary better!" +msgstr "Tack för att du hjälper till att göra Geary bättre!" -#: C/shortcuts.page:67(p) -msgid "Jump to next (older) conversation" -msgstr "Hoppa till nästa (äldre) konversation" +#. (itstool) path: title/media +#. This is a reference to an external file such as an image or video. When +#. the file changes, the md5 hash will change to let you know you need to +#. update your localized copy. The msgstr is not used at all. Set it to +#. whatever you like once you have updated your copy of the file. +#: C/index.page:5 +msgctxt "_" +msgid "external ref='figures/geary.svg' md5='18b50c9e10fe5256ae1cb12aaa3a7600'" +msgstr "" +"external ref='figures/geary.svg' md5='18b50c9e10fe5256ae1cb12aaa3a7600'" -#: C/shortcuts.page:68(key) -msgid "J" -msgstr "J" +#. (itstool) path: page/title +#: C/index.page:5 +msgid " Geary" +msgstr " Geary" -#: C/shortcuts.page:71(p) -msgid "Jump to previous (newer) conversation" -msgstr "Hoppa till föregående (nyare) konversation" +#. (itstool) path: section/title +#: C/index.page:8 +msgid "Introduction" +msgstr "Introduktion" -#: C/shortcuts.page:72(key) C/shortcuts.page:158(key) -msgid "K" -msgstr "K" +#. (itstool) path: section/title +#: C/index.page:12 +msgid "Using Geary" +msgstr "Använda Geary" -#: C/shortcuts.page:75(p) -msgid "Toggle spam" -msgstr "Växla skräppost" +#. (itstool) path: section/title +#: C/index.page:16 +msgid "Contributing and bug reporting" +msgstr "Bidra och rapportera fel" -#: C/shortcuts.page:76(p) -msgid "CtrlJ or !" -msgstr "CtrlJ eller !" +#. (itstool) path: page/title +#: C/label.page:10 +msgid "Label or move a conversation" +msgstr "Namnge eller flytta en konversation" -#: C/shortcuts.page:79(p) -msgid "Quit" -msgstr "Avsluta" +#. (itstool) path: section/title +#: C/label.page:12 +msgid "Label a conversation" +msgstr "Namnge en konversation" -#: C/shortcuts.page:80(key) C/shortcuts.page:96(key) C/shortcuts.page:100(key) -#: C/shortcuts.page:104(key) C/shortcuts.page:108(key) -#: C/shortcuts.page:112(key) C/shortcuts.page:122(key) -#: C/shortcuts.page:126(key) C/shortcuts.page:130(key) -#: C/shortcuts.page:138(key) C/shortcuts.page:146(key) -#: C/shortcuts.page:150(key) C/shortcuts.page:154(key) -#: C/shortcuts.page:158(key) C/shortcuts.page:162(key) -#: C/shortcuts.page:166(key) C/shortcuts.page:181(key) -msgid "Ctrl" -msgstr "Ctrl" +#. (itstool) path: section/p +#: C/label.page:13 +msgid "" +"Geary lets you apply one or more labels to each conversation. Geary " +"labels correspond to labels in Gmail, or ordinary folders in other mail " +"services." +msgstr "" +"Geary låter dig tillämpa en eller flera etiketter till varje " +"konversation. Geary-etiketter motsvarar etiketter i Gmail eller vanliga " +"mappar i andra e-posttjänster." -#: C/shortcuts.page:80(key) -msgid "Q" -msgstr "Q" +#. (itstool) path: section/p +#: C/label.page:15 +msgid "" +"To label one or more conversations, first select the conversation(s), then " +"do either of the following:" +msgstr "" +"För att sätta etikett på en eller flera konversationer, välj först " +"konversation(en/erna) och utför sedan en av följande:" -#: C/shortcuts.page:83(p) -msgid "Zoom in" -msgstr "Zooma in" - -#: C/shortcuts.page:84(p) -msgid "Ctrl= or =" -msgstr "Ctrl= eller =" - -#: C/shortcuts.page:87(p) -msgid "Zoom out" -msgstr "Zooma ut" - -#: C/shortcuts.page:88(p) -msgid "Ctrl- or -" -msgstr "Ctrl- eller -" - -#: C/shortcuts.page:91(p) -msgid "Reset zoom" -msgstr "Återställ zoom" - -#: C/shortcuts.page:92(p) -msgid "Ctrl0 or 0" -msgstr "Ctrl0 eller 0" - -#: C/shortcuts.page:95(p) -msgid "Close composer window" -msgstr "Stäng redigerarfönstret" - -#: C/shortcuts.page:96(key) -msgid "W" -msgstr "W" - -#: C/shortcuts.page:99(p) -msgid "Jump to search box" -msgstr "Hoppa till sökfältet" - -#: C/shortcuts.page:103(p) -msgid "Find in current conversation" -msgstr "Hitta i aktuell konversation" - -#: C/shortcuts.page:104(key) -msgid "F" -msgstr "F" - -#: C/shortcuts.page:107(p) -msgid "Find next in current conversation" -msgstr "Hitta nästa i aktuell konversation" - -#: C/shortcuts.page:108(key) C/shortcuts.page:112(key) -msgid "G" -msgstr "G" - -#: C/shortcuts.page:111(p) -msgid "Find previous in current conversation" -msgstr "Hitta föregående i aktuell konversation" - -#: C/shortcuts.page:112(key) -msgid "Shift" -msgstr "Skift" - -#: C/shortcuts.page:117(title) -msgid "Composer shortcuts" -msgstr "Redigeringsgenvägar" - -#: C/shortcuts.page:118(p) -msgid "These shortcuts are active whenever focus is in a composer." -msgstr "Dessa genvägar är aktiva då redigerare har fokus." - -#: C/shortcuts.page:121(p) -msgid "Attach file" -msgstr "Bifoga fil" - -#: C/shortcuts.page:122(key) -msgid "T" -msgstr "T" - -#: C/shortcuts.page:125(p) -msgid "Quote text" -msgstr "Citera text" - -#: C/shortcuts.page:126(key) -msgid "]" -msgstr "]" - -#: C/shortcuts.page:129(p) -msgid "Unquote text" -msgstr "Avcitera text" - -#: C/shortcuts.page:130(key) -msgid "[" -msgstr "[" - -#: C/shortcuts.page:133(p) -msgid "Close composer" -msgstr "Stäng redigerare" - -#: C/shortcuts.page:134(p) -msgid "CtrlW or Esc" -msgstr "CtrlW eller Esc" - -#: C/shortcuts.page:137(p) -msgid "Detach composer" -msgstr "Koppla loss redigerare" - -#: C/shortcuts.page:142(p) -msgid "These shortcuts are only active in composers in rich text mode." -msgstr "Dessa genvägar är endast aktiva i redigerarens ”rich text”-läge." - -#: C/shortcuts.page:145(p) -msgid "Bold text" -msgstr "Fet text" - -#: C/shortcuts.page:146(key) C/shortcuts.page:181(key) -msgid "B" -msgstr "B" - -#: C/shortcuts.page:149(p) -msgid "Italicize text" -msgstr "Kursiv text" - -#: C/shortcuts.page:150(key) -msgid "I" -msgstr "I" - -#: C/shortcuts.page:153(p) -msgid "Underline text" -msgstr "Understruken text" - -#: C/shortcuts.page:154(key) -msgid "U" -msgstr "U" - -#: C/shortcuts.page:157(p) -msgid "Strike text" -msgstr "Genomstruken text" - -#: C/shortcuts.page:161(p) -msgid "Insert a link" -msgstr "Infoga en länk" - -#: C/shortcuts.page:165(p) -msgid "Remove formatting" -msgstr "Ta bort formatering" - -#: C/shortcuts.page:166(key) C/shortcuts.page:186(key) -msgid "Space" -msgstr "Blanksteg" - -#: C/shortcuts.page:172(title) -msgid "Keyboard navigation" -msgstr "Tangentbordsnavigering" - -#: C/shortcuts.page:173(p) -msgid "" -"These shortcuts can be used to move the keyboard focus in the main window." -msgstr "" -"Dessa kortkommandon kan användas för att flytta tangentbordets fokus i " -"huvudfönstret." - -#: C/shortcuts.page:176(p) -msgid "Move focus to the next/previous pane" -msgstr "Flytta fokus till nästa/föregående panel" - -#: C/shortcuts.page:177(p) -msgid "" -"F6 / ShiftF6" -msgstr "" -"F6 / SkiftF6" - -#: C/shortcuts.page:180(p) -msgid "Move focus to conversation list" -msgstr "Flytta fokus till konversationslistan" - -#: C/shortcuts.page:184(p) -msgid "Move to the next message in a conversation" -msgstr "Gå till nästa meddelande i en konversationen" - -#: C/shortcuts.page:190(p) -msgid "Move to the next/previous message in a conversation" -msgstr "Gå till nästa/föregående meddelande i en konversation" - -#: C/shortcuts.page:191(p) -msgid "" -"CtrlDown / CtrlUp" -msgstr "" -"CtrlNed / CtrlUpp" - -#: C/shortcuts.page:197(p) -msgid "Move to the first/last message in a conversation" -msgstr "Gå till första/sista meddelandet i en konversation" - -#: C/shortcuts.page:198(p) -msgid "" -"CtrlHome / CtrlEnd" -msgstr "" -"CtrlHome / CtrlEnd" - -#: C/search.page:10(title) -msgid "Search" -msgstr "Sök" - -#: C/search.page:12(p) -msgid "" -"Geary supports a per-account full text search. To start a search, select a " -"folder associated with the account you'd like to search against. Then click " -"the search box in the toolbar (or press CtrlS) and start typing. Results will appear after a brief delay." -msgstr "" -"Geary stöder textsökning per konto. För att påbörja en sökning, välj mappen " -"associerad med kontot du vill söka i. Klicka i sökrutan i verktygsfältet " -"(eller tryck ned CtrlS) och börja " -"skriva. Resultat kommer att visas efter en kort fördröjning." - -#: C/search.page:16(p) -msgid "" -"The full text search includes email text, email addresses (to, from, and " -"cc), subject lines and attachment filenames." -msgstr "" -"Textsökningen omfattar e-posttext, e-postadresser (till, från och cc), " -"ämnesrader och bilagors filnamn." - -#: C/search.page:19(p) -msgid "" -"Keywords that match your search are highlighted in the message view. Geary " -"will match different forms of the same word, for example searching for \"walk" -"\" will also match \"walking\" and \"walked.\"" -msgstr "" -"Sökträffar markeras i meddelandevyn. Geary kommer att få träff på olika " -"former av samma ord, exempelvis ger sökning på ”hej” också träff på ”hejsan” " -"och ”hejdå”." - -#: C/search.page:23(title) -msgid "Search operators" -msgstr "Sökoperator" - -#: C/search.page:24(p) -msgid "Geary supports the following operators to limit the scope of searches:" -msgstr "" -"Geary stödjer följande operatorer för att begränsa omfattningen av sökningar:" - -#: C/search.page:27(var) -msgid "filename" -msgstr "filnamn" - -#: C/search.page:27(input) -msgid "attachment:" -msgstr "bilaga:" - -#: C/search.page:28(p) -msgid "Finds messages with attachments whose name matches filename." -msgstr "Hittar meddelanden med bilagor vars namn matchar filnamn." - -#: C/search.page:31(var) C/search.page:39(var) C/search.page:63(var) -msgid "recipient" -msgstr "mottagare" - -#: C/search.page:31(input) -msgid "bcc:" -msgstr "bcc:" - -#: C/search.page:32(p) -msgid "Finds messages where recipient matches the BCC header." -msgstr "Hittar meddelanden där mottagare matchar BCC-rubrik." - -#: C/search.page:35(var) C/search.page:59(var) -msgid "text" -msgstr "text" - -#: C/search.page:35(input) -msgid "body:" -msgstr "innehåll:" - -#: C/search.page:36(p) -msgid "Finds messages whose body contains text." -msgstr "Hittar meddelanden vars text innehåller text." - -#: C/search.page:39(input) -msgid "cc:" -msgstr "cc:" - -#: C/search.page:40(p) -msgid "Finds messages where recipient matches the CC header." -msgstr "Hittar meddelanden där mottagare matchar CC-rubrik." - -#: C/search.page:43(var) -msgid "sender" -msgstr "avsändare" - -#: C/search.page:43(input) -msgid "from:" -msgstr "från:" - -#: C/search.page:44(p) -msgid "Finds messages where sender matches the From header." -msgstr "Hittar meddelanden där avsändare matchar med från-rubriken." - -#: C/search.page:47(input) -msgid "is:read" -msgstr "är:läst" - -#: C/search.page:48(p) -msgid "Finds messages that have been marked as read." -msgstr "Hittar meddelanden som har markerats som lästa." - -#: C/search.page:51(input) -msgid "is:starred" -msgstr "är:stjärnmärkt" - -#: C/search.page:52(p) -msgid "Finds messages that have been marked as starred." -msgstr "Hittar meddelanden som har markerats som stjärnmärkta." - -#: C/search.page:55(input) -msgid "is:unread" -msgstr "är:oläst" - -#: C/search.page:56(p) -msgid "Finds messages that have been marked as not read." -msgstr "Hittar meddelanden som har markerats som ej lästa." - -#: C/search.page:59(input) -msgid "subject:" -msgstr "ämne:" - -#: C/search.page:60(p) -msgid "Finds messages whose subject contains text." -msgstr "Hittar meddelanden vars ämne innehåller text." - -#: C/search.page:63(input) -msgid "to:" -msgstr "till:" - -#: C/search.page:64(p) -msgid "" -"Finds messages where sender matches the To, CC, or BCC header." -msgstr "" -"Hittar meddelanden där avsändare matchar någon av rubrikerna " -"till, cc eller bcc." - -#: C/search.page:68(p) -msgid "" -"As a special case, the bcc, cc, from, and to operators support me as their " -"argument, which searches for the account's email address in the appropriate " -"context." -msgstr "" -"Som ett specialfall stöder operatorerna bcc, cc, from och to att ta mig " -"som sitt argument, vilket söker efter kontots e-postadress i sammanhanget." - -#: C/preferences.page:10(title) -msgid "Preferences" -msgstr "Inställningar" - -#: C/preferences.page:11(p) +#. (itstool) path: item/p +#: C/label.page:18 msgid "" -"The Preferences option is available in either Geary's application " -"menu or the gear menu in the upper-right of the toolbar. (The location " -"depends on the install desktop shell. For GNOME Shell and Unity, the " -"application menu is available near the top-left corner of the screen.)" +"Click the Label button on the toolbar and select a label from the " +"resulting drop-down menu." msgstr "" -"Alternativet Inställningar är tillgängligt i antingen Gearys " -"programmeny eller kugghjulsmenyn i övre högra hörnet av verktygsfältet. " -"(Platsen beror på installerat skrivbordsskal. För GNOME Shell och Unity är " -"programmenyn tillgänglig nära övre vänstra hörnet av skärmen.)" - -#: C/preferences.page:17(title) -msgid "Reading" -msgstr "Läsning" - -#: C/preferences.page:20(gui) -msgid "Automatically select next message" -msgstr "Välj nästa meddelande automatiskt" +"Klicka på knappen Etikett i verktygsfältet och välj en etikett " +"från rullgardinsmenyn." -#: C/preferences.page:21(p) +#. (itstool) path: item/p +#: C/label.page:20 msgid "" -"When this option is enabled, Geary automatically selects the latest message " -"in a folder when you enter the folder. In addition, after archiving a " -"message, Geary automatically selects an adjacent message." +"Hold down the Ctrl key and drag the conversation(s) from the " +"conversation list to the label in the sidebar." msgstr "" -"När detta alternativ är aktiverat väljer Geary automatiskt det senaste " -"meddelandet i en mapp när du går in i den. Geary väljer också ett " -"närliggande meddelande efter att ha arkiverat ett meddelande." +"Håll nere tangenten Ctrl och dra konversation(en/erna) från " +"konversationslistan till etiketten i sidofältet." -#: C/preferences.page:26(gui) -msgid "Display conversation preview" -msgstr "Visa förhandsvisning av konversation" +#. (itstool) path: section/title +#: C/label.page:25 +msgid "Move a conversation to a folder or label" +msgstr "Etikettera eller flytta en konversation" -#: C/preferences.page:27(p) +#. (itstool) path: section/p +#: C/label.page:26 msgid "" -"Enables message previews in the conversation list. Previews show the first " -"few lines of each message." +"To move one or more conversations to a folder or label, first select the " +"conversation(s), then do either of the following:" msgstr "" -"Aktivera förhandsvisning av meddelanden i konversationslistan. " -"Förhandsvisning visar de första få raderna av varje meddelande." - -#: C/preferences.page:31(gui) -msgid "Use three pane view" -msgstr "Använd trepanelsvy" +"För att flytta en eller flera konversationer till en mapp eller etikett, " +"markera först konversation(en/erna) och gör en av följande:" -#: C/preferences.page:32(p) +#. (itstool) path: item/p +#: C/label.page:29 msgid "" -"Show the folder list, the conversation list, and the messages side-by-side-" -"by-side in three panes. If not selected, the folder list and conversation " -"list will be stacked vertically in a single pane." +"Click the Move button on the toolbar and select a folder or label " +"from the resulting drop-down menu." msgstr "" -"Visa mapplistan, konversationslistan och meddelande sida-vid-sida i tre " -"paneler. Om ej vald kommer mapplistan och konversationslistan att staplas " -"vertikalt i en enda panel." - -#: C/preferences.page:40(title) -msgid "Composer" -msgstr "Redigerare" - -#: C/preferences.page:43(gui) -msgid "Enable spell checking" -msgstr "Aktivera stavningskontroll" +"Klicka på knappen Flytta i verktygsfältet och välj en mapp eller " +"en etikett från rullgardinsmenyn." -#: C/preferences.page:44(p) +#. (itstool) path: item/p +#: C/label.page:31 msgid "" -"When set, Geary automatically spell checks a message as you write it, " -"underlying each misspelled word in red." +"Drag the conversation(s) from the conversation list to the folder or label " +"in the sidebar." msgstr "" -"Om satt stavningskontrollerar Geary automatiskt ett meddelande under " -"skapandet och stryker under varje felstavat ord i rött." - -#: C/preferences.page:51(title) -msgid "Notifications" -msgstr "Aviseringar" - -#: C/preferences.page:54(gui) -msgid "Play notification sounds" -msgstr "Spela aviseringsljud" - -#: C/preferences.page:55(p) -msgid "When set, Geary plays a sound whenever a new message arrives." -msgstr "Om valt spelar Geary ett ljud när ett nytt meddelande inkommer." +"Dra konversation(en/erna) från konversationslistan till mappen eller " +"etiketten i sidopanelen." -#: C/preferences.page:58(gui) -msgid "Show notifications for new mail" -msgstr "Visa aviseringar för ny e-post" +#. (itstool) path: page/title +#: C/limits.page:9 +msgid "Limitations" +msgstr "Begränsningar" -#: C/preferences.page:59(p) +#. (itstool) path: page/p +#: C/limits.page:11 msgid "" -"When set, Geary displays a notification each time a new message " -"arrives. Notifications are displayed in a system-dependent manner. On GNOME " -"Shell, notifications appear at the bottom of the display (older versions) or " -"centered just below the top bar (newer versions). In Ubuntu Unity, " -"notifications appear at the upper right of the display." +"Geary is still in early development. Geary supports IMAP and has been tested " +"with Gmail, Yahoo, and the free Dovecot mail server. Experimental support " +"for Outlook.com is provided. Geary may not yet work well with some IMAP " +"servers. At this time Geary is still missing numerous features including " +"offline mode." msgstr "" -"Om satt visar Geary en avisering varje gång ett nytt meddelande " -"inkommer. Aviseringar visas på ett systemberoende sätt. I GNOME Shell visas " -"aviseringar längst ned (äldre versioner) eller centrerade precis under " -"översta fältet (nyare versioner). I Ubuntu Unity visas aviseringar längst " -"upp höger." - -#: C/preferences.page:65(gui) -msgid "Always watch for new mail" -msgstr "Kontrollera alltid om det finns ny e-post" +"Geary är fortfarande i tidig utveckling. Geary stöder IMAP och har testas " +"med Gmail, Yahoo och den fria Dovecot-servern. Experimentellt stöd för " +"Outlook.com tillhandahålls. Geary kanske inte fungerar perfekt ännu med " +"vissa IMAP-servrar. Just nu saknar Geary fortfarande några viktiga " +"funktioner som frånkopplat läge." -#: C/preferences.page:66(p) +#. (itstool) path: page/p +#: C/limits.page:17 msgid "" -"Geary will watch your accounts for new mail even when the main window is not " -"open. To do this, it will silently start when you log in to your computer, " -"and it will continue to run after you close the main window." +"To learn more about the features we're working on and the future of Geary, " +"please visit Geary's wiki " +"page." msgstr "" -"Geary kommer att kontrollera efter ny e-post även när huvudfönstret inte är " -"öppet. För att göra detta kommer det att starta tyst när du loggar in i din " -"dator och fortsätter att köra efter att du har stängt huvudfönstret." +"För att lära dig mer om funktioner vi arbetar på och Gearys framtid, besök " +"Gearys wikisida." -#: C/overview.page:8(title) +#. (itstool) path: page/title +#: C/overview.page:8 msgid "Overview" msgstr "Översikt" -#: C/overview.page:10(p) +#. (itstool) path: page/p +#: C/overview.page:10 msgid "" "Geary is a lightweight email reader for the GNOME desktop. It works with mail servers that support the IMAP " @@ -855,7 +504,8 @@ "protokollet, vilket omfattar populära tjänster som Gmail, Yahoo Mail och " "Outlook.com." -#: C/overview.page:14(p) +#. (itstool) path: page/p +#: C/overview.page:14 msgid "" "Geary groups mail messages into conversations. A conversation " "contains all messages in a single thread of discussion." @@ -863,15 +513,18 @@ "Geary grupperar e-postmeddelanden efter konversationer. En " "konversation innehåller alla meddelanden i en enskild diskussionstråd." -#: C/overview.page:17(p) +#. (itstool) path: page/p +#: C/overview.page:17 msgid "The main Geary window is divided into several areas:" msgstr "Gearys huvudfönster är uppdelat i flera områden:" -#: C/overview.page:20(title) +#. (itstool) path: section/title +#: C/overview.page:20 msgid "Folder list" msgstr "Mapplista" -#: C/overview.page:21(p) +#. (itstool) path: section/p +#: C/overview.page:21 msgid "" "The folder list at the left displays all folders and " "labels in your mail account. Geary uses the term label for " @@ -884,11 +537,13 @@ "meddelanden. (Gmails webbgränssnitt använder också denna term; de flesta " "andra gör det inte.)" -#: C/overview.page:28(title) +#. (itstool) path: section/title +#: C/overview.page:28 msgid "Conversation list" msgstr "Konversationslista" -#: C/overview.page:29(p) +#. (itstool) path: section/p +#: C/overview.page:29 msgid "" "The conversation list displays a list of conversations in the " "selected folder. Newer conversations appear at the top." @@ -896,7 +551,8 @@ "konversationslistan visar en lista över konversationer i den valda " "mappen. Nyare konversationer visas överst." -#: C/overview.page:31(p) +#. (itstool) path: section/p +#: C/overview.page:31 msgid "" "Each sender's name appears bold if there are unread messages from that " "sender. If a conversation has more than one message, Geary displays a count " @@ -906,7 +562,8 @@ "den avsändaren. Om en konversation har mer än ett meddelande visar Geary en " "totalsumma över meddelanden i konversationen." -#: C/overview.page:34(p) +#. (itstool) path: section/p +#: C/overview.page:34 msgid "" "Geary does not automatically download all messages in all of your mail " "folders. When you first visit your Inbox or any other folder, Geary " @@ -920,7 +577,8 @@ "ned i konversationslistan och Geary kommer att hämta flera meddelanden " "automatiskt." -#: C/overview.page:36(p) +#. (itstool) path: section/p +#: C/overview.page:36 msgid "" "Some commands in Geary can act on a group of conversations. To select " "multiple conversations, hold down the Ctrl key and click each " @@ -934,11 +592,13 @@ "konversationen i intervallet, håll sedan ned Skift och klicka på " "den sista konversationen." -#: C/overview.page:44(title) +#. (itstool) path: section/title +#: C/overview.page:44 msgid "Message area" msgstr "Meddelandeyta" -#: C/overview.page:45(p) +#. (itstool) path: section/p +#: C/overview.page:45 msgid "" "The message area displays all messages in the selected " "conversation, with the oldest message at the top." @@ -946,7 +606,8 @@ "Meddelandeytan visar alla meddelanden i den markerade " "konversationen med de äldsta meddelandena överst." -#: C/overview.page:47(p) +#. (itstool) path: section/p +#: C/overview.page:47 msgid "" "At the upper right of each message, Geary displays a dropdown arrow that " "lets you open the message menu with commands that operate on the " @@ -956,7 +617,8 @@ "låter dig öppna meddelandemenyn med kommandon som kan bearbeta " "meddelandet." -#: C/overview.page:49(p) +#. (itstool) path: section/p +#: C/overview.page:49 msgid "" "When you view a conversation, Geary collapses messages that you've already " "read. Click collapsed messages to expand them. Click an expanded message's " @@ -966,7 +628,8 @@ "har läst. Klicka på ihopfällda meddelanden för att fälla ut dem. Klicka på " "ett utfällt meddelandehuvud för att fälla ihop det." -#: C/overview.page:50(p) +#. (itstool) path: section/p +#: C/overview.page:50 msgid "" "Any attachments in a message appear at the bottom of the message. You can " "click an attachment to open it or right-click to save it." @@ -974,7 +637,8 @@ "Bilagor i ett meddelande visas längst ned i meddelandet. Du kan klicka på en " "bilaga för att öppna den, eller högerklicka för att spara den." -#: C/overview.page:52(p) +#. (itstool) path: section/p +#: C/overview.page:52 msgid "" "Geary uses Gravatar to " "display an avatar for each message's sender in its header." @@ -982,344 +646,910 @@ "Geary använder Gravatar " "för att visa en avatar för varje meddelandes avsändare i dess rubriker." -#: C/limits.page:11(title) -msgid "Limitations" -msgstr "Begränsningar" +#. (itstool) path: page/title +#: C/preferences.page:10 +msgid "Preferences" +msgstr "Inställningar" -#: C/limits.page:12(p) +#. (itstool) path: page/p +#: C/preferences.page:11 msgid "" -"Geary is still in early development. Geary supports IMAP and has been tested " -"with Gmail, Yahoo, and the free Dovecot mail server. Experimental support " -"for Outlook.com is provided. Geary may not yet work well with some IMAP " -"servers. At this time Geary is still missing numerous features including " -"offline mode." +"The Preferences option is available in either Geary's application " +"menu or the gear menu in the upper-right of the toolbar. (The location " +"depends on the install desktop shell. For GNOME Shell and Unity, the " +"application menu is available near the top-left corner of the screen.)" msgstr "" -"Geary är fortfarande i tidig utveckling. Geary stöder IMAP och har testas " -"med Gmail, Yahoo och den fria Dovecot-servern. Experimentellt stöd för " -"Outlook.com tillhandahålls. Geary kanske inte fungerar perfekt ännu med " -"vissa IMAP-servrar. Just nu saknar Geary fortfarande några viktiga " -"funktioner som frånkopplat läge." - -#: C/limits.page:14(p) -msgid "" -"To learn more about the features we're working on and the future of Geary, " -"please visit Geary's wiki " -"page." -msgstr "" -"För att lära dig mer om funktioner vi arbetar på och Gearys framtid, besök " -"Gearys wikisida." +"Alternativet Inställningar är tillgängligt i antingen Gearys " +"programmeny eller kugghjulsmenyn i övre högra hörnet av verktygsfältet. " +"(Platsen beror på installerat skrivbordsskal. För GNOME Shell och Unity är " +"programmenyn tillgänglig nära övre vänstra hörnet av skärmen.)" -#: C/label.page:10(title) -msgid "Label or move a conversation" -msgstr "Namnge eller flytta en konversation" +#. (itstool) path: section/title +#: C/preferences.page:17 +msgid "Reading" +msgstr "Läsning" -#: C/label.page:12(title) -msgid "Label a conversation" -msgstr "Namnge en konversation" +#. (itstool) path: item/title +#: C/preferences.page:20 +msgid "Automatically select next message" +msgstr "Välj nästa meddelande automatiskt" -#: C/label.page:13(p) +#. (itstool) path: item/p +#: C/preferences.page:21 msgid "" -"Geary lets you apply one or more labels to each conversation. Geary " -"labels correspond to labels in Gmail, or ordinary folders in other mail " -"services." +"When this option is enabled, Geary automatically selects the latest message " +"in a folder when you enter the folder. In addition, after archiving a " +"message, Geary automatically selects an adjacent message." msgstr "" -"Geary låter dig tillämpa en eller flera etiketter till varje " -"konversation. Geary-etiketter motsvarar etiketter i Gmail eller vanliga " -"mappar i andra e-posttjänster." +"När detta alternativ är aktiverat väljer Geary automatiskt det senaste " +"meddelandet i en mapp när du går in i den. Geary väljer också ett " +"närliggande meddelande efter att ha arkiverat ett meddelande." -#: C/label.page:15(p) +#. (itstool) path: item/title +#: C/preferences.page:26 +msgid "Display conversation preview" +msgstr "Visa förhandsvisning av konversation" + +#. (itstool) path: item/p +#: C/preferences.page:27 msgid "" -"To label one or more conversations, first select the conversation(s), then " -"do either of the following:" +"Enables message previews in the conversation list. Previews show the first " +"few lines of each message." msgstr "" -"För att sätta etikett på en eller flera konversationer, välj först " -"konversation(en/erna) och utför sedan en av följande:" +"Aktivera förhandsvisning av meddelanden i konversationslistan. " +"Förhandsvisning visar de första få raderna av varje meddelande." -#: C/label.page:18(p) +#. (itstool) path: item/title +#: C/preferences.page:31 +msgid "Use three pane view" +msgstr "Använd trepanelsvy" + +#. (itstool) path: item/p +#: C/preferences.page:32 msgid "" -"Click the Label button on the toolbar and select a label from the " -"resulting drop-down menu." +"Show the folder list, the conversation list, and the messages side-by-side-" +"by-side in three panes. If not selected, the folder list and conversation " +"list will be stacked vertically in a single pane." msgstr "" -"Klicka på knappen Etikett i verktygsfältet och välj en etikett " -"från rullgardinsmenyn." +"Visa mapplistan, konversationslistan och meddelande sida-vid-sida i tre " +"paneler. Om ej vald kommer mapplistan och konversationslistan att staplas " +"vertikalt i en enda panel." + +#. (itstool) path: section/title +#: C/preferences.page:40 +msgid "Notifications" +msgstr "Aviseringar" + +#. (itstool) path: item/title +#: C/preferences.page:43 +msgid "Play notification sounds" +msgstr "Spela aviseringsljud" -#: C/label.page:20(p) +#. (itstool) path: item/p +#: C/preferences.page:44 +msgid "When set, Geary plays a sound whenever a new message arrives." +msgstr "Om valt spelar Geary ett ljud när ett nytt meddelande inkommer." + +#. (itstool) path: item/title +#: C/preferences.page:47 +msgid "Show notifications for new mail" +msgstr "Visa aviseringar för ny e-post" + +#. (itstool) path: item/p +#: C/preferences.page:48 msgid "" -"Hold down the Ctrl key and drag the conversation(s) from the " -"conversation list to the label in the sidebar." +"When set, Geary displays a notification each time a new message " +"arrives. Notifications are displayed in a system-dependent manner. On GNOME " +"Shell, notifications appear at the bottom of the display (older versions) or " +"centered just below the top bar (newer versions). In Ubuntu Unity, " +"notifications appear at the upper right of the display." msgstr "" -"Håll nere tangenten Ctrl och dra konversation(en/erna) från " -"konversationslistan till etiketten i sidofältet." +"Om satt visar Geary en avisering varje gång ett nytt meddelande " +"inkommer. Aviseringar visas på ett systemberoende sätt. I GNOME Shell visas " +"aviseringar längst ned (äldre versioner) eller centrerade precis under " +"översta fältet (nyare versioner). I Ubuntu Unity visas aviseringar längst " +"upp höger." -#: C/label.page:25(title) -msgid "Move a conversation to a folder or label" -msgstr "Etikettera eller flytta en konversation" +#. (itstool) path: item/title +#: C/preferences.page:54 +msgid "Watch for new mail when closed" +msgstr "Kontrollera om det finns ny e-post vid avslut" -#: C/label.page:26(p) +#. (itstool) path: item/p +#: C/preferences.page:55 msgid "" -"To move one or more conversations to a folder or label, first select the " -"conversation(s), then do either of the following:" +"Geary will watch your accounts for new mail even when the main window is not " +"open. To do this, it will silently start when you log in to your computer, " +"and it will continue to run after you close all windows." msgstr "" -"För att flytta en eller flera konversationer till en mapp eller etikett, " -"markera först konversation(en/erna) och gör en av följande:" +"Geary kommer att kontrollera efter ny e-post även när huvudfönstret inte är " +"öppet. För att göra detta kommer det att starta tyst när du loggar in i din " +"dator och fortsätter att köra efter att du har stängt alla fönster." -#: C/label.page:29(p) +#. (itstool) path: page/title +#: C/search.page:10 +msgid "Search" +msgstr "Sök" + +#. (itstool) path: page/p +#: C/search.page:12 msgid "" -"Click the Move button on the toolbar and select a folder or label " -"from the resulting drop-down menu." +"Geary supports a per-account full text search. To start a search, select a " +"folder associated with the account you'd like to search against. Then click " +"the search box in the toolbar (or press CtrlS) and start typing. Results will appear after a brief delay." msgstr "" -"Klicka på knappen Flytta i verktygsfältet och välj en mapp eller " -"en etikett från rullgardinsmenyn." +"Geary stöder textsökning per konto. För att påbörja en sökning, välj mappen " +"associerad med kontot du vill söka i. Klicka i sökrutan i verktygsfältet " +"(eller tryck ned CtrlS) och börja " +"skriva. Resultat kommer att visas efter en kort fördröjning." -#: C/label.page:31(p) +#. (itstool) path: page/p +#: C/search.page:16 msgid "" -"Drag the conversation(s) from the conversation list to the folder or label " -"in the sidebar." +"The full text search includes email text, email addresses (to, from, and " +"cc), subject lines and attachment filenames." msgstr "" -"Dra konversation(en/erna) från konversationslistan till mappen eller " -"etiketten i sidopanelen." +"Textsökningen omfattar e-posttext, e-postadresser (till, från och cc), " +"ämnesrader och bilagors filnamn." -#. When image changes, this message will be marked fuzzy or untranslated for you. -#. It doesn't matter what you translate it to: it's not used at all. -#: C/index.page:5(None) -msgid "@@image: 'figures/geary.svg'; md5=18b50c9e10fe5256ae1cb12aaa3a7600" -msgstr "@@image: 'figures/geary.svg'; md5=18b50c9e10fe5256ae1cb12aaa3a7600" +#. (itstool) path: page/p +#: C/search.page:19 +msgid "" +"Keywords that match your search are highlighted in the message view. Geary " +"will match different forms of the same word, for example searching for \"walk" +"\" will also match \"walking\" and \"walked.\"" +msgstr "" +"Sökträffar markeras i meddelandevyn. Geary kommer att få träff på olika " +"former av samma ord, exempelvis ger sökning på ”hej” också träff på ”hejsan” " +"och ”hejdå”." -#: C/index.page:5(title) -msgid " Geary" -msgstr " Geary" +#. (itstool) path: section/title +#: C/search.page:23 +msgid "Search operators" +msgstr "Sökoperator" -#: C/index.page:9(title) -msgid "Introduction" -msgstr "Introduktion" +#. (itstool) path: section/p +#: C/search.page:24 +msgid "Geary supports the following operators to limit the scope of searches:" +msgstr "" +"Geary stödjer följande operatorer för att begränsa omfattningen av sökningar:" -#: C/index.page:13(title) -msgid "Using Geary" -msgstr "Använda Geary" +#. (itstool) path: td/p +#: C/search.page:27 +msgid "attachment:filename" +msgstr "bilaga:filnamn" + +#. (itstool) path: td/p +#: C/search.page:28 +msgid "Finds messages with attachments whose name matches filename." +msgstr "Hittar meddelanden med bilagor vars namn matchar filnamn." + +#. (itstool) path: td/p +#: C/search.page:31 +msgid "bcc:recipient" +msgstr "bcc:mottagare" + +#. (itstool) path: td/p +#: C/search.page:32 +msgid "Finds messages where recipient matches the BCC header." +msgstr "Hittar meddelanden där mottagare matchar BCC-rubrik." + +#. (itstool) path: td/p +#: C/search.page:35 +msgid "body:text" +msgstr "innehåll:text" + +#. (itstool) path: td/p +#: C/search.page:36 +msgid "Finds messages whose body contains text." +msgstr "Hittar meddelanden vars text innehåller text." + +#. (itstool) path: td/p +#: C/search.page:39 +msgid "cc:recipient" +msgstr "cc:mottagare" + +#. (itstool) path: td/p +#: C/search.page:40 +msgid "Finds messages where recipient matches the CC header." +msgstr "Hittar meddelanden där mottagare matchar CC-rubrik." + +#. (itstool) path: td/p +#: C/search.page:43 +msgid "from:sender" +msgstr "från:avsändare" + +#. (itstool) path: td/p +#: C/search.page:44 +msgid "Finds messages where sender matches the From header." +msgstr "Hittar meddelanden där avsändare matchar med från-rubriken." + +#. (itstool) path: td/p +#: C/search.page:47 +msgid "is:read" +msgstr "är:läst" + +#. (itstool) path: td/p +#: C/search.page:48 +msgid "Finds messages that have been marked as read." +msgstr "Hittar meddelanden som har markerats som lästa." + +#. (itstool) path: td/p +#: C/search.page:51 +msgid "is:starred" +msgstr "är:stjärnmärkt" + +#. (itstool) path: td/p +#: C/search.page:52 +msgid "Finds messages that have been marked as starred." +msgstr "Hittar meddelanden som har markerats som stjärnmärkta." + +#. (itstool) path: td/p +#: C/search.page:55 +msgid "is:unread" +msgstr "är:oläst" + +#. (itstool) path: td/p +#: C/search.page:56 +msgid "Finds messages that have been marked as not read." +msgstr "Hittar meddelanden som har markerats som ej lästa." -#: C/index.page:17(title) -msgid "Bugs" -msgstr "Fel" +#. (itstool) path: td/p +#: C/search.page:59 +msgid "subject:text" +msgstr "ämne:text" + +#. (itstool) path: td/p +#: C/search.page:60 +msgid "Finds messages whose subject contains text." +msgstr "Hittar meddelanden vars ämne innehåller text." -#: C/bugs.page:8(title) -msgid "Think you've found a bug?" -msgstr "Har du kanske hittat ett fel?" +#. (itstool) path: td/p +#: C/search.page:63 +msgid "to:recipient" +msgstr "till:mottagare" -#: C/bugs.page:9(p) +#. (itstool) path: td/p +#: C/search.page:64 msgid "" -"If you suspect you've found a bug in Geary, follow these steps to report it:" +"Finds messages where recipient matches the To, CC, or BCC header." msgstr "" -"Om du tror att du har hittat ett fel i Geary, följ dessa steg för att " -"rapportera det:" +"Hittar meddelanden där mottagare matchar någon av rubrikerna " +"till, cc eller bcc." -#: C/bugs.page:11(p) +#. (itstool) path: section/p +#: C/search.page:68 msgid "" -"Search Geary's bug database to see if someone else has reported the " -"bug." +"As a special case, the bcc, cc, from, and to operators support me as their " +"argument, which searches for the account's email address in the appropriate " +"context." msgstr "" -"Sök i Gearys feldatabas för att se om någon annan redan har rapporterat felet." +"Som ett specialfall stöder operatorerna bcc, cc, från och till att ta mig som sitt argument, vilket söker efter kontots e-postadress i " +"sammanhanget." -#: C/bugs.page:13(p) +#. (itstool) path: page/title +#: C/shortcuts.page:10 +msgid "Keyboard shortcuts" +msgstr "Tangentbordsgenvägar" + +#. (itstool) path: page/p +#: C/shortcuts.page:12 +msgid "" +"Geary has keyboard shortcuts for most common operations. Use the built-in " +"keyboard shortcuts help in Geary to discover the full list. This can be " +"accessed via the application menu: GearyKeyboard " +"Shortcuts or using the keyboard shortcuts listed below." +msgstr "" +"Geary har tangentbordsgenvägar för de flesta vanliga operationer. Använd den " +"inbyggda hjälpen för tangentbordsgenvägar i Geary för att se den " +"fullständiga listan. Denna kan kommas åt via programmenyn: " +"GearyTangentbordsgenvägar eller genom " +"att använda tangentbordsgenvägarna listade nedan." + +#. (itstool) path: page/p +#: C/shortcuts.page:18 +msgid "" +"The following keyboard shortcuts can be used to access on-line help from " +"Geary:" +msgstr "" +"Följande tangentbordsgenvägar kan användas för att komma åt hjälp på nätet " +"från Geary:" + +#. (itstool) path: td/p +#: C/shortcuts.page:22 +msgid "Display this User Manual" +msgstr "Visa denna användarhandbok" + +#. (itstool) path: td/p +#: C/shortcuts.page:23 +msgid "F1" +msgstr "F1" + +#. (itstool) path: td/p +#: C/shortcuts.page:26 +msgid "Display all keyboard shortcuts" +msgstr "Visa alla tangentbordsgenvägar" + +#. (itstool) path: td/p +#: C/shortcuts.page:27 msgid "" -"Don't see your bug listed? Congratulations! You've found a new bug. To " -"create an bug report, create an account on GNOME's Bugzilla and file a new bug. Be as specific as you can and describe the steps to reproduce it. " -"Don't forget to include details about your operating system and what version " -"of Geary you're running." +"Ctrl? or CtrlF1" msgstr "" -"Listas inte ditt fel? Gratulerar! Du har hittat ett nytt fel. För att skapa " -"en felrapport, skapa ett konto i GNOMEs Bugzilla och rapportera ett fel. " -"Var så specifik du kan och beskriv stegen för att framkalla den. Glöm inte " -"att skicka med detaljer om ditt operativsystem och vilken version av Geary " -"du kör." +"Ctrl? eller CtrlF1" -#: C/bugs.page:18(p) +#. (itstool) path: page/title +#: C/star.page:10 +msgid "Star a message or mark it as read/unread" +msgstr "Stjärnmärk ett meddelande eller markera det som läst/ej läst" + +#. (itstool) path: section/title +#: C/star.page:12 +msgid "Star messages" +msgstr "Stjärnmärk meddelanden" + +#. (itstool) path: section/p +#: C/star.page:13 msgid "" -"For general inquiries, please join the Geary mailing list." +"You can star messages to indicate that they're important to you. To mark a " +"conversation with a star, click its star icon in the conversation list. You " +"can star an individual message by clicking the star at the upper right of " +"the message itself." msgstr "" -"För generella frågor, anslut dig till Gearys sändlista." +"Du kan stjärnmärka meddelanden för att indikera att de är viktiga för dig. " +"För att markera en konversation med en stjärna, klicka på dess stjärnikon i " +"konversationslistan. Du kan stjärnmärka ett individuellt meddelande genom " +"att klicka på stjärnan längst upp till höger i meddelandet." -#: C/archive.page:10(title) -msgid "Delete or archive a message" -msgstr "Ta bort eller arkivera ett meddelande" +#. (itstool) path: section/p +#: C/star.page:15 +msgid "" +"With Gmail accounts, starred messages appear in the Starred folder in the " +"folder list." +msgstr "" +"Vad gäller Gmail-konton så kommer stjärnmärkta meddelanden att visas i " +"mappen Stjärnmärkt." -#: C/archive.page:12(p) +#. (itstool) path: section/title +#: C/star.page:18 +msgid "Mark messages as read or unread" +msgstr "Markera meddelanden som lästa eller olästa" + +#. (itstool) path: section/p +#: C/star.page:19 msgid "" -"When you use Geary with a Gmail account, Geary lets you archive " -"messages. The Archive toolbar button archives the selected " -"conversation(s). Archived messages appear in the All Mail folder." +"Geary marks messages as read automatically as you read them. To manually " +"toggle a conversation as read or unread, click the circle icon in the " +"conversation list." msgstr "" -"När du använder Geary med ett Gmail-konto låter Geary dig arkivera " -"meddelanden. Knappen Arkivera i verktygsfältet arkiverar de " -"markerade konversationerna. Arkiverade meddelanden visas i mappen All e-" -"post." +"Geary markerar meddelanden som automatiskt lästa allt eftersom du läser dem. " +"För att manuellt växla en konversation som läst eller oläst, klicka på " +"cirkelikonen i konversationslistan." -#: C/archive.page:16(p) +#. (itstool) path: section/p +#: C/star.page:22 msgid "" -"With other mail servers, you can trash or delete, but not archive, messages. " -"To move one or more conversations to the Trash folder, select " -"them and press the Trash button on the toolbar. To permanently " -"delete the conversations, hold down Shift and press the " -"Delete button that appears in place of the Trash " -"button." +"Alternately, the Mark as Unread in the Mark menu on " +"the toolbar can be used to toggle the read status of the selected " +"conversation(s)." msgstr "" -"Med andra e-postservar kan du ta bort eller flytta till papperskorgen, men " -"inte arkivera meddelanden. För att flytta en eller flera konversation till " -"Papperskorgen, markera dem och tryck ned Papperskorg i " -"verktygsfältet. För att permanent ta bort konversationerna , håll nere " -"Skift och tryck ned Ta bort som visas istället för " -"knappen Papperskorg." +"Alternativt kan Markera som oläst i menyn Markera i " +"verktygsfältet användas för att växla lässtatusen för en eller flera " +"markerade konversationer." -#: C/archive.page:21(p) +#. (itstool) path: section/p +#: C/star.page:25 msgid "" -"Delete is not available from every folder, such as Search. Delete is also " -"unavailable for Gmail. For Gmail, Trash will move messages to the " -"Trash folder on the server, where the user can then manually delete them. " -"The server will automatically remove trashed messages after 30 days." +"To mark an individual message as read, select Mark as Read from " +"the dropdown menu." msgstr "" -"Ta bort är inte tillgängligt från varje mapp som till exempel Sök. Ta bort " -"är inte heller tillgängligt för Gmail. För Gmail kommer Papperskorg att flytta meddelanden till Skräpkorgen på servern där användaren " -"manuellt kan ta bort dem. Servern tar automatiskt bort meddelanden i " -"papperskorgen efter 30 dagar." +"För att markera ett individuellt meddelande som läst, markera Markera " +"som läst från rullgardinsmenyn." -#: C/accounts.page:10(title) -msgid "Accounts" -msgstr "Konton" +#. (itstool) path: page/title +#: C/write.page:9 +msgid "Write a message" +msgstr "Skriv ett meddelande" -#: C/accounts.page:13(title) -msgid "Adding accounts" -msgstr "Lägga till konton" +#. (itstool) path: section/title +#: C/write.page:12 +msgid "Composing and replying" +msgstr "Skriva meddelanden och svara" -#: C/accounts.page:15(p) +#. (itstool) path: section/p +#: C/write.page:13 msgid "" -"The first time you start Geary, you will be prompted to add an email " -"account. On this screen, select if your account is Gmail, Yahoo, Outlook." -"com, or other. For other account types, you will need to enter your IMAP and " -"SMTP login settings manually." +"To compose a new message in Geary, press the New Message button " +"on the toolbar." msgstr "" -"Den första gången du startar Geary kommer du att frågas om att lägga till " -"ett e-postkonto. Vid denna dialog, markera om ditt konto är Gmail, Yahoo, " -"Outlook.com eller ett annat. För andra kontotyper behöver du ange dina IMAP- " -"och SMTP-inställningar manuellt." +"För att skapa ett nytt meddelande i Geary, tryck ned knappen Nytt " +"meddelande i verktygsfältet." -#: C/accounts.page:19(p) +#. (itstool) path: section/p +#: C/write.page:16 msgid "" -"Additional accounts can be added from the Accounts dialog. The " -"Accounts option is available in either Geary's application menu " -"or the gear menu in the upper-right of the toolbar. (The location depends on " -"the install desktop shell. For GNOME Shell and Unity, the application menu " -"is available near the top-left corner of the screen.) Alternately, " -"CtrlM will open the Accounts dialog. " -"To add an account, click the + button." +"To reply to a message, open the message menu in the upper right corner of " +"the message and choose Reply, Reply All or " +"Forward. You can also reply to the last message in a conversation " +"via the Reply, Reply All or Forward buttons " +"on the toolbar." msgstr "" -"Ytterligare konton kan läggas till från kontodialogen. Alternativet " -"Konton är tillgängligt i antingen Gearys programmeny eller " -"kugghjulsmenyn i det över högra hörnet av verktygsfältet. (Platsen beror på " -"det installerade skrivbordsskalet. I GNOME Shell och Unity finns " -"programmenyn i det övre vänstra hörnet av skärmen.) Alternativt kommer " -"CtrlM att öppna kontodialogen. För " -"att lägga till ett konto, klicka på ”+”-knappen." +"För att svara på ett meddelande, öppna meddelandemenyn i övre högra hörnet " +"av meddelandet och välj Svara, Svara alla eller " +"Vidarebefordra. Du kan också svara på det senaste meddelandet i " +"en konversation med knapparna Svara, Svara alla eller " +"Vidarebefordra i verktygsfältet." -#: C/accounts.page:28(title) -msgid "Editing existing accounts" -msgstr "Redigera existerande konton" +#. (itstool) path: section/title +#: C/write.page:21 +msgid "Features" +msgstr "Egenskaper" -#: C/accounts.page:30(p) +#. (itstool) path: section/p +#: C/write.page:23 msgid "" -"From the Accounts dialog, select an account and click the pencil icon to " -"change various settings. Please note that Geary cannot change server " -"settings on an existing account. If you need to change your IMAP or SMTP " -"server, you will need to delete the account and re-add it." +"Geary's email composer lets you adjust the font, size and color of text. You " +"can also insert hyperlinks into messages." msgstr "" -"Från kontodialogen, välj ett konto och klicka på pennikonen för att ändra " -"olika inställningar. Observera att Geary inte kan ändra server-inställningar " -"för ett existerande konto. Om du behöver ändra din IMAP eller SMTP-server " -"måste du ta bort kontot och lägga till det igen." +"Gearys e-postredigerare låter dig justera en texts typsnitt, storlek och " +"färg. Du kan också infoga länkar i meddelanden." -#: C/accounts.page:34(p) +#. (itstool) path: section/p +#: C/write.page:25 msgid "" -"To change the order that accounts are displayed in the folder list, drag the " -"accounts in the Accounts dialog to the desired order." +"Geary can also send plain text messages. In the drop-down menu, check or " +"uncheck \"Rich Text\" to toggle between plain text and rich text mode." msgstr "" -"För att ändra ordningen på de konton som visas i mapplistan, dra kontona i " -"kontodialogen till önskad ordning." +"Geary kan också skicka vanliga textmeddelanden. I rullgardinsmenyn, kryssa i " +"eller av ”Rich text” för att växla mellan lägena vanlig text och rich text." -#: C/accounts.page:37(p) -msgid "There are some advanced options available when editing accounts:" +#. (itstool) path: section/p +#: C/write.page:28 +msgid "" +"You can attach a file to a message you're writing in either of these ways:" msgstr "" -"Det finns några avancerade alternativ tillgängliga vid redigering av konton:" +"Du kan bifoga en fil med ett meddelande du skriver på ett av dessa sätt:" -#: C/accounts.page:39(p) +#. (itstool) path: item/p +#: C/write.page:30 msgid "" -"The Save sent mail checkbox controls whether Geary will push " -"successfully sent messages up to the account's Sent Mail folder. " -"For Gmail accounts, this happens automatically. Yahoo and some other " -"accounts can be configured to do this automatically as well. For other " -"accounts, if you disable this setting, you may be unable to view messages " -"you've sent." +"Press the Attach File button at the lower left of the composer " +"window, then select a file to attach." msgstr "" -"Kryssrutan Spara skickad e-post kontrollerar huruvida Geary " -"kommer att skicka vidare e-post som skickats till kontots mapp för " -"Skickad e-post. För Gmail-konton sker detta automatiskt. Yahoo " -"och några andra konton kan också konfigureras till att göra detta " -"automatiskt. För övriga konton gäller att om du inaktiverar denna " -"inställning så kan du inte se de meddelanden du skickat." +"Tryck ned knappen Bifoga fil längst ned till vänster på " +"redigerarfönstret och välj sedan fil att bifoga." -#: C/accounts.page:45(p) +#. (itstool) path: item/p +#: C/write.page:32 msgid "" -"The Sign emails checkbox indicates whether a signature will be " -"automatically inserted when a composer is opened. You may enter the " -"signature into the box immediately below. You may use HTML tags to style the " -"text. Switch to a preview of the signature using the buttons to the right." +"Drag the file from the Nautilus file manager to the composer window, and " +"drop it either on the text fields at the top of the window or on the toolbar " +"at the bottom." msgstr "" -"Kryssrutan Signera e-post visar huruvida en signatur automatiskt " -"infogas när ett redigerarfönster öppnas. Du kan ange signaturen i rutan " -"direkt under. Du kan använda HTML-taggar för att formge texten. Växla till " -"en förhandsvisning av signaturen genom att använda knapparna till höger." +"Dra filen från Nautilus filhanterare till redigerarfönstret och släpp den, " +"antingen på textfälten överst i fönstret eller på verktygsfältet längst ned." -#: C/accounts.page:50(p) +#. (itstool) path: section/p +#: C/write.page:36 msgid "" -"If you leave the signature in the Accounts dialog blank, Geary will use the " -".signature file in your home directory, if it exists. This file " -"may contain either plain text or HTML markup. In the latter case, the markup " -"will be inserted directly into the composer, without any escaping." +"A number of keyboard shortcuts are available in the composer; see for details." msgstr "" -"Om du lämnar signaturen i kontodialogen tom kommer Geary att använda filen " -".signature i din hemkatalog om den existerar. Denna fil kan " -"innehålla vanlig text eller HTML-markup. I det senare fallet kommer markupen " -"att infogas direkt i redigeraren utan kontrollsekvenser." +"Ett antal tangentbordsgenvägar är tillgängliga i redigeraren; se för detaljer." -#: C/accounts.page:55(p) +#. (itstool) path: section/p +#: C/write.page:38 msgid "" -"The Download mail drop-down allows you to configure how much mail " -"Geary will keep locally. Geary can only use locally available mail when " -"searching and forming conversations." +"You may specify a signature to be inserted into the composer in the dialog." msgstr "" -"Rullgardinsmenyn Hämta e-post låter dig konfigurera hur mycket e-" -"post Geary har lokalt. Geary kan endast använda lokalt tillgänglig e-post " -"vid sökning och skapandet av konversationer." +"Du kan ange en signatur att infoga i redigeraren i dialogen ." -#: C/accounts.page:63(title) -msgid "Removing accounts" -msgstr "Ta bort konton" +#. (itstool) path: section/title +#: C/write.page:43 +msgid "Drafts" +msgstr "Utkast" -#: C/accounts.page:65(p) +#. (itstool) path: section/p +#: C/write.page:45 msgid "" -"To delete an account, open the Accounts dialog, select the account, and " -"press the - button. Geary will delete all information associated with the " -"account." +"For mail servers that support drafts, Geary will automatically save the " +"message as you type. If you close the composer without sending, Geary will " +"prompt you to keep the draft or to discard it." msgstr "" -"För att ta bort ett konto, öppna konto-dialogen, välj konto och tryck ned -. " -"Geary kommer att ta bort all information associerad med kontot." +"För e-postservrar som stöder utkast kommer Geary automatiskt att spara " +"meddelandet allt eftersom du skriver. Om du stänger redigeraren utan att " +"skicka det kommer Geary att fråga om utkastet ska behållas eller kastas." -#. Put one translator per line, in the form of NAME , YEAR1, YEAR2 -#: C/accounts.page:0(None) -msgid "translator-credits" -msgstr "Josef Andersson , 2017" +#. (itstool) path: section/p +#: C/write.page:48 +msgid "" +"To edit an existing draft, select the Drafts folder in the folder list, " +"select the message, and click \"Edit Draft\" in the message viewer." +msgstr "" +"För att redigera ett existerande utkast, välj mappen Utkast i mapplistan, " +"välj meddelanden och klicka på ”Redigera utkast” i meddelandevisaren." + +#. (itstool) path: section/p +#: C/write.page:51 +msgid "Geary deletes the draft when you send the message." +msgstr "Geary tar bort utkastet när du skickar meddelandet." + +#~ msgid "Geary has keyboard shortcuts for most common operations." +#~ msgstr "Geary har tangentbordsgenvägar för de vanligaste åtgärderna." + +#~ msgid "Compose a new message" +#~ msgstr "Skapa ett nytt meddelande" + +#~ msgid "CtrlN or N" +#~ msgstr "CtrlN eller N" + +#~ msgid "Reply to sender" +#~ msgstr "Svara avsändare" + +#~ msgid "CtrlR or R" +#~ msgstr "CtrlR eller R" + +#~ msgid "Reply to all" +#~ msgstr "Svara alla" + +#~ msgid "" +#~ "CtrlShiftR or " +#~ "ShiftR" +#~ msgstr "" +#~ "CtrlSkiftR eller " +#~ "SkiftR" + +#~ msgid "Forward" +#~ msgstr "Vidarebefordra" + +#~ msgid "Archive" +#~ msgstr "Arkivera" + +#~ msgid "A" +#~ msgstr "A" + +#~ msgid "Trash" +#~ msgstr "Flytta till Papperskorg" + +#~ msgid "Delete or Backspace" +#~ msgstr "Delete eller Backsteg" + +#~ msgid "Delete" +#~ msgstr "Ta bort" + +#~ msgid "" +#~ "ShiftDelete or ShiftBackspace" +#~ msgstr "" +#~ "SkiftDelete eller " +#~ "SkiftBacksteg" + +#~ msgid "Star" +#~ msgstr "Stjärnmärk" + +#~ msgid "S" +#~ msgstr "S" + +#~ msgid "Unstar" +#~ msgstr "Ta bort stjärnmärkning" + +#~ msgid "D" +#~ msgstr "D" + +#~ msgid "Mark read" +#~ msgstr "Markera som läst" + +#~ msgid "" +#~ "CtrlI or ShiftI" +#~ msgstr "" +#~ "CtrlI eller SkiftI" + +#~ msgid "Mark unread" +#~ msgstr "Markera som oläst" + +#~ msgid "" +#~ "CtrlU or ShiftU" +#~ msgstr "" +#~ "CtrlU eller SkiftU" + +#~ msgid "Move the conversation" +#~ msgstr "Flytta konversationen" + +#~ msgid "M" +#~ msgstr "M" + +#~ msgid "Label the conversation" +#~ msgstr "Lägg till en etikett till konversationen" + +#~ msgid "L" +#~ msgstr "L" + +#~ msgid "Jump to next (older) conversation" +#~ msgstr "Hoppa till nästa (äldre) konversation" + +#~ msgid "J" +#~ msgstr "J" + +#~ msgid "Jump to previous (newer) conversation" +#~ msgstr "Hoppa till föregående (nyare) konversation" + +#~ msgid "K" +#~ msgstr "K" + +#~ msgid "Toggle spam" +#~ msgstr "Växla skräppost" + +#~ msgid "CtrlJ or !" +#~ msgstr "CtrlJ eller !" + +#~ msgid "Quit" +#~ msgstr "Avsluta" + +#~ msgid "Ctrl" +#~ msgstr "Ctrl" + +#~ msgid "Q" +#~ msgstr "Q" + +#~ msgid "Zoom in" +#~ msgstr "Zooma in" + +#~ msgid "Ctrl= or =" +#~ msgstr "Ctrl= eller =" + +#~ msgid "Zoom out" +#~ msgstr "Zooma ut" + +#~ msgid "Ctrl- or -" +#~ msgstr "Ctrl- eller -" + +#~ msgid "Reset zoom" +#~ msgstr "Återställ zoom" + +#~ msgid "Ctrl0 or 0" +#~ msgstr "Ctrl0 eller 0" + +#~ msgid "Close composer window" +#~ msgstr "Stäng redigerarfönstret" + +#~ msgid "W" +#~ msgstr "W" + +#~ msgid "Jump to search box" +#~ msgstr "Hoppa till sökfältet" + +#~ msgid "Find in current conversation" +#~ msgstr "Hitta i aktuell konversation" + +#~ msgid "F" +#~ msgstr "F" + +#~ msgid "Find next in current conversation" +#~ msgstr "Hitta nästa i aktuell konversation" + +#~ msgid "G" +#~ msgstr "G" + +#~ msgid "Find previous in current conversation" +#~ msgstr "Hitta föregående i aktuell konversation" + +#~ msgid "Shift" +#~ msgstr "Skift" + +#~ msgid "Composer shortcuts" +#~ msgstr "Redigeringsgenvägar" + +#~ msgid "These shortcuts are active whenever focus is in a composer." +#~ msgstr "Dessa genvägar är aktiva då redigerare har fokus." + +#~ msgid "Attach file" +#~ msgstr "Bifoga fil" + +#~ msgid "T" +#~ msgstr "T" + +#~ msgid "Quote text" +#~ msgstr "Citera text" + +#~ msgid "]" +#~ msgstr "]" + +#~ msgid "Unquote text" +#~ msgstr "Avcitera text" + +#~ msgid "[" +#~ msgstr "[" + +#~ msgid "Close composer" +#~ msgstr "Stäng redigerare" + +#~ msgid "CtrlW or Esc" +#~ msgstr "CtrlW eller Esc" + +#~ msgid "Detach composer" +#~ msgstr "Koppla loss redigerare" + +#~ msgid "These shortcuts are only active in composers in rich text mode." +#~ msgstr "Dessa genvägar är endast aktiva i redigerarens ”rich text”-läge." + +#~ msgid "Bold text" +#~ msgstr "Fet text" + +#~ msgid "B" +#~ msgstr "B" + +#~ msgid "Italicize text" +#~ msgstr "Kursiv text" + +#~ msgid "I" +#~ msgstr "I" + +#~ msgid "Underline text" +#~ msgstr "Understruken text" + +#~ msgid "U" +#~ msgstr "U" + +#~ msgid "Strike text" +#~ msgstr "Genomstruken text" + +#~ msgid "Insert a link" +#~ msgstr "Infoga en länk" + +#~ msgid "Remove formatting" +#~ msgstr "Ta bort formatering" + +#~ msgid "Space" +#~ msgstr "Blanksteg" + +#~ msgid "Keyboard navigation" +#~ msgstr "Tangentbordsnavigering" + +#~ msgid "" +#~ "These shortcuts can be used to move the keyboard focus in the main window." +#~ msgstr "" +#~ "Dessa kortkommandon kan användas för att flytta tangentbordets fokus i " +#~ "huvudfönstret." + +#~ msgid "Move focus to the next/previous pane" +#~ msgstr "Flytta fokus till nästa/föregående panel" + +#~ msgid "" +#~ "F6 / ShiftF6" +#~ msgstr "" +#~ "F6 / SkiftF6" + +#~ msgid "Move focus to conversation list" +#~ msgstr "Flytta fokus till konversationslistan" + +#~ msgid "Move to the next message in a conversation" +#~ msgstr "Gå till nästa meddelande i en konversationen" + +#~ msgid "Move to the next/previous message in a conversation" +#~ msgstr "Gå till nästa/föregående meddelande i en konversation" + +#~ msgid "Move to the first/last message in a conversation" +#~ msgstr "Gå till första/sista meddelandet i en konversation" + +#~ msgid "" +#~ "CtrlHome / CtrlEnd" +#~ msgstr "" +#~ "CtrlHome / CtrlEnd" + +#~ msgid "filename" +#~ msgstr "filnamn" + +#~ msgid "attachment:" +#~ msgstr "bilaga:" + +#~ msgid "recipient" +#~ msgstr "mottagare" + +#~ msgid "bcc:" +#~ msgstr "bcc:" + +#~ msgid "text" +#~ msgstr "text" + +#~ msgid "body:" +#~ msgstr "innehåll:" + +#~ msgid "cc:" +#~ msgstr "cc:" + +#~ msgid "sender" +#~ msgstr "avsändare" + +#~ msgid "from:" +#~ msgstr "från:" + +#~ msgid "is:read" +#~ msgstr "är:läst" + +#~ msgid "is:starred" +#~ msgstr "är:stjärnmärkt" + +#~ msgid "is:unread" +#~ msgstr "är:oläst" + +#~ msgid "subject:" +#~ msgstr "ämne:" + +#~ msgid "to:" +#~ msgstr "till:" + +#~ msgid "Composer" +#~ msgstr "Redigerare" + +#~ msgid "Enable spell checking" +#~ msgstr "Aktivera stavningskontroll" + +#~ msgid "" +#~ "When set, Geary automatically spell checks a message as you write it, " +#~ "underlying each misspelled word in red." +#~ msgstr "" +#~ "Om satt stavningskontrollerar Geary automatiskt ett meddelande under " +#~ "skapandet och stryker under varje felstavat ord i rött." + +#~ msgid "Always watch for new mail" +#~ msgstr "Kontrollera alltid om det finns ny e-post" + +#~ msgid "Bugs" +#~ msgstr "Fel" + +#~ msgid "Think you've found a bug?" +#~ msgstr "Har du kanske hittat ett fel?" + +#~ msgid "" +#~ "If you suspect you've found a bug in Geary, follow these steps to report " +#~ "it:" +#~ msgstr "" +#~ "Om du tror att du har hittat ett fel i Geary, följ dessa steg för att " +#~ "rapportera det:" + +#~ msgid "" +#~ "Search Geary's bug database to see if someone else has reported " +#~ "the bug." +#~ msgstr "" +#~ "Sök i Gearys feldatabas för att se om någon annan redan har " +#~ "rapporterat felet." + +#~ msgid "" +#~ "Don't see your bug listed? Congratulations! You've found a new bug. To " +#~ "create an bug report, create an account on GNOME's Bugzilla and file a " +#~ "new bug. Be as specific as you can and describe the steps to " +#~ "reproduce it. Don't forget to include details about your operating system " +#~ "and what version of Geary you're running." +#~ msgstr "" +#~ "Listas inte ditt fel? Gratulerar! Du har hittat ett nytt fel. För att " +#~ "skapa en felrapport, skapa ett konto i GNOMEs Bugzilla och rapportera ett " +#~ "fel. Var så specifik du kan och beskriv stegen för att framkalla " +#~ "den. Glöm inte att skicka med detaljer om ditt operativsystem och vilken " +#~ "version av Geary du kör." + +#~ msgid "" +#~ "For general inquiries, please join the Geary mailing list." +#~ msgstr "" +#~ "För generella frågor, anslut dig till Gearys sändlista." diff -Nru geary-0.12.4/help/tr/tr.po geary-3.32.0/help/tr/tr.po --- geary-0.12.4/help/tr/tr.po 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/help/tr/tr.po 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,1550 @@ +# Turkish translation for geary. +# Copyright (C) 2017 geary's COPYRIGHT HOLDER +# This file is distributed under the same license as the geary package. +# Muhammet Kara , 2018. +# Emin Tufan Çetin , 2017-2019. +# +msgid "" +msgstr "" +"Project-Id-Version: geary master\n" +"POT-Creation-Date: 2019-02-07 06:10+0000\n" +"PO-Revision-Date: 2019-02-10 11:55+0300\n" +"Last-Translator: Emin Tufan Çetin \n" +"Language-Team: Türkçe \n" +"Language: tr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: Gtranslator 3.30.1\n" + +#. Put one translator per line, in the form NAME , YEAR1, YEAR2 +msgctxt "_" +msgid "translator-credits" +msgstr "Emin Tufan Çetin , 2017, 2018, 2019" + +#. (itstool) path: page/title +#: C/accounts.page:10 +msgid "Accounts" +msgstr "Hesaplar" + +#. (itstool) path: section/title +#: C/accounts.page:13 +msgid "Adding accounts" +msgstr "Hesap ekleme" + +#. (itstool) path: section/p +#: C/accounts.page:15 +msgid "" +"The first time you start Geary, you will be prompted to add an email " +"account. On this screen, select if your account is Gmail, Yahoo, Outlook." +"com, or other. For other account types, you will need to enter your IMAP and " +"SMTP login settings manually." +msgstr "" +"Gearyʼyi ilk kez başlattığınızda e-posta hesabı eklemeniz istenecek. Bu " +"ekranda hesabınızın Gmail, Yahoo, Outlook.com veya diğer olduğunu " +"seçeceksiniz. Diğer hesap türleri için IMAP ve SMTP giriş ayarlarını elle " +"girmeniz gerekecektir." + +#. (itstool) path: section/p +#: C/accounts.page:19 +msgid "" +"Additional accounts can be added from the Accounts dialog. The " +"Accounts option is available in either Geary's application menu " +"or the gear menu in the upper-right of the toolbar. (The location depends on " +"the install desktop shell. For GNOME Shell and Unity, the application menu " +"is available near the top-left corner of the screen.) To add an account, " +"click the + button." +msgstr "" +"Ek hesaplar, Hesaplar iletişim penceresinden eklenebilir. Hesaplar seçeneği, Gearyʼnin uygulama menüsünde veya araç çubuğunun sağ üst " +"köşesindeki dişli menüde bulunabilir. (Konum, kurulu masaüstü kabuğuna " +"bağlıdır. GNOME Shell ve Unity için uygulama menüsü ekranın sol üst köşesine " +"yakın yerde bulunur.) Hesap eklemek için + düğmesine tıklayın." + +#. (itstool) path: section/title +#: C/accounts.page:27 +msgid "Editing existing accounts" +msgstr "Var olan hesapları düzenleme" + +#. (itstool) path: section/p +#: C/accounts.page:29 +msgid "" +"From the Accounts dialog, select an account and click the pencil icon to " +"change various settings. Please note that Geary cannot change server " +"settings on an existing account. If you need to change your IMAP or SMTP " +"server, you will need to delete the account and re-add it." +msgstr "" +"Hesaplar iletişim penceresinden hesap seçin ve çeşitli ayarları değiştirmek " +"için kalem simgesine tıklayın. Unutmayın ki Geary var olan hesabın sunucu " +"ayarlarını değiştiremez. Eğer IMAP veya SMTP sunucunuzu değiştirmek " +"istiyorsanız, hesabı silmeniz ve yeniden eklemeniz gerekecektir." + +#. (itstool) path: section/p +#: C/accounts.page:33 +msgid "" +"To change the order that accounts are displayed in the folder list, drag the " +"accounts in the Accounts dialog to the desired order." +msgstr "" +"Klasör listesinde gösterilen hesapların sırasını değiştirmek için, Hesaplar " +"iletişim penceresindeki hesapları istenen sıraya sürükleyin." + +#. (itstool) path: section/p +#: C/accounts.page:36 +msgid "There are some advanced options available when editing accounts:" +msgstr "" +"Hesapları düzenlerken kullanılabilicek bazı gelişmiş özellikler vardır:" + +#. (itstool) path: item/p +#: C/accounts.page:38 +msgid "" +"The Save sent mail checkbox controls whether Geary will push " +"successfully sent messages up to the account's Sent Mail folder. " +"For Gmail accounts, this happens automatically. Yahoo and some other " +"accounts can be configured to do this automatically as well. For other " +"accounts, if you disable this setting, you may be unable to view messages " +"you've sent." +msgstr "" +"Gönderilmiş postayı kaydet seçim kutusu, Gearyʼnin başarıyla " +"gönderilen iletileri hesabın Gönderilmiş Postalar klasörüne " +"itilip itilemeyeceğini denetler. Gmail hesapları için, bu kendiliğinden " +"gerçekleşir. Yahoo ve diğer bazı hesaplar da bunu kendiliğinden yapması için " +"yapılandırılabilir. Diğer hesaplar için, eğer bu ayarı devre dışı " +"bırakırsanız, gönderdiğiniz iletileri göremeyebilirsiniz." + +#. (itstool) path: item/p +#: C/accounts.page:44 +msgid "" +"The Sign emails checkbox indicates whether a signature will be " +"automatically inserted when a composer is opened. You may enter the " +"signature into the box immediately below. You may use HTML tags to style the " +"text. Switch to a preview of the signature using the buttons to the right." +msgstr "" +"E-postaları İmzala seçim kutusu, oluşturucu açıldığında imzanın " +"kendiliğinden eklenip eklenmeyeceğini denetler. İmzayı hemen aşağıda bulunan " +"kutunun içine girebilirsiniz. Metni biçimlendirmek için HTML etiketleri " +"kullanabilirsiniz. Sağdaki düğmeleri kullanarak imzanın ön izlemesine " +"geçebilirsiniz." + +#. (itstool) path: item/p +#: C/accounts.page:49 +msgid "" +"If you leave the signature in the Accounts dialog blank, Geary will use the " +".signature file in your home directory, if it exists. This file " +"may contain either plain text or HTML markup. In the latter case, the markup " +"will be inserted directly into the composer, without any escaping." +msgstr "" +"Eğer Hesaplar iletişim penceresindeki imzayı boş bırakırsanız, eğer varsa, " +"Geary ev dizininizdeki .signature dosyasını kullanacaktır. Bu " +"dosya düz metin de HTML biçimlendirmesi de içerebilir. İkinci durumda, " +"biçimlendirme doğrudan oluşturucunun içine herhangi bir boşluk olmadan " +"eklenecektir." + +#. (itstool) path: item/p +#: C/accounts.page:54 +msgid "" +"The Download mail drop-down allows you to configure how much mail " +"Geary will keep locally. Geary can only use locally available mail when " +"searching and forming conversations." +msgstr "" +"Posta indir açılırı, Gearyʼnin ne kadar postayı yerel olarak " +"tutacağını yapılandırmanızı sağlar. Geary, yerel olarak kullanılabilir olan " +"postayı yalnızca arama yaparken ve konuşmaları şekillendirirken kullanır." + +#. (itstool) path: section/title +#: C/accounts.page:62 +msgid "Removing accounts" +msgstr "Hesapları kaldırma" + +#. (itstool) path: section/p +#: C/accounts.page:64 +msgid "" +"To delete an account, open the Accounts dialog, select the account, and " +"press the - button. Geary will delete all information associated with the " +"account." +msgstr "" +"Hesabı silmek için, Hesaplar iletişim penceresini açın, hesabı seçin ve - " +"düğmesine basın. Geary, bu hesapla ilişkili tüm bilgiyi silecektir." + +#. (itstool) path: page/title +#: C/archive.page:10 +msgid "Delete or archive a message" +msgstr "İletiyi sil veya arşivle" + +#. (itstool) path: page/p +#: C/archive.page:12 +msgid "" +"When you use Geary with a Gmail account, Geary lets you archive " +"messages. The Archive toolbar button archives the selected " +"conversation(s). Archived messages appear in the All Mail folder." +msgstr "" +"Gearyʼyi Gmail hesabıyla kullandığınızda, Geary iletileri arşivlemenizi sağlar. Arşivle araç çubuğu düğmesi seçilen " +"konuşmaları arşivler. Tüm Postalar klasöründe gözükür." + +#. (itstool) path: page/p +#: C/archive.page:16 +msgid "" +"With other mail servers, you can trash or delete, but not archive, messages. " +"To move one or more conversations to the Trash folder, select " +"them and press the Trash button on the toolbar. To permanently " +"delete the conversations, hold down Shift and press the " +"Delete button that appears in place of the Trash " +"button." +msgstr "" +"Diğer posta sunucularıyla, iletleri çöpe atabilir veya silebilir ama " +"arşivleyemezsiniz. Bir veya daha çok konuşmayı Çöp klasörüne " +"taşımak için, konuşmaları seçin ve araç çubuğundaki Çöp düğmesine " +"tıklayın. Konuşmaları kalıcı olarak silmek için, Shift tuşuna " +"basılı tutun ve Çöp düğmesinin olduğu yerde beliren Sil düğmesine basın." + +#. (itstool) path: page/p +#: C/archive.page:21 +msgid "" +"Delete is not available from every folder, such as Search. Delete is also " +"unavailable for Gmail. For Gmail, Trash will move messages to the " +"Trash folder on the server, where the user can then manually delete them. " +"The server will automatically remove trashed messages after 30 days." +msgstr "" +"Arama klasörü gibi her klasörden silme işlemi yapılamaz. Gmail için silme " +"işlemi kullanılabilir değildir. Gmail için, Çöp iletileri " +"sunucudaki Çöp klasörüne taşıyacaktır, buradan kullanıcılar iletileri elle " +"silebilir. Sunucu çöpe atılmış iletileri 30 gün sonra kendiliğinden " +"kaldıracaktır." + +#. (itstool) path: page/title +#: C/bugs.page:10 +msgid "Found a bug?" +msgstr "Hata mı buldunuz?" + +#. (itstool) path: page/p +#: C/bugs.page:12 +msgid "" +"If you suspect you've found a bug in Geary, please get in touch about it so it can be " +"fixed." +msgstr "" +"Gearyʼde hata bulduğunuzu düşünüyorsanı lütfen bununla ilgili iletişime geçin ki " +"giderilebilsin." + +#. (itstool) path: page/p +#: C/bugs.page:16 +msgid "" +"To help diagnose the problem as fast as possible, please include the " +"following information:" +msgstr "" +"Sorunun hızlıca saptanmasına yardım etmek için lütfen şu bilgileri ekleyin:" + +#. (itstool) path: item/p +#: C/bugs.page:20 +msgid "Geary version and installation method (Package? Flathub? Source code?)" +msgstr "Geary sürümü ve kurulum yöntemi (Paket? Flathub? Kaynak kod?)" + +#. (itstool) path: item/p +#: C/bugs.page:22 +msgid "Your desktop (GNOME? KDE? Something else?)" +msgstr "Masaüstünüz (GNOME? KDE? Başka bir şey?)" + +#. (itstool) path: item/p +#: C/bugs.page:23 +msgid "" +"Your operating system and version (Ubuntu 16.04? Fedora 28? Rolled your own?)" +msgstr "" +"İşletim sisteminiz ve sürümü (Ubuntu 16.04? Fedora 28? Kendi yaptığınız?)" + +#. (itstool) path: item/p +#: C/bugs.page:25 +msgid "Email provider (Gmail, Yahoo!, Outlook.com, or someone else?)" +msgstr "E-posta sağlayıcı (Gmail, Yahoo!, Outlook.com veya daha başkası?)" + +#. (itstool) path: item/p +#: C/bugs.page:27 +msgid "Steps to reproduce the bug" +msgstr "Hatayı yeniden üretmek için adımlar" + +#. (itstool) path: item/p +#: C/bugs.page:28 +msgid "What happened?" +msgstr "Ne oldu?" + +#. (itstool) path: item/p +#: C/bugs.page:29 +msgid "What did you expect to happen?" +msgstr "Ne olmasını beklediniz?" + +#. (itstool) path: page/p +#: C/bugs.page:32 +msgid "Thanks for your help!" +msgstr "Yardımınız için teşekkürler!" + +#. (itstool) path: page/title +#: C/contributing.page:10 +msgid "Contribute to Geary" +msgstr "Gearyʼye katkıda bulun" + +#. (itstool) path: page/p +#: C/contributing.page:12 +msgid "" +"Want to help improve Geary? There are a number of ways you can contribute:" +msgstr "" +"Gearyʼyi iyileştirmek ister misiniz? Katkıda bulunabileceğiniz birkaç yol " +"vardır:" + +#. (itstool) path: item/p +#: C/contributing.page:16 +msgid "" +"Bug " +"reporting—report new bugs or request new features" +msgstr "" +"Hata " +"bildirimi—yeni hataları bildirin veya yeni özellikler isteyin" + +#. (itstool) path: item/p +#: C/contributing.page:19 +msgid "" +"User Experience " +"Design—research and develop Geary’s user experience" +msgstr "" +"Kullanıcı Deneyimi " +"Tasarımı—Geary’nin kullanıcı deneyimini araştırın ve geliştirin" + +#. (itstool) path: item/p +#: C/contributing.page:20 +msgid "" +"Development—fix bugs and add new features" +msgstr "" +"Geliştirme—hataları gider ve yeni özellikler ekle" + +#. (itstool) path: item/p +#: C/contributing.page:21 +msgid "" +"Translating—translate Geary’s user interface and user manual into new languages" +msgstr "" +"Çeviri—" +"Geary’nin kullanıcı arayüzünü ve kullanım kılavuzunu yeni dillere çevirin" + +#. (itstool) path: item/p +#: C/contributing.page:22 +msgid "" +"Join the " +"discussion—on the mailing list or IRC channel" +msgstr "" +"Tartışmaya katıl—posta listesinde veya IRC kanalında" + +#. (itstool) path: page/p +#: C/contributing.page:25 +msgid "Thanks for your help making Geary better!" +msgstr "Gearyʼyi daha iyi yapmaya yardım ettiğiniz için teşekkürler!" + +#. (itstool) path: title/media +#. This is a reference to an external file such as an image or video. When +#. the file changes, the md5 hash will change to let you know you need to +#. update your localized copy. The msgstr is not used at all. Set it to +#. whatever you like once you have updated your copy of the file. +#: C/index.page:5 +msgctxt "_" +msgid "external ref='figures/geary.svg' md5='18b50c9e10fe5256ae1cb12aaa3a7600'" +msgstr "" +"external ref='figures/geary.svg' md5='18b50c9e10fe5256ae1cb12aaa3a7600'" + +#. (itstool) path: page/title +#: C/index.page:5 +msgid " Geary" +msgstr " Geary" + +#. (itstool) path: section/title +#: C/index.page:8 +msgid "Introduction" +msgstr "Tanıtım" + +#. (itstool) path: section/title +#: C/index.page:12 +msgid "Using Geary" +msgstr "Gearyʼyi Kullanmak" + +#. (itstool) path: section/title +#: C/index.page:16 +msgid "Contributing and bug reporting" +msgstr "Katkıda bulunma ve hata bildirimi" + +#. (itstool) path: page/title +#: C/label.page:10 +msgid "Label or move a conversation" +msgstr "Konuşmayı etiketle veya taşı" + +#. (itstool) path: section/title +#: C/label.page:12 +msgid "Label a conversation" +msgstr "Konuşmayı etiketle" + +#. (itstool) path: section/p +#: C/label.page:13 +msgid "" +"Geary lets you apply one or more labels to each conversation. Geary " +"labels correspond to labels in Gmail, or ordinary folders in other mail " +"services." +msgstr "" +"Geary, her konuşmaya bir veya daha çok etiket uygulamanızı sağlar. " +"Geary etiketleri Gmailʼdeki etiketlerle veya diğer posta hizmetlerindeki " +"olağan klasörlerle uyuşmaktadır." + +#. (itstool) path: section/p +#: C/label.page:15 +msgid "" +"To label one or more conversations, first select the conversation(s), then " +"do either of the following:" +msgstr "" +"Bir veya daha çok konuşmayı etiketlemek için, ilk önce konuşmaları seçin, " +"ardından şunlardan birini yapın:" + +#. (itstool) path: item/p +#: C/label.page:18 +msgid "" +"Click the Label button on the toolbar and select a label from the " +"resulting drop-down menu." +msgstr "" +"Araç çubuğundaki Etiket düğmesine tıklayın ve oluşan açılır " +"menüden etiket seçin." + +#. (itstool) path: item/p +#: C/label.page:20 +msgid "" +"Hold down the Ctrl key and drag the conversation(s) from the " +"conversation list to the label in the sidebar." +msgstr "" +"Ctrl tuşuna basılı tutun ve konuşmaları konuşma listesinden kenar " +"çubuğundaki etikete sürükleyin." + +#. (itstool) path: section/title +#: C/label.page:25 +msgid "Move a conversation to a folder or label" +msgstr "Konuşmayı klasöre veya etikete taşı" + +#. (itstool) path: section/p +#: C/label.page:26 +msgid "" +"To move one or more conversations to a folder or label, first select the " +"conversation(s), then do either of the following:" +msgstr "" +"Bir veya daha çok konuşmayı klasöre veya etikete taşımak için, ilk önce " +"konuşmaları seçin, ardından şunlardan birini yapın:" + +#. (itstool) path: item/p +#: C/label.page:29 +msgid "" +"Click the Move button on the toolbar and select a folder or label " +"from the resulting drop-down menu." +msgstr "" +"Araç çubuğundaki Taşı düğmesine tıklayın ve oluşan açılır menüden " +"klasör veya etiket seçin." + +#. (itstool) path: item/p +#: C/label.page:31 +msgid "" +"Drag the conversation(s) from the conversation list to the folder or label " +"in the sidebar." +msgstr "" +"Konuşmaları konuşma listesinden kenar çubuğundaki klasöre veya etikete " +"sürükleyin." + +#. (itstool) path: page/title +#: C/limits.page:9 +msgid "Limitations" +msgstr "Kısıtlamalar" + +#. (itstool) path: page/p +#: C/limits.page:11 +msgid "" +"Geary is still in early development. Geary supports IMAP and has been tested " +"with Gmail, Yahoo, and the free Dovecot mail server. Experimental support " +"for Outlook.com is provided. Geary may not yet work well with some IMAP " +"servers. At this time Geary is still missing numerous features including " +"offline mode." +msgstr "" +"Geary hala erken gelişimdedir. Geary, IMAPʼı destekler. Gmail, Yahoo ve " +"özgür Dovecot posta sunucusuyla sınanmıştır. Outlook.com için deneysel " +"destek sağlanmıştır. Geary bazı IMAP sunucularıyla çok iyi çalışmayabilir. " +"Geary, şu anda çevrim dışı kipi de içeren birçok özellikten hala eksiktir." + +#. (itstool) path: page/p +#: C/limits.page:17 +msgid "" +"To learn more about the features we're working on and the future of Geary, " +"please visit Geary's wiki " +"page." +msgstr "" +"Gearyʼnin geleceğini ve üzerinde çalıştığımız özellikler hakkında daha " +"çoğunu öğrenmek için lütfen Gearyʼnin wiki sayfasını ziyaret edin." + +#. (itstool) path: page/title +#: C/overview.page:8 +msgid "Overview" +msgstr "Genel bakış" + +#. (itstool) path: page/p +#: C/overview.page:10 +msgid "" +"Geary is a lightweight email reader for the GNOME desktop. It works with mail servers that support the IMAP " +"protocol, including popular services such as Gmail, Yahoo Mail, and Outlook." +"com." +msgstr "" +"Geary, GNOME masaüstü için hafif bir " +"e-posta okuyucusudur. Gmail, Yahoo Mail ve Outlook.com gibi gözde hizmetleri " +"içeren, IMAP iletişim kuralını destekleyen posta sunucularıyla çalışır." + +#. (itstool) path: page/p +#: C/overview.page:14 +msgid "" +"Geary groups mail messages into conversations. A conversation " +"contains all messages in a single thread of discussion." +msgstr "" +"Geary, posta iletlerini konuşmalara kümeler. Konuşma, tüm iletileri " +"tek tartışma dizisi içinde tutar." + +#. (itstool) path: page/p +#: C/overview.page:17 +msgid "The main Geary window is divided into several areas:" +msgstr "Ana Geary penceresi birkaç alana bölünmüştür:" + +#. (itstool) path: section/title +#: C/overview.page:20 +msgid "Folder list" +msgstr "Klasör listesi" + +#. (itstool) path: section/p +#: C/overview.page:21 +msgid "" +"The folder list at the left displays all folders and " +"labels in your mail account. Geary uses the term label for " +"any folder that you have created to help organize your messages. (The Gmail " +"web interface also uses this term; most other mail services do not.)" +msgstr "" +"Sol taraftaki klasör listesi, posta hesabınızdaki tüm " +"klasörleri ve etiketleri gösterir. Geary, iletilerinizin " +"düzenine yardımcı olmak için oluşturduğunuz herhangi bir klasör için " +"etiket adlandırmasını kullanır. (Gmail web arayüzü de bu " +"adlandırmayı kullanmaktadır; diğer birçok posta hizmeti kullanmaz.)" + +#. (itstool) path: section/title +#: C/overview.page:28 +msgid "Conversation list" +msgstr "Konuşma listesi" + +#. (itstool) path: section/p +#: C/overview.page:29 +msgid "" +"The conversation list displays a list of conversations in the " +"selected folder. Newer conversations appear at the top." +msgstr "" +"Konuşma listesi, seçilen klasördeki konuşmaların listesini " +"gösterir. Yeni konuşmalar üstte gözükür." + +#. (itstool) path: section/p +#: C/overview.page:31 +msgid "" +"Each sender's name appears bold if there are unread messages from that " +"sender. If a conversation has more than one message, Geary displays a count " +"of messages in the conversation." +msgstr "" +"Her göndericinin adı, eğer bu göndericiden okunmamış iletiler varsa kalın " +"gözükür. Eğer konuşmanın birden çok iletisi varsa, Geary konuşmadaki " +"iletilerin sayısını gösterir." + +#. (itstool) path: section/p +#: C/overview.page:34 +msgid "" +"Geary does not automatically download all messages in all of your mail " +"folders. When you first visit your Inbox or any other folder, Geary " +"downloads the 50 most recent messages in that folder. To see more messages, " +"simply scroll down the conversation list and Geary will fetch more messages " +"automatically." +msgstr "" +"Geary, tüm posta klasörlerinizdeki tüm iletileri kendiliğinden indirmez. " +"Gelen Kutunuzu veya başka herhangi bir klasörü ilk kez ziyaret ettiğinizde, " +"Geary bu klasördeki en son 50 iletiyi indirir. Daha çok ileti görmek için, " +"konuşma listesini basitçe aşağı kaydırın ve Geary daha çok iletiyi " +"kendiliğinden getirecektir." + +#. (itstool) path: section/p +#: C/overview.page:36 +msgid "" +"Some commands in Geary can act on a group of conversations. To select " +"multiple conversations, hold down the Ctrl key and click each " +"conversation in turn in the conversation list. Alternatively, click the " +"first conversation in a range, then hold down Shift and click the " +"last conversation." +msgstr "" +"Gearyʼdeki bazı komutlar konuşmalar kümesi üzerinde etki eder. Birden çok " +"konuşma seçmek için, Ctrl tuşuna basılı tutun ve konuşma " +"listesinde sırasıyla her konuşmaya tıklayın. Diğer seçenek olarak, bir " +"aralıktaki ilk konuşmaya tıklayın, ardından Shiftʼe basılı tutun " +"ve son konuşmaya tıklayın." + +#. (itstool) path: section/title +#: C/overview.page:44 +msgid "Message area" +msgstr "İleti alanı" + +#. (itstool) path: section/p +#: C/overview.page:45 +msgid "" +"The message area displays all messages in the selected " +"conversation, with the oldest message at the top." +msgstr "" +"İleti alanı, seçilen konuşmadaki tüm iletileri gösterir, en eski " +"ileti üsttedir." + +#. (itstool) path: section/p +#: C/overview.page:47 +msgid "" +"At the upper right of each message, Geary displays a dropdown arrow that " +"lets you open the message menu with commands that operate on the " +"message." +msgstr "" +"Her iletinin sağ üst köşesinde, Geary, ileti üzerinde işlem yapan komutları " +"içeren ileti menüsünü açmanızı sağlayan bir açılır ok gösterir." + +#. (itstool) path: section/p +#: C/overview.page:49 +msgid "" +"When you view a conversation, Geary collapses messages that you've already " +"read. Click collapsed messages to expand them. Click an expanded message's " +"header to collapse it." +msgstr "" +"Konuşmaya bakarken, Geary zaten okuduğunuz iletileri kapatır. Kapalı " +"iletileri genişletmek için üzerine tıklayın. Kapatmak için genişletilmiş " +"iletinin başlığına tıklayın." + +#. (itstool) path: section/p +#: C/overview.page:50 +msgid "" +"Any attachments in a message appear at the bottom of the message. You can " +"click an attachment to open it or right-click to save it." +msgstr "" +"İletideki herhangi bir ek, iletinin altında gözükür. Açmak için eke " +"tıklayabilir veya kaydetmek için sağ tıklayabilirsiniz." + +#. (itstool) path: section/p +#: C/overview.page:52 +msgid "" +"Geary uses Gravatar to " +"display an avatar for each message's sender in its header." +msgstr "" +"Geary, her iletinin kendi başlığındaki göndericinin küçük resmini göstermek " +"için Gravatar kullanır." + +#. (itstool) path: page/title +#: C/preferences.page:10 +msgid "Preferences" +msgstr "Tercihler" + +#. (itstool) path: page/p +#: C/preferences.page:11 +msgid "" +"The Preferences option is available in either Geary's application " +"menu or the gear menu in the upper-right of the toolbar. (The location " +"depends on the install desktop shell. For GNOME Shell and Unity, the " +"application menu is available near the top-left corner of the screen.)" +msgstr "" +"Tercihler seçeneği, Gearyʼnin uygulama menüsünde veya araç " +"çubuğunun sağ üst köşesindeki dişli menüde bulunabilir. (Konum, kurulu " +"masaüstü kabuğuna bağlıdır. GNOME Shell ve Unity için uygulama menüsü " +"ekranın sol üst köşesine yakın bulunur.)" + +#. (itstool) path: section/title +#: C/preferences.page:17 +msgid "Reading" +msgstr "Okuma" + +#. (itstool) path: item/title +#: C/preferences.page:20 +msgid "Automatically select next message" +msgstr "Bir sonraki iletiyi kendiliğinden seç" + +#. (itstool) path: item/p +#: C/preferences.page:21 +msgid "" +"When this option is enabled, Geary automatically selects the latest message " +"in a folder when you enter the folder. In addition, after archiving a " +"message, Geary automatically selects an adjacent message." +msgstr "" +"Bu seçenek etkinken, Geary, klasöre girdiğinizde klasör içindeki son iletiyi " +"kendiliğinden seçer. Ek olarak, iletiyi arşivledikten sonra Geary bitişik " +"iletiyi seçer." + +#. (itstool) path: item/title +#: C/preferences.page:26 +msgid "Display conversation preview" +msgstr "Konuşma ön izlemesini göster" + +#. (itstool) path: item/p +#: C/preferences.page:27 +msgid "" +"Enables message previews in the conversation list. Previews show the first " +"few lines of each message." +msgstr "" +"Konuşma listesinde ileti ön izlemelerini etkinleştirir. Ön izlemeler her " +"iletinin ilk birkaç satırını gösterir." + +#. (itstool) path: item/title +#: C/preferences.page:31 +msgid "Use three pane view" +msgstr "Üç bölmeli görünümü kullan" + +#. (itstool) path: item/p +#: C/preferences.page:32 +msgid "" +"Show the folder list, the conversation list, and the messages side-by-side-" +"by-side in three panes. If not selected, the folder list and conversation " +"list will be stacked vertically in a single pane." +msgstr "" +"Klasör listesini, konuşma listesini ve iletileri üç bölme içinde yanyana " +"göster. Eğer seçilmediyse, klasör listesi ve konuşma listesi tek bölmeye " +"dikey olarak yığılacaktır." + +#. (itstool) path: section/title +#: C/preferences.page:40 +msgid "Notifications" +msgstr "Bildirimler" + +#. (itstool) path: item/title +#: C/preferences.page:43 +msgid "Play notification sounds" +msgstr "Bildirim seslerini oynat" + +#. (itstool) path: item/p +#: C/preferences.page:44 +msgid "When set, Geary plays a sound whenever a new message arrives." +msgstr "Ayarlandığında; Geary, yeni ileti geldiğinde ses oynatır." + +#. (itstool) path: item/title +#: C/preferences.page:47 +msgid "Show notifications for new mail" +msgstr "Yeni postalar için bildirim göster" + +#. (itstool) path: item/p +#: C/preferences.page:48 +msgid "" +"When set, Geary displays a notification each time a new message " +"arrives. Notifications are displayed in a system-dependent manner. On GNOME " +"Shell, notifications appear at the bottom of the display (older versions) or " +"centered just below the top bar (newer versions). In Ubuntu Unity, " +"notifications appear at the upper right of the display." +msgstr "" +"Ayarlandığında; Geary, yeni ileti geldiğinde her defasında bildirim " +"gösterir. Bildirimler, sisteme bağlı biçimde gösterilir. GNOME Shellʼde, " +"(eski sürümlerde) bildirimler ekranın altında veya (yeni sürümlerde) üst " +"çubuğun azıcık altında ortalanmış gözükür. Ubuntu Unityʼde, bildirimler " +"ekranın sağ üst köşesinde gözükür." + +#. (itstool) path: item/title +#: C/preferences.page:54 +msgid "Watch for new mail when closed" +msgstr "Kapatıldığında yeni postayı gözetle" + +#. (itstool) path: item/p +#: C/preferences.page:55 +msgid "" +"Geary will watch your accounts for new mail even when the main window is not " +"open. To do this, it will silently start when you log in to your computer, " +"and it will continue to run after you close all windows." +msgstr "" +"Geary, ana pencere açık olmadığında bile hesaplarınızı yeni posta için " +"gözetler. Bunu yapmak için, bilgisayarınıza giriş yaptığınızda sessizce " +"başlar ve tüm pencereleri kapattığınızda çalışmayı sürdürür." + +#. (itstool) path: page/title +#: C/search.page:10 +msgid "Search" +msgstr "Ara" + +#. (itstool) path: page/p +#: C/search.page:12 +msgid "" +"Geary supports a per-account full text search. To start a search, select a " +"folder associated with the account you'd like to search against. Then click " +"the search box in the toolbar (or press CtrlS) and start typing. Results will appear after a brief delay." +msgstr "" +"Geary, hesap başına tam metin aramayı destekler. Arama başlatmak için aramak " +"istediğiniz hesapla ilişkili klasör seçin. Ardından araç çubuğundaki arama " +"kutusuna tıklayın (veya CtrlS " +"tuşlarına basın) ve yazmaya başlayın. Sonuçlar kısacık gecikmenin ardından " +"gözükecektir." + +#. (itstool) path: page/p +#: C/search.page:16 +msgid "" +"The full text search includes email text, email addresses (to, from, and " +"cc), subject lines and attachment filenames." +msgstr "" +"Tam metin arama e-posta metnini, e-posta adreslerini (kime, kimden ve cc), " +"konu satırlarını ve eklerin dosya adını içerir." + +#. (itstool) path: page/p +#: C/search.page:19 +msgid "" +"Keywords that match your search are highlighted in the message view. Geary " +"will match different forms of the same word, for example searching for \"walk" +"\" will also match \"walking\" and \"walked.\"" +msgstr "" +"Aramanızla eşleşen sözcükler ileti görünümünde vurgulanmıştır. Geary, aynı " +"sözcüğün başka biçimlerini eşleyecektir, örneğin \"yürü\" için aramak ayrıca " +"\"yürüyorum\" ve \"yürüdüm\"ü de eşleyecektir." + +#. (itstool) path: section/title +#: C/search.page:23 +msgid "Search operators" +msgstr "Arama işleticileri" + +#. (itstool) path: section/p +#: C/search.page:24 +msgid "Geary supports the following operators to limit the scope of searches:" +msgstr "Geary, aramaların kapsamını sınırlamak için şu işleticileri kullanır:" + +#. (itstool) path: td/p +#: C/search.page:27 +msgid "attachment:filename" +msgstr "ek:dosyaadı" + +#. (itstool) path: td/p +#: C/search.page:28 +msgid "Finds messages with attachments whose name matches filename." +msgstr "Adı dosyaadı ile eşleşen ekleri içeren iletileri bulur." + +#. (itstool) path: td/p +#: C/search.page:31 +msgid "bcc:recipient" +msgstr "bcc:alıcı" + +#. (itstool) path: td/p +#: C/search.page:32 +msgid "Finds messages where recipient matches the BCC header." +msgstr "BCC başlığı alıcı ile eşleşen iletileri bulur." + +#. (itstool) path: td/p +#: C/search.page:35 +msgid "body:text" +msgstr "gövde:metin" + +#. (itstool) path: td/p +#: C/search.page:36 +msgid "Finds messages whose body contains text." +msgstr "Gövdesi metin içeren iletileri bulur." + +#. (itstool) path: td/p +#: C/search.page:39 +msgid "cc:recipient" +msgstr "cc:alıcı" + +#. (itstool) path: td/p +#: C/search.page:40 +msgid "Finds messages where recipient matches the CC header." +msgstr "CC başlığı alıcı ile eşleşen iletileri bulur." + +#. (itstool) path: td/p +#: C/search.page:43 +msgid "from:sender" +msgstr "kimden:gönderici" + +#. (itstool) path: td/p +#: C/search.page:44 +msgid "Finds messages where sender matches the From header." +msgstr "Kimden başlığı gönderici ile eşleşen iletileri bulur." + +#. (itstool) path: td/p +#: C/search.page:47 +msgid "is:read" +msgstr "im:okundu" + +#. (itstool) path: td/p +#: C/search.page:48 +msgid "Finds messages that have been marked as read." +msgstr "Okundu olarak imlenen iletileri bulur." + +#. (itstool) path: td/p +#: C/search.page:51 +msgid "is:starred" +msgstr "im:yıldızlı" + +#. (itstool) path: td/p +#: C/search.page:52 +msgid "Finds messages that have been marked as starred." +msgstr "Yıldızlı olarak imlenen iletileri bulur." + +#. (itstool) path: td/p +#: C/search.page:55 +msgid "is:unread" +msgstr "im:okunmadı" + +#. (itstool) path: td/p +#: C/search.page:56 +msgid "Finds messages that have been marked as not read." +msgstr "Okunmadı olarak imlenen iletileri bulur." + +#. (itstool) path: td/p +#: C/search.page:59 +msgid "subject:text" +msgstr "konu:metin" + +#. (itstool) path: td/p +#: C/search.page:60 +msgid "Finds messages whose subject contains text." +msgstr "Konusu metin içeren iletileri bulur." + +#. (itstool) path: td/p +#: C/search.page:63 +msgid "to:recipient" +msgstr "kime:alıcı" + +#. (itstool) path: td/p +#: C/search.page:64 +#| msgid "" +#| "Finds messages where sender matches the To, CC, or BCC header." +msgid "" +"Finds messages where recipient matches the To, CC, or BCC header." +msgstr "" +"Kime, CC veya BCC başlığı alıcı ile eşleşen iletileri bulur." + +#. (itstool) path: section/p +#: C/search.page:68 +msgid "" +"As a special case, the bcc, cc, from, and to operators support me as their " +"argument, which searches for the account's email address in the appropriate " +"context." +msgstr "" +"Özel durum olarak, bcc, cc, kimden ve kime işleticileri değişken olarak ben değişkenini destekler; bu değişken, uygun bağlam içinde hesabın " +"eposta adresi için arar." + +#. (itstool) path: page/title +#: C/shortcuts.page:10 +msgid "Keyboard shortcuts" +msgstr "Klavye kısayolları" + +#. (itstool) path: page/p +#: C/shortcuts.page:12 +msgid "" +"Geary has keyboard shortcuts for most common operations. Use the built-in " +"keyboard shortcuts help in Geary to discover the full list. This can be " +"accessed via the application menu: GearyKeyboard " +"Shortcuts or using the keyboard shortcuts listed below." +msgstr "" +"Geary, çoğu ortak işlem için klavye kısayollarına sahiptir. Tam listeyi " +"keşfetmek için Gearyʼdeki gömülü klavye kısayolları yardımını kullanın. Bu, " +"uygulama menüsüyle erişilebilirdir: GearyKlavye " +"Kısayolları veya aşağıda listelenmiş klavye kısayolları " +"kullanılabilir." + +#. (itstool) path: page/p +#: C/shortcuts.page:18 +msgid "" +"The following keyboard shortcuts can be used to access on-line help from " +"Geary:" +msgstr "" +"Şu klavye kısayolları Gearyʼden çevrim içi yardıma erişimde kullanılabilir:" + +#. (itstool) path: td/p +#: C/shortcuts.page:22 +msgid "Display this User Manual" +msgstr "Bu Kullanım Kılavuzunu Göster" + +#. (itstool) path: td/p +#: C/shortcuts.page:23 +msgid "F1" +msgstr "F1" + +#. (itstool) path: td/p +#: C/shortcuts.page:26 +msgid "Display all keyboard shortcuts" +msgstr "Tüm klavye kısayollarını göster" + +#. (itstool) path: td/p +#: C/shortcuts.page:27 +msgid "" +"Ctrl? or CtrlF1" +msgstr "" +"Ctrl? ya da CtrlF1" + +#. (itstool) path: page/title +#: C/star.page:10 +msgid "Star a message or mark it as read/unread" +msgstr "İletiyi yıldızla veya okundu/okunmadı olarak imle" + +#. (itstool) path: section/title +#: C/star.page:12 +msgid "Star messages" +msgstr "İletileri yıldızla" + +#. (itstool) path: section/p +#: C/star.page:13 +msgid "" +"You can star messages to indicate that they're important to you. To mark a " +"conversation with a star, click its star icon in the conversation list. You " +"can star an individual message by clicking the star at the upper right of " +"the message itself." +msgstr "" +"Sizin için önemli olduğunu belirtmek için iletileri yıldızlayabilirsiniz. " +"Konuşmayı yıldızla imlemek için, konuşma listesinde yıldız simgesine " +"dokunun. İletinin sağ üst tarafındaki yıldıza tıklayarak tekil iletiyi " +"yıldızlayabilirsiniz." + +#. (itstool) path: section/p +#: C/star.page:15 +msgid "" +"With Gmail accounts, starred messages appear in the Starred folder in the " +"folder list." +msgstr "" +"Gmail hesaplarıyla; yıldızlı iletiler, klasör listesindeki Yıldızlı " +"klasöründe gözükür." + +#. (itstool) path: section/title +#: C/star.page:18 +msgid "Mark messages as read or unread" +msgstr "İletileri okundu veya okunmamış olarak imle" + +#. (itstool) path: section/p +#: C/star.page:19 +msgid "" +"Geary marks messages as read automatically as you read them. To manually " +"toggle a conversation as read or unread, click the circle icon in the " +"conversation list." +msgstr "" +"Geary, iletileri siz onları okuduğunuz gibi kendiliğinden okundu olarak " +"imler. Konuşmayı elle okundu veya okunmamış olarak imlemek için konuşma " +"listesindeki daire simgeye tıklayın." + +#. (itstool) path: section/p +#: C/star.page:22 +msgid "" +"Alternately, the Mark as Unread in the Mark menu on " +"the toolbar can be used to toggle the read status of the selected " +"conversation(s)." +msgstr "" +"Diğer seçenek olarak; araç çubuğundaki İmle menüsünde bulunan " +"Okunmamış Olarak İmle, seçili konuşmaların okundu durumunu " +"değiştirmek için kullanılabilir." + +#. (itstool) path: section/p +#: C/star.page:25 +msgid "" +"To mark an individual message as read, select Mark as Read from " +"the dropdown menu." +msgstr "" +"Tekil iletiyi okundu olarak imlemek için açılır menüden Okundu Olarak " +"İmleyi seçin." + +#. (itstool) path: page/title +#: C/write.page:9 +msgid "Write a message" +msgstr "İleti yaz" + +#. (itstool) path: section/title +#: C/write.page:12 +msgid "Composing and replying" +msgstr "Yazma ve yanıtlama" + +#. (itstool) path: section/p +#: C/write.page:13 +msgid "" +"To compose a new message in Geary, press the New Message button " +"on the toolbar." +msgstr "" +"Gearyʼde yeni ileti yazmak için, araç çubuğundaki Yeni İleti " +"düğmesine basın." + +#. (itstool) path: section/p +#: C/write.page:16 +msgid "" +"To reply to a message, open the message menu in the upper right corner of " +"the message and choose Reply, Reply All or " +"Forward. You can also reply to the last message in a conversation " +"via the Reply, Reply All or Forward buttons " +"on the toolbar." +msgstr "" +"İletiyi yanıtlamak için, iletinin sağ üst köşesindeki ileti menüsünü açın ve " +"Yanıtla, Tümünü Yanıtla veya Yönlendirʼi " +"seçin. Ayrıca, konuşmadaki son iletiyi araç çubuğundaki Yanıtla, " +"Tümünü Yanıtla veya Yönlendir aracılığıyla " +"yanıtlayabilirsiniz." + +#. (itstool) path: section/title +#: C/write.page:21 +msgid "Features" +msgstr "Özellikler" + +#. (itstool) path: section/p +#: C/write.page:23 +msgid "" +"Geary's email composer lets you adjust the font, size and color of text. You " +"can also insert hyperlinks into messages." +msgstr "" +"Gearyʼnin e-posta oluşturucusu metnin yazı tipini, boyutunu ve rengini " +"ayarlamanızı sağlar. Ayrıca iletilerin içine köprüler ekleyebilirsiniz." + +#. (itstool) path: section/p +#: C/write.page:25 +msgid "" +"Geary can also send plain text messages. In the drop-down menu, check or " +"uncheck \"Rich Text\" to toggle between plain text and rich text mode." +msgstr "" +"Geary ayrıca düz metin iletileri gönderebilir. Açılır menüde, \"Zengin Metin" +"\"i seçerek ya da seçimi kaldırarak düz metin ve zengin metin kipleri " +"arasında geçiş yapılabilir." + +#. (itstool) path: section/p +#: C/write.page:28 +msgid "" +"You can attach a file to a message you're writing in either of these ways:" +msgstr "Yazarken iletiye şu yollarla dosya ekleyebilirsiniz:" + +#. (itstool) path: item/p +#: C/write.page:30 +msgid "" +"Press the Attach File button at the lower left of the composer " +"window, then select a file to attach." +msgstr "" +"Oluşturucu penceresinin sol alt köşesindeki Dosya Ekle düğmesine " +"basın, sonrasında eklenecek dosyayı seçin." + +#. (itstool) path: item/p +#: C/write.page:32 +msgid "" +"Drag the file from the Nautilus file manager to the composer window, and " +"drop it either on the text fields at the top of the window or on the toolbar " +"at the bottom." +msgstr "" +"Dosyayı Nautilus dosya yöneticisinden oluşturucu penceresine sürükleyin, " +"pencerenin üstündeki metin alanlarının veya alttaki araç çubuğunun üzerine " +"bırakın." + +#. (itstool) path: section/p +#: C/write.page:36 +msgid "" +"A number of keyboard shortcuts are available in the composer; see for details." +msgstr "" +"Oluşturucuda birkaç klavye kısayolu bulunur; ayrıntılar için " + +#. (itstool) path: section/p +#: C/write.page:38 +msgid "" +"You may specify a signature to be inserted into the composer in the dialog." +msgstr "" +" iletişim penceresinde oluşturucuya eklenecek imza " +"belirleyebilirsiniz." + +#. (itstool) path: section/title +#: C/write.page:43 +msgid "Drafts" +msgstr "Taslaklar" + +#. (itstool) path: section/p +#: C/write.page:45 +msgid "" +"For mail servers that support drafts, Geary will automatically save the " +"message as you type. If you close the composer without sending, Geary will " +"prompt you to keep the draft or to discard it." +msgstr "" +"Taslakları destekleyenn posta sunucuları için, Geary siz yazdıkça iletiyi " +"kendiliğinden kaydedecektir. Eğer göndermeden oluşturucuyu kapatırsanız, " +"Geary size taslağı tutmayı veya atmayı soracaktır." + +#. (itstool) path: section/p +#: C/write.page:48 +msgid "" +"To edit an existing draft, select the Drafts folder in the folder list, " +"select the message, and click \"Edit Draft\" in the message viewer." +msgstr "" +"Var olan taslağı düzenlemek için, klasör listesinde Taslaklar klasörünü " +"seçin, iletiyi seçin ve ileti göstericide \"Taslağı Düzenle\"ye tıklayın." + +#. (itstool) path: section/p +#: C/write.page:51 +msgid "Geary deletes the draft when you send the message." +msgstr "Geary, iletiyi gönderdiğinizde taslağı siler." + +#~| msgid "F" +#~ msgid "F1" +#~ msgstr "F1" + +#~ msgid "filename" +#~ msgstr "dosyaadı" + +#~ msgid "attachment:" +#~ msgstr "ek:" + +#~ msgid "recipient" +#~ msgstr "alıcı" + +#~ msgid "bcc:" +#~ msgstr "bcc:" + +#~ msgid "text" +#~ msgstr "metin" + +#~ msgid "body:" +#~ msgstr "gövde:" + +#~ msgid "cc:" +#~ msgstr "cc:" + +#~ msgid "sender" +#~ msgstr "gönderici" + +#~ msgid "from:" +#~ msgstr "kimden:" + +#~ msgid "is:read" +#~ msgstr "is:okundu" + +#~ msgid "is:starred" +#~ msgstr "is:yıldızlı" + +#~ msgid "is:unread" +#~ msgstr "is:okunmadı" + +#~ msgid "subject:" +#~ msgstr "konu:" + +#~ msgid "to:" +#~ msgstr "kime:" + +#~ msgid "Bugs" +#~ msgstr "Hatalar" + +#~ msgid "Think you've found a bug?" +#~ msgstr "Bir hata bulduğunuzu mu düşünüyorsunuz?" + +#~ msgid "" +#~ "If you suspect you've found a bug in Geary, follow these steps to report " +#~ "it:" +#~ msgstr "" +#~ "Eğer Geary'de bir hata bulduğunuzdan şüpheleniyorsanız, bildirmek için şu " +#~ "adımları takip edin:" + +#~ msgid "" +#~ "Search Geary's bug database to see if someone else has reported " +#~ "the bug." +#~ msgstr "" +#~ "Başkasının hatayı gönderip göndermediğine bakmak için Geary'nin hata veri " +#~ "tabanını arayın." + +#~ msgid "" +#~ "Don't see your bug listed? Congratulations! You've found a new bug. To " +#~ "create an bug report, create an account on GNOME's Bugzilla and file a " +#~ "new bug. Be as specific as you can and describe the steps to " +#~ "reproduce it. Don't forget to include details about your operating system " +#~ "and what version of Geary you're running." +#~ msgstr "" +#~ "Hatanız listelenmemiş mi? Tebrikler! Yeni bir hata buldunuz. Bir hata " +#~ "bildirimi oluşturmak için, GNOME'un Bugzilla'sında yeni bir hesap " +#~ "oluşturun ve yeni bir hata bildirin. Olabildiğinizce açık olun " +#~ "ve hatayı yeniden üretecek adımları açıklayın. İşletim sisteminizle " +#~ "ilgili ayrıntıları ve çalıştırdığınız Geary'nin sürümünü eklemeyi " +#~ "unutmayın." + +#~ msgid "" +#~ "For general inquiries, please join the Geary mailing list." +#~ msgstr "" +#~ "Genel sorular için lütfen Geary postalaşma listesine katılın." + +#~ msgid "Geary has keyboard shortcuts for most common operations." +#~ msgstr "Geary, çoğu ortak işlem için klavye kısayollarına sahiptir." + +#~ msgid "Compose a new message" +#~ msgstr "Yeni bir ileti yaz" + +#~ msgid "CtrlN or N" +#~ msgstr "CtrlN veya N" + +#~ msgid "Reply to sender" +#~ msgstr "Gönderene yanıtla" + +#~ msgid "CtrlR or R" +#~ msgstr "CtrlR veya R" + +#~ msgid "Reply to all" +#~ msgstr "Tümüne yanıtla" + +#~ msgid "" +#~ "CtrlShiftR or " +#~ "ShiftR" +#~ msgstr "" +#~ "CtrlShiftR veya " +#~ "ShiftR" + +#~ msgid "Forward" +#~ msgstr "Yönlendir" + +#~ msgid "Archive" +#~ msgstr "Arşivle" + +#~ msgid "A" +#~ msgstr "A" + +#~ msgid "Trash" +#~ msgstr "Çöp" + +#~ msgid "Delete or Backspace" +#~ msgstr "Delete veya Backspace" + +#~ msgid "Delete" +#~ msgstr "Sil" + +#~ msgid "" +#~ "ShiftDelete or ShiftBackspace" +#~ msgstr "" +#~ "ShiftDelete veya " +#~ "ShiftBackspace" + +#~ msgid "Star" +#~ msgstr "Yıldızla\t" + +#~ msgid "S" +#~ msgstr "S" + +#~ msgid "Unstar" +#~ msgstr "Yıldızı kaldır" + +#~ msgid "D" +#~ msgstr "D" + +#~ msgid "Mark read" +#~ msgstr "Okundu imle" + +#~ msgid "" +#~ "CtrlI or ShiftI" +#~ msgstr "" +#~ "CtrlI veya ShiftI" + +#~ msgid "Mark unread" +#~ msgstr "Okunmamış imle" + +#~ msgid "" +#~ "CtrlU or ShiftU" +#~ msgstr "" +#~ "CtrlU veya ShiftU" + +#~ msgid "Move the conversation" +#~ msgstr "Konuşmayı taşı" + +#~ msgid "M" +#~ msgstr "M" + +#~ msgid "Label the conversation" +#~ msgstr "Konuşmayı etiketle" + +#~ msgid "L" +#~ msgstr "L" + +#~ msgid "Jump to next (older) conversation" +#~ msgstr "Sonraki (eski) konuşmaya atla" + +#~ msgid "J" +#~ msgstr "J" + +#~ msgid "Jump to previous (newer) conversation" +#~ msgstr "Önceki (yeni) konuşmaya atla" + +#~ msgid "K" +#~ msgstr "K" + +#~ msgid "Toggle spam" +#~ msgstr "İstenmeyenliği değiştir" + +#~ msgid "CtrlJ or !" +#~ msgstr "CtrlJ veya !" + +#~ msgid "Quit" +#~ msgstr "Çık" + +#~ msgid "Ctrl" +#~ msgstr "Ctrl" + +#~ msgid "Q" +#~ msgstr "Q" + +#~ msgid "Zoom in" +#~ msgstr "Yakınlaştır" + +#~ msgid "Ctrl= or =" +#~ msgstr "Ctrl= veya =" + +#~ msgid "Zoom out" +#~ msgstr "Uzaklaştır" + +#~ msgid "Ctrl- or -" +#~ msgstr "Ctrl- veya -" + +#~ msgid "Reset zoom" +#~ msgstr "Yakınlaştırmayı sıfırla" + +#~ msgid "Ctrl0 or 0" +#~ msgstr "Ctrl0 veya 0" + +#~ msgid "Close composer window" +#~ msgstr "Oluşturucu pencereyi kapat" + +#~ msgid "W" +#~ msgstr "W" + +#~ msgid "Jump to search box" +#~ msgstr "Arama kutusuna atla" + +#~ msgid "Find in current conversation" +#~ msgstr "Geçerli konuşmada bul" + +#~ msgid "Find next in current conversation" +#~ msgstr "Geçerli konuşmada sonrakini bul" + +#~ msgid "G" +#~ msgstr "G" + +#~ msgid "Find previous in current conversation" +#~ msgstr "Geçerli konuşmada öncekini bul" + +#~ msgid "Shift" +#~ msgstr "Shift" + +#~ msgid "Composer shortcuts" +#~ msgstr "Oluşturucu kısayolları" + +#~ msgid "These shortcuts are active whenever focus is in a composer." +#~ msgstr "Bu kısayollar odak oluşturucuda olduğunda etkindir." + +#~ msgid "Attach file" +#~ msgstr "Dosya ekle" + +#~ msgid "T" +#~ msgstr "T" + +#~ msgid "Quote text" +#~ msgstr "Metni alıntıla" + +#~ msgid "]" +#~ msgstr "]" + +#~ msgid "Unquote text" +#~ msgstr "Metin alıntısını kaldır" + +#~ msgid "[" +#~ msgstr "[" + +#~ msgid "Close composer" +#~ msgstr "Oluşturucuyu kapat" + +#~ msgid "CtrlW or Esc" +#~ msgstr "CtrlW veya Esc" + +#~ msgid "Detach composer" +#~ msgstr "Oluşturucuyu ayır" + +#~ msgid "These shortcuts are only active in composers in rich text mode." +#~ msgstr "" +#~ "Bu kısayollar yalnızca oluşturucu zengin metin kipindeyken etkindir." + +#~ msgid "Bold text" +#~ msgstr "Metni kalınlaştır" + +#~ msgid "B" +#~ msgstr "B" + +#~ msgid "Italicize text" +#~ msgstr "Metni yatır" + +#~ msgid "I" +#~ msgstr "I" + +#~ msgid "Underline text" +#~ msgstr "Metnin altını çiz" + +#~ msgid "U" +#~ msgstr "U" + +#~ msgid "Strike text" +#~ msgstr "Metnin üzerini çiz" + +#~ msgid "Insert a link" +#~ msgstr "Bir bağlantı yerleştir" + +#~ msgid "Remove formatting" +#~ msgstr "Biçimlendirmeyi kaldır" + +#~ msgid "Space" +#~ msgstr "Boşluk" + +#~ msgid "Keyboard navigation" +#~ msgstr "Klavye gezinimi" + +#~ msgid "" +#~ "These shortcuts can be used to move the keyboard focus in the main window." +#~ msgstr "" +#~ "Bu kısayollar ana penceredeki klavye odağını taşımak için kullanılabilir." + +#~ msgid "Move focus to the next/previous pane" +#~ msgstr "Odağı sonraki/önceki bölmeye taşı" + +#~ msgid "" +#~ "F6 / ShiftF6" +#~ msgstr "" +#~ "F6 / ShiftF6" + +#~ msgid "Move focus to conversation list" +#~ msgstr "Odağı konuşma listesine taşı" + +#~ msgid "Move to the next message in a conversation" +#~ msgstr "Bir konuşmadaki sonraki iletiye taşı" + +#~ msgid "Move to the next/previous message in a conversation" +#~ msgstr "Bir konuşmadaki önceki/sonraki iletiye taşı" + +#~ msgid "Move to the first/last message in a conversation" +#~ msgstr "Bir konuşmadaki ilk/son iletiye taşı" + +#~ msgid "" +#~ "CtrlHome / CtrlEnd" +#~ msgstr "" +#~ "CtrlHome / CtrlEnd" + +#~ msgid "Composer" +#~ msgstr "Oluşturucu" + +#~ msgid "Enable spell checking" +#~ msgstr "Yazım denetimini etkinleştir" + +#~ msgid "" +#~ "When set, Geary automatically spell checks a message as you write it, " +#~ "underlying each misspelled word in red." +#~ msgstr "" +#~ "Ayarlandığında; Geary, siz yazdıkça ileti yazımını denetler, yanlış " +#~ "yazılan her sözcüğün altını kırmızı ile çizer." diff -Nru geary-0.12.4/icons/CMakeLists.txt geary-3.32.0/icons/CMakeLists.txt --- geary-0.12.4/icons/CMakeLists.txt 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/icons/CMakeLists.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,57 +0,0 @@ -set(ICONS_DEST share/icons/hicolor/scalable/actions) - -set(ICON_FILES - mail-archive-symbolic.svg - close-symbolic.svg - detach-symbolic.svg - text-x-generic-symbolic.svg - edit-symbolic.svg - format-text-remove-symbolic.svg - mail-drafts-symbolic.svg - mail-drafts-symbolic-rtl.svg - mail-forward-symbolic.svg - mail-forward-symbolic-rtl.svg - mail-inbox-symbolic.svg - mail-outbox-symbolic.svg - mail-reply-all-symbolic.svg - mail-reply-all-symbolic-rtl.svg - mail-reply-sender-symbolic.svg - mail-reply-sender-symbolic-rtl.svg - mail-sent-symbolic.svg - mail-sent-symbolic-rtl.svg - marker-symbolic.svg - tag-symbolic.svg - tag-symbolic-rtl.svg -) - -install(FILES ${ICON_FILES} DESTINATION ${ICONS_DEST}) - -# Application icon goes in theme directory -install(FILES "hicolor/16x16/apps/org.gnome.Geary.png" DESTINATION share/icons/hicolor/16x16/apps) -install(FILES "hicolor/24x24/apps/org.gnome.Geary.png" DESTINATION share/icons/hicolor/24x24/apps) -install(FILES "hicolor/32x32/apps/org.gnome.Geary.png" DESTINATION share/icons/hicolor/32x32/apps) -install(FILES "hicolor/48x48/apps/org.gnome.Geary.png" DESTINATION share/icons/hicolor/48x48/apps) -install(FILES "hicolor/256x256/apps/org.gnome.Geary.png" DESTINATION share/icons/hicolor/256x256/apps) -install(FILES "hicolor/512x512/apps/org.gnome.Geary.png" DESTINATION share/icons/hicolor/512x512/apps) -install(FILES "hicolor/symbolic/apps/org.gnome.Geary-symbolic.svg" DESTINATION share/icons/hicolor/symbolic/apps) - -# Optional: update icon cache at install time. -if (ICON_UPDATE) - install( - CODE - "execute_process (COMMAND gtk-update-icon-cache -t -f ${CMAKE_INSTALL_PREFIX}/share/icons/hicolor)" - CODE - "message (STATUS \"Updated icon cache in ${CMAKE_INSTALL_PREFIX}/share/icons/hicolor\")" - ) - - add_custom_target( - uninstall-icon-cache - COMMAND - gtk-update-icon-cache -t -f ${CMAKE_INSTALL_PREFIX}/share/icons/hicolor - COMMENT - "Updated icon cache after uninstall in ${CMAKE_INSTALL_PREFIX}/share/icons/hicolor" - ) - - add_dependencies(post-uninstall uninstall-icon-cache) -endif () - diff -Nru geary-0.12.4/icons/format-ordered-list-symbolic-rtl.svg geary-3.32.0/icons/format-ordered-list-symbolic-rtl.svg --- geary-0.12.4/icons/format-ordered-list-symbolic-rtl.svg 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/icons/format-ordered-list-symbolic-rtl.svg 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,162 @@ + + + + + + + + image/svg+xml + + Gnome Symbolic Icon Theme + + + + + + + Gnome Symbolic Icon Theme + + + + + + + + + + + + + + + + + + + + diff -Nru geary-0.12.4/icons/format-ordered-list-symbolic.svg geary-3.32.0/icons/format-ordered-list-symbolic.svg --- geary-0.12.4/icons/format-ordered-list-symbolic.svg 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/icons/format-ordered-list-symbolic.svg 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,159 @@ + + + + + + + + image/svg+xml + + Gnome Symbolic Icon Theme + + + + + + + Gnome Symbolic Icon Theme + + + + + + + + + + + + + + + + + + + + diff -Nru geary-0.12.4/icons/format-unordered-list-symbolic-rtl.svg geary-3.32.0/icons/format-unordered-list-symbolic-rtl.svg --- geary-0.12.4/icons/format-unordered-list-symbolic-rtl.svg 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/icons/format-unordered-list-symbolic-rtl.svg 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,205 @@ + + + + + + + + image/svg+xml + + Gnome Symbolic Icon Theme + + + + + + + Gnome Symbolic Icon Theme + + + + + + + + + + + + + + + + + + + + + + diff -Nru geary-0.12.4/icons/format-unordered-list-symbolic.svg geary-3.32.0/icons/format-unordered-list-symbolic.svg --- geary-0.12.4/icons/format-unordered-list-symbolic.svg 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/icons/format-unordered-list-symbolic.svg 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,199 @@ + + + + + + + + image/svg+xml + + Gnome Symbolic Icon Theme + + + + + + + Gnome Symbolic Icon Theme + + + + + + + + + + + + + + + + + + + + + + diff -Nru geary-0.12.4/icons/geary.svg geary-3.32.0/icons/geary.svg --- geary-0.12.4/icons/geary.svg 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/icons/geary.svg 1970-01-01 00:00:00.000000000 +0000 @@ -1,5816 +0,0 @@ - - - - - Pidgin - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - Jakub Steiner - - - http://jimmac.musichall.cz - - Pidgin - - - pidgin - im - instant - messaging - - - - - - - - - - - - - - - - - - - - - Hylke Bons, Lapo Calamandrei - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff -Nru geary-0.12.4/icons/gitlab-icon.svg geary-3.32.0/icons/gitlab-icon.svg --- geary-0.12.4/icons/gitlab-icon.svg 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/icons/gitlab-icon.svg 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,112 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + Binary files /tmp/tmprRlvAF/aVx0iOUZeI/geary-0.12.4/icons/hicolor/16x16/apps/org.gnome.Geary.png and /tmp/tmprRlvAF/yMyovUyAjh/geary-3.32.0/icons/hicolor/16x16/apps/org.gnome.Geary.png differ Binary files /tmp/tmprRlvAF/aVx0iOUZeI/geary-0.12.4/icons/hicolor/24x24/apps/org.gnome.Geary.png and /tmp/tmprRlvAF/yMyovUyAjh/geary-3.32.0/icons/hicolor/24x24/apps/org.gnome.Geary.png differ Binary files /tmp/tmprRlvAF/aVx0iOUZeI/geary-0.12.4/icons/hicolor/256x256/apps/org.gnome.Geary.png and /tmp/tmprRlvAF/yMyovUyAjh/geary-3.32.0/icons/hicolor/256x256/apps/org.gnome.Geary.png differ Binary files /tmp/tmprRlvAF/aVx0iOUZeI/geary-0.12.4/icons/hicolor/32x32/apps/org.gnome.Geary.png and /tmp/tmprRlvAF/yMyovUyAjh/geary-3.32.0/icons/hicolor/32x32/apps/org.gnome.Geary.png differ Binary files /tmp/tmprRlvAF/aVx0iOUZeI/geary-0.12.4/icons/hicolor/48x48/apps/org.gnome.Geary.png and /tmp/tmprRlvAF/yMyovUyAjh/geary-3.32.0/icons/hicolor/48x48/apps/org.gnome.Geary.png differ Binary files /tmp/tmprRlvAF/aVx0iOUZeI/geary-0.12.4/icons/hicolor/512x512/apps/org.gnome.Geary.png and /tmp/tmprRlvAF/yMyovUyAjh/geary-3.32.0/icons/hicolor/512x512/apps/org.gnome.Geary.png differ diff -Nru geary-0.12.4/icons/hicolor/scalable/apps/org.gnome.Geary.svg geary-3.32.0/icons/hicolor/scalable/apps/org.gnome.Geary.svg --- geary-0.12.4/icons/hicolor/scalable/apps/org.gnome.Geary.svg 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/icons/hicolor/scalable/apps/org.gnome.Geary.svg 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1 @@ + \ No newline at end of file diff -Nru geary-0.12.4/icons/hicolor/symbolic/apps/org.gnome.Geary-symbolic.svg geary-3.32.0/icons/hicolor/symbolic/apps/org.gnome.Geary-symbolic.svg --- geary-0.12.4/icons/hicolor/symbolic/apps/org.gnome.Geary-symbolic.svg 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/icons/hicolor/symbolic/apps/org.gnome.Geary-symbolic.svg 2019-03-17 13:39:29.000000000 +0000 @@ -1,125 +1,67 @@ - - - - - - + id="svg7384" + height="16"> + id="metadata90"> image/svg+xml - + Gnome Symbolic Icon Theme + Gnome Symbolic Icon Theme + + + + + + - - - - - + transform="translate(-280.98275,206.99542)" + style="display:inline" + id="layer9"> + + + id="path5946" + d="m 283.68945,-203.13086 a 0.50005,0.50005 0 0 0 -0.33789,0.86133 l 3.76367,3.68359 c 0.54827,0.54827 1.31878,0.7106 2.01758,0.6836 0.6988,-0.027 1.35268,-0.21401 1.79297,-0.6543 l -0.002,0.004 3.75976,-3.71094 a 0.50005,0.50005 0 1 0 -0.70117,-0.71094 l -3.76172,3.71094 h -0.002 c -0.1325,0.13249 -0.61975,0.3418 -1.125,0.36133 -0.50525,0.0195 -1.00067,-0.11982 -1.27148,-0.39063 l -0.002,-0.002 -3.76953,-3.68946 a 0.50005,0.50005 0 0 0 -0.36133,-0.14648 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#241f31;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + id="path5950" + d="m 291.38281,-200.0625 a 0.50005,0.50005 0 0 0 -0.29492,0.89648 l 2.70313,2.12891 a 0.50005,0.50005 0 1 0 0.61718,-0.78516 l -2.70117,-2.1289 a 0.50005,0.50005 0 0 0 -0.32422,-0.11133 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#241f31;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> - + id="path5952" + d="m 286.40234,-200.0625 a 0.50005,0.50005 0 0 0 -0.30859,0.11133 l -2.70117,2.1289 a 0.50005,0.50005 0 1 0 0.61914,0.78516 l 2.70117,-2.12891 a 0.50005,0.50005 0 0 0 -0.31055,-0.89648 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#241f31;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> diff -Nru geary-0.12.4/icons/meson.build geary-3.32.0/icons/meson.build --- geary-0.12.4/icons/meson.build 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/icons/meson.build 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,50 @@ +system_icons_dir = join_paths(datadir, 'icons', 'hicolor') + +icon_files = files( + 'mail-archive-symbolic.svg', + 'close-symbolic.svg', + 'detach-symbolic.svg', + 'text-x-generic-symbolic.svg', + 'edit-symbolic.svg', + 'format-ordered-list-symbolic.svg', + 'format-ordered-list-symbolic-rtl.svg', + 'format-text-remove-symbolic.svg', + 'format-unordered-list-symbolic.svg', + 'format-unordered-list-symbolic-rtl.svg', + 'mail-drafts-symbolic.svg', + 'mail-drafts-symbolic-rtl.svg', + 'mail-forward-symbolic.svg', + 'mail-forward-symbolic-rtl.svg', + 'mail-inbox-symbolic.svg', + 'mail-outbox-symbolic.svg', + 'mail-reply-all-symbolic.svg', + 'mail-reply-all-symbolic-rtl.svg', + 'mail-reply-sender-symbolic.svg', + 'mail-reply-sender-symbolic-rtl.svg', + 'mail-sent-symbolic.svg', + 'mail-sent-symbolic-rtl.svg', + 'marker-symbolic.svg', + 'tag-symbolic.svg', + 'tag-symbolic-rtl.svg', +) + +colour_app_icon_dirs = [ + '16x16', + '24x24', + '32x32', + '48x48', + '256x256', + '512x512', +] + +install_data(icon_files, + install_dir: join_paths(system_icons_dir, 'scalable', 'actions'), +) + +install_data(join_paths('hicolor', 'scalable', 'apps', 'org.gnome.Geary.svg'), + install_dir: join_paths(system_icons_dir, 'scalable', 'apps'), +) + +install_data(join_paths('hicolor', 'symbolic', 'apps', 'org.gnome.Geary-symbolic.svg'), + install_dir: join_paths(system_icons_dir, 'symbolic', 'apps'), +) diff -Nru geary-0.12.4/INSTALL geary-3.32.0/INSTALL --- geary-0.12.4/INSTALL 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/INSTALL 2019-03-17 13:39:29.000000000 +0000 @@ -1,124 +1,106 @@ - Building & Installing Geary - --------------------------- +Building & Installing Geary +=========================== - * Building +Building +-------- - To build Geary, run the following commands: +Geary uses the Meson and Ninja + build systems. To build Geary, run the +following commands from the top-level directory of the source code +repository: - $ ./configure - $ make + meson build + ninja -C build - By default, Geary will install under /usr/local. The configure script can - customize the prefix directory. Run ./configure --help for instructions - and other installation options. +A convenience Makefile for development only is also provided. To use +it, simply invoke make from the top-level directory. +Dependencies +------------ - * Dependencies +Building Geary requires the following major libraries and tools: - Building Geary requires at least the following major libraries: + * GTK+ 3 + * WebKitGTK+ 2 + * SQLite 3 + * Vala - * GTK+ version 3.14 - * WebKitGTK+ 2.10 - * Vala 0.26 +See the `meson.build` file in the top-level directory for the complete +list of required dependencies and minimum versions. - With a full GObject introspection repository, intltool, cmake, - desktop-file-validate, and xml2po. Vala's vapigen must be - installed as well. +Geary also requires SQLite to be built with the compiler flag +`-DSQLITE_ENABLE_FTS3`. - See the file `src/CMakeLists.txt` for the complete list of minimum - version requirements. +All required libraries and tools are available from major Linux +distribution's package repositories: - Geary also requires SQLite to be built with the compiler flag - `-DSQLITE_ENABLE_FTS3`. Further, SQLite 3.11.x specifically must - also be built with `-DSQLITE_ENABLE_FTS3_TOKENIZER`. Most - distribution's packages meet these requirements, however Fedora 24 - users and others may be required to rebuild SQLite 3.11 with the - second flag, or install SQLite 3.12 and recompile Geary. +Installing dependencies on Fedora +--------------------------------- - The developer packages and headers for the following libraries are also - required when building Geary: +Fedora 25 and later ships with the correct versions of the required +libraries. Install them by running this command: - * canberra - * gee-0.8 - * gio-2.0 - * glib-2.0 - * gmime-2.6 - * gtk+-3.0 - * libsecret-1 - * libxml-2.0 - * notify - * sqlite3 - * webkit2gtk-4.0 - * gcr-3 - * enchant - * messaging-menu (optional; enables support for Ubuntu Unity messaging - menu) - * unity (optional; enables support for Ubuntu Unity launcher) + sudo yum install vala meson desktop-file-utils iso-codes-devel \ + libcanberra-devel folks-devel libgee-devel glib2-devel \ + gmime-devel gtk3-devel libnotify-devel sqlite-devel \ + webkitgtk4-devel libsecret-devel libxml2-devel vala-tools \ + gcr-devel enchant2-devel libunwind-devel json-glib-devel \ + gnome-online-accounts-devel itstool - Most of these are standard libraries available from major distros' package - repositories. +Installing dependencies on Ubuntu/Debian +---------------------------------------- +Ubuntu 17.10 (Artful) and later ships with the correct versions of the +required libraries. - * Installing dependencies on Fedora +Ubuntu 16.04 LTS (Xenial) does not meet the minimum requirements, +users of that are encourage to use Geary 0.12 LTS instead. - Fedora 23 and later ships with the correct versions of the - required libraries. Install them by running this command: +Debian 9 (Stretch) and later ships with the correct versions of the +required libraries. - $ sudo dnf install vala gobject-introspection-devel intltool cmake \ - desktop-file-utils gnome-doc-utils libcanberra-devel libgee-devel \ - glib2-devel gmime-devel gtk3-devel libnotify-devel sqlite-devel \ - webkitgtk4-devel libsecret-devel libxml2-devel vala-tools \ - gcr-devel enchant-devel +Install them by running this command: + sudo apt-get install valac meson desktop-file-utils iso-codes \ + libcanberra-dev libfolks-dev libgee-0.8-dev libglib2.0-dev \ + libgmime-2.6-dev libgtk-3-dev libsecret-1-dev libxml2-dev \ + libnotify-dev libsqlite3-dev libwebkit2gtk-4.0-dev \ + libgcr-3-dev libenchant-dev libunwind-dev libgoa-1.0-dev \ + libjson-glib-dev itstool gettext - * Installing dependencies on Ubuntu/Debian +And for Ubuntu Unity integration: - Ubuntu 16.04 LTS (Xenial) and later ships with the correct - versions of the required libraries. + sudo apt-get install libunity-dev libmessaging-menu-dev - Debian 8 (Jessie) requires stable backports enabled for - WebKitGTK. See for - more information about using stable backports. Later versions - ships with the correct versions of the required libraries. +Running +------- - Install them by running this command: +If you wish to try Geary before installing it, you may execute it directly +from its build directory: - $ sudo apt-get install valac libgirepository1.0-dev intltool \ - cmake desktop-file-utils gnome-doc-utils libcanberra-dev \ - libgee-0.8-dev libglib2.0-dev libgmime-2.6-dev libgtk-3-dev \ - libsecret-1-dev libxml2-dev libnotify-dev libsqlite3-dev \ - libwebkit2gtk-4.0-dev libgcr-3-dev libenchant-dev + ./build/src/geary - And for Ubuntu Unity integration: +Note that certain desktop integration (such as being listed in an +application menu) requires full installation. - $ sudo apt-get install libunity-dev libmessaging-menu-dev +Installation +------------ - Ubuntu 14.04 LTS (Trusty) does not have all the dependencies to - build this version Geary. Consier using the Geary 0.11 LTS series - instead. +After Geary has built, install it by invoking the install target: + ninja -C build install - * Running +After installation, it can be uninstalled in the same way: - If you wish to try Geary before installing it, you may execute it directly - from its build directory: + ninja -C build uninstall - $ ./geary +By default, Geary will install under /usr/local. To install to a +different directory, set pass the --prefix to meson when performing +the initial configuration step: - Note that certain desktop integration (such as being listed in an - application menu) requires full installation. + meson --prefix=/usr -C build - - * Installing - - After Geary has built, run the following command to install it: - - $ sudo make install - - To uninstall, run: - - $ sudo make uninstall - - - Copyright 2016 Software Freedom Conservancy Inc. +--- +Copyright 2016 Software Freedom Conservancy Inc. +Copyright 2018 Michael Gratton diff -Nru geary-0.12.4/Makefile geary-3.32.0/Makefile --- geary-0.12.4/Makefile 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/Makefile 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,100 @@ +# +# Copyright 2016 Software Freedom Conservancy Inc. +# + +# This Makefile is for developer convenience, and is optimised for +# development work, not production. Packagers should invoke meson and +# ninja directly. See INSTALL for further information. + +CONFIGURE := meson \ + --buildtype debug \ + --warnlevel 3 +MAKE := ninja + +BUILD_DIR := build +BINARIES := geary geary-console geary-mailer + +BUILD_ARTIFACTS := \ + $(BUILD_DIR)/src/geary \ + $(BUILD_DIR)/src/console/geary-console \ + $(BUILD_DIR)/src/mailer/geary-mailer \ + $(BUILD_DIR)/src/valadoc + +.DEFAULT: all + +.PHONY: all +all: compile $(BINARIES) + +.PHONY: verbose +verbose: compile-verbose $(BINARIES) + +.PHONY: compile +compile: $(BUILD_DIR) + @$(MAKE) -C $(BUILD_DIR) + +.PHONY: compile-verbose +compile-verbose: $(BUILD_DIR) + @$(MAKE) -C $(BUILD_DIR) -v + +.PHONY: install +install: compile + @$(MAKE) -C $(BUILD_DIR) $@ + +.PHONY: uninstall +uninstall: compile + @$(MAKE) -C $(BUILD_DIR) $@ + +.PHONY: geary-pot +geary-pot: compile + @$(MAKE) -C $(BUILD_DIR) $@ + +# Keep the olde rule For compatibility +.PHONY: pot_file +pot_file: geary-pot + +.PHONY: clean +clean: $(BUILD_DIR) + @-$(MAKE) -C $(BUILD_DIR) $@ + +.PHONY: distclean +distclean: + @-rm -rf $(BUILD_DIR) + @-rm -rf $(BINARIES) + @-rm -rf valadoc + @-rm -f po/geary.pot + +.PHONY: test +test: $(BUILD_DIR) + @$(MAKE) -C $(BUILD_DIR) $@ + +.PHONY: test-engine +test-engine: $(BUILD_DIR) + cd $(BUILD_DIR) && meson test engine-tests + +.PHONY: test-client +test-client: $(BUILD_DIR) + cd $(BUILD_DIR) && meson test client-tests + +.PHONY: dist +dist: test + @$(MAKE) -C $(BUILD_DIR) $@ + @cp -v $(BUILD_DIR)/meson-dist/*.xz* .. + +# The rest of these are actual files + +$(BUILD_DIR): + @$(CONFIGURE) $@ + +valadoc: $(BUILD_DIR)/src/valadoc + cp -r $< . + +geary: $(BUILD_DIR)/src/geary + cp $< . + +geary-console: $(BUILD_DIR)/src/console/geary-console + cp $< . + +geary-mailer: $(BUILD_DIR)/src/mailer/geary-mailer + cp $< . + +$(BUILD_ARTIFACTS): compile diff -Nru geary-0.12.4/Makefile.in geary-3.32.0/Makefile.in --- geary-0.12.4/Makefile.in 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/Makefile.in 1970-01-01 00:00:00.000000000 +0000 @@ -1,59 +0,0 @@ -# Makefile.in -# -# Copyright 2016 Software Freedom Conservancy Inc. - -BUILD_DIR := build -BINARIES := geary geary-console geary-mailer - -BUILD_BINARIES := $(addprefix $(BUILD_DIR)/,$(BINARIES)) - -.PHONY: all -all: - @$(MAKE) -C $(BUILD_DIR) - @cp $(BUILD_BINARIES) . - -.PHONY: install -install: - @$(MAKE) -C $(BUILD_DIR) $@ - -.PHONY: uninstall -uninstall: - @$(MAKE) -C $(BUILD_DIR) $@ - @$(MAKE) -C $(BUILD_DIR) post-uninstall - -.PHONY: pot_file -pot_file: - @$(MAKE) -C $(BUILD_DIR) $@ - @cp build/src/geary.pot po - -.PHONY: clean -clean: - @-$(MAKE) -C $(BUILD_DIR) clean - @-rm -f $(BINARIES) - @-rm -f .stamp - -.PHONY: distclean -distclean: clean - @-rm -rf $(BUILD_DIR) - @-rm -f Makefile - -.PHONY: tests -tests: - @$(MAKE) -C $(BUILD_DIR) $@ - -.PHONY: test -test: - @$(MAKE) -C $(BUILD_DIR) tests - -.PHONY: dist -dist: tests - @$(MAKE) -C $(BUILD_DIR) dist - @cp build/*.xz . - -.PHONY: ubuntu -ubuntu: test - @$(MAKE) -C $(BUILD_DIR) ubuntu - -.PHONY: valadoc -valadoc: - @$(MAKE) -C $(BUILD_DIR) valadoc diff -Nru geary-0.12.4/meson.build geary-3.32.0/meson.build --- geary-0.12.4/meson.build 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/meson.build 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,164 @@ +project('geary', [ 'vala', 'c' ], + version: '3.32.0', + license: 'LGPL2.1+', + meson_version: '>= 0.43', +) + +gnome = import('gnome') +i18n = import('i18n') + +# Option +install_contractor_file = get_option('contractor') +iso_639_xml = get_option('iso_639_xml') +iso_3166_xml = get_option('iso_3166_xml') +reference_tracking = get_option('ref_tracking') +poodle = get_option('poodle') +enable_valadoc = get_option('valadoc') + +# Some variables +cc = meson.get_compiler('c') +valac = meson.get_compiler('vala') +config_h_dir = include_directories('.') +geary_prefix = get_option('prefix') +bindir = join_paths(geary_prefix, get_option('bindir')) +datadir = join_paths(geary_prefix, get_option('datadir')) +libdir = join_paths(geary_prefix, get_option('libdir')) +locale_dir = join_paths(geary_prefix, get_option('localedir')) +po_dir = join_paths(meson.source_root(), 'po') +vapi_dir = join_paths(meson.source_root(), 'bindings', 'vapi') +metadata_dir = join_paths(meson.source_root(), 'bindings', 'metadata') +web_extensions_dir = join_paths(libdir, 'geary', 'web-extensions') + +# Make sure Meson can find our custom VAPI's +add_project_arguments([ + '--vapidir', vapi_dir, + '--metadatadir', metadata_dir, + ], + language: 'vala' +) + +# +# Required libraries and other dependencies +# + +target_glib = '2.54' # Also passed to valac, so don't include a point rev +target_gtk = '3.22.26' +target_webkit = '2.20' + +# Primary deps +glib = dependency('glib-2.0', version: '>=' + target_glib) +gmime = dependency('gmime-2.6', version: '>= 2.6.17') +gtk = dependency('gtk+-3.0', version: '>=' + target_gtk) +sqlite = dependency('sqlite3', version: '>= 3.12') +webkit2gtk = dependency('webkit2gtk-4.0', version: '>=' + target_webkit) + +# Secondary deps - keep sorted alphabetically +enchant = dependency('enchant-2', version: '>=2.1', required: false) # see below +folks = dependency('folks', version: '>=0.11') +gck = dependency('gck-1') +gcr = dependency('gcr-3', version: '>= 3.10.1') +gdk = dependency('gdk-3.0', version: '>=' + target_gtk) +gee = dependency('gee-0.8', version: '>= 0.8.5') +gio = dependency('gio-2.0', version: '>=' + target_glib) +goa = dependency('goa-1.0') +gthread = dependency('gthread-2.0', version: '>=' + target_glib) +iso_codes = dependency('iso-codes') +javascriptcoregtk = dependency('javascriptcoregtk-4.0', version: '>=' + target_webkit) +json_glib = dependency('json-glib-1.0', version: '>= 1.0') +libcanberra = dependency('libcanberra', version: '>= 0.28') +libmath = cc.find_library('m') +libnotify = dependency('libnotify', version: '>= 0.7.5') +libsecret = dependency('libsecret-1', version: '>= 0.11') +libsoup = dependency('libsoup-2.4', version: '>= 2.48') +libunwind_dep = dependency( + 'libunwind', version: '>= 1.1', required: not get_option('libunwind_optional') +) +libunwind_generic_dep = dependency( + 'libunwind-generic', version: '>= 1.1', required: not get_option('libunwind_optional') +) +libxml = dependency('libxml-2.0', version: '>= 2.7.8') +posix = valac.find_library('posix') +webkit2gtk_web_extension = dependency('webkit2gtk-web-extension-4.0', version: '>=' + target_webkit) + +# Can currently use either Enchant 1 or 2 +if not enchant.found() + enchant = dependency('enchant', version: '>=1.6') +endif + +if libunwind_dep.found() + # Libunwind system dependencies above ensures appropriate versions, + # but this declared depencency is what we actually build against so we + # can include the custom VAPI correctly. We need to add unwind_lib to + # the search path for these so Flatpak builds can find the C lib. + unwind_lib = libunwind_dep.get_pkgconfig_variable('libdir') + libunwind = declare_dependency( + dependencies: [ + valac.find_library('libunwind', dirs: [vapi_dir, unwind_lib]), + cc.find_library('libunwind', dirs: unwind_lib), + cc.find_library('libunwind-generic', dirs: unwind_lib) + ], + ) +endif + +# Optional dependencies +appstream_util = find_program('appstream-util', required: false) +desktop_file_validate = find_program('desktop-file-validate', required: false) +libmessagingmenu = dependency('libmessaging-menu', version: '>= 12.10', required: false) +libunity = dependency('unity', version: '>= 5.12.0', required: false) + +# Ensure SQLite was built correctly +if not cc.has_header_symbol('sqlite3.h', 'SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER', dependencies: sqlite) + error('SQLite3 is missing FTS3 tokenizer support. Please compile it with -DSQLITE_ENABLE_FTS3.\n' + + 'See https://bugzilla.gnome.org/show_bug.cgi?id=763203 for details.') +endif + +# +# Build glue +# + +if enable_valadoc + valadoc = find_program('valadoc') +endif + +# Language detection +iso_codes_dir = join_paths(iso_codes.get_pkgconfig_variable('prefix'), 'share', 'xml', 'iso-codes') +if iso_639_xml == '' + iso_639_xml = join_paths(iso_codes_dir, 'iso_639.xml') +endif +if iso_3166_xml == '' + iso_3166_xml = join_paths(iso_codes_dir, 'iso_3166.xml') +endif +files(iso_639_xml, iso_3166_xml) # Check to make sure these exist + +# Configuration +conf = configuration_data() +conf.set_quoted('GETTEXT_PACKAGE', meson.project_name()) +conf.set_quoted('G_LOG_DOMAIN', meson.project_name()) +conf.set_quoted('PACKAGE_NAME', meson.project_name()) +conf.set_quoted('PACKAGE_STRING', '@0@-@1@'.format(meson.project_name(), meson.project_version())) +conf.set_quoted('PACKAGE_VERSION', meson.project_version()) +conf.set_quoted('_BUILD_ROOT_DIR', meson.build_root()) +conf.set_quoted('_SOURCE_ROOT_DIR', meson.source_root()) +conf.set_quoted('_GSETTINGS_DIR', join_paths(meson.build_root(), 'desktop')) +conf.set_quoted('_INSTALL_PREFIX', geary_prefix) +conf.set_quoted('_WEB_EXTENSIONS_DIR', web_extensions_dir) +conf.set_quoted('LANGUAGE_SUPPORT_DIRECTORY', locale_dir) +conf.set_quoted('ISO_CODE_639_XML', iso_639_xml) +conf.set_quoted('ISO_CODE_3166_XML', iso_3166_xml) +conf.set('HAVE_FTS3_TOKENIZE', true) +conf.set('VERSION', meson.project_version()) +conf.set('GCR_API_SUBJECT_TO_CHANGE', true) +configure_file(output: 'config.h', configuration: conf) + +# Post-install scripts +meson.add_install_script(join_paths('build-aux', 'post_install.py')) + +# Subfolders +subdir('desktop') +subdir('help') +subdir('icons') +subdir('po') +subdir('sql') +subdir('ui') +subdir('src') +subdir('test') diff -Nru geary-0.12.4/meson_options.txt geary-3.32.0/meson_options.txt --- geary-0.12.4/meson_options.txt 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/meson_options.txt 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,7 @@ +option('valadoc', type: 'boolean', value: false, description: 'Whether to build the documentaton (requires valadoc).') +option('contractor', type: 'boolean', value: false, description: 'Whether to install the contractor file (Elementary OS-specific).') +option('poodle', type: 'boolean', value: true, description: 'Whether to apply the POODLE SSLv3 fix.') +option('ref_tracking', type: 'boolean', value: false, description: 'Whether to use explicit reference tracking.') +option('iso_639_xml', type: 'string', value: '', description: 'Full path to the ISO 639 XML file.') +option('iso_3166_xml', type: 'string', value: '', description: 'Full path to the ISO 3166 XML file.') +option('libunwind_optional', type: 'boolean', value: false, description: 'Determines if libunwind is required.') diff -Nru geary-0.12.4/NEWS geary-3.32.0/NEWS --- geary-0.12.4/NEWS 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/NEWS 2019-03-17 13:39:29.000000000 +0000 @@ -1,104 +1,147 @@ -Version 0.12.4 -~~~~~~~~~~~~~~ -Released: 2018-08-29 - -Bug fixes included in this release: - * Fix handling folder names with IMAP reserved characters, such as - backslashes. Issue #40 - * Fix dialog windows not focused after being first shown. Issue #43 - * Actually include the fix for "Move to folder" selection bug. Issue #24 - * Fix build under vala >= 0.41. Issue #86 - * Fixes for miscellaneous crashers - -Version 0.12.3 -~~~~~~~~~~~~~~ -Released: 2018-07-14 - -Bug fixes included in this release: - * Not syncing mail using Turkish locale. Bug 795906 - * Fix crash saving an attachment with unknown content type - * Fix crash in secret_collection_get_locked. Bug 795328 - * "Move to folder" selection bug. Issue #24 - * Subfolders with special folders not displayed in list. Issue #11 - * Add OARS metadata for Flathub +Version 3.32 +~~~~~~~~~~~~ +Released: 2019-03-17 + +Enhancements included in this release: + * Application menu moved to the main window + * Desktop contacts are used for sender images + * Unknown contacts are given personalised initials and colour + * Updated application icons + * Improved server compatibility + * Custom email CSS now applied to Composer view Thanks to all who contributed code fixes and enhancements to this release: - * Nick Richards - -Version 0.12.2 -~~~~~~~~~~~~~~ -Released: 2018-04-24 - -Bug fixes included in this release: - * Fix being unable to remove attachments from a draft. Bug 792555. - * Ensure drafts are removed when composer from address changes accounts. Bug - 778976. - * Workaround composer info label being too long. Bug 790435. - * Ensure embedded composer is always scrolled to when opened. Bug 778027. - * Don't display quote expander buttons when printing a message. Bug 795216. - * Fix composer detach button position and visibility. Bug 793710. - * Actually fix second multipart/digest message body not being displayed. Bug - 788637. - * Ensure gnome-control-centre knows in advance Geary uses notifications. - * Fix gnome-shell notifications missing an icon under flatpak. Bug 790103. - * Fix message body quote button styling under WebKitGTK 2.20. - * Don't show unused header widgets when showing a message via notifications. - * Work around present() not actually raising windows under Wayland. Bug - 776881. - * Reduce CPU use when idle. Bug 783025. - * Fix some serious run-time memory leaks. + * Bilal Elmoussaoui + * Christian Kellner + * Christopher Davis + * Jakub Steiner + * Jan Tojnar + * Joel Duncan + * Konstantin Kharlamov + * Kristian Klausen + * p3732 + * Rico Tzschichholz + * Thomas Moschny Thanks also to all who contributed translations, for the user interface: + * Alexandre Franke (fr) + * Anders Jonsson (sv) + * Ask Hjorth Larsen (da) + * Balázs Úr (hu) + * Bernd Homuth (de) * Daniel Mustieles (es) - * Stas Solovey (ru) - * Мирослав Николић (sr) (sr@latin) + * Daniel Șerbănescu (ro) + * Fabio Tomat (fur) + * Jiri Grönroos (fi) + * Nathan Follens (nl) + * Piotr Drąg (pl) + * Rafael Fontenelle (pt_BR) + * Ryuta Fujii (ja) + * Serdar Sağlam (tr) + * Tim Sabsch (de) -Version 0.12.1 -~~~~~~~~~~~~~~ -Released: 2018-02-13 - -Bug fixes included in this release: - * Parts of multipart/digest message do not expand when clicked upon. Bug - 788637. - * Geary does not unlock keyring at start. Bug 784300. - * Syntax error in IMAP greeting from AliYun IMAP server. Bug 781488. - * Message body text caret (cursor) not initially visible. Bug 788797. - * Losing focus when clicking in empty part of the composer. Bug 779369. - * Line breaks lost when selecting and replying to certain messages. Bug - 781178. - * Always display an in-window app-menu under Unity. Bug 770618. - * Crash in SoupCacheInputStream when cancelling a message load. Bug 778720. - * Do not show Labels on sidebar if no label is present. Bug 754802. - * Unable to use Ctrl+C shortcut to copy e-mail subject; must use context menu - instead. Bug 788494. - * After clicking on mailto link in Geary, the body in the composer is not - writable. Bug 771504. - * Editing message does not support RTL. Bug 713607. +Version 0.13 +~~~~~~~~~~~~ +Released: 2019-02-17 + +Enhancements included in this release: + * Unread email count is now updated correctly + * Conversations load faster, smoother with better feedback + * Support for email accounts added via GNOME Online Accounts + * Improved account creation and management user interface + * Email flagged as deleted but not removed by other apps now hidden + * Individual messages in a conversation can be deleted + * Internal links in HTML email now work + * Supported ordered and unordered lists in the composer + * Rich text pasting improvements in the composer + * Plain text versions of rich text mail includes formatting + * Detached composers now remember their last used size + * Better reporting when a login, security or other problem occurs + * Reduced background synchronisation CPU use + * Improved handling when going online and offline + * Show an in-application notification when email has been sent + * Flag possibly spoofed email addresses + * Improve privacy when sending email using an alias + * Subject, sender and date are being shown when printed again + * Server compatibility improvements + * Build, testing and other infrastructure improvements + * Numerous bug fixes and minor user interface improvements + * Numerous user interface translation updates Thanks to all who contributed code fixes and enhancements to this -release: +release, including a number of new contributors: + + * Adrien Plazas * Alex Henrie + * Andre Klapper + * Erik Faye-Lund + * Federico Bruni * Gautier Pelloux-Prayer + * Georges Basile Stavracas Neto + * Greg V + * James Magahern + * Jan Tojnar + * Jiri Cerny + * Joel Duncan + * john + * Jordan Petridis + * Juraj Fiala * Kacper Bielecki + * Michael Catanzaro + * nick richards + * Niels De Graef + * Nikolas Tapia + * Oskar Viljasaar + * Piotr Drąg + * Rico Tzschichholz Thanks also to all who contributed translations, for the user interface: + * Alan Mortensen (da) * Anders Jonsson (sv) * Ask Hjorth Larsen (da) * Balázs Meskó (hu) + * Balázs Úr (hu) + * Baurzhan Muftakhidinov (kk) + * Carlos Abel Córdova Sáenz (es) + * Christian Schröder (de) + * Claude Paroz (fr) + * Daniel Mustieles (es) + * Daniel Șerbănescu (ro) * Dušan Kazik (sk) + * Emin Tufan Çetin (tr) * Federico Bruni (it) + * Frank Brütting (de) + * GNOME Translation Robot (nl) + * Isaac Ferreira Filho (pt_BR) + * Jiri Grönroos (fi) + * Jordi Mas (ca) + * Josef Andersson (sv) + * Kristjan SCHMIDT (eo) * Kukuh Syafaat (id) * Marek Cernocky (cs) * Mario Blättermann (de) + * Matej Urbančič (sl) + * Nathan Follens (nl) * Piotr Drąg (pl) * Rafael Fontenelle (pt_BR) + * Ryuta Fujii (ja) + * Sabri Ünal (tr) * Stas Solovey (ru) + * Tim Sabsch (de) + * Yuras Shumovich (be) And for the user manual: + * Anders Jonsson (sv) + * Emin Tufan Çetin (tr) + * Federico Bruni (it) + * Marek Černocký (cs) * Mario Blättermann (de) + * Muhammet Kara (tr) + * Piotr Drąg (pl) + * Rafael Fontenelle (pt_BR) Version 0.12 ~~~~~~~~~~~~ diff -Nru geary-0.12.4/org.gnome.Geary.json geary-3.32.0/org.gnome.Geary.json --- geary-0.12.4/org.gnome.Geary.json 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/org.gnome.Geary.json 2019-03-17 13:39:29.000000000 +0000 @@ -1,11 +1,27 @@ -/* flatpak-builder config for Geary. */ +/* + * Flatpak builder manifest for Geary. + * + * When updating this file after branching a new stable release, + * the following should be updated: + * + * - app branch and geary source branches + * - remove app tags and desktop-file-name-prefix + * + * When updating the Flathub manifest, copy the stable manifest over + * it and pin each source to a specific tarball. + */ + { "app-id": "org.gnome.Geary", - "branch": "geary-0.12", + "branch": "master", "runtime": "org.gnome.Platform", - "runtime-version": "3.28", + "runtime-version": "master", "sdk": "org.gnome.Sdk", "command": "geary", + + "tags": ["nightly"], + "desktop-file-name-prefix": "(Nightly) ", + "finish-args": [ /* X11 + XShm access */ "--share=ipc", "--socket=x11", @@ -28,6 +44,15 @@ /* Secrets access */ "--talk-name=org.freedesktop.secrets", + /* GOA support */ + "--talk-name=org.gnome.ControlCenter", + "--talk-name=org.gnome.OnlineAccounts", + + /* Folks contact and avatar support (via EDS) */ + "--talk-name=org.gnome.evolution.dataserver.AddressBook9", + "--talk-name=org.gnome.evolution.dataserver.Sources5", + "--filesystem=xdg-cache/evolution/addressbook:ro", + /* Needed for dconf to work */ "--filesystem=xdg-run/dconf", "--filesystem=~/.config/dconf:ro", "--talk-name=ca.desrt.dconf", "--env=DCONF_USER_CONFIG_DIR=.config/dconf", @@ -35,52 +60,114 @@ /* Let view source keep on working as-sis for now. Bug 779311. */ "--filesystem=/tmp" ], - "build-options" : { - "cflags": "-O2 -g", - "cxxflags": "-O2 -g", - "env": { - "V": "1" - } - }, "cleanup": ["/include", "/lib/pkgconfig", "/share/pkgconfig", "/share/aclocal", "/man", "/share/man", "/share/gtk-doc", - "/share/vala", + "/share/vala", "/share/girepository-1", "/share/gir-1.0", "*.la", "*.a"], "modules": [ { - "name": "gnome-doc-utils", - "build-options": { - "arch" : { - /* Lie about our arch - no actual compilation happens here. */ - "arm": { - "config-opts": [ "--build=amd64" ] - }, - "aarch64": { - "config-opts": [ "--build=amd64" ] - } - } }, + "name": "libgee", + "config-opts" : [ + "--enable-introspection=no" + ], + "sources": [ + { + "type": "git", + "url": "https://gitlab.gnome.org/GNOME/libgee.git", + "branch": "master" + } + ] + }, + { + "name": "gnome-online-accounts", + "config-opts": [ + "--disable-telepathy", + "--disable-documentation", + "--disable-backend", + /* Enabling debug via configure causes both -g and -O0 + to be set, which is bad since the former is + redundant with the default fd.o build-options, + and the latter conflicts with them. So disable + debug instead. */ + "--enable-debug=no" + ], + "sources": [ + { + "type": "git", + "url": "https://gitlab.gnome.org/GNOME/gnome-online-accounts.git", + "branch": "master" + } + ] + }, + { + "name": "libical", + "cleanup": [ + "/lib/cmake" + ], + "buildsystem": "cmake-ninja", + "config-opts": [ + "-DCMAKE_BUILD_TYPE=Release", + "-DCMAKE_INSTALL_LIBDIR=lib", + "-DBUILD_SHARED_LIBS:BOOL=ON" + ], "sources": [ { "type": "archive", - "url": "https://download.gnome.org/sources/gnome-doc-utils/0.20/gnome-doc-utils-0.20.10.tar.xz", - "sha256": "cb0639ffa9550b6ddf3b62f3b1add92fb92ab4690d351f2353cffe668be8c4a6" + "url": "https://github.com/libical/libical/releases/download/v2.0.0/libical-2.0.0.tar.gz", + "sha256": "654c11f759c19237be39f6ad401d917e5a05f36f1736385ed958e60cf21456da" } ] }, { - "name": "libgee", - "build-options" : { - "env": { - "PKG_CONFIG_GOBJECT_INTROSPECTION_1_0_GIRDIR": "/app/share/gir-1.0", - "PKG_CONFIG_GOBJECT_INTROSPECTION_1_0_TYPELIBDIR": "/app/lib/girepository-1.0" + "name": "evolution-data-server", + "cleanup": [ + "/lib/cmake", + "/lib/evolution-data-server/*-backends", + "/libexec", + "/share/dbus-1/services" + ], + "config-opts": [ + "-DCMAKE_BUILD_TYPE=Release", + "-DENABLE_GTK=ON", + "-DENABLE_GOA=ON", + "-DENABLE_UOA=OFF", + "-DENABLE_GOOGLE_AUTH=OFF", + "-DENABLE_GOOGLE=OFF", + "-DENABLE_WITH_PHONENUMBER=OFF", + "-DENABLE_VALA_BINDINGS=ON", + "-DENABLE_WEATHER=OFF", + "-DWITH_OPENLDAP=OFF", + "-DWITH_LIBDB=OFF", + "-DENABLE_INTROSPECTION=ON", + "-DENABLE_INSTALLED_TESTS=OFF", + "-DENABLE_GTK_DOC=OFF", + "-DENABLE_EXAMPLES=OFF" + ], + "buildsystem": "cmake-ninja", + "sources": [ + { + "type": "git", + "url": "https://gitlab.gnome.org/GNOME/evolution-data-server.git" } - }, + ] + }, + { + "name": "folks", + "cleanup": [ + "/bin", + "/share/GConf" + ], + "config-opts": [ + "--disable-telepathy-backend", + "--disable-inspect-tool", + "--disable-import-tool", + "--disable-fatal-warnings" + ], "sources": [ { "type": "git", - "url": "https://gitlab.gnome.org/GNOME/libgee.git", - "tag": "0.20.0" + "url": "https://gitlab.gnome.org/GNOME/folks.git" } ] }, @@ -95,12 +182,23 @@ ] }, { - "name": "geary", + "name": "libunwind", "sources": [ { "type": "git", - "url": "https://gitlab.gnome.org/GNOME/geary.git", - "branch": "geary-0.12" + "url": "https://git.savannah.gnu.org/git/libunwind.git", + "branch": "master" + } + ] + }, + { + "name": "geary", + "buildsystem": "meson", + "builddir": true, + "sources": [ + { + "type": "dir", + "path": "." } ] } diff -Nru geary-0.12.4/po/be.po geary-3.32.0/po/be.po --- geary-0.12.4/po/be.po 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/po/be.po 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,3588 @@ +# Belarusian translation for geary. +# Copyright (C) 2018 geary's COPYRIGHT HOLDER +# This file is distributed under the same license as the geary package. +# Antikruk , 2018. +# Zmicer Turok , 2018. +msgid "" +msgstr "" +"Project-Id-Version: geary master\n" +"Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/geary/issues\n" +"POT-Creation-Date: 2019-01-01 23:43+0000\n" +"PO-Revision-Date: 2019-01-07 16:52+0300\n" +"Last-Translator: Zmicer Turok \n" +"Language-Team: Belarusian \n" +"Language: be\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" +"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"n%10<=4 && (n%100<10 || n%100>=20) ? 1: 2);\n" +"X-Generator: Poedit 2.2\n" + +#: desktop/geary-attach.contract.desktop.in:3 +msgid "Send by email" +msgstr "Адправіць па электроннай пошце" + +#. Translators: Do NOT translate or transliterate this text (this is an icon file name)! +#: desktop/geary-attach.contract.desktop.in:5 +msgid "mail-send" +msgstr "mail-send" + +#: desktop/geary-attach.contract.desktop.in:6 +msgid "Send files using Geary" +msgstr "Адправіць файлы праз Geary" + +#. Translators: The application name +#: desktop/geary-autostart.desktop.in:3 +#: desktop/org.gnome.Geary.appdata.xml.in:11 +#: desktop/org.gnome.Geary.desktop.in:3 +#: src/client/accounts/accounts-editor-servers-pane.vala:442 +msgid "Geary" +msgstr "Geary" + +#: desktop/geary-autostart.desktop.in:4 desktop/org.gnome.Geary.desktop.in:4 +msgid "Email" +msgstr "Электронная пошта" + +#. Translators: The application's summary / tagline +#: desktop/geary-autostart.desktop.in:5 +#: desktop/org.gnome.Geary.appdata.xml.in:15 +#: desktop/org.gnome.Geary.desktop.in:5 +#: src/client/application/geary-application.vala:21 +msgid "Send and receive email" +msgstr "Адпраўленне і атрыманне электроннай пошты" + +#. Translators: These are desktop search terms. Do not translate semicolons, end line with a semicolon. +#: desktop/geary-autostart.desktop.in:7 +msgid "Email;E-mail;Mail;" +msgstr "Электронная пошта;Пошта;Email;E-mail;Mail;" + +#. Translators: Do NOT translate or transliterate this text (this is an icon file name)! +#: desktop/geary-autostart.desktop.in:9 desktop/org.gnome.Geary.desktop.in:9 +msgid "org.gnome.Geary" +msgstr "org.gnome.Geary" + +#. Translators: The development team's name +#: desktop/org.gnome.Geary.appdata.xml.in:13 +msgid "Geary Development Team" +msgstr "Каманда распрацоўкі Geary" + +#: desktop/org.gnome.Geary.appdata.xml.in:17 +msgid "" +"Geary is an email application built around conversations, for the GNOME 3 " +"desktop. It allows you to read, find and send email with a straightforward, " +"modern interface." +msgstr "" +"Geary - праграма для працы з электроннай поштай для працоўнага асяроддзя " +"GNOME 3. Яна прытрымліваецца прынцыпу гутарак і мае просты сучасны інтэрфейс." + +#: desktop/org.gnome.Geary.appdata.xml.in:22 +msgid "" +"Conversations allow you to read a complete discussion without having to find " +"and click from message to message." +msgstr "" +"Гутаркі дазваляюць пабачыць усю размову без неабходнасці пераключэння паміж " +"лістамі." + +#: desktop/org.gnome.Geary.appdata.xml.in:26 +msgid "Geary’s features include:" +msgstr "Магчымасці Geary:" + +#: desktop/org.gnome.Geary.appdata.xml.in:28 +msgid "Quick email account setup" +msgstr "Хуткая наладка рахунка электроннай пошты" + +#: desktop/org.gnome.Geary.appdata.xml.in:29 +msgid "Shows related messages together in conversations" +msgstr "Паказвае звязаныя лісты разам у размовах" + +#: desktop/org.gnome.Geary.appdata.xml.in:30 +msgid "Fast, full text and keyword search" +msgstr "Хуткі пошук тэксту і пошук па ключавых словах" + +#: desktop/org.gnome.Geary.appdata.xml.in:31 +msgid "Full-featured HTML and plain text message composer" +msgstr "Паўнавартасны рэдактар HTML і простага тэксту" + +#: desktop/org.gnome.Geary.appdata.xml.in:32 +msgid "Desktop notification of new mail" +msgstr "Апавяшчэнні аб новых лістах на працоўным стале" + +#: desktop/org.gnome.Geary.appdata.xml.in:33 +msgid "Compatible with GMail, Yahoo! Mail, Outlook.com and other IMAP servers" +msgstr "Працуе з GMail, Yahoo! Mail, Outlook.com і іншымі серверамі IMAP" + +#. Translators: A screenshot description. +#: desktop/org.gnome.Geary.appdata.xml.in:47 +msgid "Geary displaying a conversation" +msgstr "Geary адлюстроўвае гутарку" + +#. Translators: A screenshot description. +#: desktop/org.gnome.Geary.appdata.xml.in:52 +msgid "Geary showing the rich text composer" +msgstr "Geary адлюстроўвае фарматаваны тэкст рэдактара" + +#. Translators: These are desktop search terms. Do not translate semicolons, end line with a semicolon. +#: desktop/org.gnome.Geary.desktop.in:7 +msgid "Mail;E-mail;IMAP;GMail;Yahoo;Hotmail;Outlook;" +msgstr "Mail;E-mail;IMAP;GMail;Yahoo;Hotmail;Outlook;" + +#: desktop/org.gnome.Geary.desktop.in:21 ui/gtk/menus.ui:7 +msgid "Compose Message" +msgstr "Напісаць ліст" + +#: desktop/org.gnome.Geary.gschema.xml:8 +msgid "Maximize window" +msgstr "Разгарнуць акно" + +#: desktop/org.gnome.Geary.gschema.xml:9 +msgid "True if the application window is maximized, false otherwise." +msgstr "Ісціна, калі акно праграмы разгорнутае, хлусня - ў адваротным выпадку." + +#: desktop/org.gnome.Geary.gschema.xml:14 +msgid "Width of window" +msgstr "Шырыня акна" + +#: desktop/org.gnome.Geary.gschema.xml:15 +msgid "The last recorded width of the application window." +msgstr "Апошняя запісаная шырыня акна праграмы." + +#: desktop/org.gnome.Geary.gschema.xml:20 +msgid "Height of window" +msgstr "Вышыня акна" + +#: desktop/org.gnome.Geary.gschema.xml:21 +msgid "The last recorded height of the application window." +msgstr "Апошняя запісаная вышыня акна праграмы." + +#: desktop/org.gnome.Geary.gschema.xml:26 +msgid "Position of folder list pane" +msgstr "Пазіцыя панэлі спіса каталогаў" + +#: desktop/org.gnome.Geary.gschema.xml:27 +msgid "Position of the folder list Paned grabber." +msgstr "Пазіцыя панэлі спіса каталогаў." + +#: desktop/org.gnome.Geary.gschema.xml:32 +msgid "Position of folder list pane when horizontal" +msgstr "Гарызантальная пазіцыя панэлі спіса каталогаў" + +#: desktop/org.gnome.Geary.gschema.xml:33 +msgid "" +"Position of the folder list Paned grabber in the horizontal orientation." +msgstr "Гарызантальная пазіцыя панэлі спіса каталогаў." + +#: desktop/org.gnome.Geary.gschema.xml:38 +msgid "Position of folder list pane when vertical" +msgstr "Вертыкальная пазіцыя панэлі спіса каталогаў" + +#: desktop/org.gnome.Geary.gschema.xml:39 +msgid "Position of the folder list Paned grabber in the vertical orientation." +msgstr "Вертыкальная пазіцыя панэлі спіса каталогаў." + +#: desktop/org.gnome.Geary.gschema.xml:44 +msgid "Orientation of the folder list pane" +msgstr "Арыентацыя панэлі спіса каталогаў" + +#: desktop/org.gnome.Geary.gschema.xml:45 +msgid "True if the folder list Paned is in the horizontal orientation." +msgstr "Арыентацыя панэлі спіса каталогаў." + +#: desktop/org.gnome.Geary.gschema.xml:50 +msgid "Position of message list pane" +msgstr "Пазіцыя панэлі спіса лістоў" + +#: desktop/org.gnome.Geary.gschema.xml:51 +msgid "Position of the message list Paned grabber." +msgstr "Пазіцыя панэлі спіса лістоў." + +#: desktop/org.gnome.Geary.gschema.xml:56 +msgid "Autoselect next message" +msgstr "Аўтаматычна пераходзіць да наступнага ліста" + +#: desktop/org.gnome.Geary.gschema.xml:57 +msgid "True if we should autoselect the next available conversation." +msgstr "\"Ісціна\" - аўтаматычна пераходзіць да наступнай даступнай гутаркі." + +#: desktop/org.gnome.Geary.gschema.xml:62 +msgid "Display message previews" +msgstr "Паказваць мініяцюры лістоў" + +#: desktop/org.gnome.Geary.gschema.xml:63 +msgid "True if we should display a short preview of each message." +msgstr "\"Ісціна\" - паказваць скарочаны папярэдні прагляд кожнага ліста." + +#: desktop/org.gnome.Geary.gschema.xml:68 +msgid "Languages that shall be used in the spell checker" +msgstr "Мовы, што павінны выкарыстоўвацца ў праграме праверкі арфаграфіі" + +#: desktop/org.gnome.Geary.gschema.xml:69 +msgid "List of the languages to use in the spell checker." +msgstr "Спіс моў, што выкарыстоўваюцца ў праграме праверкі арфаграфіі." + +#: desktop/org.gnome.Geary.gschema.xml:74 +msgid "Languages that are displayed in the spell checker popover" +msgstr "Мовы, што адлюстроўваюцца ў выплыўным акне праверкі арфаграфіі" + +#: desktop/org.gnome.Geary.gschema.xml:75 +msgid "" +"List of languages that are always displayed in the popover of the spell " +"checker." +msgstr "" +"Спіс моў, што заўжды адлюстроўваюцца ў выплыўным акне праверкі арфаграфіі." + +#: desktop/org.gnome.Geary.gschema.xml:80 +msgid "Enable notification sounds" +msgstr "Уключыць гукі для апавяшчэнняў" + +#: desktop/org.gnome.Geary.gschema.xml:81 +msgid "True to play sounds for notifications and sending." +msgstr "\"Ісціна\" - прайграваць гукі для апавяшчэнняў і адпраўлення." + +#: desktop/org.gnome.Geary.gschema.xml:86 +msgid "Show notifications for new mail" +msgstr "Паказваць апавяшчэнні аб новых лістах" + +#: desktop/org.gnome.Geary.gschema.xml:87 +msgid "True to show notification bubbles." +msgstr "\"Ісціна\" - паказваць апавяшчэнні." + +#: desktop/org.gnome.Geary.gschema.xml:92 +msgid "Notify of new mail at startup" +msgstr "Апавяшчаць аб новых лістах падчас запуску" + +#: desktop/org.gnome.Geary.gschema.xml:93 +msgid "True to notify of new mail at startup." +msgstr "\"Ісціна\", каб апавяшчаць аб новых лістах падчас запуску." + +#: desktop/org.gnome.Geary.gschema.xml:98 +msgid "Ask when opening an attachment" +msgstr "Пытацца падчас адкрыцця ўкладзеных файлаў" + +#: desktop/org.gnome.Geary.gschema.xml:99 +msgid "True to ask when opening an attachment." +msgstr "\"Ісціна\", каб пытацца пры адкрыцці ўкладзеных файлаў." + +#: desktop/org.gnome.Geary.gschema.xml:104 +msgid "Whether to compose emails in HTML" +msgstr "Ці пісаць лісты ў HTML" + +#: desktop/org.gnome.Geary.gschema.xml:105 +msgid "True to compose emails in HTML; false for plain text." +msgstr "\"Ісціна\" - пісаць лісты ў HTML; \"хлусня\" - простым тэкстам." + +#: desktop/org.gnome.Geary.gschema.xml:110 +msgid "Advisory strategy for full-text searching" +msgstr "Стратэгія для поўнатэкставага пошуку" + +#: desktop/org.gnome.Geary.gschema.xml:111 +msgid "" +"Acceptable values are “exact”, “conservative”, “aggressive”, and “horizon”." +msgstr "" +"Магчымыя значэнні: \"дакладны\", \"кансерватыўны\", \"агрэсіўны\", " +"\"гарызантальны\"." + +#: desktop/org.gnome.Geary.gschema.xml:116 +msgid "Zoom of conversation viewer" +msgstr "Маштаб прагляду гутаркі" + +#: desktop/org.gnome.Geary.gschema.xml:117 +msgid "The zoom to apply on the conservation view." +msgstr "Маштаб, што ўжываецца да гутаркі." + +#: desktop/org.gnome.Geary.gschema.xml:122 +msgid "Size of detached composer window" +msgstr "Памер адасобленага акна кампазітара" + +#: desktop/org.gnome.Geary.gschema.xml:123 +msgid "The last recorded size of the detached composer window." +msgstr "Апошні запісаны памер дадатковага акна рэдактара." + +#: desktop/org.gnome.Geary.gschema.xml:128 +msgid "Base URL to look up contact avatars" +msgstr "Базавы URL-адрас для пошуку аватараў кантактаў" + +#: desktop/org.gnome.Geary.gschema.xml:129 +msgid "" +"A Gravatar or Libravatar compatible URL, set to the empty string to disable." +msgstr "" +"URL-адрас, сумяшчальны з Gravatar альбо Libravatar, пакіньце пустым, каб " +"выключыць." + +#: desktop/org.gnome.Geary.gschema.xml:134 +msgid "Whether we migrated the old settings" +msgstr "Ці можна перанесці старыя налады" + +#: desktop/org.gnome.Geary.gschema.xml:135 +msgid "" +"False to check for the old “org.yorba.geary”-schema and copy its values." +msgstr "" +"\"Хлусня\" - правяраць старую схему “org.yorba.geary” і капіраваць яе " +"значэнні." + +#: src/client/accounts/accounts-editor.vala:49 +#: ui/accounts_editor_list_pane.ui:8 +msgid "Accounts" +msgstr "Рахункі" + +#. Translators: Label for adding an email account +#. account for a generic IMAP service provider. +#: src/client/accounts/accounts-editor-add-pane.vala:98 +msgid "All others" +msgstr "Усе іншыя" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:192 +#: src/client/accounts/accounts-editor-servers-pane.vala:249 +msgid "Check your receiving login and password" +msgstr "Праверце лагін і пароль атрымання" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:198 +#: src/client/accounts/accounts-editor-servers-pane.vala:253 +msgid "Check your receiving server details" +msgstr "Праверце падрабязнасці сервера атрымання" + +#. Translators: In-app notification label +#. There was an SMTP auth error, but IMAP already +#. succeeded, so the user probably needs to +#. specify custom creds here +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:220 +#: src/client/accounts/accounts-editor-servers-pane.vala:274 +msgid "Check your sending login and password" +msgstr "Праверце лагін і пароль для адпраўлення" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:226 +#: src/client/accounts/accounts-editor-servers-pane.vala:278 +msgid "Check your sending server details" +msgstr "Праверце падрабязнасці сервера адпраўлення" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:241 +msgid "Check your email address and password" +msgstr "Праверце ваш лагін і пароль" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:246 +msgid "Could not connect, check your network" +msgstr "Не атрымалася злучыцца, праверце сетку" + +#. Translators: In-app notification label for a +#. generic error creating an account +#: src/client/accounts/accounts-editor-add-pane.vala:259 +msgid "An unexpected problem occurred" +msgstr "Узнікла нечаканая праблема" + +#. Translators: In-app notification label, the +#. string substitution is a more detailed reason. +#: src/client/accounts/accounts-editor-add-pane.vala:278 +#, c-format +msgid "Account not created: %s" +msgstr "Рахунак не створаны: %s" + +#. Translators: Label for the person's actual name when adding +#. an account +#: src/client/accounts/accounts-editor-add-pane.vala:497 +msgid "Your name" +msgstr "Вашае імя" + +#: src/client/accounts/accounts-editor-add-pane.vala:514 +msgid "Email address" +msgstr "Адрас электроннай пошты" + +#. Translators: Placeholder for the default sender address +#. when adding an account +#. Translators: This is used as a placeholder for the +#. address part of an email address when editing a user's +#. sender address preferences for an account. +#: src/client/accounts/accounts-editor-add-pane.vala:517 +#: src/client/accounts/accounts-editor-edit-pane.vala:452 +msgid "person@example.com" +msgstr "person@example.com" + +#. Translators: Label for an IMAP/SMTP service login/user name +#. when adding an account +#. Translators: Label for the user's login name for an +#. IMAP, SMTP, etc service +#: src/client/accounts/accounts-editor-add-pane.vala:531 +#: src/client/accounts/accounts-editor-servers-pane.vala:700 +msgid "Login name" +msgstr "Лагін" + +#. Translators: Label for the user's password for an IMAP, +#. SMTP, etc service +#: src/client/accounts/accounts-editor-add-pane.vala:545 +#: src/client/accounts/accounts-editor-servers-pane.vala:816 +#: ui/password-dialog.glade:108 +msgid "Password" +msgstr "Пароль" + +#. Translators: Label for the IMAP server hostname when +#. adding an account. +#. Translators: This label describes the host name or IP +#. address and port used by an account's IMAP service. +#: src/client/accounts/accounts-editor-add-pane.vala:567 +#: src/client/accounts/accounts-editor-servers-pane.vala:553 +msgid "IMAP server" +msgstr "Сервер IMAP" + +#. Translators: Placeholder for the IMAP server hostname +#. when adding an account. +#: src/client/accounts/accounts-editor-add-pane.vala:570 +msgid "imap.example.com" +msgstr "imap.example.com" + +#. Translators: Label for the SMTP server hostname when +#. adding an account. +#. Translators: This label describes the host name or IP +#. address and port used by an account's SMTP service. +#: src/client/accounts/accounts-editor-add-pane.vala:576 +#: src/client/accounts/accounts-editor-servers-pane.vala:559 +msgid "SMTP server" +msgstr "Сервер SMTP" + +#. Translators: Placeholder for the SMTP server hostname +#. when adding an account. +#: src/client/accounts/accounts-editor-add-pane.vala:579 +msgid "smtp.example.com" +msgstr "smtp.example.com" + +#. Translators: Label in the account editor for the user's +#. custom name for an account. +#: src/client/accounts/accounts-editor-edit-pane.vala:263 +#: ui/accounts_editor_remove_pane.ui:123 +msgid "Account name" +msgstr "Назва рахунка" + +#. Translators: Tooltip used to undo changing +#. the name of an account. The string +#. substitution is the old name of the +#. account. +#: src/client/accounts/accounts-editor-edit-pane.vala:296 +#, c-format +msgid "Change account name back to “%s”" +msgstr "Змяніць назву рахунка назад на “%s”" + +#. Translators: Tooltip for adding a new email sender/from +#. address's address to an account +#: src/client/accounts/accounts-editor-edit-pane.vala:320 +msgid "Add a new sender email address" +msgstr "Дадаць новы адрас электроннай пошты адпраўніка" + +#. Translators: Label used to indicate the user has +#. provided no display name for one of their sender +#. email addresses in their account settings. +#: src/client/accounts/accounts-editor-edit-pane.vala:400 +msgid "Name not set" +msgstr "Назва не вызначаная" + +#. Translators: This is used as a placeholder for the +#. display name for an email address when editing a user's +#. sender address preferences for an account. +#: src/client/accounts/accounts-editor-edit-pane.vala:439 +msgid "Sender Name" +msgstr "Імя адпраўніка" + +#: src/client/accounts/accounts-editor-edit-pane.vala:462 +msgid "Remove" +msgstr "Выдаліць" + +#. Translators: Label used for the display name part of an +#. email address when editing a user's sender address +#. preferences for an account. +#: src/client/accounts/accounts-editor-edit-pane.vala:477 +msgid "Sender name:" +msgstr "Імя адпраўніка:" + +#. Translators: Label used for the address part of an +#. email address when editing a user's sender address +#. preferences for an account. +#: src/client/accounts/accounts-editor-edit-pane.vala:484 +msgid "Email address:" +msgstr "Адрас электроннай пошты:" + +#. Translators: Label used as the undo tooltip after adding an +#. new sender email address to an account. The string +#. substitution is the email address added. +#: src/client/accounts/accounts-editor-edit-pane.vala:544 +#, c-format +msgid "Remove “%s”" +msgstr "Выдаліць “%s”" + +#. Translators: Label used as the undo tooltip after editing a +#. sender address for an account. The string substitution is +#. the email address edited. +#: src/client/accounts/accounts-editor-edit-pane.vala:584 +#, c-format +msgid "Undo changes to “%s”" +msgstr "Адрабіць змены “%s”" + +#. Translators: Label used as the undo tooltip after removing +#. a sender address from an account. The string substitution +#. is the email address edited. +#: src/client/accounts/accounts-editor-edit-pane.vala:673 +#, c-format +msgid "Add “%s” back" +msgstr "Дадаць “%s” назад" + +#. Translators: Label used as the undo tooltip after removing +#. a sender address from an account. The string substitution +#. is the email address edited. +#: src/client/accounts/accounts-editor-edit-pane.vala:711 +msgid "Undo signature changes" +msgstr "Адрабіць змены подпісу" + +#. Translators: This label describes the account +#. preference for the length of time (weeks, months or +#. years) that past email should be downloaded. +#: src/client/accounts/accounts-editor-edit-pane.vala:755 +msgid "Download mail" +msgstr "Спампаваць пошту" + +#. Translators: Tooltip for undoing a change +#. to the length of time that past email +#. should be downloaded for an account. The +#. string substitution is the duration, +#. e.g. "1 month back". +#: src/client/accounts/accounts-editor-edit-pane.vala:787 +#, c-format +msgid "Change download period back to: %s" +msgstr "Змяніць перыяд спампоўвання назад на: %s" + +#: src/client/accounts/accounts-editor-edit-pane.vala:808 +msgid "Everything" +msgstr "За ўвесь час" + +#: src/client/accounts/accounts-editor-edit-pane.vala:812 +msgid "2 weeks back" +msgstr "апошнія 2 тыдні" + +#: src/client/accounts/accounts-editor-edit-pane.vala:816 +msgid "1 month back" +msgstr "апошні месяц" + +#: src/client/accounts/accounts-editor-edit-pane.vala:820 +msgid "3 months back" +msgstr "апошнія 3 месяцы" + +#: src/client/accounts/accounts-editor-edit-pane.vala:824 +msgid "6 months back" +msgstr "апошнія 6 месяцаў" + +#: src/client/accounts/accounts-editor-edit-pane.vala:828 +msgid "1 year back" +msgstr "апошні год" + +#: src/client/accounts/accounts-editor-edit-pane.vala:832 +msgid "2 years back" +msgstr "апошнія 2 гады" + +#: src/client/accounts/accounts-editor-edit-pane.vala:836 +msgid "4 years back" +msgstr "апошнія 4 гады" + +#: src/client/accounts/accounts-editor-edit-pane.vala:842 +#, c-format +msgid "%d day back" +msgid_plural "%d days back" +msgstr[0] "апошні %d дзень" +msgstr[1] "апошнія %d дні" +msgstr[2] "апошнія %d дзён" + +#: src/client/accounts/accounts-editor-list-pane.vala:251 +msgid "Undo" +msgstr "Адрабіць" + +#: src/client/accounts/accounts-editor-list-pane.vala:261 +msgid "Redo" +msgstr "Вярнуць" + +#: src/client/accounts/accounts-editor-list-pane.vala:357 +#: src/client/accounts/accounts-editor-list-pane.vala:445 +#: src/client/accounts/accounts-editor-row.vala:278 +msgid "Gmail" +msgstr "Gmail" + +#: src/client/accounts/accounts-editor-list-pane.vala:361 +#: src/client/accounts/accounts-editor-list-pane.vala:449 +#: src/client/accounts/accounts-editor-row.vala:282 +msgid "Outlook.com" +msgstr "Outlook.com" + +#: src/client/accounts/accounts-editor-list-pane.vala:365 +#: src/client/accounts/accounts-editor-list-pane.vala:453 +#: src/client/accounts/accounts-editor-row.vala:286 +msgid "Yahoo" +msgstr "Yahoo" + +#. Translators: Tooltip for accounts that have been +#. loaded by disabled by the user. +#: src/client/accounts/accounts-editor-list-pane.vala:383 +msgid "This account has been disabled" +msgstr "Гэты рахунак быў выключаны" + +#. Translators: Tooltip for accounts that have been +#. loaded but because of some error are not able to be +#. used. +#: src/client/accounts/accounts-editor-list-pane.vala:392 +msgid "This account has encountered a problem and is unavailable" +msgstr "Гэты рахунак схібіў і цяпер недаступны" + +#. Translators: Label for adding a generic email account +#: src/client/accounts/accounts-editor-list-pane.vala:442 +msgid "Other email providers" +msgstr "Іншыя паштовыя сэрвісы" + +#. Translators: Notification shown after removing an +#. account. The string substitution is the name of the +#. account. +#: src/client/accounts/accounts-editor-list-pane.vala:559 +#, c-format +msgid "Account “%s” removed" +msgstr "Рахунак “%s” выдалены" + +#. Translators: Notification shown after removing an account +#. is undone. The string substitution is the name of the +#. account. +#: src/client/accounts/accounts-editor-list-pane.vala:566 +#, c-format +msgid "Account “%s” restored" +msgstr "Рахунак “%s” адноўлены" + +#. Translators: Tooltip for dragging list items +#: src/client/accounts/accounts-editor-row.vala:49 +msgid "Drag to move this item" +msgstr "Перацягніце, каб перамясціць аб'ект" + +#. Translators: Label describes the service provider +#. hosting the email account, e.g. Gmail, Yahoo, or some +#. other generic IMAP service. +#: src/client/accounts/accounts-editor-row.vala:294 +msgid "Service provider" +msgstr "Пастаўшчык паслуг" + +#. Translators: This label describes what form of transport +#. security (TLS, StartTLS, etc) used by an account's IMAP or SMTP +#. service. +#: src/client/accounts/accounts-editor-row.vala:467 +msgid "Connection security" +msgstr "Бяспека злучэння" + +#. Translators: Label used when no auth scheme is used +#. by an account's IMAP or SMTP service. +#: src/client/accounts/accounts-editor-row.vala:478 +#: src/client/accounts/accounts-editor-servers-pane.vala:577 +#: src/client/accounts/accounts-editor-servers-pane.vala:783 +#: src/engine/api/geary-special-folder-type.vala:58 +msgid "None" +msgstr "Няма" + +#: src/client/accounts/accounts-editor-row.vala:485 +msgid "StartTLS" +msgstr "StartTLS" + +#: src/client/accounts/accounts-editor-row.vala:492 +msgid "TLS" +msgstr "TLS" + +#. Translators: Label for source of SMTP authentication +#. credentials (none, use IMAP, custom) when adding a new +#. account +#: src/client/accounts/accounts-editor-row.vala:533 +msgid "Login" +msgstr "Лагін" + +#. Translators: ComboBox value for source of SMTP +#. authentication credentials (none) when adding a new +#. account +#: src/client/accounts/accounts-editor-row.vala:540 +msgid "No login needed" +msgstr "Лагін непатрэбны" + +#. Translators: ComboBox value for source of SMTP +#. authentication credentials (use IMAP) when adding a new +#. account +#: src/client/accounts/accounts-editor-row.vala:548 +msgid "Use same login as receiving" +msgstr "Выкарыстоўваць такі самы лагін, што пры атрыманні" + +#. Translators: ComboBox value for source of SMTP +#. authentication credentials (custom) when adding a new +#. account +#: src/client/accounts/accounts-editor-row.vala:556 +msgid "Use a different login" +msgstr "Выкарыстоўваць іншы лагін" + +#. Translators: In-app notification label, the +#. string substitution is a more detailed reason. +#: src/client/accounts/accounts-editor-servers-pane.vala:290 +#, c-format +msgid "Account not updated: %s" +msgstr "Рахунак не абноўлены: %s" + +#. Translators: This label describes the program that +#. created the account, e.g. an SSO service like GOA, or +#. locally by Geary. +#: src/client/accounts/accounts-editor-servers-pane.vala:427 +msgid "Account source" +msgstr "Крыніца рахунка" + +#: src/client/accounts/accounts-editor-servers-pane.vala:439 +msgid "GNOME Online Accounts" +msgstr "Рахункі GNOME Online" + +#. Translators: This label describes an account +#. preference. +#: src/client/accounts/accounts-editor-servers-pane.vala:495 +msgid "Save drafts on server" +msgstr "Захаваць чарнавікі на серверы" + +#. Add a suffix for OAuth2 auth so people know they +#. shouldn't expect to be prompted for a password +#. Translators: Label used when an account's IMAP or +#. SMTP service uses OAuth2. The string replacement is +#. the service's login name. +#: src/client/accounts/accounts-editor-servers-pane.vala:769 +#, c-format +msgid "%s using OAuth2" +msgstr "%s выкарыстоўвае OAuth2" + +#: src/client/accounts/accounts-editor-servers-pane.vala:779 +msgid "Use receiving server login" +msgstr "Выкарыстоўваць лагін сервера атрымання" + +#: src/client/application/geary-application.vala:22 +msgid "Copyright 2016 Software Freedom Conservancy Inc." +msgstr "Аўтарскія правы © 2016 Software Freedom Conservancy Inc." + +#: src/client/application/geary-application.vala:23 +msgid "Copyright 2016-2018 Geary Development Team." +msgstr "Аўтарскія правы © 2016-2018 Geary Development Team." + +#: src/client/application/geary-application.vala:25 +msgid "Visit the Geary web site" +msgstr "Наведаць сайт Geary" + +#: src/client/application/geary-application.vala:414 +#, c-format +msgid "About %s" +msgstr "Аб %s" + +#. Translators: add your name and email address to receive +#. credit in the About dialog For example: Yamada Taro +#. +#: src/client/application/geary-application.vala:418 +msgid "translator-credits" +msgstr "Zmicer Turok, zmicerturok@gmail.com" + +#: src/client/application/geary-args.vala:10 +msgid "Start Geary with hidden main window" +msgstr "Запускаць Geary у згорнутым выглядзе" + +#: src/client/application/geary-args.vala:11 +msgid "Output debugging information" +msgstr "Выходная адладачная інфармацыя" + +#: src/client/application/geary-args.vala:12 +msgid "Log conversation monitoring" +msgstr "Весці лог маніторынгу гутарак" + +#: src/client/application/geary-args.vala:13 +msgid "Log network deserialization" +msgstr "Весці лог дэсерыялізацыі сеткі" + +#: src/client/application/geary-args.vala:14 +msgid "Log network activity" +msgstr "Весці лог сеткавай актыўнасці" + +#. / The IMAP replay queue is how changes on the server are replicated on the client. +#. / It could also be called the IMAP events queue. +#: src/client/application/geary-args.vala:17 +msgid "Log IMAP replay queue" +msgstr "Весці лог адказаў IMAP" + +#. / Serialization is how commands and responses are converted into a stream of bytes for +#. / network transmission +#: src/client/application/geary-args.vala:20 +msgid "Log network serialization" +msgstr "Весці лог серыялізацыі сеткі" + +#: src/client/application/geary-args.vala:21 +msgid "Log periodic activity" +msgstr "Весці лог перыядычнай актыўнасці" + +#: src/client/application/geary-args.vala:22 +msgid "Log database queries (generates lots of messages)" +msgstr "Весці лог запытаў базы даных (генеруе мноства паведамленняў)" + +#. / "Normalization" can also be called "synchronization" +#: src/client/application/geary-args.vala:24 +msgid "Log folder normalization" +msgstr "Весці лог сінхранізацыі каталога" + +#: src/client/application/geary-args.vala:25 +msgid "Allow inspection of WebView" +msgstr "Дазволіць праверку WebView" + +#: src/client/application/geary-args.vala:26 +msgid "Revoke all server certificates with TLS warnings" +msgstr "Адклікаць усе сертыфікаты сервераў з папярэджаннямі TLS" + +#: src/client/application/geary-args.vala:27 +msgid "Perform a graceful quit" +msgstr "Выйсці правільна" + +#: src/client/application/geary-args.vala:28 +msgid "Display program version" +msgstr "Паказаць версію праграмы" + +#. This gives a command-line hint on how to open new composer windows with mailto: +#: src/client/application/geary-args.vala:53 +#, c-format +msgid "Use %s to open a new composer window" +msgstr "Выкарыстоўвайце %s для адкрыцця новага акна рэдактара" + +#: src/client/application/geary-args.vala:56 +msgid "Please report comments, suggestions and bugs to:" +msgstr "Калі ласка, каментары, прапановы, справаздачы пра хібы дасылайце на:" + +#. i18n: Command line arguments are invalid +#: src/client/application/geary-args.vala:63 +#, c-format +msgid "Failed to parse command line options: %s\n" +msgstr "Не атрымалася разабраць параметр загаднага радка: %s\n" + +#: src/client/application/geary-args.vala:74 +#, c-format +msgid "Unrecognized command line option “%s”\n" +msgstr "Нераспазнаны параметр загаднага радка \"%s\"\n" + +#. Translators: File name used in save chooser when saving +#. attachments that do not otherwise have a name. +#: src/client/application/geary-controller.vala:69 +msgid "Untitled" +msgstr "Без назвы" + +#: src/client/application/geary-controller.vala:745 +msgid "Unable to store server trust exception" +msgstr "Не атрымалася дадаць выключэнне бяспекі сервера" + +#. / Displayed in the space-limited status bar when a message fails to be sent due to error. +#: src/client/application/geary-controller.vala:936 +#: src/client/components/status-bar.vala:29 +msgid "Error sending email" +msgstr "Не атрымалася адправіць пошту" + +#: src/client/application/geary-controller.vala:937 +msgid "" +"Geary encountered an error sending an email. If the problem persists, " +"please manually delete the email from your Outbox folder." +msgstr "" +"Падчас адпраўлення лістоў адбылася памылка. Калі праблема не вырашаецца, " +"уласнаручна выдаліце іх з каталога \"Адпраўленыя\"." + +#. Displayed in the space-limited status bar when a message fails to be uploaded +#. to Sent Mail after being sent. +#: src/client/application/geary-controller.vala:941 +#: src/client/components/status-bar.vala:33 +msgid "Error saving sent mail" +msgstr "Не атрымалася захаваць адпраўлены ліст" + +#: src/client/application/geary-controller.vala:942 +msgid "" +"Geary encountered an error saving a sent message to Sent Mail. The message " +"will stay in your Outbox folder until you delete it." +msgstr "" +"Падчас захавання ліста ў каталог \"Адпраўленыя\" адбылася памылка. Ліст " +"будзе заставацца ў гэтым каталозе пакуль вы яго не выдаліце." + +#: src/client/application/geary-controller.vala:1009 +msgid "Labels" +msgstr "Адмеціны" + +#. give the user two options: reset the Account local store, or exit Geary. A third +#. could be done to leave the Account in an unopened state, but we don't currently +#. have provisions for that. +#: src/client/application/geary-controller.vala:1021 +#, c-format +msgid "Unable to open the database for %s" +msgstr "Не атрымалася адкрыць базу даных для %s" + +#: src/client/application/geary-controller.vala:1022 +#, c-format +msgid "" +"There was an error opening the local mail database for this account. This is " +"possibly due to corruption of the database file in this directory:\n" +"\n" +"%s\n" +"\n" +"Geary can rebuild the database and re-synchronize with the server or exit.\n" +"\n" +"Rebuilding the database will destroy all local email and its attachments. " +"The mail on the your server will not be affected." +msgstr "" +"Падчас адкрыцця лакальнай базы даных пошты для гэтага рахунка адбылася " +"памылка. Магчыма, гэта здарылася з прычыны пашкоджання файла базы даных у " +"гэтым каталозе:\n" +"\n" +"%s\n" +"\n" +"Geary можа перастварыць базу даных і паўторна сінхранізавацца з серверам " +"альбо завяршыць працу.\n" +"\n" +"Калі перастварыць базу даных, то ўсе лакальныя лісты і ўкладзеныя ў іх файлы " +"знішчацца. Гэта не закране пошту, што захоўваецца на серверы." + +#: src/client/application/geary-controller.vala:1024 +msgid "_Rebuild" +msgstr "_Перастварыць" + +#: src/client/application/geary-controller.vala:1024 +msgid "E_xit" +msgstr "_Выйсці" + +#: src/client/application/geary-controller.vala:1033 +#, c-format +msgid "Unable to rebuild database for “%s”" +msgstr "Не атрымалася перастварыць базу даных для \"%s\"" + +#: src/client/application/geary-controller.vala:1034 +#, c-format +msgid "" +"Error during rebuild:\n" +"\n" +"%s" +msgstr "" +"Не атрымалася перастварыць:\n" +"\n" +"%s" + +#. some other problem opening the account ... as with other flow path, can't run +#. Geary today with an account in unopened state, so have to exit +#: src/client/application/geary-controller.vala:1056 +#: src/client/application/geary-controller.vala:1066 +#: src/client/application/geary-controller.vala:1077 +#, c-format +msgid "Unable to open local mailbox for %s" +msgstr "Не атрымалася адкрыць лакальную паштовую скрыню для %s" + +#: src/client/application/geary-controller.vala:1057 +#, c-format +msgid "" +"There was an error opening the local mail database for this account. This is " +"possibly due to a file permissions problem.\n" +"\n" +"Please check that you have read/write permissions for all files in this " +"directory:\n" +"\n" +"%s" +msgstr "" +"Падчас адкрыцця лакальнай базы даных пошты для гэтага рахунка адбылася " +"памылка. Магчыма, гэта здарылася з прычыны праблем з правамі доступу.\n" +"\n" +"Калі ласка, праверце правы чытанне/запіс для ўсіх файлаў у гэтым каталозе:\n" +"\n" +"%s" + +#: src/client/application/geary-controller.vala:1067 +msgid "" +"The version number of the local mail database is formatted for a newer " +"version of Geary. Unfortunately, the database cannot be “rolled back” to " +"work with this version of Geary.\n" +"\n" +"Please install the latest version of Geary and try again." +msgstr "" +"У версіі лакальнай базы даных пошты фармат больш новай версіі Geary. На " +"жаль, немагчыма перарабіць базу даных для сумяшчальнасці з гэтай версіяй " +"Geary.\n" +"\n" +"Усталюйце апошнюю версію Geary і паспрабуйце зноў." + +#: src/client/application/geary-controller.vala:1078 +msgid "" +"There was an error opening the local account. This is probably due to " +"connectivity issues.\n" +"\n" +"Please check your network connection and restart Geary." +msgstr "" +"Падчас адкрыцця лакальнага рахунка адбылася памылка. Магчыма, гэта з прычыны " +"праблем са злучэннем.\n" +"\n" +"Праверце злучэнне з інтэрнэтам і перазапусціце Geary." + +#: src/client/application/geary-controller.vala:1879 +msgid "Undo move (Ctrl+Z)" +msgstr "Адрабіць перамяшчэнне (Ctrl+Z)" + +#: src/client/application/geary-controller.vala:1889 +msgid "Are you sure you want to open these attachments?" +msgstr "Сапраўды хочаце адкрыць гэтыя файлы?" + +#: src/client/application/geary-controller.vala:1890 +msgid "" +"Attachments may cause damage to your system if opened. Only open files from " +"trusted sources." +msgstr "" +"Адкрыццё ўкладзеных файлаў можа несці пагрозу аперацыйнай сістэме. " +"Адкрывайце толькі файлы, атрыманыя з надзейных крыніц." + +#: src/client/application/geary-controller.vala:1891 +msgid "Don’t _ask me again" +msgstr "_Больш не пытацца" + +#. Translators: Dialog primary label when prompting to +#. overwrite a file. The string substitution is the file'sx +#. name. +#: src/client/application/geary-controller.vala:2020 +#, c-format +msgid "A file named “%s” already exists. Do you want to replace it?" +msgstr "Файл з назвай \"%s\" ужо існуе. Хочаце замяніць яго?" + +#. Translators: Dialog secondary label when prompting to +#. overwrite a file. The string substitution is the parent +#. folder's name. +#: src/client/application/geary-controller.vala:2027 +#, c-format +msgid "" +"The file already exists in “%s”. Replacing it will overwrite its contents." +msgstr "" +"Файл ужо існуе ў \"%s\". Калі яго замяніць, то перазапішацца ўсё яго " +"змесціва." + +#: src/client/application/geary-controller.vala:2031 +msgid "_Replace" +msgstr "_Замяніць" + +#: src/client/application/geary-controller.vala:2301 +msgid "Close the draft message?" +msgid_plural "Close all draft messages?" +msgstr[0] "Закрыць чарнавік?" +msgstr[1] "Закрыць чарнавікі?" +msgstr[2] "Закрыць чарнавікі?" + +#: src/client/application/geary-controller.vala:2427 +#, c-format +msgid "Empty all email from your %s folder?" +msgstr "Выдаліць усе лісты з каталога %s?" + +#: src/client/application/geary-controller.vala:2428 +msgid "This removes the email from Geary and your email server." +msgstr "Выдаляе ўсе лісты з Geary і паштовага сервера." + +#: src/client/application/geary-controller.vala:2429 +msgid "This cannot be undone." +msgstr "Гэта немагчыма скасаваць." + +#: src/client/application/geary-controller.vala:2430 +#, c-format +msgid "Empty %s" +msgstr "Ачысціць %s" + +#: src/client/application/geary-controller.vala:2447 +#, c-format +msgid "Error emptying %s" +msgstr "Падчас ачысткі адбылася памылка %s" + +#: src/client/application/geary-controller.vala:2479 +msgid "Do you want to permanently delete this message?" +msgid_plural "Do you want to permanently delete these messages?" +msgstr[0] "Хочаце назаўсёды выдаліць гэты ліст?" +msgstr[1] "Хочаце назаўсёды выдаліць гэтыя лісты?" +msgstr[2] "Хочаце назаўсёды выдаліць гэтыя лісты?" + +#: src/client/application/geary-controller.vala:2481 +msgid "Delete" +msgstr "Выдаліць" + +#: src/client/application/geary-controller.vala:2495 +msgid "Undo trash (Ctrl+Z)" +msgstr "Адрабіць перанос у сметніцу (Ctrl+Z)" + +#: src/client/application/geary-controller.vala:2545 +msgid "Undo archive (Ctrl+Z)" +msgstr "Адрабіць перанос у архіў (Ctrl+Z)" + +#: src/client/application/geary-controller.vala:2590 +msgid "Undo (Ctrl+Z)" +msgstr "Адрабіць (Ctrl+Z)" + +#. Translators: The label for an in-app notification. The +#. string substitution is a list of recipients of the email. +#: src/client/application/geary-controller.vala:2667 +#, c-format +msgid "Successfully sent mail to %s." +msgstr "Ліст паспяхова адпраўлены %s." + +#: src/client/application/geary-controller.vala:2748 +msgid "Failed to open default text editor." +msgstr "Не атрымалася адкрыць прадвызначаны тэкставы рэдактар." + +#. Translators: Tooltip used when an entry requires a valid +#. email address to be entered, but one is not provided. +#: src/client/components/components-validator.vala:341 +msgid "An email address is required" +msgstr "Патрабуецца адрас электроннай пошты" + +#. Translators: Tooltip used when an entry requires a valid +#. email address to be entered, but the address is invalid. +#: src/client/components/components-validator.vala:345 +msgid "Not a valid email address" +msgstr "Непрыдатны адрас электроннай пошты" + +#. Translators: Tooltip used when an entry requires a valid, +#. resolvable server name to be entered, but one is not +#. provided. +#: src/client/components/components-validator.vala:391 +msgid "A server name is required" +msgstr "Патрабуецца назва сервера" + +#. Translators: Tooltip used when an entry requires a valid +#. server name to be entered, but it was unable to be +#. looked-up in the DNS. +#: src/client/components/components-validator.vala:396 +msgid "Could not look up server name" +msgstr "Не атрымалася знайсці назву сервера" + +#. Tooltips +#: src/client/components/main-toolbar.vala:69 +msgid "Delete conversation (Shift+Delete)" +msgstr "Выдаліць гутарку (Shift+Delete)" + +#: src/client/components/main-toolbar.vala:70 +msgid "Delete conversations (Shift+Delete)" +msgstr "Выдаліць гутаркі (Shift+Delete)" + +#: src/client/components/main-toolbar.vala:71 +msgid "Move conversation to Trash (Delete, Backspace)" +msgstr "Перамясціць гутарку ў сметніцу (Delete, Backspace)" + +#: src/client/components/main-toolbar.vala:72 +msgid "Move conversations to Trash (Delete, Backspace)" +msgstr "Перамясціць гутаркі ў сметніцу (Delete, Backspace)" + +#: src/client/components/main-toolbar.vala:73 +msgid "Archive conversation (A)" +msgstr "Адправіць гутарку ў архіў (A)" + +#: src/client/components/main-toolbar.vala:74 +msgid "Archive conversations (A)" +msgstr "Адправіць гутаркі ў архіў (A)" + +#: src/client/components/main-toolbar.vala:75 +msgid "Mark conversation" +msgstr "Пазначыць гутарку" + +#: src/client/components/main-toolbar.vala:76 +msgid "Mark conversations" +msgstr "Пазначыць гутаркі" + +#: src/client/components/main-toolbar.vala:77 +msgid "Add label to conversation" +msgstr "Дадаць адмеціну гутарцы" + +#: src/client/components/main-toolbar.vala:78 +msgid "Add label to conversations" +msgstr "Дадаць адмеціну гутаркам" + +#: src/client/components/main-toolbar.vala:79 +msgid "Move conversation" +msgstr "Перамясціць гутарку" + +#: src/client/components/main-toolbar.vala:80 +msgid "Move conversations" +msgstr "Перамясціць гутаркі" + +#: src/client/components/main-window.vala:441 +#, c-format +msgid "%s (%d)" +msgstr "%s (%d)" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:53 +#, c-format +msgid "Problem connecting to incoming server for %s" +msgstr "Праблема сувязі з серверам уваходных лістоў для %s" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:55 +#: src/client/components/main-window-info-bar.vala:63 +#, c-format +msgid "" +"Could not connect to %s, check your Internet access and the server name and " +"try again" +msgstr "" +"Немагчыма злучыцца з %s, праверце злучэнне з інтэрнэтам і назву сервера, " +"пасля паўтарыце спробу" + +#: src/client/components/main-window-info-bar.vala:56 +#: src/client/components/main-window-info-bar.vala:65 +msgid "Retry connecting now" +msgstr "Паспрабуйце злучыцца зараз" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:61 +#, c-format +msgid "Problem connecting to outgoing server for %s" +msgstr "Праблема сувязі з серверам выходных лістоў для %s" + +#: src/client/components/main-window-info-bar.vala:64 +msgid "Try reconnecting now" +msgstr "Паспрабуйце перазлучыцца зараз" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:70 +#, c-format +msgid "Problem with connection to incoming server for %s" +msgstr "Праблема сувязі з серверам уваходных лістоў для %s" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:72 +#: src/client/components/main-window-info-bar.vala:80 +#, c-format +msgid "Network error talking to %s, check your Internet access and try again" +msgstr "" +"Памылка сеткі, звязаная з %s, праверце злучэнне з інтэрнэтам і паўтарыце " +"спробу" + +#: src/client/components/main-window-info-bar.vala:73 +#: src/client/components/main-window-info-bar.vala:81 +#: src/client/components/main-window-info-bar.vala:89 +#: src/client/components/main-window-info-bar.vala:97 +#: src/client/components/main-window-info-bar.vala:118 +msgid "Try reconnecting" +msgstr "Паспрабуйце перазлучыцца" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:78 +#, c-format +msgid "Problem with connection to outgoing server for %s" +msgstr "Праблема сувязі з серверам выходных лістоў для %s" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:86 +#, c-format +msgid "Problem communicating with incoming server for %s" +msgstr "Праблема сувязі з серверам уваходных лістоў для %s" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:88 +#, c-format +msgid "" +"Geary did not understand a message from %s or vice versa, please file a bug " +"report" +msgstr "" +"Geary не разумее ліст ад %s альбо наадварот, калі ласка, напішыце " +"справаздачу пра хібу" + +#: src/client/components/main-window-info-bar.vala:93 +msgid "Problem communicating with outgoing mail server" +msgstr "Праблема сувязі з серверам выходных лістоў для %s" + +#. Translators: First string substitution is the server +#. name, second is the account name +#: src/client/components/main-window-info-bar.vala:96 +#, c-format +msgid "" +"Could not communicate with %s for %s, check the server name and try again in " +"a moment" +msgstr "" +"Не атрымалася звязацца з %s для %s, праверце назву сервера і паспрабуйце зноў" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:102 +#, c-format +msgid "Incoming mail server password required for %s" +msgstr "Для %s патрабуецца пароль для сервера ўваходных лістоў" + +#: src/client/components/main-window-info-bar.vala:103 +msgid "Messages cannot be received without the correct password." +msgstr "Лісты немагчыма атрымаць без правільнага паролю." + +#: src/client/components/main-window-info-bar.vala:104 +msgid "Retry receiving email, you will be prompted for a password" +msgstr "Паўтарыць спробу атрымання пошты, вам будзе прапанавана ўвесці пароль" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:109 +#, c-format +msgid "Outgoing mail server password required for %s" +msgstr "Для %s патрабуецца пароль сервера выходных лістоў" + +#: src/client/components/main-window-info-bar.vala:110 +msgid "Messages cannot be sent without the correct password." +msgstr "Без правільнага паролю немагчыма адправіць ліст." + +#: src/client/components/main-window-info-bar.vala:111 +msgid "Retry sending queued messages, you will be prompted for a password" +msgstr "" +"Паўтарыць спробу адправіць ліст з чаргі, вам будзе прапанавана ўвесці пароль" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:116 +#, c-format +msgid "A problem occurred checking mail for %s" +msgstr "Падчас праверкі пошты для %s узнікла праблема" + +#: src/client/components/main-window-info-bar.vala:117 +#: src/client/components/main-window-info-bar.vala:124 +msgid "Something went wrong, please file a bug report if the problem persists" +msgstr "" +"Нешта не так, калі ласка, адпраўце справаздачу пра хібу, калі праблема не " +"вырашылася" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:123 +#, c-format +msgid "A problem occurred sending mail for %s" +msgstr "Падчас адпраўлення пошты для %s ўзнікла праблема" + +#: src/client/components/main-window-info-bar.vala:125 +msgid "Retry sending queued messages" +msgstr "Паўтарыць спробу адправіць лісты з чаргі" + +#: src/client/components/main-window-info-bar.vala:136 +msgid "A database problem has occurred" +msgstr "Узнікла праблема з базай даных" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:138 +#, c-format +msgid "Messages for %s must be downloaded again." +msgstr "Лісты для %s мусяць спампавацца зноў." + +#: src/client/components/main-window-info-bar.vala:151 +msgid "Geary has encountered a problem" +msgstr "Geary патрапілася праблема" + +#: src/client/components/main-window-info-bar.vala:152 +msgid "" +"Please check the technical details and report the problem if it persists." +msgstr "" +"Калі ласка, праверце тэхнічныя падрабязнасці і паведаміце пра праблему, калі " +"яна паўтараецца." + +#: src/client/components/main-window-info-bar.vala:160 +msgid "_Details" +msgstr "_Падрабязнасці" + +#: src/client/components/main-window-info-bar.vala:161 +msgid "View technical details about the error" +msgstr "Прагляд тэхнічных падрабязнасцяў пра памылку" + +#: src/client/components/main-window-info-bar.vala:165 +msgid "_Retry" +msgstr "_Паўтарыць" + +#: src/client/components/main-window-info-bar.vala:252 +msgid "Details" +msgstr "Падрабязнасці" + +#: src/client/components/main-window-info-bar.vala:265 +#: src/client/components/stock.vala:23 +msgid "_Close" +msgstr "_Закрыць" + +#: src/client/components/main-window-info-bar.vala:269 +msgid "Copy to Clipboard" +msgstr "Скапіраваць у буфер абмену" + +#: src/client/components/main-window-info-bar.vala:272 +msgid "" +"Copy technical details to clipboard for pasting into an email or bug report" +msgstr "" +"Скапіраваць тэхнічныя падрабязнасці, каб уставіць іх у ліст альбо " +"справаздачу пра хібу" + +#: src/client/components/search-bar.vala:8 +#: src/client/folder-list/folder-list-search-branch.vala:38 +#: src/engine/api/geary-special-folder-type.vala:51 +msgid "Search" +msgstr "Пошук" + +#. Search entry. +#: src/client/components/search-bar.vala:23 +msgid "Search all mail in account for keywords (Ctrl+S)" +msgstr "Пошук усіх лістоў у рахунку па ключавых словах (Ctrl+S)" + +#: src/client/components/search-bar.vala:101 +#, c-format +msgid "Indexing %s account" +msgstr "Індэксацыя рахунка %s" + +#: src/client/components/search-bar.vala:112 +#: src/client/folder-list/folder-list-search-branch.vala:39 +#, c-format +msgid "Search %s account" +msgstr "Пошук рахунка %s" + +#. / Displayed in the space-limited status bar while a message is in the process of being sent. +#: src/client/components/status-bar.vala:26 +msgid "Sending…" +msgstr "Адпраўленне…" + +#: src/client/components/stock.vala:18 +msgid "_OK" +msgstr "_Добра" + +#: src/client/components/stock.vala:19 ui/password-dialog.glade:196 +msgid "_Cancel" +msgstr "_Скасаваць" + +#: src/client/components/stock.vala:21 ui/gtk/menus.ui:34 +msgid "_About" +msgstr "_Аб праграме" + +#: src/client/components/stock.vala:22 +msgid "_Add" +msgstr "_Дадаць" + +#: src/client/components/stock.vala:24 +msgid "_Discard" +msgstr "_Адкінуць" + +#: src/client/components/stock.vala:25 ui/gtk/menus.ui:29 +msgid "_Help" +msgstr "_Даведка" + +#: src/client/components/stock.vala:26 ui/conversation-email-menus.ui:77 +msgid "_Open" +msgstr "_Адкрыць" + +#: src/client/components/stock.vala:27 ui/gtk/menus.ui:17 +msgid "_Preferences" +msgstr "_Налады" + +#. Translators: Menu item to print a single, specific message +#: src/client/components/stock.vala:28 ui/conversation-email-menus.ui:64 +msgid "_Print…" +msgstr "_Друкаваць…" + +#: src/client/components/stock.vala:29 ui/gtk/menus.ui:38 +msgid "_Quit" +msgstr "_Выйсці" + +#: src/client/components/stock.vala:30 +msgid "_Remove" +msgstr "_Выдаліць" + +#: src/client/components/stock.vala:31 ui/conversation-email-menus.ui:83 +msgid "_Save" +msgstr "_Захаваць" + +#: src/client/components/stock.vala:32 +msgid "_Keep" +msgstr "_Пакінуць" + +#: src/client/composer/composer-link-popover.vala:149 +msgid "Link URL is not correctly formatted, e.g. http://example.com" +msgstr "У спасылкі няправільны фармат, узор: http://example.com" + +#: src/client/composer/composer-link-popover.vala:156 +msgid "Invalid link URL" +msgstr "Хібная URL-спасылка" + +#: src/client/composer/composer-link-popover.vala:156 +msgid "Invalid email address" +msgstr "Хібны адрас электроннай пошты" + +#: src/client/composer/composer-widget.vala:158 +msgid "Saved" +msgstr "Захавана" + +#: src/client/composer/composer-widget.vala:159 +msgid "Saving" +msgstr "Захаванне" + +#: src/client/composer/composer-widget.vala:160 +msgid "Error saving" +msgstr "Не атрымалася захаваць" + +#: src/client/composer/composer-widget.vala:161 +msgid "Press Backspace to delete quote" +msgstr "Націсніце Backspace, каб выдаліць цытату" + +#. Translators: This is list of keywords, separated by pipe ("|") +#. characters, that suggest an attachment; since this is full-word +#. checking, include all variants of each word. No spaces are +#. allowed. +#: src/client/composer/composer-widget.vala:170 +msgid "" +"attach|attaching|attaches|attachment|attachments|attached|enclose|enclosed|" +"enclosing|encloses|enclosure|enclosures" +msgstr "" +"укладзеныя файлы|attach|attaching|attaches|attachment|attachments|attached|" +"enclose|enclosed|enclosing|encloses|enclosure|enclosures" + +#. Translators: This dialog text is displayed to the +#. user when closing a composer where the options are +#. Keep, Discard or Cancel. +#: src/client/composer/composer-widget.vala:1103 +msgid "Do you want to keep or discard this draft message?" +msgstr "Вы хочаце пакінуць ці адмовіцца ад гэтага чарнавіка?" + +#. Translators: This dialog text is displayed to the +#. user when closing a composer where the options are +#. only Discard or Cancel. +#: src/client/composer/composer-widget.vala:1131 +msgid "Do you want to discard this draft message?" +msgstr "Хочаце адмовіцца ад гэтага чарнавіка?" + +#: src/client/composer/composer-widget.vala:1248 +msgid "Send message with an empty subject and body?" +msgstr "Адправіць ліст без тэмы і тэксту?" + +#: src/client/composer/composer-widget.vala:1250 +msgid "Send message with an empty subject?" +msgstr "Адправіць ліст без тэмы?" + +#: src/client/composer/composer-widget.vala:1252 +msgid "Send message with an empty body?" +msgstr "Адправіць ліст без тэксту?" + +#: src/client/composer/composer-widget.vala:1256 +msgid "Send message without an attachment?" +msgstr "Адправіць ліст без укладзеных файлаў?" + +#: src/client/composer/composer-widget.vala:1561 +#, c-format +msgid "“%s” already attached for delivery." +msgstr "\"%s\" ужо ўкладзены ў ліст." + +#. / In the composer, the filename followed by its filesize, i.e. "notes.txt (1.12KB)" +#. Translators: The first argument will be a +#. description of the document type, the second will +#. be a human-friendly size string. For example: +#. Document (100.9MB) +#: src/client/composer/composer-widget.vala:1569 +#: src/client/conversation-viewer/conversation-email.vala:136 +#, c-format +msgid "%s (%s)" +msgstr "%s (%s)" + +#: src/client/composer/composer-widget.vala:1606 +#, c-format +msgid "“%s” could not be found." +msgstr "Немагчыма знайсці \"%s\"." + +#: src/client/composer/composer-widget.vala:1612 +#, c-format +msgid "“%s” is a folder." +msgstr "\"%s\" - каталог." + +#: src/client/composer/composer-widget.vala:1618 +#, c-format +msgid "“%s” is an empty file." +msgstr "\"%s\" - пусты файл." + +#: src/client/composer/composer-widget.vala:1631 +#, c-format +msgid "“%s” could not be opened for reading." +msgstr "\"%s\" немагчыма адкрыць для чытання." + +#: src/client/composer/composer-widget.vala:1639 +msgid "Cannot add attachment" +msgstr "Немагчыма ўкласці файлы" + +#: src/client/composer/composer-widget.vala:1688 +msgid "To: " +msgstr "Каму: " + +#: src/client/composer/composer-widget.vala:1691 +msgid "Cc: " +msgstr "Копія: " + +#: src/client/composer/composer-widget.vala:1694 +msgid "Bcc: " +msgstr "Схаваная копія: " + +#: src/client/composer/composer-widget.vala:1697 +msgid "Reply-To: " +msgstr "Адказаць: " + +#: src/client/composer/composer-widget.vala:1835 +msgid "Select Color" +msgstr "Абраць колер" + +#. Displayed in the From dropdown to indicate an +#. "alternate email address" for an account. The first +#. printf argument will be the alternate email address, +#. and the second will be the account's primary email +#. address. +#: src/client/composer/composer-widget.vala:2030 +#, c-format +msgid "%1$s via %2$s" +msgstr "%1$s праз %2$s" + +#. Composer label (with mnemonic underscore) for the account selector +#. when choosing what address to send a message from. +#: src/client/composer/composer-widget.vala:2090 +msgid "_From:" +msgstr "_Ад:" + +#. Translators: This is the name of the file chooser filter +#. when inserting an image in the composer. +#: src/client/composer/composer-widget.vala:2315 +msgid "Images" +msgstr "Выявы" + +#: src/client/composer/composer-window.vala:14 +msgid "New Message" +msgstr "Новы ліст" + +#: src/client/composer/spell-check-popover.vala:117 +msgid "Remove this language from the preferred list" +msgstr "Выдаліць гэтую мову са спіса пераваг" + +#: src/client/composer/spell-check-popover.vala:121 +msgid "Add this language to the preferred list" +msgstr "Дадаць гэтую мову ў спіс пераваг" + +#: src/client/composer/spell-check-popover.vala:217 +msgid "Search for more languages" +msgstr "Пошук дадатковых моў" + +#: src/client/conversation-list/conversation-list-view.vala:283 +msgid "Delete conversation" +msgstr "Выдаліць гутарку" + +#: src/client/conversation-list/conversation-list-view.vala:286 +#: ui/main-toolbar-menus.ui:16 +msgid "Mark as _Read" +msgstr "Пазначыць як _прачытанае" + +#: src/client/conversation-list/conversation-list-view.vala:289 +#: ui/main-toolbar-menus.ui:20 +msgid "Mark as _Unread" +msgstr "Пазначыць як _нечытанае" + +#: src/client/conversation-list/conversation-list-view.vala:292 +#: ui/main-toolbar-menus.ui:28 +msgid "U_nstar" +msgstr "_Прыбраць з выбранага" + +#: src/client/conversation-list/conversation-list-view.vala:294 +#: ui/main-toolbar-menus.ui:24 +msgid "_Star" +msgstr "_Дадаць у выбранае" + +#. Translators: Menu item to reply to a specific message. +#: src/client/conversation-list/conversation-list-view.vala:297 +#: ui/conversation-email-menus.ui:9 +msgid "_Reply" +msgstr "_Адказаць" + +#: src/client/conversation-list/conversation-list-view.vala:298 +msgid "R_eply All" +msgstr "Ад_казаць усім" + +#. Translators: Menu item to forward a specific message. +#: src/client/conversation-list/conversation-list-view.vala:299 +#: ui/conversation-email-menus.ui:21 +msgid "_Forward" +msgstr "_Пераслаць" + +#: src/client/conversation-list/formatted-conversation-data.vala:11 +msgid "Me" +msgstr "Я" + +#. Translators: This is the file type displayed for +#. attachments with unknown file types. +#: src/client/conversation-viewer/conversation-email.vala:122 +msgid "Unknown" +msgstr "Невядома" + +#: src/client/conversation-viewer/conversation-email.vala:811 +msgid "From:" +msgstr "Ад:" + +#: src/client/conversation-viewer/conversation-email.vala:815 +#: ui/conversation-message.ui:313 +msgid "To:" +msgstr "Каму:" + +#: src/client/conversation-viewer/conversation-email.vala:819 +#: ui/conversation-message.ui:358 +msgid "Cc:" +msgstr "Копія:" + +#: src/client/conversation-viewer/conversation-email.vala:823 +#: ui/conversation-message.ui:403 +msgid "Bcc:" +msgstr "Схаваная копія:" + +#: src/client/conversation-viewer/conversation-email.vala:827 +msgid "Date:" +msgstr "Дата:" + +#: src/client/conversation-viewer/conversation-email.vala:831 +msgid "Subject:" +msgstr "Тэма:" + +#: src/client/conversation-viewer/conversation-message.vala:60 +msgid "This email address may have been forged" +msgstr "Магчыма, гэты адрас электроннай пошты быў падроблены" + +#. Preview headers +#. Translators: This is displayed in place of the from address +#. when the message has no from address. +#: src/client/conversation-viewer/conversation-message.vala:330 +msgid "No sender" +msgstr "Адпраўнік не вызначаны" + +#. Translators: This separates multiple 'from' +#. addresses in the header preview for a message. +#: src/client/conversation-viewer/conversation-message.vala:589 +msgid ", " +msgstr ", " + +#. Translators: This string is used as the HTML IMG ALT +#. attribute value when displaying an inline image in an email +#. that did not specify a file name. E.g. Image" +msgstr "Ад " + +#: ui/conversation-message.ui:80 ui/conversation-message.ui:179 +msgid "1/1/1970\t" +msgstr "1/1/1970\t" + +#: ui/conversation-message.ui:103 +msgid "Preview body text." +msgstr "Папярэдні прагляд асноўнага тэксту." + +#: ui/conversation-message.ui:203 +msgid "Sent by:" +msgstr "Адпраўлена:" + +#: ui/conversation-message.ui:248 +msgid "Reply to:" +msgstr "Адказаць:" + +#: ui/conversation-message.ui:292 +msgid "Subject" +msgstr "Тэма" + +#: ui/conversation-message.ui:502 +msgid "Show Images" +msgstr "Паказаць выявы" + +#: ui/conversation-message.ui:515 +msgid "Always Show From Sender" +msgstr "Заўсёды паказваць выявы ад адпраўніка" + +#: ui/conversation-message.ui:543 +msgid "Remote images not shown" +msgstr "Адлеглыя выявы не паказваюцца" + +#: ui/conversation-message.ui:560 +msgid "Only show remote images from senders you trust." +msgstr "Паказваць адлеглыя выявы толькі ад давераных адпраўнікоў." + +#: ui/conversation-message.ui:692 +msgid "But actually goes to:" +msgstr "Але накіроўвае на:" + +#: ui/conversation-message.ui:723 +msgid "The link appears to go to:" +msgstr "Выглядае, што спасылка накіроўвае на:" + +#: ui/conversation-message.ui:735 +msgid "Deceptive link found" +msgstr "Выяўлена спасылка з перанакіраваннем" + +#: ui/conversation-message.ui:750 +msgid "The email sender may be leading you to the wrong web site." +msgstr "Здаецца, адпраўнік накіроўвае вас на няправільны сайт." + +#: ui/conversation-message.ui:763 +msgid "If unsure, contact the sender and ask before continuing." +msgstr "" +"Калі ўпэўненасці няма, звяжыцеся з адпраўніком і спытайце перш чым " +"працягнуць." + +#: ui/conversation-viewer.ui:60 +msgid "Find in conversation" +msgstr "Знайсці ў гутарцы" + +#: ui/conversation-viewer.ui:74 +msgid "Find the previous occurrence of the search string." +msgstr "Знайсці папярэдняе супадзенне радка пошуку." + +#: ui/conversation-viewer.ui:95 +msgid "Find the next occurrence of the search string." +msgstr "Знайсці наступнае супадзенне радка пошуку." + +#: ui/find_bar.glade:66 +msgid "Find:" +msgstr "Пошук:" + +#: ui/find_bar.glade:89 +msgid "_Previous" +msgstr "_Папярэдняе" + +#: ui/find_bar.glade:107 +msgid "_Next" +msgstr "_Наступнае" + +#: ui/find_bar.glade:125 +msgid "_Case sensitive" +msgstr "_Зважаць на рэгістр" + +#: ui/find_bar.glade:145 +msgid "label" +msgstr "адмеціна" + +#: ui/gtk/help-overlay.ui:9 +msgid "Conversation Shortcuts" +msgstr "Спалучэнні клавіш гутарак" + +#: ui/gtk/help-overlay.ui:13 ui/gtk/help-overlay.ui:254 +msgctxt "shortcut window" +msgid "General" +msgstr "Асноўнае" + +#: ui/gtk/help-overlay.ui:17 +msgctxt "shortcut window" +msgid "Move focus to the next/previous pane" +msgstr "Перамясціць фокус на наступную/папярэднюю панэль" + +#: ui/gtk/help-overlay.ui:24 +msgctxt "shortcut window" +msgid "Move focus to conversation list" +msgstr "Перамясціць фокус на спіс гутарак" + +#: ui/gtk/help-overlay.ui:31 +msgctxt "shortcut window" +msgid "Detach composer window" +msgstr "Адчапіць акно рэдактара" + +#: ui/gtk/help-overlay.ui:38 +msgctxt "shortcut window" +msgid "Close composer window" +msgstr "Закрыць акно рэдактара" + +#: ui/gtk/help-overlay.ui:45 +msgctxt "shortcut window" +msgid "Show keyboard shortcuts" +msgstr "Паказаць спалучэнні клавіш" + +#: ui/gtk/help-overlay.ui:52 +msgctxt "shortcut window" +msgid "Show help" +msgstr "Паказаць даведку" + +#: ui/gtk/help-overlay.ui:59 +msgctxt "shortcut window" +msgid "Quit the application" +msgstr "Выйсці з праграмы" + +#: ui/gtk/help-overlay.ui:68 +msgctxt "shortcut window" +msgid "Search" +msgstr "Пошук" + +#: ui/gtk/help-overlay.ui:72 +msgctxt "shortcut window" +msgid "Jump to search box" +msgstr "Перайсці да радка пошуку" + +#: ui/gtk/help-overlay.ui:79 +msgctxt "shortcut window" +msgid "Find in current conversation" +msgstr "Знайсці ў бягучай гутарцы" + +#: ui/gtk/help-overlay.ui:86 +msgctxt "shortcut window" +msgid "Find next/previous in current conversation" +msgstr "Знайсці наступнае/папярэдняе ў бягучай гутарцы" + +#: ui/gtk/help-overlay.ui:95 ui/gtk/help-overlay.ui:274 +msgctxt "shortcut window" +msgid "Actions" +msgstr "Дзеянні" + +#: ui/gtk/help-overlay.ui:99 +msgctxt "shortcut window" +msgid "Compose a new message" +msgstr "Напісаць новы ліст" + +#: ui/gtk/help-overlay.ui:106 +msgctxt "shortcut window" +msgid "Reply to sender " +msgstr "Адказаць адпраўніку " + +#: ui/gtk/help-overlay.ui:113 +msgctxt "shortcut window" +msgid "Reply to all" +msgstr "Адказаць усім" + +#: ui/gtk/help-overlay.ui:120 +msgctxt "shortcut window" +msgid "Forward" +msgstr "Наперад" + +#: ui/gtk/help-overlay.ui:127 +msgctxt "shortcut window" +msgid "Archive" +msgstr "Архіў" + +#: ui/gtk/help-overlay.ui:134 +msgctxt "shortcut window" +msgid "Move to trash" +msgstr "Перамясціць у сметніцу" + +#: ui/gtk/help-overlay.ui:141 +msgctxt "shortcut window" +msgid "Toggle spam" +msgstr "Пераключыць спам" + +#: ui/gtk/help-overlay.ui:148 +msgctxt "shortcut window" +msgid "Move the conversation" +msgstr "Перамясціць гутарку" + +#: ui/gtk/help-overlay.ui:155 +msgctxt "shortcut window" +msgid "Label the conversation" +msgstr "Адмеціна гутаркі" + +#: ui/gtk/help-overlay.ui:163 +msgctxt "shortcut window" +msgid "Mark read" +msgstr "Пазначыць прачытаным" + +#: ui/gtk/help-overlay.ui:170 +msgctxt "shortcut window" +msgid "Mark unread" +msgstr "Пазначыць нечытаным" + +#: ui/gtk/help-overlay.ui:179 +msgctxt "shortcut window" +msgid "View" +msgstr "Выгляд" + +#: ui/gtk/help-overlay.ui:183 +msgctxt "shortcut window" +msgid "Zoom in" +msgstr "Наблізіць" + +#: ui/gtk/help-overlay.ui:190 +msgctxt "shortcut window" +msgid "Zoom out" +msgstr "Аддаліць" + +#: ui/gtk/help-overlay.ui:197 +msgctxt "shortcut window" +msgid "Reset zoom" +msgstr "Скінуць маштаб" + +#: ui/gtk/help-overlay.ui:206 +msgctxt "shortcut window" +msgid "Additional Shortcuts" +msgstr "Дадатковыя спалучэнні клавіш" + +#: ui/gtk/help-overlay.ui:210 +msgctxt "shortcut window" +msgid "Star" +msgstr "Дадаць у выбранае" + +#: ui/gtk/help-overlay.ui:217 +msgctxt "shortcut window" +msgid "Unstar" +msgstr "Прыбраць з выбранага" + +#: ui/gtk/help-overlay.ui:224 +msgctxt "shortcut window" +msgid "Delete" +msgstr "Выдаліць" + +#: ui/gtk/help-overlay.ui:231 +msgctxt "shortcut window" +msgid "Jump to next (older) conversation" +msgstr "Перайсці да папярэдняй (ранейшай) гутаркі" + +#: ui/gtk/help-overlay.ui:238 +msgctxt "shortcut window" +msgid "Jump to previous (newer) conversation" +msgstr "Перайсці да папярэдняй (пазнейшай) гутаркі" + +#: ui/gtk/help-overlay.ui:250 +msgid "Composer Shortcuts" +msgstr "Спалучэнні клавіш рэдактара" + +#: ui/gtk/help-overlay.ui:258 +msgctxt "shortcut window" +msgid "Quote text" +msgstr "Цытаваць тэкст" + +#: ui/gtk/help-overlay.ui:265 +msgctxt "shortcut window" +msgid "Unquote text" +msgstr "Прыбраць цытаванне" + +#: ui/gtk/help-overlay.ui:278 +msgctxt "shortcut window" +msgid "Send" +msgstr "Адправіць" + +#: ui/gtk/help-overlay.ui:285 +msgctxt "shortcut window" +msgid "Add attachment" +msgstr "Укласці файлы" + +#: ui/gtk/help-overlay.ui:294 +msgctxt "shortcut window" +msgid "Rich text mode" +msgstr "Рэжым фарматаванага тэксту" + +#: ui/gtk/help-overlay.ui:298 +msgctxt "shortcut window" +msgid "Bold text" +msgstr "Тлусты тэкст" + +#: ui/gtk/help-overlay.ui:305 +msgctxt "shortcut window" +msgid "Italicize text" +msgstr "Курсіў" + +#: ui/gtk/help-overlay.ui:312 +msgctxt "shortcut window" +msgid "Underline text" +msgstr "Падкрэслены тэкст" + +#: ui/gtk/help-overlay.ui:319 +msgctxt "shortcut window" +msgid "Strike text" +msgstr "Закрэслены тэкст" + +#: ui/gtk/help-overlay.ui:326 +msgctxt "shortcut window" +msgid "Insert a link" +msgstr "Уставіць спасылку" + +#: ui/gtk/help-overlay.ui:333 +msgctxt "shortcut window" +msgid "Remove formatting" +msgstr "Прыбраць фарматаванне" + +#: ui/gtk/menus.ui:13 +msgid "A_ccounts" +msgstr "_Рахункі" + +#: ui/gtk/menus.ui:23 +msgid "_Keyboard Shortcuts" +msgstr "_Спалучэнні клавіш" + +#: ui/main-toolbar.ui:51 +msgid "Toggle search bar" +msgstr "Пераключыць панэль пошуку" + +#: ui/main-toolbar.ui:72 +msgid "Empty Spam or Trash folders" +msgstr "Ачысціць спам альбо сметніцу" + +#: ui/main-toolbar.ui:112 +msgid "Reply" +msgstr "Адказаць" + +#: ui/main-toolbar.ui:135 +msgid "Reply All" +msgstr "Адказаць усім" + +#: ui/main-toolbar.ui:158 +msgid "Forward" +msgstr "Наперад" + +#: ui/main-toolbar.ui:264 +msgid "Toggle find bar" +msgstr "Пераключыць панэль пошуку" + +#: ui/main-toolbar.ui:306 +msgid "_Archive" +msgstr "_Адправіць у архіў" + +#: ui/main-toolbar-menus.ui:5 +msgid "Empty _Spam…" +msgstr "Ачысціць _спам…" + +#: ui/main-toolbar-menus.ui:9 +msgid "Empty _Trash…" +msgstr "Ачысціць _сметніцу…" + +#: ui/main-toolbar-menus.ui:32 +msgid "Mark as S_pam" +msgstr "Пазначыць як _спам" + +#: ui/main-toolbar-menus.ui:36 +msgid "Mark as not S_pam" +msgstr "Пазначыць як не _спам" + +#: ui/main-window-info-bar.ui:97 +msgid "" +"If the problem is serious or persists, please copy and send these details to " +"the mailing list " +"or file a new " +"bug report." +msgstr "" +"Калі праблема сур’ёзная альбо паўтараецца, скапіруйце і адпраўце гэтыя " +"даныяспіс рассылкі " +"альбо напішыце новая справаздача пра хібу." + +#: ui/main-window-info-bar.ui:113 +msgid "Details:" +msgstr "Падрабязнасці:" + +#: ui/password-dialog.glade:74 +msgid "SMTP Credentials" +msgstr "Уліковыя даныя SMTP" + +#: ui/password-dialog.glade:91 +msgid "Username" +msgstr "Імя карыстальніка" + +#: ui/password-dialog.glade:152 +msgid "_Remember password" +msgstr "_Запомніць пароль" + +#: ui/password-dialog.glade:210 +msgid "_Authenticate" +msgstr "_Аўтарызавацца" + +#: ui/preferences-dialog.ui:38 +msgid "Reading" +msgstr "Чытанне" + +#: ui/preferences-dialog.ui:51 +msgid "_Automatically select next message" +msgstr "_Аўтаматычна пераходзіць да наступнага ліста" + +#: ui/preferences-dialog.ui:70 +msgid "_Display conversation preview" +msgstr "_Паказваць мініяцюры гутарак" + +#: ui/preferences-dialog.ui:89 +msgid "Use _three pane view" +msgstr "Выкарыстоўваць інтэрфейс з _трох панэляў" + +#: ui/preferences-dialog.ui:113 +msgid "Notifications" +msgstr "Апавяшчэнні" + +#: ui/preferences-dialog.ui:126 +msgid "_Play notification sounds" +msgstr "_Прайграваць гукавыя апавяшчэнні" + +#: ui/preferences-dialog.ui:145 +msgid "Show _notifications for new mail" +msgstr "Паказваць _апавяшчэнні аб новых лістах" + +#: ui/preferences-dialog.ui:164 +msgid "_Watch for new mail when closed" +msgstr "_Сачыць за атрыманнем новых лістоў калі праграма закрытая" + +#: ui/preferences-dialog.ui:168 +msgid "Geary will keep running after all windows are closed" +msgstr "Geary будзе працаваць пасля закрыцця ўсіх акон" + +#: ui/preferences-dialog.ui:195 +msgid "Preferences" +msgstr "Налады" + +#: ui/upgrade_dialog.glade:60 +msgid "Geary update in progress…" +msgstr "Geary абнаўляецца…" diff -Nru geary-0.12.4/po/ca.po geary-3.32.0/po/ca.po --- geary-0.12.4/po/ca.po 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/po/ca.po 2019-03-17 13:39:29.000000000 +0000 @@ -1035,7 +1035,7 @@ #. addresses in the header preview for a message. #: ../src/client/conversation-viewer/conversation-message.vala:586 msgid ", " -msgstr "," +msgstr ", " #. Translators: This string is used as the HTML IMG ALT #. attribute value when displaying an inline image in an email @@ -1172,7 +1172,7 @@ #: ../src/client/dialogs/password-dialog.vala:16 msgid "Geary requires your email password to continue" msgstr "" -"El Geary requereix de la contrasenya de correu electrònic per a continuar" +"El Geary requereix la contrasenya de correu electrònic per a continuar" #. Label displaying total number of email messages in a folder #: ../src/client/folder-list/folder-list-folder-entry.vala:32 @@ -1283,20 +1283,20 @@ #. / See http://developer.gnome.org/glib/2.32/glib-GDateTime.html#g-date-time-format #: ../src/client/util/util-date.vala:88 msgid "%B %-e, %Y %-l:%M %P" -msgstr "%-e de %B del %Y a les %-l:%M %P" +msgstr "%-e %B del %Y a les %-l:%M %P" #. / Verbose datetime format for 24-hour time, i.e. November 8, 2010 16:35 #. / See http://developer.gnome.org/glib/2.32/glib-GDateTime.html#g-date-time-format #: ../src/client/util/util-date.vala:91 msgid "%B %-e, %Y %-H:%M" -msgstr "%-e de %B del %Y a les %-H:%M" +msgstr "%-e %B del %Y a les %-H:%M" #. / Verbose datetime format for the locale default (full month, day and time) #. / See http://developer.gnome.org/glib/2.32/glib-GDateTime.html#g-date-time-format #: ../src/client/util/util-date.vala:94 msgctxt "Default full date" msgid "%B %-e, %Y %-l:%M %P" -msgstr "%-e de %B del %Y a les %-l:%M %P" +msgstr "%-e %B del %Y a les %-l:%M %P" #: ../src/client/util/util-date.vala:164 msgid "Now" @@ -1656,7 +1656,7 @@ #. / See http://developer.gnome.org/glib/2.32/glib-GDateTime.html#g-date-time-format #: ../src/engine/rfc822/rfc822-utils.vala:236 msgid "%a, %b %-e, %Y at %-l:%M %p" -msgstr "%a, %-e de %b %Y a les %-l:%M %p" +msgstr "%a, %-e %b %Y a les %-l:%M %p" #. / The quoted header for a message being replied to. #. / %1$s will be substituted for the date, and %2$s will be substituted for @@ -1786,7 +1786,7 @@ #. Note that this button and the Update button will never be shown at the same time to the user. #: ../ui/composer-link-popover.ui.h:2 msgid "Insert the new link with this URL" -msgstr "Insereix el nou enllaç amb aquesta URL" +msgstr "Insereix el nou enllaç amb aquest URL" #: ../ui/composer-link-popover.ui.h:3 msgid "Link URL" @@ -1851,7 +1851,7 @@ #: ../ui/composer-menus.ui.h:12 msgid "Cu_t" -msgstr "_Talla" +msgstr "_Retalla" #: ../ui/composer-menus.ui.h:13 ../ui/conversation-message-menus.ui.h:7 msgid "_Copy" diff -Nru geary-0.12.4/po/CMakeLists.txt geary-3.32.0/po/CMakeLists.txt --- geary-0.12.4/po/CMakeLists.txt 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/po/CMakeLists.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,10 +0,0 @@ - -file(STRINGS "LINGUAS" TRANSLATED) - -IF (XGETTEXT_FOUND) - GETTEXT_CREATE_TRANSLATIONS(ALL ${TRANSLATED} - COMMENT "Creating translations.") -ELSE () - message(STATUS "xgettext not found") -ENDIF() - diff -Nru geary-0.12.4/po/cs.po geary-3.32.0/po/cs.po --- geary-0.12.4/po/cs.po 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/po/cs.po 2019-03-17 13:39:29.000000000 +0000 @@ -5,15 +5,14 @@ # # Translators: # petr.simacek , 2012, 2013. -# Marek Černocký , 2014, 2015, 2016, 2017. +# Marek Černocký , 2014, 2015, 2016, 2017, 2018, 2019. # msgid "" msgstr "" -"Project-Id-Version: geary geary-0.12\n" -"Report-Msgid-Bugs-To: https://bugzilla.gnome.org/enter_bug.cgi?" -"product=geary&keywords=I18N+L10N&component=internationalization\n" -"POT-Creation-Date: 2017-10-02 14:41+0000\n" -"PO-Revision-Date: 2017-10-02 17:20+0200\n" +"Project-Id-Version: geary\n" +"Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/geary/issues\n" +"POT-Creation-Date: 2019-02-12 00:25+0000\n" +"PO-Revision-Date: 2019-02-13 09:59+0100\n" "Last-Translator: Marek Černocký \n" "Language-Team: čeština \n" "Language: cs\n" @@ -23,27 +22,55 @@ "Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" "X-Generator: Gtranslator 2.91.7\n" +#: desktop/geary-attach.contract.desktop.in:3 +msgid "Send by email" +msgstr "Odeslat e-mailem" + +#. Translators: Do NOT translate or transliterate this text (this is an icon file name)! +#: desktop/geary-attach.contract.desktop.in:5 +msgid "mail-send" +msgstr "mail-send" + +#: desktop/geary-attach.contract.desktop.in:6 +msgid "Send files using Geary" +msgstr "Odeslat soubory pomocí Geary" + #. Translators: The application name -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:2 -#: ../desktop/org.gnome.Geary.desktop.in.h:1 -#: ../desktop/geary-autostart.desktop.in.h:1 +#: desktop/geary-autostart.desktop.in:3 +#: desktop/org.gnome.Geary.appdata.xml.in:11 +#: desktop/org.gnome.Geary.desktop.in:3 +#: src/client/accounts/accounts-editor-servers-pane.vala:539 msgid "Geary" msgstr "Geary" -#. Translators: The development team's name -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:4 -msgid "Geary Development Team" -msgstr "Vývojářský tým aplikace Geary" +#: desktop/geary-autostart.desktop.in:4 desktop/org.gnome.Geary.desktop.in:4 +msgid "Email" +msgstr "E-mail" #. Translators: The application's summary / tagline -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:6 -#: ../desktop/org.gnome.Geary.desktop.in.h:4 -#: ../desktop/geary-autostart.desktop.in.h:4 -#: ../src/client/application/geary-application.vala:21 +#: desktop/geary-autostart.desktop.in:5 +#: desktop/org.gnome.Geary.appdata.xml.in:15 +#: desktop/org.gnome.Geary.desktop.in:5 +#: src/client/application/geary-application.vala:22 msgid "Send and receive email" msgstr "Odesílejte a přijímejte e-maily" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:7 +#. Translators: These are desktop search terms. Do not translate semicolons, end line with a semicolon. +#: desktop/geary-autostart.desktop.in:7 +msgid "Email;E-mail;Mail;" +msgstr "email;e-mail;mail;pošta;e-pošta;zpráva;zprávy;" + +#. Translators: Do NOT translate or transliterate this text (this is an icon file name)! +#: desktop/geary-autostart.desktop.in:9 desktop/org.gnome.Geary.desktop.in:9 +msgid "org.gnome.Geary" +msgstr "org.gnome.Geary" + +#. Translators: The development team's name +#: desktop/org.gnome.Geary.appdata.xml.in:13 +msgid "Geary Development Team" +msgstr "Vývojářský tým aplikace Geary" + +#: desktop/org.gnome.Geary.appdata.xml.in:17 msgid "" "Geary is an email application built around conversations, for the GNOME 3 " "desktop. It allows you to read, find and send email with a straightforward, " @@ -53,7 +80,7 @@ "soustředí na konverzace jako celek. Umožňuje číst, vyhledávat a odesílat e-" "maily v přímočarém a moderním prostředí." -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:8 +#: desktop/org.gnome.Geary.appdata.xml.in:22 msgid "" "Conversations allow you to read a complete discussion without having to find " "and click from message to message." @@ -61,218 +88,694 @@ "Díky zaměření na konverzace můžete číst ucelené diskuze bez nutnosti " "vyhledávat jednotlivé navazující zprávy a klikat na ně." -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:9 +#: desktop/org.gnome.Geary.appdata.xml.in:26 msgid "Geary’s features include:" msgstr "Mezi funkcemi aplikace Geary najdete:" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:10 +#: desktop/org.gnome.Geary.appdata.xml.in:28 msgid "Quick email account setup" msgstr "Rychlé nastavení poštovního účtu" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:11 +#: desktop/org.gnome.Geary.appdata.xml.in:29 msgid "Shows related messages together in conversations" msgstr "Zobrazení souvisejících zprávy dohromady v podobě konverzace" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:12 +#: desktop/org.gnome.Geary.appdata.xml.in:30 msgid "Fast, full text and keyword search" msgstr "Rychlé vyhledávání v celém textu a na základě klíčových slov" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:13 +#: desktop/org.gnome.Geary.appdata.xml.in:31 msgid "Full-featured HTML and plain text message composer" msgstr "Plnohodnotný editor zpráv v HTML i prostém textu" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:14 +#: desktop/org.gnome.Geary.appdata.xml.in:32 msgid "Desktop notification of new mail" msgstr "Upozornění na nové zprávy přímo v uživatelském prostředí" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:15 +#: desktop/org.gnome.Geary.appdata.xml.in:33 msgid "Compatible with GMail, Yahoo! Mail, Outlook.com and other IMAP servers" msgstr "Kompatibilitu s GMail, Yahoo! Mail, Outlook.com a dalšími servery IMAP" #. Translators: A screenshot description. -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:17 +#: desktop/org.gnome.Geary.appdata.xml.in:47 msgid "Geary displaying a conversation" msgstr "Geary zobrazující konverzaci" #. Translators: A screenshot description. -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:19 +#: desktop/org.gnome.Geary.appdata.xml.in:52 msgid "Geary showing the rich text composer" msgstr "Geary se zobrazeným editorem formátovaného textu" -#: ../desktop/org.gnome.Geary.desktop.in.h:2 -#: ../desktop/geary-autostart.desktop.in.h:2 -msgid "Email" -msgstr "E-mail" - -#: ../desktop/org.gnome.Geary.desktop.in.h:3 -msgid "Geary Email" -msgstr "Pošta Geary" - #. Translators: These are desktop search terms. Do not translate semicolons, end line with a semicolon. -#: ../desktop/org.gnome.Geary.desktop.in.h:6 +#: desktop/org.gnome.Geary.desktop.in:7 msgid "Mail;E-mail;IMAP;GMail;Yahoo;Hotmail;Outlook;" msgstr "pošta;mail;e-mail;elektronická pošta;imap;gmail;yahoo;hotmail;outlook;" -#: ../desktop/org.gnome.Geary.desktop.in.h:7 ../ui/gtk/menus.ui.h:1 +#: desktop/org.gnome.Geary.desktop.in:21 ui/gtk/menus.ui:7 msgid "Compose Message" msgstr "Napsat zprávu" -#: ../desktop/geary-autostart.desktop.in.h:3 -msgid "Geary Mail" -msgstr "Pošta Geary" - -#: ../desktop/geary-autostart.desktop.in.h:5 -msgid "Email;E-mail;Mail;" -msgstr "email;e-mail;mail;pošta;e-pošta;zpráva;zprávy;" - -#: ../desktop/geary-attach.contract.in.h:1 -msgid "Send by email" -msgstr "Odeslat e-mailem" - -#: ../desktop/geary-attach.contract.in.h:2 -msgid "Send files using Geary" -msgstr "Odeslat soubory pomocí aplikace Geary" +#: desktop/org.gnome.Geary.gschema.xml:8 +msgid "Maximize window" +msgstr "Maximalizované okno" + +#: desktop/org.gnome.Geary.gschema.xml:9 +msgid "True if the application window is maximized, false otherwise." +msgstr "Zapnuto, když je okno s aplikací maximalizované, jinak vypnuto." + +#: desktop/org.gnome.Geary.gschema.xml:14 +msgid "Width of window" +msgstr "Šířka okna" + +#: desktop/org.gnome.Geary.gschema.xml:15 +msgid "The last recorded width of the application window." +msgstr "Poslední zaznamenaná šířka okna aplikace." + +#: desktop/org.gnome.Geary.gschema.xml:20 +msgid "Height of window" +msgstr "Výška okna" + +#: desktop/org.gnome.Geary.gschema.xml:21 +msgid "The last recorded height of the application window." +msgstr "Poslední zaznamenaná výška okna aplikace." + +#: desktop/org.gnome.Geary.gschema.xml:26 +msgid "Position of folder list pane" +msgstr "Umístění panelu se seznamem složek" + +#: desktop/org.gnome.Geary.gschema.xml:27 +msgid "Position of the folder list Paned grabber." +msgstr "Umístění úchytu panelu se seznamem složek." + +#: desktop/org.gnome.Geary.gschema.xml:32 +msgid "Position of folder list pane when horizontal" +msgstr "Umístění vodorovného panelu se seznamem složek" + +#: desktop/org.gnome.Geary.gschema.xml:33 +msgid "" +"Position of the folder list Paned grabber in the horizontal orientation." +msgstr "" +"Umístění úchytu panelu se seznamem složek, když je panel otočený vodorovně." + +#: desktop/org.gnome.Geary.gschema.xml:38 +msgid "Position of folder list pane when vertical" +msgstr "Umístění svislého panelu se seznamem složek" + +#: desktop/org.gnome.Geary.gschema.xml:39 +msgid "Position of the folder list Paned grabber in the vertical orientation." +msgstr "" +"Umístění úchytu panelu se seznamem složek, když je panel otočený svisle." + +#: desktop/org.gnome.Geary.gschema.xml:44 +msgid "Orientation of the folder list pane" +msgstr "Otočení panelu se seznamem složek" + +#: desktop/org.gnome.Geary.gschema.xml:45 +msgid "True if the folder list Paned is in the horizontal orientation." +msgstr "Zapnuto, když má být panel se seznamem složek otočený vodorovně." + +#: desktop/org.gnome.Geary.gschema.xml:50 +msgid "Position of message list pane" +msgstr "Umístění panelu se seznamem zprávy" + +#: desktop/org.gnome.Geary.gschema.xml:51 +msgid "Position of the message list Paned grabber." +msgstr "Umístění úchytu panelu se seznamem zpráv." + +#: desktop/org.gnome.Geary.gschema.xml:56 +msgid "Autoselect next message" +msgstr "Automatický výběr další zprávy" + +#: desktop/org.gnome.Geary.gschema.xml:57 +msgid "True if we should autoselect the next available conversation." +msgstr "Zapnuto, pokud se má automaticky vybírat další dostupná konverzace." + +#: desktop/org.gnome.Geary.gschema.xml:62 +msgid "Display message previews" +msgstr "Zobrazit náhledy zpráv" + +#: desktop/org.gnome.Geary.gschema.xml:63 +msgid "True if we should display a short preview of each message." +msgstr "Zapnuto, když se má zobrazovat krátký náhled u jednotlivých zpráv." + +#: desktop/org.gnome.Geary.gschema.xml:68 +msgid "Languages that shall be used in the spell checker" +msgstr "Jazyky používané v kontrole pravopisu" + +#: desktop/org.gnome.Geary.gschema.xml:69 +msgid "List of the languages to use in the spell checker." +msgstr "Seznam jazyků, které se mají používat při kontrole pravopisu." + +#: desktop/org.gnome.Geary.gschema.xml:74 +msgid "Languages that are displayed in the spell checker popover" +msgstr "Jazyky zobrazované ve vyskakovací nabídce kontroly pravopisu" + +#: desktop/org.gnome.Geary.gschema.xml:75 +msgid "" +"List of languages that are always displayed in the popover of the spell " +"checker." +msgstr "" +"Seznam jazyků, které se mají zobrazit ve vyskakovací nabídce kontroly " +"pravopisu." + +#: desktop/org.gnome.Geary.gschema.xml:80 +msgid "Enable notification sounds" +msgstr "Povolit zvuková upozornění" + +#: desktop/org.gnome.Geary.gschema.xml:81 +msgid "True to play sounds for notifications and sending." +msgstr "Zapnuto, když se mají přehrávat zvuky při upozornění nebo odesílání." + +#: desktop/org.gnome.Geary.gschema.xml:86 +msgid "Show notifications for new mail" +msgstr "Zobrazovat upozornění na nový e-mail" + +#: desktop/org.gnome.Geary.gschema.xml:87 +msgid "True to show notification bubbles." +msgstr "Zapnuto, když se mají zobrazovat bubliny s upozorněními." + +#: desktop/org.gnome.Geary.gschema.xml:92 +msgid "Notify of new mail at startup" +msgstr "Upozorňovat na novou poštu při spuštění" + +#: desktop/org.gnome.Geary.gschema.xml:93 +msgid "True to notify of new mail at startup." +msgstr "Zapnuto, když se má upozorňovat na novou poštu při spuštění aplikace." + +#: desktop/org.gnome.Geary.gschema.xml:98 +msgid "Ask when opening an attachment" +msgstr "Dotazovat se při otevírání přílohy" + +#: desktop/org.gnome.Geary.gschema.xml:99 +msgid "True to ask when opening an attachment." +msgstr "Zapnuto, pokud máte být dotazováni, když otevíráte přílohu." + +#: desktop/org.gnome.Geary.gschema.xml:104 +msgid "Whether to compose emails in HTML" +msgstr "Zda psát e-maily v HTML" + +#: desktop/org.gnome.Geary.gschema.xml:105 +msgid "True to compose emails in HTML; false for plain text." +msgstr "" +"Zapnuto, když se mají vytvářet e-maily ve formátu HTML, vypnuto, když v " +"prostém textu." + +#: desktop/org.gnome.Geary.gschema.xml:110 +msgid "Advisory strategy for full-text searching" +msgstr "Strategie pro hledání v celém textu" + +#: desktop/org.gnome.Geary.gschema.xml:111 +msgid "" +"Acceptable values are “exact”, “conservative”, “aggressive”, and “horizon”." +msgstr "" +"Přijímané hodnoty jsou „exact“ (jen přesná shoda), „conservative“ (jen malá " +"sada variant hledaného výrazu a drobné rozdíly ve shodě), „aggresive“ (širší " +"sada variant a větší rozdíly ve shodě) a „horizon“ (všechny varianty výrazu)." + +#: desktop/org.gnome.Geary.gschema.xml:116 +msgid "Zoom of conversation viewer" +msgstr "Příblížení zobrazení konverzace" + +#: desktop/org.gnome.Geary.gschema.xml:117 +msgid "The zoom to apply on the conservation view." +msgstr "Přiblížení, které se má použit při zobrazení konverzace." + +#: desktop/org.gnome.Geary.gschema.xml:122 +msgid "Size of detached composer window" +msgstr "Velikost odpojeného okno pro psaní zprávy" + +#: desktop/org.gnome.Geary.gschema.xml:123 +msgid "The last recorded size of the detached composer window." +msgstr "Poslední zaznamenaná velikost odpojeného okna pro psaní zprávy." + +#: desktop/org.gnome.Geary.gschema.xml:128 +msgid "Base URL to look up contact avatars" +msgstr "Základní adresa URL pro vyhledávání avatarů kontaktů" + +#: desktop/org.gnome.Geary.gschema.xml:129 +msgid "" +"A Gravatar or Libravatar compatible URL, set to the empty string to disable." +msgstr "" +"Adresa URL kompatibilní se službami Gravatar nebo Libravatar, prázdným " +"řetězcem se vyhledávání zakáže." + +#: desktop/org.gnome.Geary.gschema.xml:134 +msgid "Whether we migrated the old settings" +msgstr "Zda bylo přeneseno staré nastavení" + +#: desktop/org.gnome.Geary.gschema.xml:135 +msgid "" +"False to check for the old “org.yorba.geary”-schema and copy its values." +msgstr "" +"Vypnuto, když se má zkontrolovat staré schéma „org.yorba.geary“ a zkopírovat " +"jeho hodnoty." + +#. Translators: In-app notification label, when +#. the app had a problem pinning an otherwise +#. untrusted TLS certificate +#: src/client/accounts/accounts-editor.vala:203 +msgid "Failed to store certificate" +msgstr "Selhalo uložení certifikátu" + +#. Translators: Label for adding an email account +#. account for a generic IMAP service provider. +#: src/client/accounts/accounts-editor-add-pane.vala:109 +msgid "All others" +msgstr "Všechny ostatní" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:196 +#: src/client/accounts/accounts-editor-servers-pane.vala:300 +msgid "Check your receiving login and password" +msgstr "Zkontrolujte své přihlašovací jméno a heslo pro příjem" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:211 +#: src/client/accounts/accounts-editor-servers-pane.vala:313 +msgid "Check your receiving server details" +msgstr "Zkontrolujte údaje přijimacího serveru" + +#. Translators: In-app notification label +#. There was an SMTP auth error, but IMAP already +#. succeeded, so the user probably needs to +#. specify custom creds here +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:233 +#: src/client/accounts/accounts-editor-servers-pane.vala:334 +msgid "Check your sending login and password" +msgstr "Zkontrolujte své přihlašovací jméno a heslo pro odesílání" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:247 +#: src/client/accounts/accounts-editor-servers-pane.vala:347 +msgid "Check your sending server details" +msgstr "Zkontrolujte údaje odesilacího serveru" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:262 +msgid "Check your email address and password" +msgstr "Zkontrolujte svoji e-mailovou adresu a heslo" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:273 +msgid "Could not connect, check your network" +msgstr "Nelze se připojit, zkontrolujte svoji síť" + +#. Translators: In-app notification label for a +#. generic error creating an account +#: src/client/accounts/accounts-editor-add-pane.vala:286 +msgid "An unexpected problem occurred" +msgstr "Vyskytl se neočekávaný problém" + +#. Translators: In-app notification label, the +#. string substitution is a more detailed reason. +#: src/client/accounts/accounts-editor-add-pane.vala:304 +#, c-format +msgid "Account not created: %s" +msgstr "Účet nebyl vytvořen: %s" + +#. Translators: Label for the person's actual name when adding +#. an account +#: src/client/accounts/accounts-editor-add-pane.vala:552 +msgid "Your name" +msgstr "Vaše jméno" + +#. Translators: Label used for the address part of an +#. email address when editing a user's sender address +#. preferences for an account. +#: src/client/accounts/accounts-editor-add-pane.vala:569 +#: src/client/accounts/accounts-editor-edit-pane.vala:501 +msgid "Email address" +msgstr "E-mailová adresa" + +#. Translators: Placeholder for the default sender address +#. when adding an account +#. Translators: This is used as a placeholder for the +#. address part of an email address when editing a user's +#. sender address preferences for an account. +#: src/client/accounts/accounts-editor-add-pane.vala:572 +#: src/client/accounts/accounts-editor-edit-pane.vala:469 +msgid "person@example.com" +msgstr "osoba@priklad.cz" + +#. Translators: Label for an IMAP/SMTP service login/user name +#. when adding an account +#. Translators: Label for the user's login name for an +#. IMAP, SMTP, etc service +#: src/client/accounts/accounts-editor-add-pane.vala:586 +#: src/client/accounts/accounts-editor-servers-pane.vala:809 +msgid "Login name" +msgstr "Přihlašovací jméno" + +#. Translators: Label for the user's password for an IMAP, +#. SMTP, etc service +#: src/client/accounts/accounts-editor-add-pane.vala:600 +#: src/client/accounts/accounts-editor-servers-pane.vala:928 +#: ui/password-dialog.glade:108 +msgid "Password" +msgstr "Heslo" -#: ../src/client/accounts/account-dialog-add-edit-pane.vala:51 -#: ../src/client/components/stock.vala:31 -#: ../ui/conversation-email-menus.ui.h:10 -msgid "_Save" -msgstr "_Uložit" +#. Translators: Label for the IMAP server hostname when +#. adding an account. +#. Translators: This label describes the host name or IP +#. address and port used by an account's IMAP service. +#: src/client/accounts/accounts-editor-add-pane.vala:622 +#: src/client/accounts/accounts-editor-servers-pane.vala:656 +msgid "IMAP server" +msgstr "Server IMAP" + +#. Translators: Placeholder for the IMAP server hostname +#. when adding an account. +#: src/client/accounts/accounts-editor-add-pane.vala:625 +msgid "imap.example.com" +msgstr "imap.priklad.cz" -#: ../src/client/accounts/account-dialog-add-edit-pane.vala:51 -#: ../src/client/components/stock.vala:22 -msgid "_Add" -msgstr "_Přidat" +#. Translators: Label for the SMTP server hostname when +#. adding an account. +#. Translators: This label describes the host name or IP +#. address and port used by an account's SMTP service. +#: src/client/accounts/accounts-editor-add-pane.vala:631 +#: src/client/accounts/accounts-editor-servers-pane.vala:662 +msgid "SMTP server" +msgstr "Server SMTP" + +#. Translators: Placeholder for the SMTP server hostname +#. when adding an account. +#: src/client/accounts/accounts-editor-add-pane.vala:634 +msgid "smtp.example.com" +msgstr "smtp.priklad.cz" -#. reset/clear widgets -#: ../src/client/accounts/account-dialog-edit-alternate-emails-pane.vala:120 +#. Translators: Label in the account editor for the user's +#. custom name for an account. +#: src/client/accounts/accounts-editor-edit-pane.vala:278 +#: ui/accounts_editor_remove_pane.ui:123 +msgid "Account name" +msgstr "Název účtu" + +#. Translators: Tooltip used to undo changing +#. the name of an account. The string +#. substitution is the old name of the +#. account. +#: src/client/accounts/accounts-editor-edit-pane.vala:312 +#, c-format +msgid "Change account name back to “%s”" +msgstr "Změnit název účtu zpět na „%s“" + +#. Translators: Tooltip for adding a new email sender/from +#. address's address to an account +#: src/client/accounts/accounts-editor-edit-pane.vala:336 +msgid "Add a new sender email address" +msgstr "Přidat novou e-mailovou adresu odesilatele" + +#. Translators: Label used to indicate the user has +#. provided no display name for one of their sender +#. email addresses in their account settings. +#: src/client/accounts/accounts-editor-edit-pane.vala:417 +msgid "Name not set" +msgstr "Název není nastavený" + +#. Translators: This is used as a placeholder for the +#. display name for an email address when editing a user's +#. sender address preferences for an account. +#: src/client/accounts/accounts-editor-edit-pane.vala:456 +msgid "Sender Name" +msgstr "Jméno odesilatele" + +#: src/client/accounts/accounts-editor-edit-pane.vala:479 +msgid "Remove" +msgstr "Odebrat" + +#. Translators: Label used for the display name part of an +#. email address when editing a user's sender address +#. preferences for an account. +#: src/client/accounts/accounts-editor-edit-pane.vala:494 +msgid "Sender name" +msgstr "Jméno odesilatele" + +#. Translators: Label used as the undo tooltip after adding an +#. new sender email address to an account. The string +#. substitution is the email address added. +#: src/client/accounts/accounts-editor-edit-pane.vala:561 +#, c-format +msgid "Remove “%s”" +msgstr "Odebrat „%s“" + +#. Translators: Label used as the undo tooltip after editing a +#. sender address for an account. The string substitution is +#. the email address edited. +#: src/client/accounts/accounts-editor-edit-pane.vala:601 +#, c-format +msgid "Undo changes to “%s”" +msgstr "Vrátit změny na „%s“" + +#. Translators: Label used as the undo tooltip after removing +#. a sender address from an account. The string substitution +#. is the email address edited. +#: src/client/accounts/accounts-editor-edit-pane.vala:690 +#, c-format +msgid "Add “%s” back" +msgstr "Přidat zpět „%s“" + +#. Translators: Label used as the undo tooltip after removing +#. a sender address from an account. The string substitution +#. is the email address edited. +#: src/client/accounts/accounts-editor-edit-pane.vala:728 +msgid "Undo signature changes" +msgstr "Vrátit zpět změny v podpisu" + +#. Translators: This label describes the account +#. preference for the length of time (weeks, months or +#. years) that past email should be downloaded. +#: src/client/accounts/accounts-editor-edit-pane.vala:772 +msgid "Download mail" +msgstr "Stahovat e-maily" + +#. Translators: Tooltip for undoing a change +#. to the length of time that past email +#. should be downloaded for an account. The +#. string substitution is the duration, +#. e.g. "1 month back". +#: src/client/accounts/accounts-editor-edit-pane.vala:804 #, c-format -msgid "Additional addresses for %s" -msgstr "Dodatečné adresy pro %s" - -#. Sets min size. -#: ../src/client/accounts/account-dialog.vala:21 -msgid "Accounts" -msgstr "Účty" +msgid "Change download period back to: %s" +msgstr "Změnit stahované období zpět na %s" -#. Copyright 2016 Software Freedom Conservancy Inc. -#. * -#. * This software is licensed under the GNU Lesser General Public License -#. * (version 2.1 or later). See the COPYING file in this distribution. -#. -#. Page for adding or editing an account. -#. / Placeholder text indicating that the user should type their first name and last name -#: ../src/client/accounts/add-edit-page.vala:10 -msgid "First Last" -msgstr "První poslední" - -#: ../src/client/accounts/add-edit-page.vala:235 -msgid "Welcome to Geary." -msgstr "Vítejte v Geary." - -#: ../src/client/accounts/add-edit-page.vala:235 -msgid "Enter your account information to get started." -msgstr "Nejprve vyplňte informace o svém účtu." +#: src/client/accounts/accounts-editor-edit-pane.vala:825 +msgid "Everything" +msgstr "Vše" -#: ../src/client/accounts/add-edit-page.vala:255 +#: src/client/accounts/accounts-editor-edit-pane.vala:829 msgid "2 weeks back" -msgstr "2 týdny nazpět" +msgstr "2 týdny zpětně" -#. IDs are # of days -#: ../src/client/accounts/add-edit-page.vala:256 +#: src/client/accounts/accounts-editor-edit-pane.vala:833 msgid "1 month back" -msgstr "1 měsíc nazpět" +msgstr "1 měsíc zpětně" -#: ../src/client/accounts/add-edit-page.vala:257 +#: src/client/accounts/accounts-editor-edit-pane.vala:837 msgid "3 months back" -msgstr "3 měsíce nazpět" +msgstr "3 měsíce zpětně" -#: ../src/client/accounts/add-edit-page.vala:258 +#: src/client/accounts/accounts-editor-edit-pane.vala:841 msgid "6 months back" -msgstr "6 měsíců nazpět" +msgstr "6 měsíců zpětně" -#: ../src/client/accounts/add-edit-page.vala:259 +#: src/client/accounts/accounts-editor-edit-pane.vala:845 msgid "1 year back" -msgstr "1 rok nazpět" +msgstr "1 rok zpětně" -#: ../src/client/accounts/add-edit-page.vala:260 +#: src/client/accounts/accounts-editor-edit-pane.vala:849 msgid "2 years back" -msgstr "2 roky nazpět" +msgstr "2 roky zpětně" -#: ../src/client/accounts/add-edit-page.vala:261 +#: src/client/accounts/accounts-editor-edit-pane.vala:853 msgid "4 years back" -msgstr "4 roky nazpět" +msgstr "4 roky zpětně" -#. Separator -#: ../src/client/accounts/add-edit-page.vala:263 -msgid "Everything" -msgstr "Vše" +#: src/client/accounts/accounts-editor-edit-pane.vala:859 +#, c-format +msgid "%d day back" +msgid_plural "%d days back" +msgstr[0] "%d den zpětně" +msgstr[1] "%d dny zpětně" +msgstr[2] "%d dní zpětně" + +#: src/client/accounts/accounts-editor-list-pane.vala:243 +msgid "Undo" +msgstr "Zpět" + +#: src/client/accounts/accounts-editor-list-pane.vala:251 +msgid "Redo" +msgstr "Znovu" + +#: src/client/accounts/accounts-editor-list-pane.vala:345 +#: src/client/accounts/accounts-editor-list-pane.vala:433 +#: src/client/accounts/accounts-editor-row.vala:278 +msgid "Gmail" +msgstr "Gmail" -#: ../src/client/accounts/add-edit-page.vala:283 -msgid "Edit" -msgstr "Úprava" - -#: ../src/client/accounts/add-edit-page.vala:285 -msgid "Preview" -msgstr "Náhled" - -#: ../src/client/accounts/add-edit-page.vala:751 -msgid "Remem_ber passwords" -msgstr "Pama_tovat si hesla" - -#: ../src/client/accounts/add-edit-page.vala:758 ../ui/login.glade.h:7 -msgid "Remem_ber password" -msgstr "Pama_tovat si heslo" - -#: ../src/client/accounts/add-edit-page.vala:792 -msgid "Unable to validate:\n" -msgstr "Nelze ověřit:\n" - -#: ../src/client/accounts/add-edit-page.vala:794 -msgid " • Invalid account nickname.\n" -msgstr " • Neplatná přezdívka účtu.\n" - -#: ../src/client/accounts/add-edit-page.vala:797 -msgid " • Email address already added to Geary.\n" -msgstr " • E-mailová adresa už byla do Geary přidána.\n" - -#: ../src/client/accounts/add-edit-page.vala:801 -msgid " • IMAP connection error.\n" -msgstr " • Chyba připojení IMAP.\n" - -#: ../src/client/accounts/add-edit-page.vala:804 -msgid " • IMAP username or password incorrect.\n" -msgstr " • Nesprávné uživatelské jméno nebo heslo pro IMAP.\n" - -#: ../src/client/accounts/add-edit-page.vala:807 -msgid " • SMTP connection error.\n" -msgstr " • Chyba připojení SMTP.\n" - -#: ../src/client/accounts/add-edit-page.vala:810 -msgid " • SMTP username or password incorrect.\n" -msgstr " • Nesprávné uživatelské jméno nebo heslo pro SMTP.\n" - -#: ../src/client/accounts/add-edit-page.vala:814 -msgid " • Connection error.\n" -msgstr " • Chyba připojení.\n" - -#: ../src/client/accounts/add-edit-page.vala:818 -msgid " • Username or password incorrect.\n" -msgstr " • Nesprávné uživatelské jméno nebo heslo.\n" +#: src/client/accounts/accounts-editor-list-pane.vala:349 +#: src/client/accounts/accounts-editor-list-pane.vala:437 +#: src/client/accounts/accounts-editor-row.vala:282 +msgid "Outlook.com" +msgstr "Outlook.com" + +#: src/client/accounts/accounts-editor-list-pane.vala:353 +#: src/client/accounts/accounts-editor-list-pane.vala:441 +#: src/client/accounts/accounts-editor-row.vala:286 +msgid "Yahoo" +msgstr "Yahoo" + +#. Translators: Tooltip for accounts that have been +#. loaded but disabled by the user. +#: src/client/accounts/accounts-editor-list-pane.vala:371 +msgid "This account has been disabled" +msgstr "Tento účet byl zakázán" + +#. Translators: Tooltip for accounts that have been +#. loaded but because of some error are not able to be +#. used. +#: src/client/accounts/accounts-editor-list-pane.vala:380 +msgid "This account has encountered a problem and is unavailable" +msgstr "U tohoto účtu účtu se objevil problém a proto je nedostupný" + +#. Translators: Label for adding a generic email account +#: src/client/accounts/accounts-editor-list-pane.vala:430 +msgid "Other email providers" +msgstr "Jiný poskytovatel e-mailu" + +#. Translators: Notification shown after removing an +#. account. The string substitution is the name of the +#. account. +#: src/client/accounts/accounts-editor-list-pane.vala:547 +#, c-format +msgid "Account “%s” removed" +msgstr "Účet „%s“ byl odebrán" + +#. Translators: Notification shown after removing an account +#. is undone. The string substitution is the name of the +#. account. +#: src/client/accounts/accounts-editor-list-pane.vala:554 +#, c-format +msgid "Account “%s” restored" +msgstr "Účet „%s“ byl obnoven" + +#. Translators: Tooltip for dragging list items +#: src/client/accounts/accounts-editor-row.vala:49 +msgid "Drag to move this item" +msgstr "Položku můžete přesunout přetažením" + +#. Translators: Label describes the service provider +#. hosting the email account, e.g. Gmail, Yahoo, or some +#. other generic IMAP service. +#: src/client/accounts/accounts-editor-row.vala:294 +msgid "Service provider" +msgstr "Poskytovatel služby" + +#. Translators: This label describes what form of transport +#. security (TLS, StartTLS, etc) used by an account's IMAP or SMTP +#. service. +#: src/client/accounts/accounts-editor-row.vala:467 +msgid "Connection security" +msgstr "Zabezpečení připojení" + +#. Translators: Label used when no auth scheme is used +#. by an account's IMAP or SMTP service. +#: src/client/accounts/accounts-editor-row.vala:478 +#: src/client/accounts/accounts-editor-servers-pane.vala:681 +#: src/client/accounts/accounts-editor-servers-pane.vala:893 +#: src/engine/api/geary-special-folder-type.vala:58 +msgid "None" +msgstr "Žádné" + +#: src/client/accounts/accounts-editor-row.vala:485 +msgid "StartTLS" +msgstr "StartTLS" + +#: src/client/accounts/accounts-editor-row.vala:492 +msgid "TLS" +msgstr "TLS" + +#. Translators: Label for source of SMTP authentication +#. credentials (none, use IMAP, custom) when adding a new +#. account +#. Button label for retrying when a login error has occurred +#: src/client/accounts/accounts-editor-row.vala:533 ui/main-window.ui:455 +msgid "Login" +msgstr "Přihlášení" + +#. Translators: ComboBox value for source of SMTP +#. authentication credentials (none) when adding a new +#. account +#: src/client/accounts/accounts-editor-row.vala:540 +msgid "No login needed" +msgstr "přihlášení není zapotřebí" + +#. Translators: ComboBox value for source of SMTP +#. authentication credentials (use IMAP) when adding a new +#. account +#: src/client/accounts/accounts-editor-row.vala:548 +msgid "Use same login as receiving" +msgstr "používat stejné přihlášení jako pro příjem" + +#. Translators: ComboBox value for source of SMTP +#. authentication credentials (custom) when adding a new +#. account +#: src/client/accounts/accounts-editor-row.vala:556 +msgid "Use a different login" +msgstr "používat jiné přihlášení" + +#. Translators: In-app notification label, the +#. string substitution is a more detailed reason. +#: src/client/accounts/accounts-editor-servers-pane.vala:361 +#, c-format +msgid "Account not updated: %s" +msgstr "Účet nebyl aktualizován: %s" + +#. Translators: This label describes the program that +#. created the account, e.g. an SSO service like GOA, or +#. locally by Geary. +#: src/client/accounts/accounts-editor-servers-pane.vala:524 +msgid "Account source" +msgstr "Původ účtu" + +#: src/client/accounts/accounts-editor-servers-pane.vala:536 +msgid "GNOME Online Accounts" +msgstr "Účty on-line GNOME" + +#. Translators: This label describes an account +#. preference. +#: src/client/accounts/accounts-editor-servers-pane.vala:595 +msgid "Save drafts on server" +msgstr "Ukládat koncepty na server" + +#. Add a suffix for OAuth2 auth so people know they +#. shouldn't expect to be prompted for a password +#. Translators: Label used when an account's IMAP or +#. SMTP service uses OAuth2. The string replacement is +#. the service's login name. +#: src/client/accounts/accounts-editor-servers-pane.vala:879 +#, c-format +msgid "%s using OAuth2" +msgstr "%s pomocí OAuth2" + +#: src/client/accounts/accounts-editor-servers-pane.vala:889 +msgid "Use receiving server login" +msgstr "Používat přihlášení přijimacího serveru" -#: ../src/client/application/geary-application.vala:22 +#: src/client/application/geary-application.vala:23 msgid "Copyright 2016 Software Freedom Conservancy Inc." msgstr "Copyright 2016 Software Freedom Conservancy Inc." -#: ../src/client/application/geary-application.vala:23 -msgid "Copyright 2016-2017 Geary Development Team." -msgstr "© 2016 – 2017 Vývojářský tým aplikace Geary" +#: src/client/application/geary-application.vala:24 +msgid "Copyright 2016-2019 Geary Development Team." +msgstr "© 2016 - 2019 Vývojářský tým aplikace Geary" -#: ../src/client/application/geary-application.vala:25 +#: src/client/application/geary-application.vala:26 msgid "Visit the Geary web site" msgstr "Navštívit webové stránky Geary" -#: ../src/client/application/geary-application.vala:466 +#: src/client/application/geary-application.vala:413 #, c-format msgid "About %s" msgstr "O aplikaci %s" @@ -280,318 +783,113 @@ #. Translators: add your name and email address to receive #. credit in the About dialog For example: Yamada Taro #. -#: ../src/client/application/geary-application.vala:470 +#: src/client/application/geary-application.vala:417 msgid "translator-credits" msgstr "" "Petr Šimáček \n" "Marek Černocký " -#: ../src/client/application/geary-args.vala:10 +#: src/client/application/geary-args.vala:10 msgid "Start Geary with hidden main window" msgstr "Spustit Geary se skrytým hlavním oknem" -#: ../src/client/application/geary-args.vala:11 +#: src/client/application/geary-args.vala:11 msgid "Output debugging information" msgstr "Vypisovat ladicí informace" -#: ../src/client/application/geary-args.vala:12 +#: src/client/application/geary-args.vala:12 msgid "Log conversation monitoring" msgstr "Zaznamenávat sledování konverzace" -#: ../src/client/application/geary-args.vala:13 +#: src/client/application/geary-args.vala:13 msgid "Log network deserialization" msgstr "Zaznamenávat síťové deserializace" -#: ../src/client/application/geary-args.vala:14 +#: src/client/application/geary-args.vala:14 msgid "Log network activity" msgstr "Zaznamenávat aktivity sítě" #. / The IMAP replay queue is how changes on the server are replicated on the client. #. / It could also be called the IMAP events queue. -#: ../src/client/application/geary-args.vala:17 +#: src/client/application/geary-args.vala:17 msgid "Log IMAP replay queue" msgstr "Zaznamenávat přehrání fronty IMAP" #. / Serialization is how commands and responses are converted into a stream of bytes for #. / network transmission -#: ../src/client/application/geary-args.vala:20 +#: src/client/application/geary-args.vala:20 msgid "Log network serialization" msgstr "Zaznamenávat síťové serializace" -#: ../src/client/application/geary-args.vala:21 +#: src/client/application/geary-args.vala:21 msgid "Log periodic activity" msgstr "Zaznamenávat opakující se aktivity" -#: ../src/client/application/geary-args.vala:22 +#: src/client/application/geary-args.vala:22 msgid "Log database queries (generates lots of messages)" msgstr "Zaznamenávat databázové dotazy (generuje velké množství zpráv)" #. / "Normalization" can also be called "synchronization" -#: ../src/client/application/geary-args.vala:24 +#: src/client/application/geary-args.vala:24 msgid "Log folder normalization" msgstr "Zaznamenávat normalizace složky" -#: ../src/client/application/geary-args.vala:25 +#: src/client/application/geary-args.vala:25 msgid "Allow inspection of WebView" msgstr "Povolit kontrolu WebView" -#: ../src/client/application/geary-args.vala:26 +#: src/client/application/geary-args.vala:26 msgid "Revoke all server certificates with TLS warnings" msgstr "Odvolat všechny serverové certifikáty s varováními TLS" -#: ../src/client/application/geary-args.vala:27 +#: src/client/application/geary-args.vala:27 msgid "Perform a graceful quit" msgstr "Korektně ukončit" -#: ../src/client/application/geary-args.vala:28 +#: src/client/application/geary-args.vala:28 msgid "Display program version" msgstr "Zobrazit verzi programu" #. This gives a command-line hint on how to open new composer windows with mailto: -#: ../src/client/application/geary-args.vala:53 +#: src/client/application/geary-args.vala:53 #, c-format msgid "Use %s to open a new composer window" msgstr "Použít %s k otevření nového okna pro psaní zprávy" -#: ../src/client/application/geary-args.vala:56 +#: src/client/application/geary-args.vala:56 msgid "Please report comments, suggestions and bugs to:" msgstr "Připomínky, návrhy a chyby hlaste prosím na:" #. i18n: Command line arguments are invalid -#: ../src/client/application/geary-args.vala:63 +#: src/client/application/geary-args.vala:63 #, c-format msgid "Failed to parse command line options: %s\n" msgstr "Nepodařilo se zpracovat přepínače příkazového řádku: %s\n" -#: ../src/client/application/geary-args.vala:74 +#: src/client/application/geary-args.vala:74 #, c-format msgid "Unrecognized command line option “%s”\n" msgstr "Nerozpoznaný přepínač příkazového řádku „%s“\n" -#: ../src/client/application/geary-controller.vala:57 -msgid "Delete conversation" -msgstr "Vymazat konverzaci" - -#: ../src/client/application/geary-controller.vala:58 -msgid "Delete conversation (Shift+Delete)" -msgstr "Vymazat konverzaci (Shift+Delete)" - -#: ../src/client/application/geary-controller.vala:59 -msgid "Delete conversations (Shift+Delete)" -msgstr "Vymazat konverzace (Shift+Delete)" - -#. This refers to the action ("move email to the trash"), not the Trash folder itself -#: ../src/client/application/geary-controller.vala:63 -msgid "Move conversation to Trash (Delete, Backspace)" -msgstr "Přesunout konverzaci do Koše (Delete, Backspace)" - -#: ../src/client/application/geary-controller.vala:64 -msgid "Move conversations to Trash (Delete, Backspace)" -msgstr "Přesunout konverzace do Koše (Delete, Backspace)" - -#. This refers to the action ("archive an email"), not the Archive folder itself -#: ../src/client/application/geary-controller.vala:68 -msgid "_Archive" -msgstr "_Archivovat" - -#: ../src/client/application/geary-controller.vala:69 -msgid "Archive conversation (A)" -msgstr "Archivovat konverzaci (A)" - -#: ../src/client/application/geary-controller.vala:70 -msgid "Archive conversations (A)" -msgstr "Archivovat konverzace (A)" - -#: ../src/client/application/geary-controller.vala:73 -msgid "Mark as S_pam" -msgstr "Označit jako nevyžá_dané" - -#: ../src/client/application/geary-controller.vala:74 -msgid "Mark as not S_pam" -msgstr "Označit, že není nevyžá_dané" - -#: ../src/client/application/geary-controller.vala:76 -#: ../src/client/application/geary-controller.vala:448 -msgid "Mark conversation" -msgstr "Označit konverzaci" - -#: ../src/client/application/geary-controller.vala:77 -msgid "Mark conversations" -msgstr "Označit konverzace" - -#: ../src/client/application/geary-controller.vala:78 -msgid "Add label to conversation" -msgstr "Přiřadit konverzaci štítek" - -#: ../src/client/application/geary-controller.vala:79 -msgid "Add label to conversations" -msgstr "Přiřadit konverzacím štítek" - -#: ../src/client/application/geary-controller.vala:80 -#: ../src/client/application/geary-controller.vala:487 -msgid "Move conversation" -msgstr "Přesunout konverzaci" - -#: ../src/client/application/geary-controller.vala:81 -msgid "Move conversations" -msgstr "Přesunout konverzace" - -#: ../src/client/application/geary-controller.vala:450 -msgid "_Mark as…" -msgstr "Oz_načit jako…" - -#: ../src/client/application/geary-controller.vala:456 -msgid "Mark as _Read" -msgstr "Označit jako _přečtené" - -#: ../src/client/application/geary-controller.vala:462 -msgid "Mark as _Unread" -msgstr "Označit jako _nepřečtené" - -#: ../src/client/application/geary-controller.vala:468 -msgid "_Star" -msgstr "_Hvězdička" - -#: ../src/client/application/geary-controller.vala:473 -msgid "U_nstar" -msgstr "O_debrat hvězdičku" - -#: ../src/client/application/geary-controller.vala:483 -msgid "Add label" -msgstr "Přidat štítek" - -#: ../src/client/application/geary-controller.vala:484 -msgid "_Label" -msgstr "Štít_ek" - -#: ../src/client/application/geary-controller.vala:488 -msgid "_Move" -msgstr "_Přesunout" - -#: ../src/client/application/geary-controller.vala:492 -msgid "Compose new message (Ctrl+N, N)" -msgstr "Napsat novou zprávu (Ctrl+N, N)" - -#: ../src/client/application/geary-controller.vala:496 -#: ../ui/conversation-email-menus.ui.h:1 -msgid "_Reply" -msgstr "_Odpovědět" - -#: ../src/client/application/geary-controller.vala:497 -msgid "Reply (Ctrl+R, R)" -msgstr "Odpovědět (Ctrl+R, R)" - -#: ../src/client/application/geary-controller.vala:501 -msgid "R_eply All" -msgstr "O_dpovědět všem" - -#: ../src/client/application/geary-controller.vala:502 -msgid "Reply all (Ctrl+Shift+R, Shift+R)" -msgstr "Odpovědět všem (Ctrl+Shift+R, Shift+R)" - -#: ../src/client/application/geary-controller.vala:507 -#: ../ui/conversation-email-menus.ui.h:3 -msgid "_Forward" -msgstr "_Přeposlat" - -#: ../src/client/application/geary-controller.vala:508 -msgid "Forward (Ctrl+L, F)" -msgstr "Přeposlat (Ctrl+L, F)" - -#: ../src/client/application/geary-controller.vala:538 -msgid "Empty _Spam…" -msgstr "Vyprázdnit _nevyžádanou…" +#. Translators: File name used in save chooser when saving +#. attachments that do not otherwise have a name. +#: src/client/application/geary-controller.vala:61 +msgid "Untitled" +msgstr "Bez názvu" -#: ../src/client/application/geary-controller.vala:542 -msgid "Empty _Trash…" -msgstr "Vyprázdnit koš…" - -#. No callback is connected, since we bind the toggle button to the search bar visibility -#: ../src/client/application/geary-controller.vala:574 -msgid "Toggle search bar" -msgstr "Přepnout vyhledávací lištu" - -#. No callback is connected, since we bind the toggle button to the find bar visibility -#: ../src/client/application/geary-controller.vala:579 -msgid "Toggle find bar" -msgstr "Přepnout vyhledávací lištu" - -#: ../src/client/application/geary-controller.vala:757 -msgid "Unable to store server trust exception" -msgstr "Nelze uložit výjimku důvěry k serveru" - -#: ../src/client/application/geary-controller.vala:992 -msgid "Your settings are insecure" -msgstr "Vaše nastavení není bezpečené" - -#: ../src/client/application/geary-controller.vala:993 -msgid "" -"Your IMAP and/or SMTP settings do not specify SSL or TLS. This means your " -"username and password could be read by another person on the network. Are " -"you sure you want to do this?" -msgstr "" -"Vaše nastavení IMAP a/nebo SMTP není v režimu SSL nebo TLS. To znamená, že " -"vaše uživatelské jméno a heslo může číst jiná osoba na síti. Opravdu to tak " -"chcete?" - -#: ../src/client/application/geary-controller.vala:994 -msgid "Co_ntinue" -msgstr "Po_kračovat" - -#: ../src/client/application/geary-controller.vala:1041 -msgid "Error connecting to the server" -msgstr "Chyba během připojování k serveru" - -#: ../src/client/application/geary-controller.vala:1042 -msgid "" -"Geary encountered an error while connecting to the server. Please try again " -"in a few moments." -msgstr "" -"Geary během připojování k serveru narazil na chybu. Zkuste to prosím po " -"chvíli znovu." - -#. / Displayed in the space-limited status bar when a message fails to be sent due to error. -#: ../src/client/application/geary-controller.vala:1079 -#: ../src/client/components/status-bar.vala:29 -msgid "Error sending email" -msgstr "Chyba při odesílání e-mailu" - -#: ../src/client/application/geary-controller.vala:1080 -msgid "" -"Geary encountered an error sending an email. If the problem persists, " -"please manually delete the email from your Outbox folder." -msgstr "" -"V Geary došlo k chybě při odesílání e-mailu. Pokud problém přetrvává, " -"odstraňte prosím e-mail ručně ze složky Pošta k odeslání." - -#. Displayed in the space-limited status bar when a message fails to be uploaded -#. to Sent Mail after being sent. -#: ../src/client/application/geary-controller.vala:1084 -#: ../src/client/components/status-bar.vala:33 -msgid "Error saving sent mail" -msgstr "Chyba při ukládání odeslané pošty" - -#: ../src/client/application/geary-controller.vala:1085 -msgid "" -"Geary encountered an error saving a sent message to Sent Mail. The message " -"will stay in your Outbox folder until you delete it." -msgstr "" -"V Geary došlo k chybě při ukládání odeslané zprávy. Zpráva zůstane ve vaší " -"složce Pošta k odeslání, dokud ji nesmažete." - -#: ../src/client/application/geary-controller.vala:1154 +#: src/client/application/geary-controller.vala:907 msgid "Labels" msgstr "Štítky" #. give the user two options: reset the Account local store, or exit Geary. A third #. could be done to leave the Account in an unopened state, but we don't currently #. have provisions for that. -#: ../src/client/application/geary-controller.vala:1166 +#: src/client/application/geary-controller.vala:920 #, c-format msgid "Unable to open the database for %s" msgstr "Nelze otevřít databázi pro %s" -#: ../src/client/application/geary-controller.vala:1167 +#: src/client/application/geary-controller.vala:921 #, c-format msgid "" "There was an error opening the local mail database for this account. This is " @@ -615,20 +913,20 @@ "Opětovné sestavení databáze zničí všechny místně uložené e-maily a jejich " "přílohy Na e-maily na vašem serveru to nebude mít vliv." -#: ../src/client/application/geary-controller.vala:1169 +#: src/client/application/geary-controller.vala:923 msgid "_Rebuild" msgstr "Znovu _sestavit" -#: ../src/client/application/geary-controller.vala:1169 +#: src/client/application/geary-controller.vala:923 msgid "E_xit" msgstr "S_končit" -#: ../src/client/application/geary-controller.vala:1178 +#: src/client/application/geary-controller.vala:932 #, c-format msgid "Unable to rebuild database for “%s”" msgstr "Nelze znovu sestavit databázi pro „%s“" -#: ../src/client/application/geary-controller.vala:1179 +#: src/client/application/geary-controller.vala:933 #, c-format msgid "" "Error during rebuild:\n" @@ -639,69 +937,15 @@ "\n" "%s" -#. some other problem opening the account ... as with other flow path, can't run -#. Geary today with an account in unopened state, so have to exit -#: ../src/client/application/geary-controller.vala:1201 -#: ../src/client/application/geary-controller.vala:1211 -#: ../src/client/application/geary-controller.vala:1222 -#, c-format -msgid "Unable to open local mailbox for %s" -msgstr "Nelze otevřít místní poštovní schránku pro %s" - -#: ../src/client/application/geary-controller.vala:1202 -#, c-format -msgid "" -"There was an error opening the local mail database for this account. This is " -"possibly due to a file permissions problem.\n" -"\n" -"Please check that you have read/write permissions for all files in this " -"directory:\n" -"\n" -"%s" -msgstr "" -"Došlo k chybě při otevírání místní databáze e-mailů pro tento účet. Stalo se " -"to možná kvůli oprávněním k souboru.\n" -"\n" -"Zkontrolujte, zda máte práva pro čtení/zápisu pro všechny soubory v této " -"složce:\n" -"\n" -"%s" - -#: ../src/client/application/geary-controller.vala:1212 -msgid "" -"The version number of the local mail database is formatted for a newer " -"version of Geary. Unfortunately, the database cannot be “rolled back” to " -"work with this version of Geary.\n" -"\n" -"Please install the latest version of Geary and try again." -msgstr "" -"Číslo verze databáze místní pošty je formátováno na novější verzi Geary. " -"Bohužel, databázi nelze vrátit zpět s touto verzí Geary.\n" -"\n" -"Nainstalujte prosím nejnovější verzi Geary a zkuste to znovu." - -#: ../src/client/application/geary-controller.vala:1223 -msgid "" -"There was an error opening the local account. This is probably due to " -"connectivity issues.\n" -"\n" -"Please check your network connection and restart Geary." -msgstr "" -"Došlo k chybě při otevírání místního účtu. To je pravděpodobně způsobeno " -"problémy s připojením.\n" -"\n" -"\n" -"Zkontrolujte prosím připojení k síti a restartujte Geary." - -#: ../src/client/application/geary-controller.vala:1999 +#: src/client/application/geary-controller.vala:1780 msgid "Undo move (Ctrl+Z)" msgstr "Zpět přesun (Ctrl+Z)" -#: ../src/client/application/geary-controller.vala:2009 +#: src/client/application/geary-controller.vala:1790 msgid "Are you sure you want to open these attachments?" msgstr "Opravdu chcete otevřít tuto přílohu?" -#: ../src/client/application/geary-controller.vala:2010 +#: src/client/application/geary-controller.vala:1791 msgid "" "Attachments may cause damage to your system if opened. Only open files from " "trusted sources." @@ -709,199 +953,492 @@ "Příloha by po otevření mohla poškodit váš počítač. Otevírejte pouze soubory " "z důvěryhodných zdrojů. " -#: ../src/client/application/geary-controller.vala:2011 +#: src/client/application/geary-controller.vala:1792 msgid "Don’t _ask me again" msgstr "Příště se nept_at" -#: ../src/client/application/geary-controller.vala:2121 +#. Translators: Dialog primary label when prompting to +#. overwrite a file. The string substitution is the file'sx +#. name. +#: src/client/application/geary-controller.vala:1921 #, c-format msgid "A file named “%s” already exists. Do you want to replace it?" msgstr "Soubor s názvem „%s“ již existuje. Chcete ho nahradit?" -#: ../src/client/application/geary-controller.vala:2123 +#. Translators: Dialog secondary label when prompting to +#. overwrite a file. The string substitution is the parent +#. folder's name. +#: src/client/application/geary-controller.vala:1928 #, c-format msgid "" "The file already exists in “%s”. Replacing it will overwrite its contents." msgstr "Soubor již existuje v „%s“. Nahrazením přepíšete jeho obsah." -#: ../src/client/application/geary-controller.vala:2126 +#: src/client/application/geary-controller.vala:1932 msgid "_Replace" msgstr "Nah_radit" -#. Find out what to do with the inline composers. -#. TODO: Remove this in favor of automatically saving drafts -#: ../src/client/application/geary-controller.vala:2374 -msgid "Close open draft messages?" -msgstr "Zavřít otevřený koncept zprávy?" +#: src/client/application/geary-controller.vala:2208 +msgid "Close the draft message?" +msgid_plural "Close all draft messages?" +msgstr[0] "Zavřít otevřený koncept zprávy?" +msgstr[1] "Zavřít všechny otevřené koncepty zpráv?" +msgstr[2] "Zavřít všechny otevřené koncepty zpráv?" -#: ../src/client/application/geary-controller.vala:2496 +#: src/client/application/geary-controller.vala:2334 #, c-format msgid "Empty all email from your %s folder?" msgstr "Vyprázdnit všechny zprávy z vaší složky %s?" -#: ../src/client/application/geary-controller.vala:2497 +#: src/client/application/geary-controller.vala:2335 msgid "This removes the email from Geary and your email server." msgstr "Tímto se odstraní pošta z aplikace Geary i z poštovního serveru." -#: ../src/client/application/geary-controller.vala:2498 +#: src/client/application/geary-controller.vala:2336 msgid "This cannot be undone." msgstr "Nebude možné to vrátit zpět." -#: ../src/client/application/geary-controller.vala:2499 +#: src/client/application/geary-controller.vala:2337 #, c-format msgid "Empty %s" msgstr "Vyprázdnit %s" -#: ../src/client/application/geary-controller.vala:2516 +#: src/client/application/geary-controller.vala:2354 #, c-format msgid "Error emptying %s" msgstr "Chyba při vyprazďňování složky %s" -#: ../src/client/application/geary-controller.vala:2546 +#: src/client/application/geary-controller.vala:2386 msgid "Do you want to permanently delete this message?" msgid_plural "Do you want to permanently delete these messages?" msgstr[0] "Chcete trvale smazat tuto zprávu?" msgstr[1] "Chcete trvale smazat tyto zprávy?" msgstr[2] "Chcete trvale smazat tyto zprávy?" -#: ../src/client/application/geary-controller.vala:2548 +#: src/client/application/geary-controller.vala:2388 msgid "Delete" msgstr "Smazat" -#: ../src/client/application/geary-controller.vala:2580 -msgid "Undo archive (Ctrl+Z)" -msgstr "Zpět archivace (Ctrl+Z)" - -#: ../src/client/application/geary-controller.vala:2595 +#: src/client/application/geary-controller.vala:2402 msgid "Undo trash (Ctrl+Z)" msgstr "Zpět přesunutí do koše (Ctrl+Z)" -#: ../src/client/application/geary-controller.vala:2649 +#: src/client/application/geary-controller.vala:2452 +msgid "Undo archive (Ctrl+Z)" +msgstr "Zpět archivace (Ctrl+Z)" + +#: src/client/application/geary-controller.vala:2497 msgid "Undo (Ctrl+Z)" msgstr "Zpět (Ctrl+Z)" -#: ../src/client/application/geary-controller.vala:2780 +#. Translators: The label for an in-app notification. The +#. string substitution is a list of recipients of the email. +#: src/client/application/geary-controller.vala:2574 +#, c-format +msgid "Successfully sent mail to %s." +msgstr "Byl úspěšně odeslán e-mail na %s." + +#: src/client/application/geary-controller.vala:2656 msgid "Failed to open default text editor." msgstr "Nepodařilo se otevřít výchozí textový editor." -#: ../src/client/components/main-window.vala:389 +#. Translators: Tooltip used when an entry requires a valid +#. email address to be entered, but one is not provided. +#: src/client/components/components-validator.vala:341 +msgid "An email address is required" +msgstr "Je vyžadována e-mailová adresa" + +#. Translators: Tooltip used when an entry requires a valid +#. email address to be entered, but the address is invalid. +#: src/client/components/components-validator.vala:345 +msgid "Not a valid email address" +msgstr "E-mailová adresa není platná" + +#. Translators: Tooltip used when an entry requires a valid, +#. resolvable server name to be entered, but one is not +#. provided. +#: src/client/components/components-validator.vala:391 +msgid "A server name is required" +msgstr "Je vyžadován název serveru" + +#. Translators: Tooltip used when an entry requires a valid +#. server name to be entered, but it was unable to be +#. looked-up in the DNS. +#: src/client/components/components-validator.vala:396 +msgid "Could not look up server name" +msgstr "Nelze najít název serveru" + +#. Tooltips +#: src/client/components/main-toolbar.vala:69 +msgid "Delete conversation (Shift+Delete)" +msgstr "Vymazat konverzaci (Shift+Delete)" + +#: src/client/components/main-toolbar.vala:70 +msgid "Delete conversations (Shift+Delete)" +msgstr "Vymazat konverzace (Shift+Delete)" + +#: src/client/components/main-toolbar.vala:71 +msgid "Move conversation to Trash (Delete, Backspace)" +msgstr "Přesunout konverzaci do Koše (Delete, Backspace)" + +#: src/client/components/main-toolbar.vala:72 +msgid "Move conversations to Trash (Delete, Backspace)" +msgstr "Přesunout konverzace do Koše (Delete, Backspace)" + +#: src/client/components/main-toolbar.vala:73 +msgid "Archive conversation (A)" +msgstr "Archivovat konverzaci (A)" + +#: src/client/components/main-toolbar.vala:74 +msgid "Archive conversations (A)" +msgstr "Archivovat konverzace (A)" + +#: src/client/components/main-toolbar.vala:75 +msgid "Mark conversation" +msgstr "Označit konverzaci" + +#: src/client/components/main-toolbar.vala:76 +msgid "Mark conversations" +msgstr "Označit konverzace" + +#: src/client/components/main-toolbar.vala:77 +msgid "Add label to conversation" +msgstr "Přiřadit konverzaci štítek" + +#: src/client/components/main-toolbar.vala:78 +msgid "Add label to conversations" +msgstr "Přiřadit konverzacím štítek" + +#: src/client/components/main-toolbar.vala:79 +msgid "Move conversation" +msgstr "Přesunout konverzaci" + +#: src/client/components/main-toolbar.vala:80 +msgid "Move conversations" +msgstr "Přesunout konverzace" + +#: src/client/components/main-window.vala:500 #, c-format msgid "%s (%d)" msgstr "%s (%d)" -#: ../src/client/components/search-bar.vala:8 -#: ../src/client/folder-list/folder-list-search-branch.vala:38 -#: ../src/engine/api/geary-special-folder-type.vala:51 +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:47 +#, c-format +msgid "Problem connecting to incoming server for %s" +msgstr "Problém s připojováním k serveru příchozí pošty pro účet %s" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:49 +#: src/client/components/main-window-info-bar.vala:58 +#, c-format +msgid "" +"Could not connect to %s, check your Internet access and the server name and " +"try again" +msgstr "" +"Nelze se připojit k serveru %s. Zkontrolujte své připojení k Internetu a " +"název serveru a zkuste to znovu." + +#. Translators: Tooltip label for Retry button +#. Button tooltip for retrying an account problem +#: src/client/components/main-window-info-bar.vala:51 +#: src/client/components/main-window-info-bar.vala:60 +#: src/client/components/main-window-info-bar.vala:69 +#: src/client/components/main-window-info-bar.vala:78 +#: src/client/components/main-window-info-bar.vala:87 +#: src/client/components/main-window-info-bar.vala:96 +#: src/client/components/main-window-info-bar.vala:136 ui/main-window.ui:265 +msgid "Try reconnecting" +msgstr "Zkusit znovu připojit" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:56 +#, c-format +msgid "Problem connecting to outgoing server for %s" +msgstr "Problém s připojováním k serveru odchozí pošty pro účet %s" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:65 +#: src/client/components/main-window-info-bar.vala:83 +#, c-format +msgid "Problem communicating with incoming server for %s" +msgstr "Problém s komunikací se serverem příchozí pošty pro účet %s" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:67 +#: src/client/components/main-window-info-bar.vala:76 +#, c-format +msgid "Network error talking to %s, check your Internet access and try again" +msgstr "" +"Síťová chyba při komunikaci se serverem %s. Zkontrolujte své připojení k " +"Internetu a zkuste to znovu." + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:74 +#: src/client/components/main-window-info-bar.vala:91 +msgid "Problem communicating with outgoing mail server" +msgstr "Problém s komunikací se serverem odchozí pošty" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:85 +#, c-format +msgid "" +"Geary did not understand a message from %s or vice versa, please file a bug " +"report" +msgstr "" +"Geary nerozumí zprávě ze serveru %s nebo server jemu. Nahlaste prosím chybu." + +#. Translators: First string substitution is the server +#. name, second is the account name +#: src/client/components/main-window-info-bar.vala:94 +#, c-format +msgid "" +"Could not communicate with %s for %s, check the server name and try again in " +"a moment" +msgstr "" +"Nyní nelze komunikovat se serverem %s pro účet %s. Zkontrolujte název " +"serveru a zkuste to za chvíli znovu." + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:101 +#, c-format +msgid "Incoming mail server password required for %s" +msgstr "Server příchozí pošty požaduje heslo pro účet %s" + +#: src/client/components/main-window-info-bar.vala:102 +msgid "Messages cannot be received without the correct password." +msgstr "Bez správného hesla nelze přijmout zprávy." + +#. Translators: Tooltip label for Retry button +#: src/client/components/main-window-info-bar.vala:104 +msgid "Retry receiving email, you will be prompted for a password" +msgstr "Zkusit znovu přijmou poštu, budete dotázáni na heslo" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:109 +#, c-format +msgid "Outgoing mail server password required for %s" +msgstr "Server odchozí pošty požaduje heslo pro účet %s" + +#: src/client/components/main-window-info-bar.vala:110 +msgid "Messages cannot be sent without the correct password." +msgstr "Bez správného hesla nelze odeslat zprávy." + +#. Translators: Tooltip label for Retry button +#: src/client/components/main-window-info-bar.vala:112 +msgid "Retry sending queued messages, you will be prompted for a password" +msgstr "Zkusit znovu odeslat frontu zpráv, budete dotázáni na heslo" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:117 +#, c-format +msgid "Incoming mail server security is not trusted for %s" +msgstr "Server příchozí pošty není důvěryhodný pro účet %s" + +#: src/client/components/main-window-info-bar.vala:118 +msgid "Messages will not be received until checked." +msgstr "Dokud nedojte ke kontrole, nebudou přijímány zprávy." + +#. Translators: Tooltip label for Retry button +#: src/client/components/main-window-info-bar.vala:120 +#: src/client/components/main-window-info-bar.vala:128 +msgid "Check security details" +msgstr "Zkontrolovat údaje zabezpečení" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:125 +#, c-format +msgid "Outgoing mail server security is not trusted for %s" +msgstr "Server odchozí pošty není důvěryhodný pro účet %s" + +#: src/client/components/main-window-info-bar.vala:126 +msgid "Messages cannot be sent until checked." +msgstr "Dokud nedojte ke kontrole, nelze odesílat zprávy." + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:133 +#, c-format +msgid "A problem occurred checking mail for %s" +msgstr "Vyskytl se problém při kontrole pošty pro účet %s" + +#: src/client/components/main-window-info-bar.vala:134 +#: src/client/components/main-window-info-bar.vala:142 +msgid "Something went wrong, please file a bug report if the problem persists" +msgstr "Něco je špatně. Pokud bude problém přetrvávat, nahlaste prosím chybu" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:141 +#, c-format +msgid "A problem occurred sending mail for %s" +msgstr "Vyskytl se problém při odesílání pošty účtu %s" + +#. Translators: Tooltip label for Retry button +#: src/client/components/main-window-info-bar.vala:144 +msgid "Retry sending queued messages" +msgstr "Zkusit znovu odeslat frontu zpráv" + +#: src/client/components/main-window-info-bar.vala:155 +msgid "A database problem has occurred" +msgstr "Vyskytl se problém s databází" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:157 +#, c-format +msgid "Messages for %s must be downloaded again." +msgstr "Zprávy pro účet %s musí být znovu staženy." + +#: src/client/components/main-window-info-bar.vala:170 +msgid "Geary has encountered a problem" +msgstr "Aplikace Geary narazila na problém" + +#: src/client/components/main-window-info-bar.vala:171 +msgid "" +"Please check the technical details and report the problem if it persists." +msgstr "" +"Pokud bude problém přetrvávat, podívejte se prosím na technické podrobnosti " +"a nahlaste jej." + +#: src/client/components/main-window-info-bar.vala:179 +msgid "_Details" +msgstr "Po_drobnosti" + +#. Button tooltip for displaying technical details about an account problem +#: src/client/components/main-window-info-bar.vala:180 ui/main-window.ui:250 +msgid "View technical details about the error" +msgstr "Zobrazit technické podrobnosti o chybě" + +#: src/client/components/main-window-info-bar.vala:184 +msgid "_Retry" +msgstr "Zkusit z_novu" + +#: src/client/components/search-bar.vala:8 +#: src/client/folder-list/folder-list-search-branch.vala:38 +#: src/engine/api/geary-special-folder-type.vala:51 msgid "Search" msgstr "Hledat" #. Search entry. -#: ../src/client/components/search-bar.vala:23 +#: src/client/components/search-bar.vala:23 msgid "Search all mail in account for keywords (Ctrl+S)" msgstr "Prohledat všechny e-maily v účtu podle klíčového slova (Ctrl+S)" -#: ../src/client/components/search-bar.vala:100 +#: src/client/components/search-bar.vala:101 #, c-format msgid "Indexing %s account" msgstr "Indexuje se účet %s" -#: ../src/client/components/search-bar.vala:111 -#: ../src/client/folder-list/folder-list-search-branch.vala:39 +#: src/client/components/search-bar.vala:112 +#: src/client/folder-list/folder-list-search-branch.vala:39 #, c-format msgid "Search %s account" msgstr "Prohledat účet %s" #. / Displayed in the space-limited status bar while a message is in the process of being sent. -#: ../src/client/components/status-bar.vala:26 +#: src/client/components/status-bar.vala:26 msgid "Sending…" msgstr "Odesílá se…" -#: ../src/client/components/stock.vala:18 ../ui/account_cannot_remove.glade.h:3 +#. / Displayed in the space-limited status bar when a message fails to be sent due to error. +#: src/client/components/status-bar.vala:29 +msgid "Error sending email" +msgstr "Chyba při odesílání e-mailu" + +#. Displayed in the space-limited status bar when a message fails to be uploaded +#. to Sent Mail after being sent. +#: src/client/components/status-bar.vala:33 +msgid "Error saving sent mail" +msgstr "Chyba při ukládání odeslané pošty" + +#: src/client/components/stock.vala:18 msgid "_OK" msgstr "_Budiž" -#: ../src/client/components/stock.vala:19 ../ui/edit_alternate_emails.glade.h:3 -#: ../ui/password-dialog.glade.h:5 ../ui/remove_confirm.glade.h:5 +#: src/client/components/stock.vala:19 ui/password-dialog.glade:196 msgid "_Cancel" msgstr "_Zrušit" -#: ../src/client/components/stock.vala:21 ../ui/gtk/menus.ui.h:6 +#: src/client/components/stock.vala:21 ui/gtk/menus.ui:34 msgid "_About" msgstr "O _aplikaci" -#: ../src/client/components/stock.vala:23 +#: src/client/components/stock.vala:22 +msgid "_Add" +msgstr "_Přidat" + +#: src/client/components/stock.vala:23 msgid "_Close" msgstr "_Zavřít" -#: ../src/client/components/stock.vala:24 +#: src/client/components/stock.vala:24 msgid "_Discard" msgstr "_Vyřadit" -#: ../src/client/components/stock.vala:25 ../ui/gtk/menus.ui.h:5 +#: src/client/components/stock.vala:25 ui/gtk/menus.ui:29 msgid "_Help" msgstr "Nápo_věda" -#: ../src/client/components/stock.vala:26 ../ui/conversation-email-menus.ui.h:9 +#: src/client/components/stock.vala:26 ui/conversation-email-menus.ui:77 msgid "_Open" msgstr "_Otevřít" -#: ../src/client/components/stock.vala:27 ../ui/gtk/menus.ui.h:3 +#: src/client/components/stock.vala:27 ui/gtk/menus.ui:17 msgid "_Preferences" msgstr "_Předvolby" -#: ../src/client/components/stock.vala:28 ../ui/conversation-email-menus.ui.h:7 +#. Translators: Menu item to print a single, specific message +#: src/client/components/stock.vala:28 ui/conversation-email-menus.ui:64 msgid "_Print…" msgstr "_Tisk…" -#: ../src/client/components/stock.vala:29 ../ui/gtk/menus.ui.h:7 +#: src/client/components/stock.vala:29 ui/gtk/menus.ui:38 msgid "_Quit" msgstr "U_končit" -#: ../src/client/components/stock.vala:30 ../ui/remove_confirm.glade.h:6 +#: src/client/components/stock.vala:30 msgid "_Remove" msgstr "_Odstranit" -#: ../src/client/components/stock.vala:32 +#: src/client/components/stock.vala:31 ui/conversation-email-menus.ui:83 +msgid "_Save" +msgstr "_Uložit" + +#: src/client/components/stock.vala:32 msgid "_Keep" msgstr "_Zachovat" -#: ../src/client/composer/composer-link-popover.vala:150 +#: src/client/composer/composer-link-popover.vala:149 msgid "Link URL is not correctly formatted, e.g. http://example.com" msgstr "Adresa URL odkazu není ve správném formátu, např. http://example.com" -#: ../src/client/composer/composer-link-popover.vala:157 +#: src/client/composer/composer-link-popover.vala:156 msgid "Invalid link URL" msgstr "Neplatná adresa URL odkazu" -#: ../src/client/composer/composer-link-popover.vala:157 +#: src/client/composer/composer-link-popover.vala:156 msgid "Invalid email address" msgstr "Neplatná e-mailová adresa" -#: ../src/client/composer/composer-widget.vala:154 +#: src/client/composer/composer-widget.vala:158 msgid "Saved" msgstr "Uloženo" -#: ../src/client/composer/composer-widget.vala:155 +#: src/client/composer/composer-widget.vala:159 msgid "Saving" msgstr "Ukládá se" -#: ../src/client/composer/composer-widget.vala:156 +#: src/client/composer/composer-widget.vala:160 msgid "Error saving" msgstr "Chyba při ukládání" -#: ../src/client/composer/composer-widget.vala:157 +#: src/client/composer/composer-widget.vala:161 msgid "Press Backspace to delete quote" msgstr "Citaci smažete zmáčknutím Backspace" -#: ../src/client/composer/composer-widget.vala:158 -msgid "New Message" -msgstr "Nová zpráva" - #. Translators: This is list of keywords, separated by pipe ("|") #. characters, that suggest an attachment; since this is full-word #. checking, include all variants of each word. No spaces are #. allowed. -#: ../src/client/composer/composer-widget.vala:167 +#: src/client/composer/composer-widget.vala:170 msgid "" "attach|attaching|attaches|attachment|attachments|attached|enclose|enclosed|" "enclosing|encloses|enclosure|enclosures" @@ -910,28 +1447,37 @@ "přiloženým|přiloženou|přiložit|přikládám|přiložil|přikládá|přikládáme|" "přiložili|přiložily" -#: ../src/client/composer/composer-widget.vala:1122 -#: ../src/client/composer/composer-widget.vala:1141 -msgid "Do you want to discard this message?" -msgstr "Chcete zahodit tuto zprávu?" +#. Translators: This dialog text is displayed to the +#. user when closing a composer where the options are +#. Keep, Discard or Cancel. +#: src/client/composer/composer-widget.vala:1108 +msgid "Do you want to keep or discard this draft message?" +msgstr "Chcete tento koncept zprávy zachovat nebo zahodit?" + +#. Translators: This dialog text is displayed to the +#. user when closing a composer where the options are +#. only Discard or Cancel. +#: src/client/composer/composer-widget.vala:1136 +msgid "Do you want to discard this draft message?" +msgstr "Chcete zahodit tento koncept zprávy?" -#: ../src/client/composer/composer-widget.vala:1245 +#: src/client/composer/composer-widget.vala:1253 msgid "Send message with an empty subject and body?" msgstr "Poslat zprávu s prázdným předmětem a tělem?" -#: ../src/client/composer/composer-widget.vala:1247 +#: src/client/composer/composer-widget.vala:1255 msgid "Send message with an empty subject?" msgstr "Poslat zprávu s prázdným předmětem?" -#: ../src/client/composer/composer-widget.vala:1249 +#: src/client/composer/composer-widget.vala:1257 msgid "Send message with an empty body?" msgstr "Poslat zprávu s prázdným tělem?" -#: ../src/client/composer/composer-widget.vala:1253 +#: src/client/composer/composer-widget.vala:1261 msgid "Send message without an attachment?" msgstr "Poslat zprávu bez přílohy?" -#: ../src/client/composer/composer-widget.vala:1515 +#: src/client/composer/composer-widget.vala:1566 #, c-format msgid "“%s” already attached for delivery." msgstr "Soubor „%s“ už byl pro doručení přiložen." @@ -941,168 +1487,260 @@ #. description of the document type, the second will #. be a human-friendly size string. For example: #. Document (100.9MB) -#: ../src/client/composer/composer-widget.vala:1523 -#: ../src/client/conversation-viewer/conversation-email.vala:138 +#: src/client/composer/composer-widget.vala:1574 +#: src/client/conversation-viewer/conversation-email.vala:173 #, c-format msgid "%s (%s)" msgstr "%s (%s)" -#: ../src/client/composer/composer-widget.vala:1560 +#: src/client/composer/composer-widget.vala:1611 #, c-format msgid "“%s” could not be found." msgstr "Soubor „%s“ nebyl nalezen." -#: ../src/client/composer/composer-widget.vala:1566 +#: src/client/composer/composer-widget.vala:1617 #, c-format msgid "“%s” is a folder." msgstr "„%s“ je složka." -#: ../src/client/composer/composer-widget.vala:1572 +#: src/client/composer/composer-widget.vala:1623 #, c-format msgid "“%s” is an empty file." msgstr "Soubor „%s“ je prázdný." -#: ../src/client/composer/composer-widget.vala:1585 +#: src/client/composer/composer-widget.vala:1636 #, c-format msgid "“%s” could not be opened for reading." msgstr "Soubor „%s“ se nezdařilo otevřít pro čtení." -#: ../src/client/composer/composer-widget.vala:1593 +#: src/client/composer/composer-widget.vala:1644 msgid "Cannot add attachment" msgstr "Nelze připojit přílohu" -#: ../src/client/composer/composer-widget.vala:1645 -msgid "To: " -msgstr "Komu: " - -#: ../src/client/composer/composer-widget.vala:1648 -msgid "Cc: " -msgstr "Kopie: " - -#: ../src/client/composer/composer-widget.vala:1651 -msgid "Bcc: " -msgstr "Skrytá kopie: " +#. Translators: Human-readable version of the RFC 822 To header +#: src/client/composer/composer-widget.vala:1694 +#: src/client/conversation-viewer/conversation-email.vala:969 +#: src/engine/rfc822/rfc822-utils.vala:298 ui/conversation-message.ui:313 +msgid "To:" +msgstr "Komu:" -#: ../src/client/composer/composer-widget.vala:1654 +#. Translators: Human-readable version of the RFC 822 CC header +#: src/client/composer/composer-widget.vala:1700 +#: src/client/conversation-viewer/conversation-email.vala:974 +#: src/engine/rfc822/rfc822-utils.vala:303 ui/conversation-message.ui:358 +msgid "Cc:" +msgstr "Kopie:" + +#. Translators: Human-readable version of the RFC 822 BCC header +#: src/client/composer/composer-widget.vala:1706 +#: src/client/conversation-viewer/conversation-email.vala:979 +#: ui/conversation-message.ui:403 +msgid "Bcc:" +msgstr "Skrytá kopie:" + +#. Translators: Human-readable version of the RFC 822 Reply-To header +#: src/client/composer/composer-widget.vala:1712 msgid "Reply-To: " msgstr "Odpověď:" -#: ../src/client/composer/composer-widget.vala:1786 +#: src/client/composer/composer-widget.vala:1852 msgid "Select Color" msgstr "Vybrat barvu" -#. Displayed in the From dropdown to indicate an "alternate email address" -#. for an account. The first printf argument will be the alternate email -#. address, and the second will be the account's primary email address. -#: ../src/client/composer/composer-widget.vala:1986 +#. Displayed in the From dropdown to indicate an +#. "alternate email address" for an account. The first +#. printf argument will be the alternate email address, +#. and the second will be the account's primary email +#. address. +#: src/client/composer/composer-widget.vala:2038 #, c-format msgid "%1$s via %2$s" msgstr "%1$s přes %2$s" #. Composer label (with mnemonic underscore) for the account selector #. when choosing what address to send a message from. -#: ../src/client/composer/composer-widget.vala:2028 +#: src/client/composer/composer-widget.vala:2097 msgid "_From:" msgstr "_Od:" #. Translators: This is the name of the file chooser filter #. when inserting an image in the composer. -#: ../src/client/composer/composer-widget.vala:2252 +#: src/client/composer/composer-widget.vala:2322 msgid "Images" msgstr "Obrázky" -#: ../src/client/composer/spell-check-popover.vala:117 +#: src/client/composer/composer-window.vala:14 +msgid "New Message" +msgstr "Nová zpráva" + +#: src/client/composer/spell-check-popover.vala:117 msgid "Remove this language from the preferred list" msgstr "Odebrat tento jazyk ze seznamu upřednostňovaných" -#: ../src/client/composer/spell-check-popover.vala:121 +#: src/client/composer/spell-check-popover.vala:121 msgid "Add this language to the preferred list" msgstr "Přidat tento jazyk do seznamu upřednostňovaných" -#: ../src/client/composer/spell-check-popover.vala:217 +#: src/client/composer/spell-check-popover.vala:217 msgid "Search for more languages" msgstr "Vyhledat další jazyky" -#: ../src/client/conversation-list/formatted-conversation-data.vala:11 +#: src/client/conversation-list/conversation-list-view.vala:283 +msgid "Delete conversation" +msgstr "Vymazat konverzaci" + +#: src/client/conversation-list/conversation-list-view.vala:286 +#: ui/main-toolbar-menus.ui:16 +msgid "Mark as _Read" +msgstr "Označit jako _přečtené" + +#: src/client/conversation-list/conversation-list-view.vala:289 +#: ui/main-toolbar-menus.ui:20 +msgid "Mark as _Unread" +msgstr "Označit jako _nepřečtené" + +#: src/client/conversation-list/conversation-list-view.vala:292 +#: ui/main-toolbar-menus.ui:28 +msgid "U_nstar" +msgstr "O_debrat hvězdičku" + +#: src/client/conversation-list/conversation-list-view.vala:294 +#: ui/main-toolbar-menus.ui:24 +msgid "_Star" +msgstr "_Hvězdička" + +#. Translators: Menu item to reply to a specific message. +#: src/client/conversation-list/conversation-list-view.vala:297 +#: ui/conversation-email-menus.ui:9 +msgid "_Reply" +msgstr "_Odpovědět" + +#: src/client/conversation-list/conversation-list-view.vala:298 +msgid "R_eply All" +msgstr "O_dpovědět všem" + +#. Translators: Menu item to forward a specific message. +#: src/client/conversation-list/conversation-list-view.vala:299 +#: ui/conversation-email-menus.ui:21 +msgid "_Forward" +msgstr "_Přeposlat" + +#: src/client/conversation-list/formatted-conversation-data.vala:11 msgid "Me" msgstr "Já" #. Translators: This is the file type displayed for #. attachments with unknown file types. -#: ../src/client/conversation-viewer/conversation-email.vala:124 +#: src/client/conversation-viewer/conversation-email.vala:159 msgid "Unknown" msgstr "Neznámý" -#. Preview headers +#. Translators: Human-readable version of the RFC 822 From header +#: src/client/conversation-viewer/conversation-email.vala:964 +#: src/engine/rfc822/rfc822-utils.vala:289 +msgid "From:" +msgstr "Od:" + +#. Translators: Human-readable version of the RFC 822 Date header +#: src/client/conversation-viewer/conversation-email.vala:984 +#: src/engine/rfc822/rfc822-utils.vala:294 +msgid "Date:" +msgstr "Datum:" + +#. Translators: Human-readable version of the RFC 822 Subject header +#: src/client/conversation-viewer/conversation-email.vala:989 +#: src/engine/rfc822/rfc822-utils.vala:292 +msgid "Subject:" +msgstr "Předmět:" + +#: src/client/conversation-viewer/conversation-message.vala:64 +msgid "This email address may have been forged" +msgstr "Tato e-mailová adresa může být falešná" + +#. Compact headers #. Translators: This is displayed in place of the from address #. when the message has no from address. -#: ../src/client/conversation-viewer/conversation-message.vala:331 +#: src/client/conversation-viewer/conversation-message.vala:392 msgid "No sender" msgstr "Bez odesilatele" #. Translators: This separates multiple 'from' -#. addresses in the header preview for a message. -#: ../src/client/conversation-viewer/conversation-message.vala:586 +#. addresses in the compact header for a message. +#: src/client/conversation-viewer/conversation-message.vala:765 msgid ", " msgstr ", " #. Translators: This string is used as the HTML IMG ALT #. attribute value when displaying an inline image in an email #. that did not specify a file name. E.g. ImageCannot remove account " -msgstr "Nelze odstranit účet" +#. This title is shown to users when confirming if they want to remove an account. The string substitution is replaced with the account's name. +#: ui/accounts_editor_remove_pane.ui:73 +#, c-format +msgid "Confirm removing: %s" +msgstr "Potvrzení odebrání účtu %s" -#: ../ui/account_cannot_remove.glade.h:2 +#: ui/accounts_editor_remove_pane.ui:91 msgid "" -"A composer window associated with this account is currently open. Send or " -"discard the message and try again." +"Removing an account will remove it from Geary and delete locally cached " +"email data from your computer, but not from your service provider." msgstr "" -"Okno nové zprávy spojené s tímto účtem je v současné době otevřeno. Odešlete " -"nebo zrušte zprávu a zkuste to znovu." - -#: ../ui/account_list.glade.h:1 -msgid "Add account" -msgstr "Přidat účet" - -#: ../ui/account_list.glade.h:2 -msgid "Edit account" -msgstr "Upravit účet" +"Odebráním se účet odstraní z aplikace Geary a smažou se všechny místně " +"uložené e-maily v mezipaměti vašeho počítače, u vašeho poskytovatele ale " +"zůstanou zachovány." -#: ../ui/account_list.glade.h:3 +#: ui/accounts_editor_remove_pane.ui:122 msgid "Remove account" msgstr "Odstranit účet" -#: ../ui/account_spinner.glade.h:1 -msgid "Please wait while Geary validates your account." -msgstr "Vyčkejte prosím, než Geary ověří váš účet." +#: ui/accounts_editor_servers_pane.ui:17 +msgid "Cancel" +msgstr "Zrušit" + +#: ui/accounts_editor_servers_pane.ui:47 +msgid "Apply" +msgstr "Použít" -#: ../ui/certificate_warning_dialog.glade.h:1 +#: ui/certificate_warning_dialog.glade:7 msgid "Untrusted Connection" msgstr "Nedůvěryhodné připojení" -#: ../ui/certificate_warning_dialog.glade.h:2 +#: ui/certificate_warning_dialog.glade:29 msgid "_Always Trust This Server" msgstr "_Vždy důvěřovat tomuto serveru" -#: ../ui/certificate_warning_dialog.glade.h:3 +#: ui/certificate_warning_dialog.glade:43 msgid "_Trust This Server" msgstr "_Důvěřovat tomuto serveru" -#: ../ui/certificate_warning_dialog.glade.h:4 +#: ui/certificate_warning_dialog.glade:57 msgid "_Don’t Trust This Server" msgstr "_Nedůvěřovat tomuto serveru" -#: ../ui/composer-headerbar.ui.h:1 +#: ui/composer-headerbar.ui:17 ui/composer-headerbar.ui:174 msgid "Detach (Ctrl+D)" msgstr "Odpojit (Ctrl+D)" -#: ../ui/composer-headerbar.ui.h:2 +#: ui/composer-headerbar.ui:57 ui/composer-headerbar.ui:83 msgid "Attach File (Ctrl+T)" msgstr "Přiložit soubor (Ctrl+T)" -#: ../ui/composer-headerbar.ui.h:3 +#: ui/composer-headerbar.ui:107 msgid "Include Original Attachments" msgstr "Vložit původní přílohy" -#: ../ui/composer-headerbar.ui.h:4 -msgid "Send (Ctrl+Enter)" -msgstr "Odeslat (Ctrl+Enter)" - -#: ../ui/composer-headerbar.ui.h:5 +#: ui/composer-headerbar.ui:202 msgid "_Send" msgstr "_Odeslat" -#: ../ui/composer-headerbar.ui.h:6 +#: ui/composer-headerbar.ui:207 +msgid "Send (Ctrl+Enter)" +msgstr "Odeslat (Ctrl+Enter)" + +#: ui/composer-headerbar.ui:230 msgid "Discard and Close" msgstr "Zahodit a zavřít" -#: ../ui/composer-headerbar.ui.h:7 +#: ui/composer-headerbar.ui:254 msgid "Save and Close" msgstr "Uložit a zavřít" #. Note that this button and the Update button will never be shown at the same time to the user. -#: ../ui/composer-link-popover.ui.h:2 +#: ui/composer-link-popover.ui:41 msgid "Insert the new link with this URL" msgstr "Vložit nový odkaz s touto adresou URL" -#: ../ui/composer-link-popover.ui.h:3 +#: ui/composer-link-popover.ui:52 msgid "Link URL" msgstr "Adresa URL odkazu" #. Note that this button and the Insert button will never be shown at the same time to the user. -#: ../ui/composer-link-popover.ui.h:5 +#: ui/composer-link-popover.ui:66 msgid "Update this link’s URL" msgstr "Aktualizovat tuto adresu URL odkazu" -#: ../ui/composer-link-popover.ui.h:6 +#: ui/composer-link-popover.ui:86 msgid "Delete this link" msgstr "Smazat tento odkaz" -#: ../ui/composer-link-popover.ui.h:7 +#: ui/composer-link-popover.ui:106 msgid "Open this link" msgstr "Otevřít tento odkaz" -#: ../ui/composer-menus.ui.h:1 +#: ui/composer-menus.ui:7 msgid "S_ans Serif" msgstr "B_ezpatkové" -#: ../ui/composer-menus.ui.h:2 +#: ui/composer-menus.ui:12 msgid "S_erif" msgstr "P_atkové" -#: ../ui/composer-menus.ui.h:3 +#: ui/composer-menus.ui:17 msgid "_Fixed Width" msgstr "_Pevná šířka" -#: ../ui/composer-menus.ui.h:4 +#: ui/composer-menus.ui:24 msgid "_Small" msgstr "_Malé" -#: ../ui/composer-menus.ui.h:5 +#: ui/composer-menus.ui:29 msgid "_Medium" msgstr "_Střední" -#: ../ui/composer-menus.ui.h:6 +#: ui/composer-menus.ui:34 msgid "Lar_ge" msgstr "Vel_ké" -#: ../ui/composer-menus.ui.h:7 +#: ui/composer-menus.ui:41 msgid "C_olor" msgstr "B_arva" -#: ../ui/composer-menus.ui.h:8 +#: ui/composer-menus.ui:47 ui/composer-menus.ui:62 msgid "_Rich Text" msgstr "Fo_rmátovaný text" -#: ../ui/composer-menus.ui.h:9 +#: ui/composer-menus.ui:53 ui/composer-menus.ui:68 msgid "Show Extended Fields" msgstr "Zobrazit rozšiřující pole" -#: ../ui/composer-menus.ui.h:10 +#: ui/composer-menus.ui:78 msgid "_Undo" msgstr "_Zpět" -#: ../ui/composer-menus.ui.h:11 +#: ui/composer-menus.ui:82 msgid "_Redo" msgstr "Zno_vu" -#: ../ui/composer-menus.ui.h:12 +#: ui/composer-menus.ui:88 ui/composer-menus.ui:106 msgid "Cu_t" msgstr "_Vyjmout" -#: ../ui/composer-menus.ui.h:13 ../ui/conversation-message-menus.ui.h:7 +#: ui/composer-menus.ui:92 ui/composer-menus.ui:110 +#: ui/conversation-message-menus.ui:37 msgid "_Copy" msgstr "_Kopírovat" -#: ../ui/composer-menus.ui.h:14 +#: ui/composer-menus.ui:96 ui/composer-menus.ui:114 msgid "_Paste" msgstr "V_ložit" -#: ../ui/composer-menus.ui.h:15 -msgctxt "Clipboard paste with rich text" -msgid "Paste _With Formatting" -msgstr "Vložit _s formátováním" +#: ui/composer-menus.ui:100 +msgctxt "Clipboard paste as plain text" +msgid "Paste _Without Formatting" +msgstr "Vložit _bez formátování" -#: ../ui/composer-menus.ui.h:16 +#: ui/composer-menus.ui:120 msgid "Select _All" msgstr "Vybrat _vše" -#: ../ui/composer-menus.ui.h:17 ../ui/conversation-message-menus.ui.h:9 +#: ui/composer-menus.ui:127 ui/conversation-message-menus.ui:49 msgid "_Inspect…" msgstr "_Kontrolovat…" #. Address(es) e-mail is to be sent to -#: ../ui/composer-widget.ui.h:2 +#: ui/composer-widget.ui:56 msgid "_To" msgstr "_Komu" -#: ../ui/composer-widget.ui.h:3 +#: ui/composer-widget.ui:75 msgid "_Cc" msgstr "Ko_pie" -#: ../ui/composer-widget.ui.h:4 +#: ui/composer-widget.ui:130 msgid "_Subject" msgstr "_Předmět" -#: ../ui/composer-widget.ui.h:5 +#: ui/composer-widget.ui:149 msgid "_Bcc" msgstr "_Skrytá kopie" -#: ../ui/composer-widget.ui.h:6 +#: ui/composer-widget.ui:179 msgid "_Reply-To" msgstr "_Odpověď" #. Geary account mail will be sent from -#: ../ui/composer-widget.ui.h:8 +#: ui/composer-widget.ui:208 msgid "From" msgstr "Od" -#: ../ui/composer-widget.ui.h:9 +#: ui/composer-widget.ui:293 msgid "Drop files here" msgstr "upusťte soubory zde" -#: ../ui/composer-widget.ui.h:10 +#: ui/composer-widget.ui:309 msgid "To add them as attachments" msgstr "Chcete-li je přidat jako přílohy" -#: ../ui/composer-widget.ui.h:11 +#: ui/composer-widget.ui:348 msgid "Undo last edit (Ctrl+Z)" msgstr "Zpět poslední úpravu (Ctrl+Z)" -#: ../ui/composer-widget.ui.h:12 +#: ui/composer-widget.ui:372 msgid "Redo last edit (Ctrl+Shift+Z)" msgstr "Znovu poslední úpravu (Ctrl+Shift+Z)" -#: ../ui/composer-widget.ui.h:13 +#: ui/composer-widget.ui:410 msgid "Bold (Ctrl+B)" msgstr "Tučný (Ctrl+B)" -#: ../ui/composer-widget.ui.h:14 +#: ui/composer-widget.ui:434 msgid "Italic (Ctrl+I)" msgstr "Kurzíva (Ctrl+I)" -#: ../ui/composer-widget.ui.h:15 +#: ui/composer-widget.ui:458 msgid "Underline (Ctrl+U)" msgstr "Podtržené (Ctrl+U)" -#: ../ui/composer-widget.ui.h:16 +#: ui/composer-widget.ui:482 msgid "Strikethrough (Ctrl+K)" msgstr "Přeškrtnuté (Ctrl+K)" -#: ../ui/composer-widget.ui.h:17 +#: ui/composer-widget.ui:520 +msgid "Insert unordered list" +msgstr "Vložit neseřazený seznam" + +#: ui/composer-widget.ui:544 +msgid "Insert ordered list" +msgstr "Vložit seřazený seznam" + +#: ui/composer-widget.ui:582 msgid "Quote text (Ctrl+])" msgstr "Citace textu (Ctrl+])" -#: ../ui/composer-widget.ui.h:18 +#: ui/composer-widget.ui:606 msgid "Unquote text (Ctrl+[)" msgstr "Konec citace textu (Ctrl+[)" -#: ../ui/composer-widget.ui.h:19 +#: ui/composer-widget.ui:644 msgid "Insert or update selection link (Ctrl+L)" msgstr "Vložit nebo aktualizovat vybraný odkaz (Ctrl+L)" -#: ../ui/composer-widget.ui.h:20 +#: ui/composer-widget.ui:668 msgid "Insert an image (Ctrl+G)" msgstr "Vložit obrázek (Ctrl+G)" -#: ../ui/composer-widget.ui.h:21 +#: ui/composer-widget.ui:702 msgid "Remove selection formatting (Ctrl+Space)" msgstr "Odstranit vybrané formátování (Ctrl+Space)" -#: ../ui/composer-widget.ui.h:22 +#: ui/composer-widget.ui:726 msgid "Select spell checking languages" msgstr "Vybrat jazyky pro kontrolu pravopisu" -#: ../ui/conversation-email.ui.h:1 +#: ui/conversation-email.ui:27 msgid "Save all attachments" msgstr "Uložit všechny přílohy" #. Note: The application will never show this button at the same time as unstar_button, one will always be hidden. -#: ../ui/conversation-email.ui.h:3 +#: ui/conversation-email.ui:50 msgid "Mark this message as starred" msgstr "Označit tuto zprávu hvězdičkou" #. Note: The application will never show this button at the same time as star_button, one will always be hidden. -#: ../ui/conversation-email.ui.h:5 +#: ui/conversation-email.ui:72 msgid "Mark this message as not starred" msgstr "Zrušit označení této zprávy hvězdičkou" -#: ../ui/conversation-email.ui.h:6 +#: ui/conversation-email.ui:95 msgid "Display the message menu" msgstr "Zobrazit nabídku zpráv" -#: ../ui/conversation-email.ui.h:7 +#: ui/conversation-email.ui:161 msgid "Open selected attachments" msgstr "Otevřít vybrané přílohy" -#: ../ui/conversation-email.ui.h:8 +#: ui/conversation-email.ui:178 msgid "Save selected attachments" msgstr "Uložit vybrané přílohy" -#: ../ui/conversation-email.ui.h:9 +#: ui/conversation-email.ui:195 msgid "Select all attachments" msgstr "Vybrat všechny přílohy" -#: ../ui/conversation-email.ui.h:10 +#: ui/conversation-email.ui:240 msgid "Edit Draft" msgstr "Upravit koncept" -#: ../ui/conversation-email.ui.h:11 +#: ui/conversation-email.ui:267 msgid "Draft message" msgstr "Koncept zprávy" -#: ../ui/conversation-email.ui.h:12 +#: ui/conversation-email.ui:283 msgid "This message has not yet been sent." msgstr "Tato zpráva zatím nebyla odeslána." -#: ../ui/conversation-email.ui.h:13 -msgid "Try Again" -msgstr "Zkusit znovu" - -#: ../ui/conversation-email.ui.h:14 +#: ui/conversation-email.ui:329 msgid "Message not saved" msgstr "Zpráva není uložená" -#: ../ui/conversation-email.ui.h:15 +#: ui/conversation-email.ui:345 msgid "This message was sent, but has not been saved to your account." msgstr "Tato zpráva byla úspěšně odeslána, ale nebyla uložena do vašeho účtu." -#: ../ui/conversation-email-menus.ui.h:2 +#. Translators: Menu item to reply to a specific message. +#: ui/conversation-email-menus.ui:15 msgid "Reply to _All" msgstr "Odpovědět _všem" -#: ../ui/conversation-email-menus.ui.h:4 +#. Translators: Menu item to mark a specific message as +#. read. +#: ui/conversation-email-menus.ui:30 msgid "_Mark Read" msgstr "_Označit jako přečtené" -#: ../ui/conversation-email-menus.ui.h:5 +#: ui/conversation-email-menus.ui:36 msgid "_Mark Unread" msgstr "_Označit jako nepřečtené" -#: ../ui/conversation-email-menus.ui.h:6 +#. Translators: Menu item to mark all messages in a +#. conversation from this one as unread. +#: ui/conversation-email-menus.ui:42 msgid "Mark Unread From _Here" msgstr "Označit jako nepřečtené z_de" -#: ../ui/conversation-email-menus.ui.h:8 +#. Translators: Menu item to move a single, specific message +#. to the trash +#: ui/conversation-email-menus.ui:50 +msgid "_Trash" +msgstr "_Koš" + +#. Translators: Menu item to delete a single, specific message +#: ui/conversation-email-menus.ui:57 +msgid "_Delete…" +msgstr "_Smazat…" + +#. Translators: Menu item to view the source for a message +#: ui/conversation-email-menus.ui:69 msgid "_View Source" msgstr "_Zobrazit zdroj" -#: ../ui/conversation-email-menus.ui.h:11 +#: ui/conversation-email-menus.ui:87 msgid "_Save All" msgstr "_Uložit vše" -#: ../ui/conversation-message-menus.ui.h:1 +#: ui/conversation-message-menus.ui:7 msgid "_Open Link" msgstr "_Otevřít odkaz" -#: ../ui/conversation-message-menus.ui.h:2 +#: ui/conversation-message-menus.ui:11 msgid "Copy Link _Address" msgstr "Kopírovat _adresu odkazu" -#: ../ui/conversation-message-menus.ui.h:3 +#: ui/conversation-message-menus.ui:17 msgid "Send New _Message…" msgstr "Poslat novou _zprávu…" -#: ../ui/conversation-message-menus.ui.h:4 +#: ui/conversation-message-menus.ui:21 msgid "Copy Email _Address" msgstr "Kopírovat e-mailovou _adresu" -#: ../ui/conversation-message-menus.ui.h:5 +#: ui/conversation-message-menus.ui:27 msgid "Save _Image As…" msgstr "Uložit o_brázek jako…" -#: ../ui/conversation-message-menus.ui.h:6 +#: ui/conversation-message-menus.ui:33 msgid "_Select All" msgstr "Vybrat _vše" -#: ../ui/conversation-message-menus.ui.h:8 +#: ui/conversation-message-menus.ui:43 msgid "Search for messages from" msgstr "Vyhledat zprávy od" -#: ../ui/conversation-message.ui.h:1 +#: ui/conversation-message.ui:64 msgid "From " msgstr "Od " -#: ../ui/conversation-message.ui.h:2 +#: ui/conversation-message.ui:80 ui/conversation-message.ui:179 msgid "1/1/1970\t" msgstr "1.1.1970\t" -#: ../ui/conversation-message.ui.h:3 +#: ui/conversation-message.ui:103 msgid "Preview body text." msgstr "Náhled textu těla zprávy." -#: ../ui/conversation-message.ui.h:4 +#: ui/conversation-message.ui:203 msgid "Sent by:" msgstr "Odeslat jako:" -#: ../ui/conversation-message.ui.h:5 +#: ui/conversation-message.ui:248 msgid "Reply to:" msgstr "Odpovědět:" -#: ../ui/conversation-message.ui.h:6 +#: ui/conversation-message.ui:292 msgid "Subject" msgstr "Předmět" -#: ../ui/conversation-message.ui.h:7 -msgid "To:" -msgstr "Komu:" - -#: ../ui/conversation-message.ui.h:8 -msgid "Cc:" -msgstr "Kopie:" - -#: ../ui/conversation-message.ui.h:9 -msgid "Bcc:" -msgstr "Skrytá kopie:" - -#: ../ui/conversation-message.ui.h:10 +#: ui/conversation-message.ui:502 msgid "Show Images" msgstr "Zobrazit obrázky" -#: ../ui/conversation-message.ui.h:11 +#: ui/conversation-message.ui:515 msgid "Always Show From Sender" msgstr "Vždy zobrazovat Od odesílatele" -#: ../ui/conversation-message.ui.h:12 +#: ui/conversation-message.ui:543 msgid "Remote images not shown" msgstr "Vzdálené obrázky nejsou zobrazené" -#: ../ui/conversation-message.ui.h:13 +#: ui/conversation-message.ui:560 msgid "Only show remote images from senders you trust." msgstr "Vzdálené obrázky se zobrazují jen od odesilatelů, kterým důvěřujete." -#: ../ui/conversation-message.ui.h:14 +#: ui/conversation-message.ui:693 msgid "But actually goes to:" msgstr "ale ve skutečnosti jde na:" -#: ../ui/conversation-message.ui.h:15 +#: ui/conversation-message.ui:724 msgid "The link appears to go to:" msgstr "Tento odkaz vypadá, že míří na:" -#: ../ui/conversation-message.ui.h:16 +#: ui/conversation-message.ui:736 msgid "Deceptive link found" msgstr "Nalezen klamný odkaz" -#: ../ui/conversation-message.ui.h:17 +#: ui/conversation-message.ui:751 msgid "The email sender may be leading you to the wrong web site." msgstr "Odesilatel zprávy se vás snaží navést na nesprávné webové stránky." -#: ../ui/conversation-message.ui.h:18 +#: ui/conversation-message.ui:764 msgid "If unsure, contact the sender and ask before continuing." msgstr "" "Pokud máte pochybnosti, před pokračování kontaktujte odesilatele a vše si " "ověřte." -#: ../ui/conversation-viewer.ui.h:1 +#: ui/conversation-viewer.ui:60 msgid "Find in conversation" msgstr "Najít v konverzaci" -#: ../ui/conversation-viewer.ui.h:2 +#: ui/conversation-viewer.ui:74 msgid "Find the previous occurrence of the search string." msgstr "Najít předchozí výskyt hledaného řetězce." -#: ../ui/conversation-viewer.ui.h:3 +#: ui/conversation-viewer.ui:95 msgid "Find the next occurrence of the search string." msgstr "Najít následující výskyt hledaného řetězce." -#: ../ui/edit_alternate_emails.glade.h:1 -msgid "Remove email address" -msgstr "Odebrat e-mailové adresy" - -#: ../ui/edit_alternate_emails.glade.h:2 -msgid "" -"Some email services require additional addresses be configured on the " -"server. Contact your email provider for more information." -msgstr "" -"Některé e-mailové služby potřebují, aby byly na serveru nastaveny dodatečné " -"adresy. Kontaktujte svého poskytovatele e-mailu ohledně více informací." - -#: ../ui/edit_alternate_emails.glade.h:4 -msgid "_Update" -msgstr "Akt_ualizovat" - -#: ../ui/find_bar.glade.h:1 +#: ui/find_bar.glade:66 msgid "Find:" msgstr "Hledat:" -#: ../ui/find_bar.glade.h:2 +#: ui/find_bar.glade:89 msgid "_Previous" msgstr "_Předchozí" -#: ../ui/find_bar.glade.h:3 +#: ui/find_bar.glade:107 msgid "_Next" msgstr "_Následující" -#: ../ui/find_bar.glade.h:4 +#: ui/find_bar.glade:125 msgid "_Case sensitive" msgstr "_Rozlišovat velikost písmen" -#: ../ui/find_bar.glade.h:5 +#: ui/find_bar.glade:145 msgid "label" msgstr "štítek" -#: ../ui/gtk/help-overlay.ui.h:1 +#: ui/gtk/help-overlay.ui:9 msgid "Conversation Shortcuts" msgstr "Klávesové zkratky konverzace" -#: ../ui/gtk/help-overlay.ui.h:2 +#: ui/gtk/help-overlay.ui:13 ui/gtk/help-overlay.ui:254 msgctxt "shortcut window" msgid "General" msgstr "Obecné" -#: ../ui/gtk/help-overlay.ui.h:3 +#: ui/gtk/help-overlay.ui:17 msgctxt "shortcut window" msgid "Move focus to the next/previous pane" msgstr "Přesunout zaměření na následující/předchozí panel" -#: ../ui/gtk/help-overlay.ui.h:4 +#: ui/gtk/help-overlay.ui:24 msgctxt "shortcut window" msgid "Move focus to conversation list" msgstr "Přesunout zaměření na seznam konverzací" -#: ../ui/gtk/help-overlay.ui.h:5 +#: ui/gtk/help-overlay.ui:31 msgctxt "shortcut window" msgid "Detach composer window" msgstr "Odpojit okno pro psaní zprávy" -#: ../ui/gtk/help-overlay.ui.h:6 +#: ui/gtk/help-overlay.ui:38 msgctxt "shortcut window" msgid "Close composer window" msgstr "Zavřít okno pro psaní zprávy" -#: ../ui/gtk/help-overlay.ui.h:7 +#: ui/gtk/help-overlay.ui:45 msgctxt "shortcut window" msgid "Show keyboard shortcuts" msgstr "Zobrazit klávesové zkratky" -#: ../ui/gtk/help-overlay.ui.h:8 +#: ui/gtk/help-overlay.ui:52 msgctxt "shortcut window" msgid "Show help" msgstr "Zobrazit nápovědu" -#: ../ui/gtk/help-overlay.ui.h:9 +#: ui/gtk/help-overlay.ui:59 msgctxt "shortcut window" msgid "Quit the application" msgstr "Ukončit aplikaci" -#: ../ui/gtk/help-overlay.ui.h:10 +#: ui/gtk/help-overlay.ui:68 msgctxt "shortcut window" msgid "Search" msgstr "Hledat" -#: ../ui/gtk/help-overlay.ui.h:11 +#: ui/gtk/help-overlay.ui:72 msgctxt "shortcut window" msgid "Jump to search box" msgstr "Přejít do vyhledávacího pole" -#: ../ui/gtk/help-overlay.ui.h:12 +#: ui/gtk/help-overlay.ui:79 msgctxt "shortcut window" msgid "Find in current conversation" msgstr "Najít v aktuální konverzaci" -#: ../ui/gtk/help-overlay.ui.h:13 +#: ui/gtk/help-overlay.ui:86 msgctxt "shortcut window" msgid "Find next/previous in current conversation" msgstr "Najít následující/předchozí v aktuální konverzaci" -#: ../ui/gtk/help-overlay.ui.h:14 +#: ui/gtk/help-overlay.ui:95 ui/gtk/help-overlay.ui:274 msgctxt "shortcut window" msgid "Actions" msgstr "Činnosti" -#: ../ui/gtk/help-overlay.ui.h:15 +#: ui/gtk/help-overlay.ui:99 msgctxt "shortcut window" msgid "Compose a new message" msgstr "Napsat novou zprávu" -#: ../ui/gtk/help-overlay.ui.h:16 +#: ui/gtk/help-overlay.ui:106 msgctxt "shortcut window" msgid "Reply to sender " msgstr "Odpovědět odesilateli" -#: ../ui/gtk/help-overlay.ui.h:17 +#: ui/gtk/help-overlay.ui:113 msgctxt "shortcut window" msgid "Reply to all" msgstr "Odpovědět všem" -#: ../ui/gtk/help-overlay.ui.h:18 +#: ui/gtk/help-overlay.ui:120 msgctxt "shortcut window" msgid "Forward" msgstr "Přeposlat" -#: ../ui/gtk/help-overlay.ui.h:19 +#: ui/gtk/help-overlay.ui:127 msgctxt "shortcut window" msgid "Archive" msgstr "Archivovat" -#: ../ui/gtk/help-overlay.ui.h:20 +#: ui/gtk/help-overlay.ui:134 msgctxt "shortcut window" msgid "Move to trash" msgstr "Přesunout do koše" -#: ../ui/gtk/help-overlay.ui.h:21 +#: ui/gtk/help-overlay.ui:141 msgctxt "shortcut window" msgid "Toggle spam" msgstr "Přepnout příznak nevyžadné zprávy" -#: ../ui/gtk/help-overlay.ui.h:22 +#: ui/gtk/help-overlay.ui:148 msgctxt "shortcut window" msgid "Move the conversation" msgstr "Přesunout konverzaci" -#: ../ui/gtk/help-overlay.ui.h:23 +#: ui/gtk/help-overlay.ui:155 msgctxt "shortcut window" msgid "Label the conversation" msgstr "Přidat konverzaci štítek" -#: ../ui/gtk/help-overlay.ui.h:24 +#: ui/gtk/help-overlay.ui:163 msgctxt "shortcut window" msgid "Mark read" msgstr "Označit jako přečtené" -#: ../ui/gtk/help-overlay.ui.h:25 +#: ui/gtk/help-overlay.ui:170 msgctxt "shortcut window" msgid "Mark unread" msgstr "Označit jako nepřečtené" -#: ../ui/gtk/help-overlay.ui.h:26 +#: ui/gtk/help-overlay.ui:179 msgctxt "shortcut window" msgid "View" msgstr "Zobrazení" -#: ../ui/gtk/help-overlay.ui.h:27 +#: ui/gtk/help-overlay.ui:183 msgctxt "shortcut window" msgid "Zoom in" msgstr "Přiblížit" -#: ../ui/gtk/help-overlay.ui.h:28 +#: ui/gtk/help-overlay.ui:190 msgctxt "shortcut window" msgid "Zoom out" msgstr "Oddálit" -#: ../ui/gtk/help-overlay.ui.h:29 +#: ui/gtk/help-overlay.ui:197 msgctxt "shortcut window" msgid "Reset zoom" msgstr "Výchozí přiblížení" -#: ../ui/gtk/help-overlay.ui.h:30 +#: ui/gtk/help-overlay.ui:206 msgctxt "shortcut window" msgid "Additional Shortcuts" msgstr "Další klávesové zkratky" -#: ../ui/gtk/help-overlay.ui.h:31 +#: ui/gtk/help-overlay.ui:210 msgctxt "shortcut window" msgid "Star" msgstr "Označit hvězdičkou" -#: ../ui/gtk/help-overlay.ui.h:32 +#: ui/gtk/help-overlay.ui:217 msgctxt "shortcut window" msgid "Unstar" msgstr "Odebrat hvězdičku" -#: ../ui/gtk/help-overlay.ui.h:33 +#: ui/gtk/help-overlay.ui:224 msgctxt "shortcut window" msgid "Delete" msgstr "Smazat" -#: ../ui/gtk/help-overlay.ui.h:34 +#: ui/gtk/help-overlay.ui:231 msgctxt "shortcut window" msgid "Jump to next (older) conversation" msgstr "Přejít na následující (starší) konverzaci" -#: ../ui/gtk/help-overlay.ui.h:35 +#: ui/gtk/help-overlay.ui:238 msgctxt "shortcut window" msgid "Jump to previous (newer) conversation" msgstr "Přejít na předchozí (novější) konverzaci" -#: ../ui/gtk/help-overlay.ui.h:36 +#: ui/gtk/help-overlay.ui:250 msgid "Composer Shortcuts" msgstr "Klávesové zkratky editoru" -#: ../ui/gtk/help-overlay.ui.h:37 +#: ui/gtk/help-overlay.ui:258 msgctxt "shortcut window" msgid "Quote text" msgstr "Citovat text" -#: ../ui/gtk/help-overlay.ui.h:38 +#: ui/gtk/help-overlay.ui:265 msgctxt "shortcut window" msgid "Unquote text" msgstr "Zrušit citaci textu" -#: ../ui/gtk/help-overlay.ui.h:39 +#: ui/gtk/help-overlay.ui:278 msgctxt "shortcut window" msgid "Send" msgstr "Odeslat" -#: ../ui/gtk/help-overlay.ui.h:40 +#: ui/gtk/help-overlay.ui:285 msgctxt "shortcut window" msgid "Add attachment" msgstr "Přidat přílohu" -#: ../ui/gtk/help-overlay.ui.h:41 +#: ui/gtk/help-overlay.ui:294 msgctxt "shortcut window" msgid "Rich text mode" msgstr "Režim formátovaného textu" -#: ../ui/gtk/help-overlay.ui.h:42 +#: ui/gtk/help-overlay.ui:298 msgctxt "shortcut window" msgid "Bold text" msgstr "Tučný text" -#: ../ui/gtk/help-overlay.ui.h:43 +#: ui/gtk/help-overlay.ui:305 msgctxt "shortcut window" msgid "Italicize text" msgstr "Kurzíva" -#: ../ui/gtk/help-overlay.ui.h:44 +#: ui/gtk/help-overlay.ui:312 msgctxt "shortcut window" msgid "Underline text" msgstr "Podtržený text" -#: ../ui/gtk/help-overlay.ui.h:45 +#: ui/gtk/help-overlay.ui:319 msgctxt "shortcut window" msgid "Strike text" msgstr "Přeškrtnutý text" -#: ../ui/gtk/help-overlay.ui.h:46 +#: ui/gtk/help-overlay.ui:326 msgctxt "shortcut window" msgid "Insert a link" msgstr "Vložit odkaz" -#: ../ui/gtk/help-overlay.ui.h:47 +#: ui/gtk/help-overlay.ui:333 msgctxt "shortcut window" msgid "Remove formatting" msgstr "Odstranit formátování" -#: ../ui/gtk/menus.ui.h:2 +#: ui/gtk/menus.ui:13 msgid "A_ccounts" msgstr "Úč_ty" -#: ../ui/gtk/menus.ui.h:4 +#: ui/gtk/menus.ui:23 msgid "_Keyboard Shortcuts" msgstr "_Klávesové zkratky" -#: ../ui/login.glade.h:1 -msgid "email@example.com" -msgstr "e-mail@priklad.cz" +#: ui/main-toolbar.ui:51 +msgid "Toggle search bar" +msgstr "Přepnout vyhledávací lištu" -#: ../ui/login.glade.h:2 ../ui/password-dialog.glade.h:3 -msgid "Password" -msgstr "Heslo" +#: ui/main-toolbar.ui:72 +msgid "Empty Spam or Trash folders" +msgstr "Vyprázdnit složku Nevyžádané nebo Koš" -#: ../ui/login.glade.h:3 -msgid "E_mail address" -msgstr "E-_mailová adresa" - -#: ../ui/login.glade.h:4 -msgid "_Password" -msgstr "_Heslo" - -#: ../ui/login.glade.h:5 -msgid "S_ervice" -msgstr "S_lužba" - -#: ../ui/login.glade.h:6 -msgid "N_ame" -msgstr "J_méno" - -#: ../ui/login.glade.h:8 -msgid "N_ickname" -msgstr "Přez_dívka" - -#: ../ui/login.glade.h:9 -msgid "Work, Home, etc." -msgstr "Do práce, domů, atd." - -#: ../ui/login.glade.h:10 -msgid "_Save sent mail" -msgstr "_Ukládat odeslanou poštu" - -#: ../ui/login.glade.h:11 -msgid "Addi_tional email addresses…" -msgstr "Doda_tečné e-mailové adresy…" - -#: ../ui/login.glade.h:12 -msgid "IMAP settings" -msgstr "Nastavení IMAP" - -#: ../ui/login.glade.h:13 -msgid "Se_rver" -msgstr "Se_rver" +#: ui/main-toolbar.ui:112 +msgid "Reply" +msgstr "Odpovědět" -#: ../ui/login.glade.h:14 -msgid "imap.example.com" -msgstr "imap.priklad.cz" +#: ui/main-toolbar.ui:135 +msgid "Reply All" +msgstr "Odpovědět všem" -#: ../ui/login.glade.h:15 -msgid "P_ort" -msgstr "P_ort" +#: ui/main-toolbar.ui:158 +msgid "Forward" +msgstr "Přeposlat" -#: ../ui/login.glade.h:16 -msgid "smtp.example.com" -msgstr "smtp.priklad.cz" +#: ui/main-toolbar.ui:264 +msgid "Toggle find bar" +msgstr "Přepnout vyhledávací lištu" -#: ../ui/login.glade.h:17 -msgid "Ser_ver" -msgstr "Ser_ver" - -#: ../ui/login.glade.h:18 -msgid "Por_t" -msgstr "Por_t" - -#: ../ui/login.glade.h:19 -msgid "SMTP settings" -msgstr "Nastavení SMTP" - -#: ../ui/login.glade.h:20 -msgid "User_name" -msgstr "Uživatelské _jméno" - -#: ../ui/login.glade.h:21 -msgid "Pass_word" -msgstr "Hes_lo" - -#: ../ui/login.glade.h:22 -msgid "SMTP username" -msgstr "Uživatelské jméno pro SMTP" - -#: ../ui/login.glade.h:23 -msgid "SMTP password" -msgstr "Heslo pro SMTP" - -#: ../ui/login.glade.h:24 -msgid "_Username" -msgstr "_Uživatelské jméno" - -#: ../ui/login.glade.h:25 -msgid "IMAP username" -msgstr "Uživatelské jméno pro IMAP" - -#: ../ui/login.glade.h:26 -msgid "IMAP password" -msgstr "Heslo pro IMAP" - -#: ../ui/login.glade.h:27 -msgid "Encr_yption" -msgstr "Ši_frování" - -#: ../ui/login.glade.h:28 -msgid "Encrypt_ion" -msgstr "Š_ifrování" - -#: ../ui/login.glade.h:30 -msgid "SSL/TLS" -msgstr "SSL/TLS" - -#: ../ui/login.glade.h:31 -msgid "STARTTLS" -msgstr "STARTTLS" - -#: ../ui/login.glade.h:32 -msgid "No authentication re_quired" -msgstr "Ne_vyžaduje ověření" - -#: ../ui/login.glade.h:33 -msgid "Use IMAP cre_dentials" -msgstr "Použít přihlašovací ú_daje IMAP" - -#: ../ui/login.glade.h:34 -msgid "Composer" -msgstr "Editor" - -#: ../ui/login.glade.h:35 -msgid "Save dra_fts on server" -msgstr "Ukládat kon_cepty na server" - -#: ../ui/login.glade.h:36 -msgid "Si_gn emails (HTML allowed):" -msgstr "Patička e-_mailů (HTML povoleno):" - -#: ../ui/login.glade.h:37 -msgid "Storage" -msgstr "Úložiště" - -#: ../ui/login.glade.h:38 -msgid "_Download mail" -msgstr "Stá_hnout e-mail" +#: ui/main-toolbar.ui:306 +msgid "_Archive" +msgstr "_Archivovat" -#: ../ui/main-toolbar.ui.h:1 -msgid "Empty Spam or Trash folders" -msgstr "Vyprázdnit složku Nevyžádané nebo Koš" +#: ui/main-toolbar-menus.ui:5 +msgid "Empty _Spam…" +msgstr "Vyprázdnit _nevyžádanou…" + +#: ui/main-toolbar-menus.ui:9 +msgid "Empty _Trash…" +msgstr "Vyprázdnit koš…" -#: ../ui/password-dialog.glade.h:1 +#: ui/main-toolbar-menus.ui:32 +msgid "Mark as S_pam" +msgstr "Označit jako nevyžá_dané" + +#: ui/main-toolbar-menus.ui:36 +msgid "Mark as not S_pam" +msgstr "Označit, že není nevyžá_dané" + +#. Infobar title when one or more accounts are offline +#: ui/main-window.ui:183 +msgid "Working offline" +msgstr "Pracuje se v režimu odpojení" + +#. Label and tooltip for offline infobar +#: ui/main-window.ui:197 +msgid "" +"Your computer does not appear to be connected to the Internet.\n" +"You will not be able to send or receive email until it is re-connected." +msgstr "" +"Vypadá to, že váš počítač není připojený k Internetu.\n" +"Dokud se znovu nepřipojí, nebudete moci odesílat a přijímat e-maily." + +#. Label and tooltip for offline infobar +#: ui/main-window.ui:200 +msgid "You will not be able to send or receive email until re-connected." +msgstr "Dokud se znovu nepřipojíte, nebudete moci odesílat a přijímat e-maily." + +#. Button label for displaying technical details about an account problem +#. Dialog title for displaying technical details of a problem. Same as the button that invokes it. +#: ui/main-window.ui:247 ui/problem-details-dialog.ui:13 +msgid "Details" +msgstr "Podrobnosti" + +#. Button label for retrying an account problem +#: ui/main-window.ui:261 +msgid "Retry" +msgstr "Zkusit znovu" + +#. Infobar title when one or more accounts have encounted an error +#: ui/main-window.ui:294 +msgid "Account problem" +msgstr "Problém s účtem" + +#. Label and tooltip for account service problem infobar +#: ui/main-window.ui:308 +msgid "" +"Geary encountered a problem connecting to an account.\n" +"Please check your Internet connection, the server configuration and try " +"again." +msgstr "" +"Aplikace Geary narazila na problém s připojením k účtu.\n" +"Zkontrolujte své připojení k Internetu a nastavení serveru a zkuste to znovu." + +#. Label and tooltip for account service problem infobar +#: ui/main-window.ui:311 +msgid "Geary encountered a problem connecting to an account." +msgstr "Aplikace Geary narazila na problém s připojením k účtu." + +#. Button label for retrying TLS cert validation +#: ui/main-window.ui:358 +msgid "Check" +msgstr "Zkontrolovat" + +#. Button tooltip for retrying TLS cert validation +#: ui/main-window.ui:362 +msgid "Check the security details for the connection" +msgstr "Zkontrolovat údaje zabezpečení pro připojení" + +#. Infobar title when one or more accounts have a TLS cert validation error +#: ui/main-window.ui:391 +msgid "Security problem" +msgstr "Problém se zabezpečením" + +#. Label and tooltip for TLS cert validation error infobar +#: ui/main-window.ui:405 +msgid "" +"An account has reported an untrusted server.\n" +"Please check the server configuration and try again." +msgstr "" +"Účet oznámil nedůvěryhodný server.\n" +"Zkontrolujte prosím nastavení serveru a zkuste to znovu." + +#. Label and tooltip for TLS cert validation error infobar +#: ui/main-window.ui:408 +msgid "An account has reported an untrusted server." +msgstr "Účet oznámil nedůvěryhodný server." + +#. Button tooltip for retrying when a login error has occurred +#: ui/main-window.ui:459 +msgid "Retry login, you will be prompted for your password" +msgstr "Zkusit znovu přihlásit, budete dotázáni na heslo" + +#. Infobar title when one or more accounts have a login error +#: ui/main-window.ui:488 +msgid "Login problem" +msgstr "Problém s přihlášením" + +#. Label and tooltip for authentication problem infobar +#: ui/main-window.ui:502 +msgid "" +"An account has reported an incorrect login or password.\n" +"Please check your login name and try again." +msgstr "" +"Účet oznámil nesprávné přihlašovací jméno nebo heslo.\n" +"Zkontrolujte prosím své přihlašovací jméno a zkuste to znovu." + +#. Label and tooltip for authentication problem infobar +#: ui/main-window.ui:505 +msgid "An account has reported an incorrect login or password." +msgstr "Účet oznámil nesprávné přihlašovací jméno nebo heslo." + +#: ui/password-dialog.glade:74 msgid "SMTP Credentials" msgstr "Přihlašovací údaje SMTP" -#: ../ui/password-dialog.glade.h:2 +#: ui/password-dialog.glade:91 msgid "Username" msgstr "Uživatelské jméno" -#: ../ui/password-dialog.glade.h:4 +#: ui/password-dialog.glade:152 msgid "_Remember password" msgstr "_Pamatovat si heslo" -#: ../ui/password-dialog.glade.h:6 +#: ui/password-dialog.glade:210 msgid "_Authenticate" msgstr "_Ověřit" -#: ../ui/preferences-dialog.ui.h:1 +#: ui/preferences-dialog.ui:38 msgid "Reading" msgstr "Čtení" -#: ../ui/preferences-dialog.ui.h:2 +#: ui/preferences-dialog.ui:51 msgid "_Automatically select next message" msgstr "_Automaticky vybrat další zprávu" -#: ../ui/preferences-dialog.ui.h:3 +#: ui/preferences-dialog.ui:70 msgid "_Display conversation preview" msgstr "_Zobrazit náhled konverzace" -#: ../ui/preferences-dialog.ui.h:4 +#: ui/preferences-dialog.ui:89 msgid "Use _three pane view" msgstr "Používat _třípanelové zobrazení" -#: ../ui/preferences-dialog.ui.h:5 +#: ui/preferences-dialog.ui:113 msgid "Notifications" msgstr "Upozornění" -#: ../ui/preferences-dialog.ui.h:6 +#: ui/preferences-dialog.ui:126 msgid "_Play notification sounds" msgstr "_Přehrávat zvuková upozornění" -#: ../ui/preferences-dialog.ui.h:7 +#: ui/preferences-dialog.ui:145 msgid "Show _notifications for new mail" msgstr "Zobrazit _upozornění na nový e-mail" -#: ../ui/preferences-dialog.ui.h:8 -msgid "Always _watch for new mail" -msgstr "_Vždy sledovat novou poštu" - -#: ../ui/preferences-dialog.ui.h:9 -msgid "Geary will run in the background and notify of new mail" -msgstr "Geary poběží na pozadí a bude oznamovat příchod nové pošty" +#: ui/preferences-dialog.ui:164 +msgid "_Watch for new mail when closed" +msgstr "S_ledovat novou poštu při zavřené aplikaci" + +#: ui/preferences-dialog.ui:168 +msgid "Geary will keep running after all windows are closed" +msgstr "Aplikace Geary zůstane po zavření všech oken spuštěná na pozadí" -#: ../ui/preferences-dialog.ui.h:10 +#: ui/preferences-dialog.ui:195 msgid "Preferences" msgstr "Předvolby" -#: ../ui/remove_confirm.glade.h:1 +#. Button label for copying technical information to the clipboard +#: ui/problem-details-dialog.ui:17 +msgid "Copy to Clipboard" +msgstr "Zkopírovat do schránky" + +#. Button tooltip for copying technical information to the clipboard +#: ui/problem-details-dialog.ui:21 msgid "" -"Are you sure you want to remove this " -"account? " +"Copy technical details to clipboard for pasting into an email or bug report" msgstr "" -"Opravdu chcete odstranit tento účet?" +"Zkopírovat technické podrobnosti do schránky, abyste je mohli vložit do e-" +"mailu nebo do nahlášení chyby" -#: ../ui/remove_confirm.glade.h:2 +#: ui/problem-details-dialog.ui:73 msgid "" -"All email associated with this account will be removed from your computer. " -"This will not affect email on the server." +"If the problem is serious or persists, please copy and send these details to " +"the mailing list " +"or file a new " +"bug report." msgstr "" -"Všechny e-maily spojené s tímto účtem budou odstraněny z vašeho počítače. " -"Neovlivní to ale e-maily na serveru." - -#: ../ui/remove_confirm.glade.h:3 -msgid "Nickname:" -msgstr "Přezdívka:" +"Pokud je problém vážný, nebo přetrvává, zkopírujte a odešlete tyto údaje do " +"poštovní konference nebo vyplňte nové hlášení chyby." -#: ../ui/remove_confirm.glade.h:4 -msgid "Email address:" -msgstr "E-mailová adresa:" +#: ui/problem-details-dialog.ui:89 +msgid "Details:" +msgstr "Podrobnosti:" -#: ../ui/upgrade_dialog.glade.h:1 +#: ui/upgrade_dialog.glade:60 msgid "Geary update in progress…" msgstr "Probíhá aktualizace aplikace Geary…" diff -Nru geary-0.12.4/po/da.po geary-3.32.0/po/da.po --- geary-0.12.4/po/da.po 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/po/da.po 2019-03-17 13:39:29.000000000 +0000 @@ -1,20 +1,19 @@ # po/geary.pot # PO message string template file for Geary email client -# Copyright 2016 Software Freedom Conservancy Inc. +# Copyright 2018 Software Freedom Conservancy Inc. # This file is distributed under the GNU LGPL, version 2.1. # # Translators: # # Nikolaj64 , 2013, 2014. # Ask Hjorth Larsen , 2016. -# Alan Mortensen , 2016, 2017. +# Alan Mortensen , 2016-19. msgid "" msgstr "" "Project-Id-Version: geary-0.4.1\n" -"Report-Msgid-Bugs-To: https://bugzilla.gnome.org/enter_bug.cgi?" -"product=geary&keywords=I18N+L10N&component=internationalization\n" -"POT-Creation-Date: 2017-10-24 20:18+0000\n" -"PO-Revision-Date: 2017-10-25 18:16+0200\n" +"Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/geary/issues\n" +"POT-Creation-Date: 2019-01-10 06:53+0000\n" +"PO-Revision-Date: 2019-01-24 19:56+0100\n" "Last-Translator: Alan Mortensen \n" "Language-Team: Danish (http://www.transifex.com/projects/p/geary/language/" "da/)\n" @@ -23,29 +22,57 @@ "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Poedit 1.8.7.1\n" +"X-Generator: Poedit 2.0.6\n" + +#: desktop/geary-attach.contract.desktop.in:3 +msgid "Send by email" +msgstr "Send via e-mail" + +#. Translators: Do NOT translate or transliterate this text (this is an icon file name)! +#: desktop/geary-attach.contract.desktop.in:5 +msgid "mail-send" +msgstr "mail-send" + +#: desktop/geary-attach.contract.desktop.in:6 +msgid "Send files using Geary" +msgstr "Send filer med Geary" #. Translators: The application name -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:2 -#: ../desktop/org.gnome.Geary.desktop.in.h:1 -#: ../desktop/geary-autostart.desktop.in.h:1 +#: desktop/geary-autostart.desktop.in:3 +#: desktop/org.gnome.Geary.appdata.xml.in:11 +#: desktop/org.gnome.Geary.desktop.in:3 +#: src/client/accounts/accounts-editor-servers-pane.vala:539 msgid "Geary" msgstr "Geary" -#. Translators: The development team's name -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:4 -msgid "Geary Development Team" -msgstr "Gearys udviklerhold" +#: desktop/geary-autostart.desktop.in:4 desktop/org.gnome.Geary.desktop.in:4 +msgid "Email" +msgstr "E-mail" #. Translators: The application's summary / tagline -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:6 -#: ../desktop/org.gnome.Geary.desktop.in.h:4 -#: ../desktop/geary-autostart.desktop.in.h:4 -#: ../src/client/application/geary-application.vala:21 +#: desktop/geary-autostart.desktop.in:5 +#: desktop/org.gnome.Geary.appdata.xml.in:15 +#: desktop/org.gnome.Geary.desktop.in:5 +#: src/client/application/geary-application.vala:21 msgid "Send and receive email" msgstr "Send og modtag e-mail" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:7 +#. Translators: These are desktop search terms. Do not translate semicolons, end line with a semicolon. +#: desktop/geary-autostart.desktop.in:7 +msgid "Email;E-mail;Mail;" +msgstr "Email;E-mail;Mail;Post;E-brev;" + +#. Translators: Do NOT translate or transliterate this text (this is an icon file name)! +#: desktop/geary-autostart.desktop.in:9 desktop/org.gnome.Geary.desktop.in:9 +msgid "org.gnome.Geary" +msgstr "org.gnome.Geary" + +#. Translators: The development team's name +#: desktop/org.gnome.Geary.appdata.xml.in:13 +msgid "Geary Development Team" +msgstr "Gearys udviklerhold" + +#: desktop/org.gnome.Geary.appdata.xml.in:17 msgid "" "Geary is an email application built around conversations, for the GNOME 3 " "desktop. It allows you to read, find and send email with a straightforward, " @@ -55,227 +82,705 @@ "omkring samtaler. Det lader dig læse, finde og sende e-mail via en enkel, " "moderne brugerflade." -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:8 +#: desktop/org.gnome.Geary.appdata.xml.in:22 msgid "" "Conversations allow you to read a complete discussion without having to find " "and click from message to message." msgstr "" "Samtaler lader dig læse en hel samtale uden at skulle finde hver enkelt " -"meddelelse frem én ad gangen." +"besked frem én ad gangen." -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:9 +#: desktop/org.gnome.Geary.appdata.xml.in:26 msgid "Geary’s features include:" msgstr "Geary tilbyder:" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:10 +#: desktop/org.gnome.Geary.appdata.xml.in:28 msgid "Quick email account setup" msgstr "Hurtig opsætning af e-mailkonto" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:11 +#: desktop/org.gnome.Geary.appdata.xml.in:29 msgid "Shows related messages together in conversations" -msgstr "Viser relaterede meddelelser sammen i samtaler" +msgstr "Viser relaterede beskeder sammen i samtaler" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:12 +#: desktop/org.gnome.Geary.appdata.xml.in:30 msgid "Fast, full text and keyword search" msgstr "Hurtig fuldtekst- og nøgleordssøgning" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:13 +#: desktop/org.gnome.Geary.appdata.xml.in:31 msgid "Full-featured HTML and plain text message composer" -msgstr "Mange funktioner til redigering af HTML-meddelelser samt ren tekst" +msgstr "Mange funktioner til redigering af HTML-beskeder samt ren tekst" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:14 +#: desktop/org.gnome.Geary.appdata.xml.in:32 msgid "Desktop notification of new mail" msgstr "Skrivebordspåmindelser om ny e-mail" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:15 +#: desktop/org.gnome.Geary.appdata.xml.in:33 msgid "Compatible with GMail, Yahoo! Mail, Outlook.com and other IMAP servers" msgstr "Kompatibel med GMail, Yahoo! Mail, Outlook.com og andre IMAP-servere" #. Translators: A screenshot description. -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:17 +#: desktop/org.gnome.Geary.appdata.xml.in:47 msgid "Geary displaying a conversation" msgstr "Geary som en samtale ser ud" #. Translators: A screenshot description. -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:19 +#: desktop/org.gnome.Geary.appdata.xml.in:52 msgid "Geary showing the rich text composer" msgstr "Geary som redigering af formateret tekst ser ud" -#: ../desktop/org.gnome.Geary.desktop.in.h:2 -#: ../desktop/geary-autostart.desktop.in.h:2 -msgid "Email" -msgstr "E-mail" - -#: ../desktop/org.gnome.Geary.desktop.in.h:3 -msgid "Geary Email" -msgstr "Geary E-mail" - #. Translators: These are desktop search terms. Do not translate semicolons, end line with a semicolon. -#: ../desktop/org.gnome.Geary.desktop.in.h:6 +#: desktop/org.gnome.Geary.desktop.in:7 msgid "Mail;E-mail;IMAP;GMail;Yahoo;Hotmail;Outlook;" msgstr "" "Mail;E-mail;Email;Post;E-post;Brev;E-brev;IMAP;GMail;Yahoo;Hotmail;Outlook;" -#: ../desktop/org.gnome.Geary.desktop.in.h:7 ../ui/gtk/menus.ui.h:1 +#: desktop/org.gnome.Geary.desktop.in:21 ui/gtk/menus.ui:7 msgid "Compose Message" msgstr "Skriv besked" -#: ../desktop/geary-autostart.desktop.in.h:3 -msgid "Geary Mail" -msgstr "Geary mail" - -#: ../desktop/geary-autostart.desktop.in.h:5 -msgid "Email;E-mail;Mail;" -msgstr "Email;E-mail;Mail;Post;E-brev;" - -#: ../desktop/geary-attach.contract.in.h:1 -msgid "Send by email" -msgstr "Send via e-mail" +#: desktop/org.gnome.Geary.gschema.xml:8 +msgid "Maximize window" +msgstr "Maksimér vindue" + +#: desktop/org.gnome.Geary.gschema.xml:9 +msgid "True if the application window is maximized, false otherwise." +msgstr "Sand (true) hvis programvinduet er maksimeret og ellers falsk (false)." + +#: desktop/org.gnome.Geary.gschema.xml:14 +msgid "Width of window" +msgstr "Vinduesbredde" + +#: desktop/org.gnome.Geary.gschema.xml:15 +msgid "The last recorded width of the application window." +msgstr "Den sidste registrerede bredde af programvinduet." + +#: desktop/org.gnome.Geary.gschema.xml:20 +msgid "Height of window" +msgstr "Vindueshøjde" + +#: desktop/org.gnome.Geary.gschema.xml:21 +msgid "The last recorded height of the application window." +msgstr "Den sidste registrerede højde af programvinduet." + +#: desktop/org.gnome.Geary.gschema.xml:26 +msgid "Position of folder list pane" +msgstr "Placering af ruden med mappeoversigten" + +# Paned betyder sprosset (men hvorfor er det med stort?) - i denne sammenhæng måske rudeopdelt. Men hvad betyder grabber her? +#: desktop/org.gnome.Geary.gschema.xml:27 +msgid "Position of the folder list Paned grabber." +msgstr "Placering af mappelisten i rudeopdelt visning." + +#: desktop/org.gnome.Geary.gschema.xml:32 +msgid "Position of folder list pane when horizontal" +msgstr "Placering af den vandrette rude med mappeoversigten" + +#: desktop/org.gnome.Geary.gschema.xml:33 +msgid "" +"Position of the folder list Paned grabber in the horizontal orientation." +msgstr "Placering af mappelisten i rudeopdelt visning i vandret orientering." + +#: desktop/org.gnome.Geary.gschema.xml:38 +msgid "Position of folder list pane when vertical" +msgstr "Placering af den lodrette rude med mappeoversigten" + +#: desktop/org.gnome.Geary.gschema.xml:39 +msgid "Position of the folder list Paned grabber in the vertical orientation." +msgstr "Placering af mappelisten i rudeopdelt visning i lodret orientering." + +#: desktop/org.gnome.Geary.gschema.xml:44 +msgid "Orientation of the folder list pane" +msgstr "Orientering af ruden med mappeoversigten" + +#: desktop/org.gnome.Geary.gschema.xml:45 +msgid "True if the folder list Paned is in the horizontal orientation." +msgstr "" +"Sand (true) hvis mappelisten i rudeopdelt visning er i vandret orientering." + +#: desktop/org.gnome.Geary.gschema.xml:50 +msgid "Position of message list pane" +msgstr "Placering af ruden med beskedoversigten" + +#: desktop/org.gnome.Geary.gschema.xml:51 +msgid "Position of the message list Paned grabber." +msgstr "Placering af beskedlisten i rudeopdelt visning." + +#: desktop/org.gnome.Geary.gschema.xml:56 +msgid "Autoselect next message" +msgstr "Vælg automatisk næste besked" + +#: desktop/org.gnome.Geary.gschema.xml:57 +msgid "True if we should autoselect the next available conversation." +msgstr "" +"Sand (true) hvis den næste tilgængelige samtale skal vælges automatisk." + +#: desktop/org.gnome.Geary.gschema.xml:62 +msgid "Display message previews" +msgstr "Forhåndsvis beskeder" + +#: desktop/org.gnome.Geary.gschema.xml:63 +msgid "True if we should display a short preview of each message." +msgstr "" +"Sand (true) hvis der skal vises en kort forhåndsvisning af hver besked." + +#: desktop/org.gnome.Geary.gschema.xml:68 +msgid "Languages that shall be used in the spell checker" +msgstr "Sprog der skal bruges til stavekontrol" + +#: desktop/org.gnome.Geary.gschema.xml:69 +msgid "List of the languages to use in the spell checker." +msgstr "Liste med sprog der skal bruges til stavekontrol." + +#: desktop/org.gnome.Geary.gschema.xml:74 +msgid "Languages that are displayed in the spell checker popover" +msgstr "Sprog der vises i pop-overen til stavekontrollen" + +#: desktop/org.gnome.Geary.gschema.xml:75 +msgid "" +"List of languages that are always displayed in the popover of the spell " +"checker." +msgstr "Liste med sprog der altid vises i pop-overen til stavekontrollen" + +#: desktop/org.gnome.Geary.gschema.xml:80 +msgid "Enable notification sounds" +msgstr "Aktivér påmindelseslyde" + +#: desktop/org.gnome.Geary.gschema.xml:81 +msgid "True to play sounds for notifications and sending." +msgstr "Sand (true) for at afspille lyde ved påmindelser og afsending." + +#: desktop/org.gnome.Geary.gschema.xml:86 +msgid "Show notifications for new mail" +msgstr "Vis påmindelse for ny e-mail" + +#: desktop/org.gnome.Geary.gschema.xml:87 +msgid "True to show notification bubbles." +msgstr "Sand (true) for at vise påmindelsesbobler." + +#: desktop/org.gnome.Geary.gschema.xml:92 +msgid "Notify of new mail at startup" +msgstr "Giv besked om ny e-mail ved opstart" + +#: desktop/org.gnome.Geary.gschema.xml:93 +msgid "True to notify of new mail at startup." +msgstr "Sand (true) for at få besked om ny e-mail ved opstart." + +#: desktop/org.gnome.Geary.gschema.xml:98 +msgid "Ask when opening an attachment" +msgstr "Spørg før en vedhæftet fil åbnes" + +#: desktop/org.gnome.Geary.gschema.xml:99 +msgid "True to ask when opening an attachment." +msgstr "Sand (true) for at spørge inden en vedhæftet fil åbnes." + +#: desktop/org.gnome.Geary.gschema.xml:104 +msgid "Whether to compose emails in HTML" +msgstr "Om e-mail skal formateres som HTML" + +#: desktop/org.gnome.Geary.gschema.xml:105 +msgid "True to compose emails in HTML; false for plain text." +msgstr "" +"Sand (true) for at formatere e-mail som HTML; falsk (false) for klartekst." + +#: desktop/org.gnome.Geary.gschema.xml:110 +msgid "Advisory strategy for full-text searching" +msgstr "Vejledningsstrategi for fuldtekstsøgning" + +#: desktop/org.gnome.Geary.gschema.xml:111 +msgid "" +"Acceptable values are “exact”, “conservative”, “aggressive”, and “horizon”." +msgstr "Mulige værdier er “exact”, “conservative”, “aggressive” og “horizon”." + +#: desktop/org.gnome.Geary.gschema.xml:116 +msgid "Zoom of conversation viewer" +msgstr "Zoomniveau på samtalevisningen" + +#: desktop/org.gnome.Geary.gschema.xml:117 +msgid "The zoom to apply on the conservation view." +msgstr "Zoom der skal anvendes på samtalevisningen." + +#: desktop/org.gnome.Geary.gschema.xml:122 +msgid "Size of detached composer window" +msgstr "Størrelse på løsrevet redigeringsvindue" + +#: desktop/org.gnome.Geary.gschema.xml:123 +msgid "The last recorded size of the detached composer window." +msgstr "Den sidste registrerede størrelse på det løsrevede redigeringsvindue." + +#: desktop/org.gnome.Geary.gschema.xml:128 +msgid "Base URL to look up contact avatars" +msgstr "Basis-URL til at slå kontaktavatarer op" + +#: desktop/org.gnome.Geary.gschema.xml:129 +msgid "" +"A Gravatar or Libravatar compatible URL, set to the empty string to disable." +msgstr "" +"En URL som er kompatibel med Gravatar eller Libravatar; lad strengen være " +"tom for at deaktivere." + +#: desktop/org.gnome.Geary.gschema.xml:134 +msgid "Whether we migrated the old settings" +msgstr "Hvorvidt de gamle indstillinger blev overført" + +#: desktop/org.gnome.Geary.gschema.xml:135 +msgid "" +"False to check for the old “org.yorba.geary”-schema and copy its values." +msgstr "" +"Falsk (false) for at tjekke for det gamle “org.yorba.geary”-skema og kopiere " +"dets værdier." + +#. Translators: In-app notification label, when +#. the app had a problem pinning an otherwise +#. untrusted TLS certificate +#: src/client/accounts/accounts-editor.vala:203 +msgid "Failed to store certificate" +msgstr "Kunne ikke gemme certifikatet" + +#. Translators: Label for adding an email account +#. account for a generic IMAP service provider. +#: src/client/accounts/accounts-editor-add-pane.vala:109 +msgid "All others" +msgstr "Alle andre" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:196 +#: src/client/accounts/accounts-editor-servers-pane.vala:300 +msgid "Check your receiving login and password" +msgstr "Tjek indgående login og adgangskode" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:211 +#: src/client/accounts/accounts-editor-servers-pane.vala:313 +msgid "Check your receiving server details" +msgstr "Gennemse detaljer for indgående server" + +#. Translators: In-app notification label +#. There was an SMTP auth error, but IMAP already +#. succeeded, so the user probably needs to +#. specify custom creds here +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:233 +#: src/client/accounts/accounts-editor-servers-pane.vala:334 +msgid "Check your sending login and password" +msgstr "Tjek udgående login og adgangskode" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:247 +#: src/client/accounts/accounts-editor-servers-pane.vala:347 +msgid "Check your sending server details" +msgstr "Gennemse detaljer for udgående server" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:262 +msgid "Check your email address and password" +msgstr "Tjek din e-mailadresse og adgangskode" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:273 +msgid "Could not connect, check your network" +msgstr "Kunne ikke forbinde, tjek dit netværk" + +#. Translators: In-app notification label for a +#. generic error creating an account +#: src/client/accounts/accounts-editor-add-pane.vala:286 +msgid "An unexpected problem occurred" +msgstr "Der opstod et uventet problem" + +#. Translators: In-app notification label, the +#. string substitution is a more detailed reason. +#: src/client/accounts/accounts-editor-add-pane.vala:304 +#, c-format +msgid "Account not created: %s" +msgstr "Konto ikke oprettet: %s" + +#. Translators: Label for the person's actual name when adding +#. an account +#: src/client/accounts/accounts-editor-add-pane.vala:552 +msgid "Your name" +msgstr "Dit navn" + +#: src/client/accounts/accounts-editor-add-pane.vala:569 +msgid "Email address" +msgstr "E-mailadresse" + +#. Translators: Placeholder for the default sender address +#. when adding an account +#. Translators: This is used as a placeholder for the +#. address part of an email address when editing a user's +#. sender address preferences for an account. +#: src/client/accounts/accounts-editor-add-pane.vala:572 +#: src/client/accounts/accounts-editor-edit-pane.vala:469 +msgid "person@example.com" +msgstr "person@eksempel.dk" + +#. Translators: Label for an IMAP/SMTP service login/user name +#. when adding an account +#. Translators: Label for the user's login name for an +#. IMAP, SMTP, etc service +#: src/client/accounts/accounts-editor-add-pane.vala:586 +#: src/client/accounts/accounts-editor-servers-pane.vala:809 +msgid "Login name" +msgstr "Loginnavn" + +#. Translators: Label for the user's password for an IMAP, +#. SMTP, etc service +#: src/client/accounts/accounts-editor-add-pane.vala:600 +#: src/client/accounts/accounts-editor-servers-pane.vala:928 +#: ui/password-dialog.glade:108 +msgid "Password" +msgstr "Adgangskode" -#: ../desktop/geary-attach.contract.in.h:2 -msgid "Send files using Geary" -msgstr "Send filer med Geary" +#. Translators: Label for the IMAP server hostname when +#. adding an account. +#. Translators: This label describes the host name or IP +#. address and port used by an account's IMAP service. +#: src/client/accounts/accounts-editor-add-pane.vala:622 +#: src/client/accounts/accounts-editor-servers-pane.vala:656 +msgid "IMAP server" +msgstr "IMAP-server" + +#. Translators: Placeholder for the IMAP server hostname +#. when adding an account. +#: src/client/accounts/accounts-editor-add-pane.vala:625 +msgid "imap.example.com" +msgstr "imap.eksempel.dk" -#: ../src/client/accounts/account-dialog-add-edit-pane.vala:51 -#: ../src/client/components/stock.vala:31 -#: ../ui/conversation-email-menus.ui.h:10 -msgid "_Save" -msgstr "_Gem" +#. Translators: Label for the SMTP server hostname when +#. adding an account. +#. Translators: This label describes the host name or IP +#. address and port used by an account's SMTP service. +#: src/client/accounts/accounts-editor-add-pane.vala:631 +#: src/client/accounts/accounts-editor-servers-pane.vala:662 +#| msgid "SMTP username" +msgid "SMTP server" +msgstr "SMTP-server" + +#. Translators: Placeholder for the SMTP server hostname +#. when adding an account. +#: src/client/accounts/accounts-editor-add-pane.vala:634 +msgid "smtp.example.com" +msgstr "smtp.eksempel.dk" -#: ../src/client/accounts/account-dialog-add-edit-pane.vala:51 -#: ../src/client/components/stock.vala:22 -msgid "_Add" -msgstr "Til_føj" +#. Translators: Label in the account editor for the user's +#. custom name for an account. +#: src/client/accounts/accounts-editor-edit-pane.vala:278 +#: ui/accounts_editor_remove_pane.ui:123 +msgid "Account name" +msgstr "Kontonavn" + +#. Translators: Tooltip used to undo changing +#. the name of an account. The string +#. substitution is the old name of the +#. account. +#: src/client/accounts/accounts-editor-edit-pane.vala:312 +#, c-format +msgid "Change account name back to “%s”" +msgstr "Skift kontonavnet tilbage til “%s”" + +#. Translators: Tooltip for adding a new email sender/from +#. address's address to an account +#: src/client/accounts/accounts-editor-edit-pane.vala:336 +msgid "Add a new sender email address" +msgstr "Tilføj en ny e-mailadresse for afsender" + +#. Translators: Label used to indicate the user has +#. provided no display name for one of their sender +#. email addresses in their account settings. +#: src/client/accounts/accounts-editor-edit-pane.vala:417 +msgid "Name not set" +msgstr "Navn ikke angivet" + +#. Translators: This is used as a placeholder for the +#. display name for an email address when editing a user's +#. sender address preferences for an account. +#: src/client/accounts/accounts-editor-edit-pane.vala:456 +msgid "Sender Name" +msgstr "Navn på afsender" + +#: src/client/accounts/accounts-editor-edit-pane.vala:479 +msgid "Remove" +msgstr "Fjern" + +#. Translators: Label used for the display name part of an +#. email address when editing a user's sender address +#. preferences for an account. +#: src/client/accounts/accounts-editor-edit-pane.vala:494 +msgid "Sender name:" +msgstr "Navn på afsender:" + +#. Translators: Label used for the address part of an +#. email address when editing a user's sender address +#. preferences for an account. +#: src/client/accounts/accounts-editor-edit-pane.vala:501 +msgid "Email address:" +msgstr "E-mailadresse:" -#. reset/clear widgets -#: ../src/client/accounts/account-dialog-edit-alternate-emails-pane.vala:120 +#. Translators: Label used as the undo tooltip after adding an +#. new sender email address to an account. The string +#. substitution is the email address added. +#: src/client/accounts/accounts-editor-edit-pane.vala:561 +#, c-format +msgid "Remove “%s”" +msgstr "Fjern “%s”" + +#. Translators: Label used as the undo tooltip after editing a +#. sender address for an account. The string substitution is +#. the email address edited. +#: src/client/accounts/accounts-editor-edit-pane.vala:601 +#, c-format +msgid "Undo changes to “%s”" +msgstr "Fortryd ændringer af “%s”" + +#. Translators: Label used as the undo tooltip after removing +#. a sender address from an account. The string substitution +#. is the email address edited. +#: src/client/accounts/accounts-editor-edit-pane.vala:690 +#, c-format +msgid "Add “%s” back" +msgstr "Tilføj “%s” igen" + +#. Translators: Label used as the undo tooltip after removing +#. a sender address from an account. The string substitution +#. is the email address edited. +#: src/client/accounts/accounts-editor-edit-pane.vala:728 +msgid "Undo signature changes" +msgstr "Fortryd ændringer af signatur" + +#. Translators: This label describes the account +#. preference for the length of time (weeks, months or +#. years) that past email should be downloaded. +#: src/client/accounts/accounts-editor-edit-pane.vala:772 +msgid "Download mail" +msgstr "Hent mail" + +#. Translators: Tooltip for undoing a change +#. to the length of time that past email +#. should be downloaded for an account. The +#. string substitution is the duration, +#. e.g. "1 month back". +#: src/client/accounts/accounts-editor-edit-pane.vala:804 #, c-format -msgid "Additional addresses for %s" -msgstr "Yderligere adresser for %s" +msgid "Change download period back to: %s" +msgstr "Skift downloadperiode tilbage til: %s" -#. Sets min size. -#: ../src/client/accounts/account-dialog.vala:21 -msgid "Accounts" -msgstr "Konti" - -#. Copyright 2016 Software Freedom Conservancy Inc. -#. * -#. * This software is licensed under the GNU Lesser General Public License -#. * (version 2.1 or later). See the COPYING file in this distribution. -#. -#. Page for adding or editing an account. -#. / Placeholder text indicating that the user should type their first name and last name -#: ../src/client/accounts/add-edit-page.vala:10 -msgid "First Last" -msgstr "Fornavn Efternavn" - -#: ../src/client/accounts/add-edit-page.vala:235 -msgid "Welcome to Geary." -msgstr "Velkommen til Geary." - -#: ../src/client/accounts/add-edit-page.vala:235 -msgid "Enter your account information to get started." -msgstr "Indtast kontoinformation for at starte." +#: src/client/accounts/accounts-editor-edit-pane.vala:825 +msgid "Everything" +msgstr "Alle" -#: ../src/client/accounts/add-edit-page.vala:255 +#: src/client/accounts/accounts-editor-edit-pane.vala:829 msgid "2 weeks back" -msgstr "2 uger siden" +msgstr "2 uger" -#. IDs are # of days -#: ../src/client/accounts/add-edit-page.vala:256 +#: src/client/accounts/accounts-editor-edit-pane.vala:833 msgid "1 month back" -msgstr "1 måned siden" +msgstr "1 måned" -#: ../src/client/accounts/add-edit-page.vala:257 +#: src/client/accounts/accounts-editor-edit-pane.vala:837 msgid "3 months back" -msgstr "3 måneder siden" +msgstr "3 måneder" -#: ../src/client/accounts/add-edit-page.vala:258 +#: src/client/accounts/accounts-editor-edit-pane.vala:841 msgid "6 months back" -msgstr "6 måneder siden" +msgstr "6 måneder" -#: ../src/client/accounts/add-edit-page.vala:259 +#: src/client/accounts/accounts-editor-edit-pane.vala:845 msgid "1 year back" -msgstr "1 år siden" +msgstr "1 år" -#: ../src/client/accounts/add-edit-page.vala:260 +#: src/client/accounts/accounts-editor-edit-pane.vala:849 msgid "2 years back" -msgstr "2 år siden" +msgstr "2 år" -#: ../src/client/accounts/add-edit-page.vala:261 +#: src/client/accounts/accounts-editor-edit-pane.vala:853 msgid "4 years back" -msgstr "4 år siden" - -#. Separator -#: ../src/client/accounts/add-edit-page.vala:263 -msgid "Everything" -msgstr "Alt" - -#: ../src/client/accounts/add-edit-page.vala:283 -msgid "Edit" -msgstr "Redigér" - -#: ../src/client/accounts/add-edit-page.vala:285 -msgid "Preview" -msgstr "Forhåndsvisning" - -#: ../src/client/accounts/add-edit-page.vala:751 -msgid "Remem_ber passwords" -msgstr "Hus_k adgangskoder" - -#: ../src/client/accounts/add-edit-page.vala:758 ../ui/login.glade.h:7 -msgid "Remem_ber password" -msgstr "Hu_sk adgangskode" - -#: ../src/client/accounts/add-edit-page.vala:792 -msgid "Unable to validate:\n" -msgstr "Kunne ikke validere:\n" - -#: ../src/client/accounts/add-edit-page.vala:794 -msgid " • Invalid account nickname.\n" -msgstr " • Ugyldigt kaldenavn til bruger.\n" - -#: ../src/client/accounts/add-edit-page.vala:797 -msgid " • Email address already added to Geary.\n" -msgstr " • E-mailadressen er allerede tilføjet til Geary.\n" +msgstr "4 år" -#: ../src/client/accounts/add-edit-page.vala:801 -msgid " • IMAP connection error.\n" -msgstr " • IMAP-forbindelsesfejl.\n" - -#: ../src/client/accounts/add-edit-page.vala:804 -msgid " • IMAP username or password incorrect.\n" -msgstr " • Forkert brugernavn eller adgangskode til IMAP.\n" - -#: ../src/client/accounts/add-edit-page.vala:807 -msgid " • SMTP connection error.\n" -msgstr " • SMTP-forbindelsesfejl.\n" +#: src/client/accounts/accounts-editor-edit-pane.vala:859 +#, c-format +msgid "%d day back" +msgid_plural "%d days back" +msgstr[0] "%d dag" +msgstr[1] "%d dage" + +#: src/client/accounts/accounts-editor-list-pane.vala:243 +msgid "Undo" +msgstr "Fortryd" + +#: src/client/accounts/accounts-editor-list-pane.vala:251 +msgid "Redo" +msgstr "Genskab" + +#: src/client/accounts/accounts-editor-list-pane.vala:345 +#: src/client/accounts/accounts-editor-list-pane.vala:433 +#: src/client/accounts/accounts-editor-row.vala:278 +msgid "Gmail" +msgstr "Gmail" -#: ../src/client/accounts/add-edit-page.vala:810 -msgid " • SMTP username or password incorrect.\n" -msgstr " • Forkert brugernavn eller adgangskode til SMTP.\n" +#: src/client/accounts/accounts-editor-list-pane.vala:349 +#: src/client/accounts/accounts-editor-list-pane.vala:437 +#: src/client/accounts/accounts-editor-row.vala:282 +msgid "Outlook.com" +msgstr "Outlook.com" -#: ../src/client/accounts/add-edit-page.vala:814 -msgid " • Connection error.\n" -msgstr " • Forbindelsesfejl.\n" +#: src/client/accounts/accounts-editor-list-pane.vala:353 +#: src/client/accounts/accounts-editor-list-pane.vala:441 +#: src/client/accounts/accounts-editor-row.vala:286 +msgid "Yahoo" +msgstr "Yahoo" + +#. Translators: Tooltip for accounts that have been +#. loaded but disabled by the user. +#: src/client/accounts/accounts-editor-list-pane.vala:371 +msgid "This account has been disabled" +msgstr "Denne konto er deaktiveret" + +#. Translators: Tooltip for accounts that have been +#. loaded but because of some error are not able to be +#. used. +#: src/client/accounts/accounts-editor-list-pane.vala:380 +msgid "This account has encountered a problem and is unavailable" +msgstr "Denne konto er stødt på et problem og er utilgængelig" + +#. Translators: Label for adding a generic email account +#: src/client/accounts/accounts-editor-list-pane.vala:430 +msgid "Other email providers" +msgstr "Andre e-mailudbydere" + +#. Translators: Notification shown after removing an +#. account. The string substitution is the name of the +#. account. +#: src/client/accounts/accounts-editor-list-pane.vala:547 +#, c-format +msgid "Account “%s” removed" +msgstr "Konto “%s” fjernet" + +#. Translators: Notification shown after removing an account +#. is undone. The string substitution is the name of the +#. account. +#: src/client/accounts/accounts-editor-list-pane.vala:554 +#, c-format +msgid "Account “%s” restored" +msgstr "Konto “%s” genskabt" + +#. Translators: Tooltip for dragging list items +#: src/client/accounts/accounts-editor-row.vala:49 +msgid "Drag to move this item" +msgstr "Træk for at flytte dette element" + +#. Translators: Label describes the service provider +#. hosting the email account, e.g. Gmail, Yahoo, or some +#. other generic IMAP service. +#: src/client/accounts/accounts-editor-row.vala:294 +msgid "Service provider" +msgstr "Serviceudbyder" + +#. Translators: This label describes what form of transport +#. security (TLS, StartTLS, etc) used by an account's IMAP or SMTP +#. service. +#: src/client/accounts/accounts-editor-row.vala:467 +msgid "Connection security" +msgstr "Forbindelsessikkerhed" + +#. Translators: Label used when no auth scheme is used +#. by an account's IMAP or SMTP service. +#: src/client/accounts/accounts-editor-row.vala:478 +#: src/client/accounts/accounts-editor-servers-pane.vala:681 +#: src/client/accounts/accounts-editor-servers-pane.vala:893 +#: src/engine/api/geary-special-folder-type.vala:58 +msgid "None" +msgstr "Ingen" -#: ../src/client/accounts/add-edit-page.vala:818 -msgid " • Username or password incorrect.\n" -msgstr " • Forkert brugernavn eller adgangskode.\n" +#: src/client/accounts/accounts-editor-row.vala:485 +#| msgctxt "shortcut window" +#| msgid "Star" +msgid "StartTLS" +msgstr "StartTLS" + +#: src/client/accounts/accounts-editor-row.vala:492 +#| msgid "SSL/TLS" +msgid "TLS" +msgstr "TLS" + +#. Translators: Label for source of SMTP authentication +#. credentials (none, use IMAP, custom) when adding a new +#. account +#. Button label for retrying when a login error has occurred +#: src/client/accounts/accounts-editor-row.vala:533 ui/main-window.ui:455 +msgid "Login" +msgstr "Login" + +#. Translators: ComboBox value for source of SMTP +#. authentication credentials (none) when adding a new +#. account +#: src/client/accounts/accounts-editor-row.vala:540 +msgid "No login needed" +msgstr "Login ikke nødvendig" + +#. Translators: ComboBox value for source of SMTP +#. authentication credentials (use IMAP) when adding a new +#. account +#: src/client/accounts/accounts-editor-row.vala:548 +msgid "Use same login as receiving" +msgstr "Brug samme login som indgående" + +#. Translators: ComboBox value for source of SMTP +#. authentication credentials (custom) when adding a new +#. account +#: src/client/accounts/accounts-editor-row.vala:556 +msgid "Use a different login" +msgstr "Brug et andet login" + +#. Translators: In-app notification label, the +#. string substitution is a more detailed reason. +#: src/client/accounts/accounts-editor-servers-pane.vala:361 +#, c-format +msgid "Account not updated: %s" +msgstr "Konto ikke opdateret: %s" + +#. Translators: This label describes the program that +#. created the account, e.g. an SSO service like GOA, or +#. locally by Geary. +#: src/client/accounts/accounts-editor-servers-pane.vala:524 +msgid "Account source" +msgstr "Kontokilde" + +#: src/client/accounts/accounts-editor-servers-pane.vala:536 +msgid "GNOME Online Accounts" +msgstr "GNOME Onlinekonti" + +#. Translators: This label describes an account +#. preference. +#: src/client/accounts/accounts-editor-servers-pane.vala:595 +msgid "Save drafts on server" +msgstr "Gem kladder på serveren" + +#. Add a suffix for OAuth2 auth so people know they +#. shouldn't expect to be prompted for a password +#. Translators: Label used when an account's IMAP or +#. SMTP service uses OAuth2. The string replacement is +#. the service's login name. +#: src/client/accounts/accounts-editor-servers-pane.vala:879 +#, c-format +msgid "%s using OAuth2" +msgstr "%s gennem OAuth2" + +#: src/client/accounts/accounts-editor-servers-pane.vala:889 +msgid "Use receiving server login" +msgstr "Brug login til indgående server" -#: ../src/client/application/geary-application.vala:22 +#: src/client/application/geary-application.vala:22 msgid "Copyright 2016 Software Freedom Conservancy Inc." msgstr "Ophavsret 2016 Software Freedom Conservancy Inc." -#: ../src/client/application/geary-application.vala:23 -msgid "Copyright 2016-2017 Geary Development Team." -msgstr "Ophavsret 2016-2017 Gearys udviklerhold." +#: src/client/application/geary-application.vala:23 +msgid "Copyright 2016-2018 Geary Development Team." +msgstr "Ophavsret 2016-2018 Gearys udviklerhold." -#: ../src/client/application/geary-application.vala:25 +#: src/client/application/geary-application.vala:25 msgid "Visit the Geary web site" msgstr "Besøg Gearys hjemmeside" -#: ../src/client/application/geary-application.vala:466 +#: src/client/application/geary-application.vala:414 #, c-format msgid "About %s" msgstr "Om %s" @@ -283,7 +788,7 @@ #. Translators: add your name and email address to receive #. credit in the About dialog For example: Yamada Taro #. -#: ../src/client/application/geary-application.vala:470 +#: src/client/application/geary-application.vala:418 msgid "translator-credits" msgstr "" "Nikolaj Holmquist Pedersen \n" @@ -293,312 +798,107 @@ "Dansk-gruppen \n" "Mere info: http://www.dansk-gruppen.dk" -#: ../src/client/application/geary-args.vala:10 +#: src/client/application/geary-args.vala:10 msgid "Start Geary with hidden main window" msgstr "Start Geary med skjult hovedvindue" -#: ../src/client/application/geary-args.vala:11 +#: src/client/application/geary-args.vala:11 msgid "Output debugging information" msgstr "Vis fejlretningsinformation" -#: ../src/client/application/geary-args.vala:12 +#: src/client/application/geary-args.vala:12 msgid "Log conversation monitoring" msgstr "Log samtaleovervågning" -#: ../src/client/application/geary-args.vala:13 +#: src/client/application/geary-args.vala:13 msgid "Log network deserialization" msgstr "Log netværksdeserialisering" -#: ../src/client/application/geary-args.vala:14 +#: src/client/application/geary-args.vala:14 msgid "Log network activity" msgstr "Log netværksaktivitet" #. / The IMAP replay queue is how changes on the server are replicated on the client. #. / It could also be called the IMAP events queue. -#: ../src/client/application/geary-args.vala:17 +#: src/client/application/geary-args.vala:17 msgid "Log IMAP replay queue" msgstr "Log køen af IMAP-hændelser" #. / Serialization is how commands and responses are converted into a stream of bytes for #. / network transmission -#: ../src/client/application/geary-args.vala:20 +#: src/client/application/geary-args.vala:20 msgid "Log network serialization" msgstr "Log netværksserialisering" -#: ../src/client/application/geary-args.vala:21 +#: src/client/application/geary-args.vala:21 msgid "Log periodic activity" msgstr "Log periodisk aktivitet" -#: ../src/client/application/geary-args.vala:22 +#: src/client/application/geary-args.vala:22 msgid "Log database queries (generates lots of messages)" -msgstr "Log databaseforespørgsler (generérer mange meddelelser)" +msgstr "Log databaseforespørgsler (genererer mange beskeder)" #. / "Normalization" can also be called "synchronization" -#: ../src/client/application/geary-args.vala:24 +#: src/client/application/geary-args.vala:24 msgid "Log folder normalization" msgstr "Log mappenormalisering" -#: ../src/client/application/geary-args.vala:25 +#: src/client/application/geary-args.vala:25 msgid "Allow inspection of WebView" msgstr "Aktiver undersøgelse af web-visning" -#: ../src/client/application/geary-args.vala:26 +#: src/client/application/geary-args.vala:26 msgid "Revoke all server certificates with TLS warnings" msgstr "Tilbagekald alle servercertifikater med TLS-advarsler" -#: ../src/client/application/geary-args.vala:27 +#: src/client/application/geary-args.vala:27 msgid "Perform a graceful quit" msgstr "Afslut på kontrolleret vis" -#: ../src/client/application/geary-args.vala:28 +#: src/client/application/geary-args.vala:28 msgid "Display program version" msgstr "Vis programversion" #. This gives a command-line hint on how to open new composer windows with mailto: -#: ../src/client/application/geary-args.vala:53 +#: src/client/application/geary-args.vala:53 #, c-format msgid "Use %s to open a new composer window" msgstr "Brug %s for at åbne et nyt redigeringsvindue" -#: ../src/client/application/geary-args.vala:56 +#: src/client/application/geary-args.vala:56 msgid "Please report comments, suggestions and bugs to:" msgstr "Send kommentarer, idéer og fejlrapporter til:" #. i18n: Command line arguments are invalid -#: ../src/client/application/geary-args.vala:63 +#: src/client/application/geary-args.vala:63 #, c-format msgid "Failed to parse command line options: %s\n" msgstr "Kunne ikke genkende indstillingerne på kommandolinjen: %s\n" -#: ../src/client/application/geary-args.vala:74 +#: src/client/application/geary-args.vala:74 #, c-format msgid "Unrecognized command line option “%s”\n" msgstr "Ukendt kommandolinje-indstilling “%s”\n" -#: ../src/client/application/geary-controller.vala:57 -msgid "Delete conversation" -msgstr "Slet samtale" - -#: ../src/client/application/geary-controller.vala:58 -msgid "Delete conversation (Shift+Delete)" -msgstr "Slet samtale (skift+delete)" - -#: ../src/client/application/geary-controller.vala:59 -msgid "Delete conversations (Shift+Delete)" -msgstr "Slet samtaler (skift+delete)" - -#. This refers to the action ("move email to the trash"), not the Trash folder itself -#: ../src/client/application/geary-controller.vala:63 -msgid "Move conversation to Trash (Delete, Backspace)" -msgstr "Flyt samtalen til papirkurven (delete, tilbagetast)" - -#: ../src/client/application/geary-controller.vala:64 -msgid "Move conversations to Trash (Delete, Backspace)" -msgstr "Flyt samtalerne til papirkurven (delete, tilbagetast)" - -#. This refers to the action ("archive an email"), not the Archive folder itself -#: ../src/client/application/geary-controller.vala:68 -msgid "_Archive" -msgstr "_Arkivér" - -#: ../src/client/application/geary-controller.vala:69 -msgid "Archive conversation (A)" -msgstr "Arkivér samtale (A)" - -#: ../src/client/application/geary-controller.vala:70 -msgid "Archive conversations (A)" -msgstr "Arkivér samtaler (A)" - -#: ../src/client/application/geary-controller.vala:73 -msgid "Mark as S_pam" -msgstr "Markér som s_pam" - -#: ../src/client/application/geary-controller.vala:74 -msgid "Mark as not S_pam" -msgstr "Marker som ikke s_pam" - -#: ../src/client/application/geary-controller.vala:76 -#: ../src/client/application/geary-controller.vala:448 -msgid "Mark conversation" -msgstr "Markér samtale" - -#: ../src/client/application/geary-controller.vala:77 -msgid "Mark conversations" -msgstr "Markér samtaler" - -#: ../src/client/application/geary-controller.vala:78 -msgid "Add label to conversation" -msgstr "Tilføj label til samtale" - -#: ../src/client/application/geary-controller.vala:79 -msgid "Add label to conversations" -msgstr "Tilføj label til samtaler" - -#: ../src/client/application/geary-controller.vala:80 -#: ../src/client/application/geary-controller.vala:487 -msgid "Move conversation" -msgstr "Flyt samtale" - -#: ../src/client/application/geary-controller.vala:81 -msgid "Move conversations" -msgstr "Flyt samtaler" - -#: ../src/client/application/geary-controller.vala:450 -msgid "_Mark as…" -msgstr "_Markér som …" - -#: ../src/client/application/geary-controller.vala:456 -msgid "Mark as _Read" -msgstr "Markér som _læst" - -#: ../src/client/application/geary-controller.vala:462 -msgid "Mark as _Unread" -msgstr "Markér som _ulæst" - -#: ../src/client/application/geary-controller.vala:468 -msgid "_Star" -msgstr "St_jern" - -#: ../src/client/application/geary-controller.vala:473 -msgid "U_nstar" -msgstr "Stj_ern ikke" - -#: ../src/client/application/geary-controller.vala:483 -msgid "Add label" -msgstr "Tilføj etiket" - -#: ../src/client/application/geary-controller.vala:484 -msgid "_Label" -msgstr "_Etiket" - -#: ../src/client/application/geary-controller.vala:488 -msgid "_Move" -msgstr "_Flyt" - -#: ../src/client/application/geary-controller.vala:492 -msgid "Compose new message (Ctrl+N, N)" -msgstr "Skriv ny besked (Ctrl+N, N)" - -#: ../src/client/application/geary-controller.vala:496 -#: ../ui/conversation-email-menus.ui.h:1 -msgid "_Reply" -msgstr "_Svar" - -#: ../src/client/application/geary-controller.vala:497 -msgid "Reply (Ctrl+R, R)" -msgstr "Svar (Ctrl+R, R)" - -#: ../src/client/application/geary-controller.vala:501 -msgid "R_eply All" -msgstr "S_var til alle" - -#: ../src/client/application/geary-controller.vala:502 -msgid "Reply all (Ctrl+Shift+R, Shift+R)" -msgstr "Svar til alle (Ctrl+Skift+R, Skift+R)" +#. Translators: File name used in save chooser when saving +#. attachments that do not otherwise have a name. +#: src/client/application/geary-controller.vala:61 +msgid "Untitled" +msgstr "Unavngivet" -#: ../src/client/application/geary-controller.vala:507 -#: ../ui/conversation-email-menus.ui.h:3 -msgid "_Forward" -msgstr "_Videresend" - -#: ../src/client/application/geary-controller.vala:508 -msgid "Forward (Ctrl+L, F)" -msgstr "Videresend (Ctrl+L, F)" - -#: ../src/client/application/geary-controller.vala:538 -msgid "Empty _Spam…" -msgstr "Tøm _spam …" - -#: ../src/client/application/geary-controller.vala:542 -msgid "Empty _Trash…" -msgstr "Tøm _papirkurv …" - -#. No callback is connected, since we bind the toggle button to the search bar visibility -#: ../src/client/application/geary-controller.vala:574 -msgid "Toggle search bar" -msgstr "Slå søgebjælke til/fra" - -#. No callback is connected, since we bind the toggle button to the find bar visibility -#: ../src/client/application/geary-controller.vala:579 -msgid "Toggle find bar" -msgstr "Slå find-bjælke til/fra" - -#: ../src/client/application/geary-controller.vala:757 -msgid "Unable to store server trust exception" -msgstr "Kan ikke gemme undtagelse for tillid til server" - -#: ../src/client/application/geary-controller.vala:992 -msgid "Your settings are insecure" -msgstr "Dine indstillinger er usikre" - -#: ../src/client/application/geary-controller.vala:993 -msgid "" -"Your IMAP and/or SMTP settings do not specify SSL or TLS. This means your " -"username and password could be read by another person on the network. Are " -"you sure you want to do this?" -msgstr "" -"Dine IMAP- og/eller SMTP-indstillinger anvender ikke SSL eller TLS. Dette " -"betyder, at dit brugernavn og din adgangskode kan læses af andre på " -"netværket. Er du sikker på, at du vil gære dette?" - -#: ../src/client/application/geary-controller.vala:994 -msgid "Co_ntinue" -msgstr "F_ortsæt" - -#: ../src/client/application/geary-controller.vala:1041 -msgid "Error connecting to the server" -msgstr "Fejl i forsøget på at forbinde til serveren" - -#: ../src/client/application/geary-controller.vala:1042 -msgid "" -"Geary encountered an error while connecting to the server. Please try again " -"in a few moments." -msgstr "" -"Geary stødte på en fejl i forsøget på at forbinde til serveren. Prøv igen om " -"lidt." - -#. / Displayed in the space-limited status bar when a message fails to be sent due to error. -#: ../src/client/application/geary-controller.vala:1079 -#: ../src/client/components/status-bar.vala:29 -msgid "Error sending email" -msgstr "Der opstod en fejl ved afsendelse af e-mail" - -#: ../src/client/application/geary-controller.vala:1080 -msgid "" -"Geary encountered an error sending an email. If the problem persists, " -"please manually delete the email from your Outbox folder." -msgstr "" -"Geary stødte ind i en fejl under afsendelse af en e-mail. Hvis dette problem " -"fortsætter, bør du manuelt slette beskeden fra din “Udbakke”-mappe." - -#. Displayed in the space-limited status bar when a message fails to be uploaded -#. to Sent Mail after being sent. -#: ../src/client/application/geary-controller.vala:1084 -#: ../src/client/components/status-bar.vala:33 -msgid "Error saving sent mail" -msgstr "Der opstod en fejl ved lagring af den afsendte e-mail" - -#: ../src/client/application/geary-controller.vala:1085 -msgid "" -"Geary encountered an error saving a sent message to Sent Mail. The message " -"will stay in your Outbox folder until you delete it." -msgstr "" -"Der opstod en fejl, da Geary forsøgte på at gemme en sendt besked til mappen " -"med afsendt e-mail. Beskeden vil blive i din Udbakke, indtil du sletter den." - -#: ../src/client/application/geary-controller.vala:1154 +#: src/client/application/geary-controller.vala:899 msgid "Labels" msgstr "Etiketter" #. give the user two options: reset the Account local store, or exit Geary. A third #. could be done to leave the Account in an unopened state, but we don't currently #. have provisions for that. -#: ../src/client/application/geary-controller.vala:1166 +#: src/client/application/geary-controller.vala:911 #, c-format msgid "Unable to open the database for %s" msgstr "Kunne ikke åbne databasen for %s" -#: ../src/client/application/geary-controller.vala:1167 +#: src/client/application/geary-controller.vala:912 #, c-format msgid "" "There was an error opening the local mail database for this account. This is " @@ -622,20 +922,20 @@ "Genopbygning af databasen vil slette al lokal e-mail og dets vedhæftede " "filer. Mail'en på serveren vil ikke blive rørt." -#: ../src/client/application/geary-controller.vala:1169 +#: src/client/application/geary-controller.vala:914 msgid "_Rebuild" msgstr "_Genopbyg" -#: ../src/client/application/geary-controller.vala:1169 +#: src/client/application/geary-controller.vala:914 msgid "E_xit" msgstr "_Afslut" -#: ../src/client/application/geary-controller.vala:1178 +#: src/client/application/geary-controller.vala:923 #, c-format msgid "Unable to rebuild database for “%s”" msgstr "Kunne ikke genopbygge databasen for “%s”" -#: ../src/client/application/geary-controller.vala:1179 +#: src/client/application/geary-controller.vala:924 #, c-format msgid "" "Error during rebuild:\n" @@ -646,68 +946,15 @@ "\n" "%s" -#. some other problem opening the account ... as with other flow path, can't run -#. Geary today with an account in unopened state, so have to exit -#: ../src/client/application/geary-controller.vala:1201 -#: ../src/client/application/geary-controller.vala:1211 -#: ../src/client/application/geary-controller.vala:1222 -#, c-format -msgid "Unable to open local mailbox for %s" -msgstr "Kunne ikke åbne den lokale postkasse for %s" - -#: ../src/client/application/geary-controller.vala:1202 -#, c-format -msgid "" -"There was an error opening the local mail database for this account. This is " -"possibly due to a file permissions problem.\n" -"\n" -"Please check that you have read/write permissions for all files in this " -"directory:\n" -"\n" -"%s" -msgstr "" -"Der opstod en fejl under åbningen af den lokale e-maildatabase til denne " -"konto. Dette sker sikkert på grund af problemer med filrettigheder.\n" -"\n" -"Tjek at du har læse- og skriverettigheder for alle filer i denne mappe.\n" -"\n" -"%s" - -#: ../src/client/application/geary-controller.vala:1212 -msgid "" -"The version number of the local mail database is formatted for a newer " -"version of Geary. Unfortunately, the database cannot be “rolled back” to " -"work with this version of Geary.\n" -"\n" -"Please install the latest version of Geary and try again." -msgstr "" -"Den lokale e-maildatabases versionsnummer er til en nyere version af Geary. " -"Desværre kan databasen ikke blive “rullet tilbage”, så den virker med denne " -"version of Geary.\n" -"\n" -"Installér venligst den nyeste version af Geary og prøv igen." - -#: ../src/client/application/geary-controller.vala:1223 -msgid "" -"There was an error opening the local account. This is probably due to " -"connectivity issues.\n" -"\n" -"Please check your network connection and restart Geary." -msgstr "" -"Der opstod en felj under åbning af den lokele konto. Dette sker sikkert på " -"grund af forbindelsesproblemer.\n" -"\n" -"Tjek, at din internetforbindelse virker og genstart derefter Geary." - -#: ../src/client/application/geary-controller.vala:2008 +#: src/client/application/geary-controller.vala:1738 msgid "Undo move (Ctrl+Z)" msgstr "Fortryd flyt (Ctrl+Z)" -#: ../src/client/application/geary-controller.vala:2018 +#: src/client/application/geary-controller.vala:1748 msgid "Are you sure you want to open these attachments?" msgstr "Er du sikker på, at du vil åbne disse vedhæftede filer?" -#: ../src/client/application/geary-controller.vala:2019 +#: src/client/application/geary-controller.vala:1749 msgid "" "Attachments may cause damage to your system if opened. Only open files from " "trusted sources." @@ -715,16 +962,22 @@ "Vedhæftede filer kan skade dit system, når de åbnes. Åbn kun filer fra " "folk, du stoler på." -#: ../src/client/application/geary-controller.vala:2020 +#: src/client/application/geary-controller.vala:1750 msgid "Don’t _ask me again" msgstr "S_pørg mig ikke igen" -#: ../src/client/application/geary-controller.vala:2130 +#. Translators: Dialog primary label when prompting to +#. overwrite a file. The string substitution is the file'sx +#. name. +#: src/client/application/geary-controller.vala:1879 #, c-format msgid "A file named “%s” already exists. Do you want to replace it?" msgstr "Filen “%s” findes allerede. Vil du erstatte den?" -#: ../src/client/application/geary-controller.vala:2132 +#. Translators: Dialog secondary label when prompting to +#. overwrite a file. The string substitution is the parent +#. folder's name. +#: src/client/application/geary-controller.vala:1886 #, c-format msgid "" "The file already exists in “%s”. Replacing it will overwrite its contents." @@ -732,183 +985,484 @@ "Filen findes allerede i “%s”. Hvis du erstatter den, overskrives dens " "indhold." -#: ../src/client/application/geary-controller.vala:2135 +#: src/client/application/geary-controller.vala:1890 msgid "_Replace" msgstr "_Erstat" -#. Find out what to do with the inline composers. -#. TODO: Remove this in favor of automatically saving drafts -#: ../src/client/application/geary-controller.vala:2383 -msgid "Close open draft messages?" -msgstr "Luk åbne kladder?" +#: src/client/application/geary-controller.vala:2160 +msgid "Close the draft message?" +msgid_plural "Close all draft messages?" +msgstr[0] "Luk kladden?" +msgstr[1] "Luk alle kladder?" -#: ../src/client/application/geary-controller.vala:2505 +#: src/client/application/geary-controller.vala:2286 #, c-format msgid "Empty all email from your %s folder?" msgstr "Tøm al e-mail fra din %s-mappe?" -#: ../src/client/application/geary-controller.vala:2506 +#: src/client/application/geary-controller.vala:2287 msgid "This removes the email from Geary and your email server." msgstr "Dette fjerner e-mail fra Geary samt din e-mailserver." -#: ../src/client/application/geary-controller.vala:2507 +#: src/client/application/geary-controller.vala:2288 msgid "This cannot be undone." msgstr "Dette kan ikke fortrydes." -#: ../src/client/application/geary-controller.vala:2508 +#: src/client/application/geary-controller.vala:2289 #, c-format msgid "Empty %s" msgstr "Tøm %s" -#: ../src/client/application/geary-controller.vala:2525 +#: src/client/application/geary-controller.vala:2306 #, c-format msgid "Error emptying %s" msgstr "Fejl ved tømning af %s" -#: ../src/client/application/geary-controller.vala:2555 +#: src/client/application/geary-controller.vala:2338 msgid "Do you want to permanently delete this message?" msgid_plural "Do you want to permanently delete these messages?" msgstr[0] "Ønsker du at slette denne besked permanent?" msgstr[1] "Ønsker du at slette disse beskeder permanent?" -#: ../src/client/application/geary-controller.vala:2557 +#: src/client/application/geary-controller.vala:2340 msgid "Delete" msgstr "Slet" -#: ../src/client/application/geary-controller.vala:2589 -msgid "Undo archive (Ctrl+Z)" -msgstr "Fortryd arkivér (Ctrl+Z)" - -#: ../src/client/application/geary-controller.vala:2604 +#: src/client/application/geary-controller.vala:2354 msgid "Undo trash (Ctrl+Z)" msgstr "Fortryd smid i papirkurv (Ctrl+Z)" -#: ../src/client/application/geary-controller.vala:2658 +#: src/client/application/geary-controller.vala:2404 +msgid "Undo archive (Ctrl+Z)" +msgstr "Fortryd arkivér (Ctrl+Z)" + +#: src/client/application/geary-controller.vala:2449 msgid "Undo (Ctrl+Z)" msgstr "Fortryd (Ctrl+Z)" -#: ../src/client/application/geary-controller.vala:2789 +#. Translators: The label for an in-app notification. The +#. string substitution is a list of recipients of the email. +#: src/client/application/geary-controller.vala:2526 +#, c-format +msgid "Successfully sent mail to %s." +msgstr "Sendte mail til %s." + +#: src/client/application/geary-controller.vala:2607 msgid "Failed to open default text editor." msgstr "Kunne ikke åbne standardprogrammet til tekstredigéring." -#: ../src/client/components/main-window.vala:389 -#, c-format -msgid "%s (%d)" -msgstr "%s (%d)" +#. Translators: Tooltip used when an entry requires a valid +#. email address to be entered, but one is not provided. +#: src/client/components/components-validator.vala:341 +msgid "An email address is required" +msgstr "En e-mailadresse er påkrævet" + +#. Translators: Tooltip used when an entry requires a valid +#. email address to be entered, but the address is invalid. +#: src/client/components/components-validator.vala:345 +msgid "Not a valid email address" +msgstr "Ikke en gyldig e-mailadresse" + +#. Translators: Tooltip used when an entry requires a valid, +#. resolvable server name to be entered, but one is not +#. provided. +#: src/client/components/components-validator.vala:391 +msgid "A server name is required" +msgstr "Et servernavn er påkrævet" + +#. Translators: Tooltip used when an entry requires a valid +#. server name to be entered, but it was unable to be +#. looked-up in the DNS. +#: src/client/components/components-validator.vala:396 +msgid "Could not look up server name" +msgstr "Kunne ikke slå servernavnet op" + +#. Tooltips +#: src/client/components/main-toolbar.vala:69 +msgid "Delete conversation (Shift+Delete)" +msgstr "Slet samtale (skift+delete)" + +#: src/client/components/main-toolbar.vala:70 +msgid "Delete conversations (Shift+Delete)" +msgstr "Slet samtaler (skift+delete)" + +#: src/client/components/main-toolbar.vala:71 +msgid "Move conversation to Trash (Delete, Backspace)" +msgstr "Flyt samtalen til papirkurven (delete, tilbagetast)" + +#: src/client/components/main-toolbar.vala:72 +msgid "Move conversations to Trash (Delete, Backspace)" +msgstr "Flyt samtalerne til papirkurven (delete, tilbagetast)" + +#: src/client/components/main-toolbar.vala:73 +msgid "Archive conversation (A)" +msgstr "Arkivér samtale (A)" + +#: src/client/components/main-toolbar.vala:74 +msgid "Archive conversations (A)" +msgstr "Arkivér samtaler (A)" + +#: src/client/components/main-toolbar.vala:75 +msgid "Mark conversation" +msgstr "Markér samtale" + +#: src/client/components/main-toolbar.vala:76 +msgid "Mark conversations" +msgstr "Markér samtaler" + +#: src/client/components/main-toolbar.vala:77 +msgid "Add label to conversation" +msgstr "Tilføj label til samtale" + +#: src/client/components/main-toolbar.vala:78 +msgid "Add label to conversations" +msgstr "Tilføj label til samtaler" + +#: src/client/components/main-toolbar.vala:79 +msgid "Move conversation" +msgstr "Flyt samtale" + +#: src/client/components/main-toolbar.vala:80 +msgid "Move conversations" +msgstr "Flyt samtaler" + +#: src/client/components/main-window.vala:497 +#, c-format +msgid "%s (%d)" +msgstr "%s (%d)" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:47 +#, c-format +msgid "Problem connecting to incoming server for %s" +msgstr "Problem med at forbinde til indgående server for %s" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:49 +#: src/client/components/main-window-info-bar.vala:57 +#, c-format +msgid "" +"Could not connect to %s, check your Internet access and the server name and " +"try again" +msgstr "" +"Kunne ikke forbinde til %s. Tjek din internetadgang og servernavnet og prøv " +"igen" + +# Eller skal det være: Prøver nu at forbinde igen +#. Button tooltip for retrying an account problem +#: src/client/components/main-window-info-bar.vala:50 +#: src/client/components/main-window-info-bar.vala:59 ui/main-window.ui:265 +msgid "Retry connecting now" +msgstr "Prøv igen at forbinde nu" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:55 +#, c-format +msgid "Problem connecting to outgoing server for %s" +msgstr "Problem med at forbinde til udgående server for %s" + +#: src/client/components/main-window-info-bar.vala:58 +msgid "Try reconnecting now" +msgstr "Prøv at forbinde igen nu" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:64 +#, c-format +msgid "Problem with connection to incoming server for %s" +msgstr "Problem med forbindelsen til indgående server for %s" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:66 +#: src/client/components/main-window-info-bar.vala:74 +#, c-format +msgid "Network error talking to %s, check your Internet access and try again" +msgstr "" +"Netværksfejl ved kommunikation med %s. Tjek din internetadgang og prøv igen" + +#: src/client/components/main-window-info-bar.vala:67 +#: src/client/components/main-window-info-bar.vala:75 +#: src/client/components/main-window-info-bar.vala:83 +#: src/client/components/main-window-info-bar.vala:91 +#: src/client/components/main-window-info-bar.vala:126 +msgid "Try reconnecting" +msgstr "Prøv at forbinde igen" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:72 +#, c-format +msgid "Problem with connection to outgoing server for %s" +msgstr "Problem med forbindelsen til udgående server for %s" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:80 +#, c-format +msgid "Problem communicating with incoming server for %s" +msgstr "Problem med kommunikationen til indgående server for %s" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:82 +#, c-format +msgid "" +"Geary did not understand a message from %s or vice versa, please file a bug " +"report" +msgstr "" +"Geary forstod ikke en besked fra %s eller omvendt. Indsend venligst en " +"fejlrapport" + +#: src/client/components/main-window-info-bar.vala:87 +msgid "Problem communicating with outgoing mail server" +msgstr "Problem med kommunikationen til udgående mailserver" + +#. Translators: First string substitution is the server +#. name, second is the account name +#: src/client/components/main-window-info-bar.vala:90 +#, c-format +msgid "" +"Could not communicate with %s for %s, check the server name and try again in " +"a moment" +msgstr "" +"Kunne ikke kommunikere med %s for %s. Tjek servernavnet og prøv igen om lidt" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:96 +#, c-format +msgid "Incoming mail server password required for %s" +msgstr "Der kræves en adgangskode for %s til den indgående mailserver" + +#: src/client/components/main-window-info-bar.vala:97 +msgid "Messages cannot be received without the correct password." +msgstr "Beskeder kan ikke modtages uden den rigtige adgangskode." + +#: src/client/components/main-window-info-bar.vala:98 +msgid "Retry receiving email, you will be prompted for a password" +msgstr "Prøv igen at modtage e-mail; du vil blive bedt om en adgangskode" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:103 +#, c-format +msgid "Outgoing mail server password required for %s" +msgstr "Der kræves en adgangskode for %s til den udgående mailserver" + +#: src/client/components/main-window-info-bar.vala:104 +msgid "Messages cannot be sent without the correct password." +msgstr "Beskeder kan ikke sendes uden den rigtige adgangskode." + +#: src/client/components/main-window-info-bar.vala:105 +msgid "Retry sending queued messages, you will be prompted for a password" +msgstr "" +"Prøv igen at sende beskederne i køen; du vil blive bedt om en adgangskode" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:110 +#, c-format +msgid "Incoming mail server security is not trusted for %s" +msgstr "Indgående mailservers sikkerhed er ikke pålidelig for %s" + +# Går ud fra der henvises til sikkerheden i foregående +#: src/client/components/main-window-info-bar.vala:111 +msgid "Messages will not be received until checked." +msgstr "Beskeder vil ikke modtages, indtil den er undersøgt." + +#: src/client/components/main-window-info-bar.vala:112 +#: src/client/components/main-window-info-bar.vala:119 +msgid "Check security details" +msgstr "Undersøg sikkerhedsdetaljer" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:117 +#, c-format +msgid "Outgoing mail server security is not trusted for %s" +msgstr "Udgående mailservers sikkerhed er ikke betroet for %s" + +#: src/client/components/main-window-info-bar.vala:118 +msgid "Messages cannot be sent until checked." +msgstr "Beskeder kan ikke sendes, indtil den er undersøgt." + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:124 +#, c-format +msgid "A problem occurred checking mail for %s" +msgstr "Der opstod et problem, da der skulle tjekkes for mail for %s" + +#: src/client/components/main-window-info-bar.vala:125 +#: src/client/components/main-window-info-bar.vala:132 +msgid "Something went wrong, please file a bug report if the problem persists" +msgstr "" +"Noget gik galt. Indsend venligst en fejlrapport, hvis der vedbliver at være " +"et problem" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:131 +#, c-format +msgid "A problem occurred sending mail for %s" +msgstr "Der opstod et problem, da der skulle sendes mail for %s" + +#: src/client/components/main-window-info-bar.vala:133 +msgid "Retry sending queued messages" +msgstr "Prøv igen at sende beskederne i køen" + +#: src/client/components/main-window-info-bar.vala:144 +msgid "A database problem has occurred" +msgstr "Der opstod et problem med databasen" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:146 +#, c-format +msgid "Messages for %s must be downloaded again." +msgstr "Beskeder for %s skal hentes igen." + +#: src/client/components/main-window-info-bar.vala:159 +msgid "Geary has encountered a problem" +msgstr "Geary er stødt på et problem" + +#: src/client/components/main-window-info-bar.vala:160 +msgid "" +"Please check the technical details and report the problem if it persists." +msgstr "" +"Undersøg venligst de tekniske detaljer og rapportér, hvis der vedbliver at " +"være et problem." + +#: src/client/components/main-window-info-bar.vala:168 +msgid "_Details" +msgstr "_Detaljer" + +#. Button tooltip for displaying technical details about an account problem +#: src/client/components/main-window-info-bar.vala:169 ui/main-window.ui:250 +msgid "View technical details about the error" +msgstr "Vis tekniske detaljer om fejlen" + +#: src/client/components/main-window-info-bar.vala:173 +msgid "_Retry" +msgstr "_Prøv igen" -#: ../src/client/components/search-bar.vala:8 -#: ../src/client/folder-list/folder-list-search-branch.vala:38 -#: ../src/engine/api/geary-special-folder-type.vala:51 +#: src/client/components/search-bar.vala:8 +#: src/client/folder-list/folder-list-search-branch.vala:38 +#: src/engine/api/geary-special-folder-type.vala:51 msgid "Search" msgstr "Søg" #. Search entry. -#: ../src/client/components/search-bar.vala:23 +#: src/client/components/search-bar.vala:23 msgid "Search all mail in account for keywords (Ctrl+S)" msgstr "Søg efter ord i al e-mail tilhørende kontoen (Ctrl+S)" -#: ../src/client/components/search-bar.vala:100 +#: src/client/components/search-bar.vala:101 #, c-format msgid "Indexing %s account" msgstr "Indeksérer %s konto" -#: ../src/client/components/search-bar.vala:111 -#: ../src/client/folder-list/folder-list-search-branch.vala:39 +#: src/client/components/search-bar.vala:112 +#: src/client/folder-list/folder-list-search-branch.vala:39 #, c-format msgid "Search %s account" msgstr "Søg %s-konto" #. / Displayed in the space-limited status bar while a message is in the process of being sent. -#: ../src/client/components/status-bar.vala:26 +#: src/client/components/status-bar.vala:26 msgid "Sending…" msgstr "Sender …" -#: ../src/client/components/stock.vala:18 ../ui/account_cannot_remove.glade.h:3 +#. / Displayed in the space-limited status bar when a message fails to be sent due to error. +#: src/client/components/status-bar.vala:29 +msgid "Error sending email" +msgstr "Der opstod en fejl ved afsendelse af e-mail" + +#. Displayed in the space-limited status bar when a message fails to be uploaded +#. to Sent Mail after being sent. +#: src/client/components/status-bar.vala:33 +msgid "Error saving sent mail" +msgstr "Der opstod en fejl ved lagring af den afsendte e-mail" + +#: src/client/components/stock.vala:18 msgid "_OK" msgstr "_OK" -#: ../src/client/components/stock.vala:19 ../ui/edit_alternate_emails.glade.h:3 -#: ../ui/password-dialog.glade.h:5 ../ui/remove_confirm.glade.h:5 +#: src/client/components/stock.vala:19 ui/password-dialog.glade:196 msgid "_Cancel" msgstr "_Annullér" -#: ../src/client/components/stock.vala:21 ../ui/gtk/menus.ui.h:6 +#: src/client/components/stock.vala:21 ui/gtk/menus.ui:34 msgid "_About" msgstr "_Om" -#: ../src/client/components/stock.vala:23 +#: src/client/components/stock.vala:22 +msgid "_Add" +msgstr "Til_føj" + +#: src/client/components/stock.vala:23 msgid "_Close" msgstr "_Luk" -#: ../src/client/components/stock.vala:24 +#: src/client/components/stock.vala:24 msgid "_Discard" msgstr "_Kassér" -#: ../src/client/components/stock.vala:25 ../ui/gtk/menus.ui.h:5 +#: src/client/components/stock.vala:25 ui/gtk/menus.ui:29 msgid "_Help" msgstr "_Hjælp" -#: ../src/client/components/stock.vala:26 ../ui/conversation-email-menus.ui.h:9 +#: src/client/components/stock.vala:26 ui/conversation-email-menus.ui:77 msgid "_Open" msgstr "_Åbn" -#: ../src/client/components/stock.vala:27 ../ui/gtk/menus.ui.h:3 +#: src/client/components/stock.vala:27 ui/gtk/menus.ui:17 msgid "_Preferences" msgstr "_Indstillinger" -#: ../src/client/components/stock.vala:28 ../ui/conversation-email-menus.ui.h:7 +#. Translators: Menu item to print a single, specific message +#: src/client/components/stock.vala:28 ui/conversation-email-menus.ui:64 msgid "_Print…" msgstr "_Udskriv …" -#: ../src/client/components/stock.vala:29 ../ui/gtk/menus.ui.h:7 +#: src/client/components/stock.vala:29 ui/gtk/menus.ui:38 msgid "_Quit" msgstr "Af_slut" -#: ../src/client/components/stock.vala:30 ../ui/remove_confirm.glade.h:6 +#: src/client/components/stock.vala:30 msgid "_Remove" msgstr "_Fjern" -#: ../src/client/components/stock.vala:32 +#: src/client/components/stock.vala:31 ui/conversation-email-menus.ui:83 +msgid "_Save" +msgstr "_Gem" + +#: src/client/components/stock.vala:32 msgid "_Keep" msgstr "_Behold" -#: ../src/client/composer/composer-link-popover.vala:150 +#: src/client/composer/composer-link-popover.vala:149 msgid "Link URL is not correctly formatted, e.g. http://example.com" msgstr "Link-URL er ikke korrekt formateret, f.eks. http://eksempel.dk" -#: ../src/client/composer/composer-link-popover.vala:157 +#: src/client/composer/composer-link-popover.vala:156 msgid "Invalid link URL" msgstr "Ugyldig link-URL" -#: ../src/client/composer/composer-link-popover.vala:157 +#: src/client/composer/composer-link-popover.vala:156 msgid "Invalid email address" msgstr "Ugyldig e-mailadresse" -#: ../src/client/composer/composer-widget.vala:154 +#: src/client/composer/composer-widget.vala:158 msgid "Saved" msgstr "Gemt" -#: ../src/client/composer/composer-widget.vala:155 +#: src/client/composer/composer-widget.vala:159 msgid "Saving" msgstr "Gemmer" -#: ../src/client/composer/composer-widget.vala:156 +#: src/client/composer/composer-widget.vala:160 msgid "Error saving" msgstr "Der opstod en fejl under lagring" -#: ../src/client/composer/composer-widget.vala:157 +#: src/client/composer/composer-widget.vala:161 msgid "Press Backspace to delete quote" msgstr "Tryk tilbagetast for at slette citat" -#: ../src/client/composer/composer-widget.vala:158 -msgid "New Message" -msgstr "Ny besked" - #. Translators: This is list of keywords, separated by pipe ("|") #. characters, that suggest an attachment; since this is full-word #. checking, include all variants of each word. No spaces are #. allowed. -#: ../src/client/composer/composer-widget.vala:167 +#: src/client/composer/composer-widget.vala:170 msgid "" "attach|attaching|attaches|attachment|attachments|attached|enclose|enclosed|" "enclosing|encloses|enclosure|enclosures" @@ -917,28 +1471,37 @@ "vedlægge|vedhæftede|attach|attaching|attaches|attachment|attachments|" "attached|enclose|enclosed|enclosing|encloses|enclosure|enclosures" -#: ../src/client/composer/composer-widget.vala:1122 -#: ../src/client/composer/composer-widget.vala:1141 -msgid "Do you want to discard this message?" -msgstr "Ønsker du at kassére denne besked?" +#. Translators: This dialog text is displayed to the +#. user when closing a composer where the options are +#. Keep, Discard or Cancel. +#: src/client/composer/composer-widget.vala:1103 +msgid "Do you want to keep or discard this draft message?" +msgstr "Ønsker du at beholde eller kassere denne kladde?" + +#. Translators: This dialog text is displayed to the +#. user when closing a composer where the options are +#. only Discard or Cancel. +#: src/client/composer/composer-widget.vala:1131 +msgid "Do you want to discard this draft message?" +msgstr "Ønsker du at kassere denne kladde?" -#: ../src/client/composer/composer-widget.vala:1245 +#: src/client/composer/composer-widget.vala:1248 msgid "Send message with an empty subject and body?" msgstr "Send beskeden uden tekst og emne?" -#: ../src/client/composer/composer-widget.vala:1247 +#: src/client/composer/composer-widget.vala:1250 msgid "Send message with an empty subject?" msgstr "Send beskeden uden et emne?" -#: ../src/client/composer/composer-widget.vala:1249 +#: src/client/composer/composer-widget.vala:1252 msgid "Send message with an empty body?" msgstr "Send beskeden uden nogen tekst?" -#: ../src/client/composer/composer-widget.vala:1253 +#: src/client/composer/composer-widget.vala:1256 msgid "Send message without an attachment?" msgstr "Send beskeden uden en vedhæftet fil?" -#: ../src/client/composer/composer-widget.vala:1515 +#: src/client/composer/composer-widget.vala:1561 #, c-format msgid "“%s” already attached for delivery." msgstr "“%s” er allerede vedhæftet." @@ -948,168 +1511,243 @@ #. description of the document type, the second will #. be a human-friendly size string. For example: #. Document (100.9MB) -#: ../src/client/composer/composer-widget.vala:1523 -#: ../src/client/conversation-viewer/conversation-email.vala:138 +#: src/client/composer/composer-widget.vala:1569 +#: src/client/conversation-viewer/conversation-email.vala:136 #, c-format msgid "%s (%s)" msgstr "%s (%s)" -#: ../src/client/composer/composer-widget.vala:1560 +#: src/client/composer/composer-widget.vala:1606 #, c-format msgid "“%s” could not be found." msgstr "“%s” blev ikke fundet." -#: ../src/client/composer/composer-widget.vala:1566 +#: src/client/composer/composer-widget.vala:1612 #, c-format msgid "“%s” is a folder." msgstr "“%s” er en mappe." -#: ../src/client/composer/composer-widget.vala:1572 +#: src/client/composer/composer-widget.vala:1618 #, c-format msgid "“%s” is an empty file." msgstr "“%s” er en tom fil." -#: ../src/client/composer/composer-widget.vala:1585 +#: src/client/composer/composer-widget.vala:1631 #, c-format msgid "“%s” could not be opened for reading." msgstr "“%s” kunne ikke åbnes til læsning." -#: ../src/client/composer/composer-widget.vala:1593 +#: src/client/composer/composer-widget.vala:1639 msgid "Cannot add attachment" msgstr "Kan ikke tilføje filen" -#: ../src/client/composer/composer-widget.vala:1645 +#: src/client/composer/composer-widget.vala:1688 msgid "To: " msgstr "Til: " -#: ../src/client/composer/composer-widget.vala:1648 +#: src/client/composer/composer-widget.vala:1691 msgid "Cc: " msgstr "Cc: " -#: ../src/client/composer/composer-widget.vala:1651 +#: src/client/composer/composer-widget.vala:1694 msgid "Bcc: " msgstr "Bcc: " -#: ../src/client/composer/composer-widget.vala:1654 +#: src/client/composer/composer-widget.vala:1697 msgid "Reply-To: " msgstr "Svar-Til: " -#: ../src/client/composer/composer-widget.vala:1786 +#: src/client/composer/composer-widget.vala:1835 msgid "Select Color" msgstr "Vælg farve" -#. Displayed in the From dropdown to indicate an "alternate email address" -#. for an account. The first printf argument will be the alternate email -#. address, and the second will be the account's primary email address. -#: ../src/client/composer/composer-widget.vala:1986 +#. Displayed in the From dropdown to indicate an +#. "alternate email address" for an account. The first +#. printf argument will be the alternate email address, +#. and the second will be the account's primary email +#. address. +#: src/client/composer/composer-widget.vala:2030 #, c-format msgid "%1$s via %2$s" msgstr "%1$s via %2$s" #. Composer label (with mnemonic underscore) for the account selector #. when choosing what address to send a message from. -#: ../src/client/composer/composer-widget.vala:2028 +#: src/client/composer/composer-widget.vala:2090 msgid "_From:" msgstr "_Fra:" #. Translators: This is the name of the file chooser filter #. when inserting an image in the composer. -#: ../src/client/composer/composer-widget.vala:2252 +#: src/client/composer/composer-widget.vala:2315 msgid "Images" msgstr "Billeder" -#: ../src/client/composer/spell-check-popover.vala:117 +#: src/client/composer/composer-window.vala:14 +msgid "New Message" +msgstr "Ny besked" + +#: src/client/composer/spell-check-popover.vala:117 msgid "Remove this language from the preferred list" msgstr "Fjern dette sprog fra listen over foretrukne" -#: ../src/client/composer/spell-check-popover.vala:121 +#: src/client/composer/spell-check-popover.vala:121 msgid "Add this language to the preferred list" msgstr "Tilføj dette sprog til listen over foretrukne" -#: ../src/client/composer/spell-check-popover.vala:217 +#: src/client/composer/spell-check-popover.vala:217 msgid "Search for more languages" msgstr "Søg efter flere sprog" -#: ../src/client/conversation-list/formatted-conversation-data.vala:11 +#: src/client/conversation-list/conversation-list-view.vala:283 +msgid "Delete conversation" +msgstr "Slet samtale" + +#: src/client/conversation-list/conversation-list-view.vala:286 +#: ui/main-toolbar-menus.ui:16 +msgid "Mark as _Read" +msgstr "Markér som _læst" + +#: src/client/conversation-list/conversation-list-view.vala:289 +#: ui/main-toolbar-menus.ui:20 +msgid "Mark as _Unread" +msgstr "Markér som _ulæst" + +#: src/client/conversation-list/conversation-list-view.vala:292 +#: ui/main-toolbar-menus.ui:28 +msgid "U_nstar" +msgstr "Stj_ern ikke" + +#: src/client/conversation-list/conversation-list-view.vala:294 +#: ui/main-toolbar-menus.ui:24 +msgid "_Star" +msgstr "St_jern" + +#. Translators: Menu item to reply to a specific message. +#: src/client/conversation-list/conversation-list-view.vala:297 +#: ui/conversation-email-menus.ui:9 +msgid "_Reply" +msgstr "_Svar" + +#: src/client/conversation-list/conversation-list-view.vala:298 +msgid "R_eply All" +msgstr "S_var til alle" + +#. Translators: Menu item to forward a specific message. +#: src/client/conversation-list/conversation-list-view.vala:299 +#: ui/conversation-email-menus.ui:21 +msgid "_Forward" +msgstr "_Videresend" + +#: src/client/conversation-list/formatted-conversation-data.vala:11 msgid "Me" msgstr "Mig" #. Translators: This is the file type displayed for #. attachments with unknown file types. -#: ../src/client/conversation-viewer/conversation-email.vala:124 +#: src/client/conversation-viewer/conversation-email.vala:122 msgid "Unknown" msgstr "Ukendt" +#: src/client/conversation-viewer/conversation-email.vala:811 +msgid "From:" +msgstr "Fra:" + +#: src/client/conversation-viewer/conversation-email.vala:815 +#: ui/conversation-message.ui:313 +msgid "To:" +msgstr "Til:" + +#: src/client/conversation-viewer/conversation-email.vala:819 +#: ui/conversation-message.ui:358 +msgid "Cc:" +msgstr "Cc:" + +#: src/client/conversation-viewer/conversation-email.vala:823 +#: ui/conversation-message.ui:403 +msgid "Bcc:" +msgstr "Bcc:" + +#: src/client/conversation-viewer/conversation-email.vala:827 +msgid "Date:" +msgstr "Dato:" + +#: src/client/conversation-viewer/conversation-email.vala:831 +msgid "Subject:" +msgstr "Emne:" + +#: src/client/conversation-viewer/conversation-message.vala:60 +msgid "This email address may have been forged" +msgstr "Denne e-mailadresse kan være forfalsket" + #. Preview headers #. Translators: This is displayed in place of the from address #. when the message has no from address. -#: ../src/client/conversation-viewer/conversation-message.vala:331 +#: src/client/conversation-viewer/conversation-message.vala:330 msgid "No sender" msgstr "Ingen afsender" #. Translators: This separates multiple 'from' #. addresses in the header preview for a message. -#: ../src/client/conversation-viewer/conversation-message.vala:586 +#: src/client/conversation-viewer/conversation-message.vala:589 msgid ", " msgstr ", " #. Translators: This string is used as the HTML IMG ALT #. attribute value when displaying an inline image in an email #. that did not specify a file name. E.g. ImageCannot remove account " -msgstr "Kan ikke fjerne konto" +#: ui/accounts_editor_add_pane.ui:8 ui/accounts_editor_list_pane.ui:127 +msgid "Add an account" +msgstr "Tilføj en konto" + +#: ui/accounts_editor_add_pane.ui:53 +msgid "Create" +msgstr "Opret" + +#: ui/accounts_editor_add_pane.ui:130 ui/accounts_editor_servers_pane.ui:125 +msgid "Receiving" +msgstr "Modtager" + +#: ui/accounts_editor_add_pane.ui:178 ui/accounts_editor_servers_pane.ui:165 +msgid "Sending" +msgstr "Sender" -#: ../ui/account_cannot_remove.glade.h:2 -msgid "" -"A composer window associated with this account is currently open. Send or " -"discard the message and try again." -msgstr "" -"Et beskedskrivningsvindue associéret med denne bruger er i forvejen åbent. " -"Send eller kassér beskeden og prøv igen." +#: ui/accounts_editor_edit_pane.ui:8 +msgid "Edit Account" +msgstr "Redigér konto" -#: ../ui/account_list.glade.h:1 -msgid "Add account" -msgstr "Tilføj konto" +#: ui/accounts_editor_edit_pane.ui:9 ui/accounts_editor_servers_pane.ui:9 +msgid "Account Name" +msgstr "Kontonavn" + +#: ui/accounts_editor_edit_pane.ui:124 +msgid "Email addresses" +msgstr "E-mailadresse" + +#: ui/accounts_editor_edit_pane.ui:164 +msgid "Signature" +msgstr "Signatur" -#: ../ui/account_list.glade.h:2 -msgid "Edit account" -msgstr "Redigér konto" +#: ui/accounts_editor_edit_pane.ui:201 +msgid "Settings" +msgstr "Indstillinger" + +#. This is a button in the account settings to show server settings. +#: ui/accounts_editor_edit_pane.ui:243 ui/accounts_editor_servers_pane.ui:8 +msgid "Server Settings" +msgstr "Serverindstillinger" + +#. This is the remove account button in the account settings. +#: ui/accounts_editor_edit_pane.ui:258 ui/accounts_editor_remove_pane.ui:23 +msgid "Remove Account" +msgstr "Fjern konto" + +#: ui/accounts_editor_edit_pane.ui:262 ui/accounts_editor_remove_pane.ui:27 +msgid "Remove this account from Geary" +msgstr "Fjern denne konto fra Geary" -#: ../ui/account_list.glade.h:3 +#: ui/accounts_editor_list_pane.ui:8 +msgid "Accounts" +msgstr "Konti" + +#: ui/accounts_editor_list_pane.ui:63 +msgid "To get started, select an email provider below." +msgstr "Vælg en e-mailudbyder nedenfor for at komme i gang." + +#: ui/accounts_editor_list_pane.ui:76 +msgid "Welcome to Geary" +msgstr "Velkommen til Geary" + +#. This title is shown to users when confirming if they want to remove an account. The string substitution is replaced with the account's name. +#: ui/accounts_editor_remove_pane.ui:73 +#, c-format +msgid "Confirm removing: %s" +msgstr "Bekræft fjernelse: %s" + +#: ui/accounts_editor_remove_pane.ui:91 +msgid "" +"Removing an account will remove it from Geary and delete locally cached " +"email data from your computer, but not from your service provider." +msgstr "" +"Fjernes en konto, vil kontoen blive fjernet i Geary, og lokalt gemte e-" +"maildata vil blive slettet fra computeren, men ikke hos serviceudbyderen." + +#: ui/accounts_editor_remove_pane.ui:122 msgid "Remove account" msgstr "Fjern konto" -#: ../ui/account_spinner.glade.h:1 -msgid "Please wait while Geary validates your account." -msgstr "Vent venligst mens Geary validérer din konto." +#: ui/accounts_editor_servers_pane.ui:17 +msgid "Cancel" +msgstr "Annullér" + +#: ui/accounts_editor_servers_pane.ui:47 +msgid "Apply" +msgstr "Anvend" -#: ../ui/certificate_warning_dialog.glade.h:1 +#: ui/certificate_warning_dialog.glade:7 msgid "Untrusted Connection" msgstr "Ubetroet forbindelse" -#: ../ui/certificate_warning_dialog.glade.h:2 +#: ui/certificate_warning_dialog.glade:29 msgid "_Always Trust This Server" msgstr "Stol _altid på serveren" -#: ../ui/certificate_warning_dialog.glade.h:3 +#: ui/certificate_warning_dialog.glade:43 msgid "_Trust This Server" msgstr "_Stol på serveren" -#: ../ui/certificate_warning_dialog.glade.h:4 +#: ui/certificate_warning_dialog.glade:57 msgid "_Don’t Trust This Server" msgstr "Stol _ikke på serveren" -#: ../ui/composer-headerbar.ui.h:1 +#: ui/composer-headerbar.ui:17 ui/composer-headerbar.ui:174 msgid "Detach (Ctrl+D)" msgstr "Fjern bilag (Ctrl+D)" -#: ../ui/composer-headerbar.ui.h:2 +#: ui/composer-headerbar.ui:57 ui/composer-headerbar.ui:83 msgid "Attach File (Ctrl+T)" msgstr "Vedhæft fil (Ctrl+T)" -#: ../ui/composer-headerbar.ui.h:3 +#: ui/composer-headerbar.ui:107 msgid "Include Original Attachments" msgstr "Medtag oprindelige vedhæftede filer" -#: ../ui/composer-headerbar.ui.h:4 -msgid "Send (Ctrl+Enter)" -msgstr "Send (Ctrl+Enter)" - -#: ../ui/composer-headerbar.ui.h:5 +#: ui/composer-headerbar.ui:202 msgid "_Send" msgstr "_Send" -#: ../ui/composer-headerbar.ui.h:6 +#: ui/composer-headerbar.ui:207 +msgid "Send (Ctrl+Enter)" +msgstr "Send (Ctrl+Enter)" + +#: ui/composer-headerbar.ui:230 msgid "Discard and Close" msgstr "Forkast og luk" -#: ../ui/composer-headerbar.ui.h:7 +#: ui/composer-headerbar.ui:254 msgid "Save and Close" msgstr "Gem og luk" #. Note that this button and the Update button will never be shown at the same time to the user. -#: ../ui/composer-link-popover.ui.h:2 +#: ui/composer-link-popover.ui:41 msgid "Insert the new link with this URL" msgstr "Indsæt det nye link med denne URL" -#: ../ui/composer-link-popover.ui.h:3 +#: ui/composer-link-popover.ui:52 msgid "Link URL" msgstr "Link-URL" #. Note that this button and the Insert button will never be shown at the same time to the user. -#: ../ui/composer-link-popover.ui.h:5 +#: ui/composer-link-popover.ui:66 msgid "Update this link’s URL" msgstr "Opdatér dette links URL" -#: ../ui/composer-link-popover.ui.h:6 +#: ui/composer-link-popover.ui:86 msgid "Delete this link" msgstr "Slet dette link" -#: ../ui/composer-link-popover.ui.h:7 +#: ui/composer-link-popover.ui:106 msgid "Open this link" msgstr "Åbn dette link" -#: ../ui/composer-menus.ui.h:1 +#: ui/composer-menus.ui:7 msgid "S_ans Serif" msgstr "S_ans serif" -#: ../ui/composer-menus.ui.h:2 +#: ui/composer-menus.ui:12 msgid "S_erif" msgstr "S_erif" -#: ../ui/composer-menus.ui.h:3 +#: ui/composer-menus.ui:17 msgid "_Fixed Width" msgstr "_Fast bredde" -#: ../ui/composer-menus.ui.h:4 +#: ui/composer-menus.ui:24 msgid "_Small" msgstr "_Lille" -#: ../ui/composer-menus.ui.h:5 +#: ui/composer-menus.ui:29 msgid "_Medium" msgstr "_Mellem" -#: ../ui/composer-menus.ui.h:6 +#: ui/composer-menus.ui:34 msgid "Lar_ge" msgstr "_Stor" -#: ../ui/composer-menus.ui.h:7 +#: ui/composer-menus.ui:41 msgid "C_olor" msgstr "F_arve" -#: ../ui/composer-menus.ui.h:8 +#: ui/composer-menus.ui:47 ui/composer-menus.ui:62 msgid "_Rich Text" msgstr "_Formateret tekst" -#: ../ui/composer-menus.ui.h:9 +#: ui/composer-menus.ui:53 ui/composer-menus.ui:68 msgid "Show Extended Fields" msgstr "Vis udvidede felter" -#: ../ui/composer-menus.ui.h:10 +#: ui/composer-menus.ui:78 msgid "_Undo" msgstr "_Fortryd" -#: ../ui/composer-menus.ui.h:11 +#: ui/composer-menus.ui:82 msgid "_Redo" msgstr "_Genskab" -#: ../ui/composer-menus.ui.h:12 +#: ui/composer-menus.ui:88 ui/composer-menus.ui:106 msgid "Cu_t" msgstr "_Klip" -#: ../ui/composer-menus.ui.h:13 ../ui/conversation-message-menus.ui.h:7 +#: ui/composer-menus.ui:92 ui/composer-menus.ui:110 +#: ui/conversation-message-menus.ui:37 msgid "_Copy" msgstr "_Kopiér" -#: ../ui/composer-menus.ui.h:14 +#: ui/composer-menus.ui:96 ui/composer-menus.ui:114 msgid "_Paste" msgstr "Sæt _ind" -#: ../ui/composer-menus.ui.h:15 -msgctxt "Clipboard paste with rich text" -msgid "Paste _With Formatting" -msgstr "Sæt ind _med formatering" +#: ui/composer-menus.ui:100 +msgctxt "Clipboard paste as plain text" +msgid "Paste _Without Formatting" +msgstr "Sæt ind _uden formatering" -#: ../ui/composer-menus.ui.h:16 +#: ui/composer-menus.ui:120 msgid "Select _All" msgstr "Markér _alt" -#: ../ui/composer-menus.ui.h:17 ../ui/conversation-message-menus.ui.h:9 +#: ui/composer-menus.ui:127 ui/conversation-message-menus.ui:49 msgid "_Inspect…" msgstr "_Undersøg …" #. Address(es) e-mail is to be sent to -#: ../ui/composer-widget.ui.h:2 +#: ui/composer-widget.ui:56 msgid "_To" msgstr "_Til" -#: ../ui/composer-widget.ui.h:3 +#: ui/composer-widget.ui:75 msgid "_Cc" msgstr "_Cc" -#: ../ui/composer-widget.ui.h:4 +#: ui/composer-widget.ui:130 msgid "_Subject" msgstr "_Emne" -#: ../ui/composer-widget.ui.h:5 +#: ui/composer-widget.ui:149 msgid "_Bcc" msgstr "_Bcc" -#: ../ui/composer-widget.ui.h:6 +#: ui/composer-widget.ui:179 msgid "_Reply-To" msgstr "_Svar-Til" #. Geary account mail will be sent from -#: ../ui/composer-widget.ui.h:8 +#: ui/composer-widget.ui:208 msgid "From" msgstr "Fra" -#: ../ui/composer-widget.ui.h:9 +#: ui/composer-widget.ui:293 msgid "Drop files here" msgstr "Flyt filer hertil" -#: ../ui/composer-widget.ui.h:10 +#: ui/composer-widget.ui:309 msgid "To add them as attachments" msgstr "For at vedhæfte dem" -#: ../ui/composer-widget.ui.h:11 +#: ui/composer-widget.ui:348 msgid "Undo last edit (Ctrl+Z)" msgstr "Fortryd seneste redigering (Ctrl+Z)" -#: ../ui/composer-widget.ui.h:12 +#: ui/composer-widget.ui:372 msgid "Redo last edit (Ctrl+Shift+Z)" msgstr "Omgør seneste redigering (Ctrl+Skift+Z)" -#: ../ui/composer-widget.ui.h:13 +#: ui/composer-widget.ui:410 msgid "Bold (Ctrl+B)" msgstr "Fed (Ctrl+B)" -#: ../ui/composer-widget.ui.h:14 +#: ui/composer-widget.ui:434 msgid "Italic (Ctrl+I)" msgstr "Kursiv (Ctrl+I)" -#: ../ui/composer-widget.ui.h:15 +#: ui/composer-widget.ui:458 msgid "Underline (Ctrl+U)" msgstr "Understreget (Ctrl+U)" -#: ../ui/composer-widget.ui.h:16 +#: ui/composer-widget.ui:482 msgid "Strikethrough (Ctrl+K)" msgstr "Overstreget (Ctrl+K)" -#: ../ui/composer-widget.ui.h:17 +#: ui/composer-widget.ui:520 +msgid "Insert unordered list" +msgstr "Indsæt usorteret liste" + +#: ui/composer-widget.ui:544 +msgid "Insert ordered list" +msgstr "Indsæt sorteret liste" + +#: ui/composer-widget.ui:582 msgid "Quote text (Ctrl+])" msgstr "Citér tekst (Ctrl+])" -#: ../ui/composer-widget.ui.h:18 +#: ui/composer-widget.ui:606 msgid "Unquote text (Ctrl+[)" msgstr "Fjern citat (Ctrl+[)" -#: ../ui/composer-widget.ui.h:19 +#: ui/composer-widget.ui:644 msgid "Insert or update selection link (Ctrl+L)" msgstr "Indsæt eller opdatér markeringens link (Ctrl+L)" -#: ../ui/composer-widget.ui.h:20 +#: ui/composer-widget.ui:668 msgid "Insert an image (Ctrl+G)" msgstr "Indsæt et billede (Ctrl+G)" -#: ../ui/composer-widget.ui.h:21 +#: ui/composer-widget.ui:702 msgid "Remove selection formatting (Ctrl+Space)" msgstr "Fjern markeringens formatering (Ctrl+Mellemrum)" -#: ../ui/composer-widget.ui.h:22 +#: ui/composer-widget.ui:726 msgid "Select spell checking languages" msgstr "Vælg sprog for stavekontrol" -#: ../ui/conversation-email.ui.h:1 +#: ui/conversation-email.ui:27 msgid "Save all attachments" msgstr "Gem alle vedhæftede filer" #. Note: The application will never show this button at the same time as unstar_button, one will always be hidden. -#: ../ui/conversation-email.ui.h:3 +#: ui/conversation-email.ui:50 msgid "Mark this message as starred" -msgstr "Markér denne meddelelse med stjerne" +msgstr "Markér denne besked med stjerne" # El. blot Fjern stjerne fra denne meddelelse? #. Note: The application will never show this button at the same time as star_button, one will always be hidden. -#: ../ui/conversation-email.ui.h:5 +#: ui/conversation-email.ui:72 msgid "Mark this message as not starred" -msgstr "Markér denne meddelelse som uden stjerne" +msgstr "Markér denne besked som uden stjerne" -#: ../ui/conversation-email.ui.h:6 +#: ui/conversation-email.ui:95 msgid "Display the message menu" -msgstr "Vis meddelelsesmenuen" +msgstr "Vis beskedmenuen" -#: ../ui/conversation-email.ui.h:7 +#: ui/conversation-email.ui:161 msgid "Open selected attachments" msgstr "Åbn valgte bilag" -#: ../ui/conversation-email.ui.h:8 +#: ui/conversation-email.ui:178 msgid "Save selected attachments" msgstr "Gem valgte bilag" -#: ../ui/conversation-email.ui.h:9 +#: ui/conversation-email.ui:195 msgid "Select all attachments" msgstr "Vælg alle bilag" -#: ../ui/conversation-email.ui.h:10 +#: ui/conversation-email.ui:240 msgid "Edit Draft" msgstr "Redigér kladde" -#: ../ui/conversation-email.ui.h:11 +#: ui/conversation-email.ui:267 msgid "Draft message" msgstr "Kladde" -#: ../ui/conversation-email.ui.h:12 +#: ui/conversation-email.ui:283 msgid "This message has not yet been sent." -msgstr "Denne meddelelse er endnu ikke blevet sendt." - -#: ../ui/conversation-email.ui.h:13 -msgid "Try Again" -msgstr "Prøv igen" +msgstr "Denne besked er endnu ikke blevet sendt." -#: ../ui/conversation-email.ui.h:14 +#: ui/conversation-email.ui:329 msgid "Message not saved" -msgstr "Meddelelse ikke gemt" +msgstr "Besked ikke gemt" -#: ../ui/conversation-email.ui.h:15 +#: ui/conversation-email.ui:345 msgid "This message was sent, but has not been saved to your account." msgstr "Denne besked blev sendt, men er ikke blevet gemt på din konto." -#: ../ui/conversation-email-menus.ui.h:2 +#. Translators: Menu item to reply to a specific message. +#: ui/conversation-email-menus.ui:15 msgid "Reply to _All" msgstr "Svar til _alle" -#: ../ui/conversation-email-menus.ui.h:4 +#. Translators: Menu item to mark a specific message as +#. read. +#: ui/conversation-email-menus.ui:30 msgid "_Mark Read" msgstr "_Markér som læst" -#: ../ui/conversation-email-menus.ui.h:5 +#: ui/conversation-email-menus.ui:36 msgid "_Mark Unread" msgstr "_Markér som ulæst" -#: ../ui/conversation-email-menus.ui.h:6 +#. Translators: Menu item to mark all messages in a +#. conversation from this one as unread. +#: ui/conversation-email-menus.ui:42 msgid "Mark Unread From _Here" msgstr "Markér som ulæst _herfra" -#: ../ui/conversation-email-menus.ui.h:8 +#. Translators: Menu item to move a single, specific message +#. to the trash +#: ui/conversation-email-menus.ui:50 +msgid "_Trash" +msgstr "Flyt til _Papirkurven" + +#. Translators: Menu item to delete a single, specific message +#: ui/conversation-email-menus.ui:57 +msgid "_Delete…" +msgstr "_Slet …" + +#. Translators: Menu item to view the source for a message +#: ui/conversation-email-menus.ui:69 msgid "_View Source" msgstr "Vis _kilde" -#: ../ui/conversation-email-menus.ui.h:11 +#: ui/conversation-email-menus.ui:87 msgid "_Save All" msgstr "_Gem alle" -#: ../ui/conversation-message-menus.ui.h:1 +#: ui/conversation-message-menus.ui:7 msgid "_Open Link" msgstr "_Åbn link" -#: ../ui/conversation-message-menus.ui.h:2 +#: ui/conversation-message-menus.ui:11 msgid "Copy Link _Address" msgstr "Kopiér link-_adresse" -#: ../ui/conversation-message-menus.ui.h:3 +#: ui/conversation-message-menus.ui:17 msgid "Send New _Message…" msgstr "Send ny _besked …" -#: ../ui/conversation-message-menus.ui.h:4 +#: ui/conversation-message-menus.ui:21 msgid "Copy Email _Address" msgstr "Kopiér e-mail_adresse" -#: ../ui/conversation-message-menus.ui.h:5 +#: ui/conversation-message-menus.ui:27 msgid "Save _Image As…" msgstr "Gem _billede som …" -#: ../ui/conversation-message-menus.ui.h:6 +#: ui/conversation-message-menus.ui:33 msgid "_Select All" msgstr "Markér _alt" -#: ../ui/conversation-message-menus.ui.h:8 +#: ui/conversation-message-menus.ui:43 msgid "Search for messages from" -msgstr "Søg efter meddelelser fra" +msgstr "Søg efter beskeder fra" -#: ../ui/conversation-message.ui.h:1 +#: ui/conversation-message.ui:64 msgid "From " msgstr "Fra " -#: ../ui/conversation-message.ui.h:2 +#: ui/conversation-message.ui:80 ui/conversation-message.ui:179 msgid "1/1/1970\t" msgstr "1/1/1970\t" -#: ../ui/conversation-message.ui.h:3 +#: ui/conversation-message.ui:103 msgid "Preview body text." msgstr "Forhåndsvisning af tekst." -#: ../ui/conversation-message.ui.h:4 +#: ui/conversation-message.ui:203 msgid "Sent by:" msgstr "Sendt af:" -#: ../ui/conversation-message.ui.h:5 +#: ui/conversation-message.ui:248 msgid "Reply to:" msgstr "Svar til:" -#: ../ui/conversation-message.ui.h:6 +#: ui/conversation-message.ui:292 msgid "Subject" msgstr "Emne" -#: ../ui/conversation-message.ui.h:7 -msgid "To:" -msgstr "Til:" - -#: ../ui/conversation-message.ui.h:8 -msgid "Cc:" -msgstr "Cc:" - -#: ../ui/conversation-message.ui.h:9 -msgid "Bcc:" -msgstr "Bcc:" - -#: ../ui/conversation-message.ui.h:10 +#: ui/conversation-message.ui:502 msgid "Show Images" msgstr "Vis billeder" -#: ../ui/conversation-message.ui.h:11 +#: ui/conversation-message.ui:515 msgid "Always Show From Sender" msgstr "Vis altid fra denne afsender" -#: ../ui/conversation-message.ui.h:12 +#: ui/conversation-message.ui:543 msgid "Remote images not shown" msgstr "Fjernt lagrede billeder vises ikke" -#: ../ui/conversation-message.ui.h:13 +#: ui/conversation-message.ui:560 msgid "Only show remote images from senders you trust." msgstr "Vis kun fjernlagrede billeder fra afsendere, du stoler på." -#: ../ui/conversation-message.ui.h:14 +#: ui/conversation-message.ui:692 msgid "But actually goes to:" msgstr "men fører faktisk til:" -#: ../ui/conversation-message.ui.h:15 +#: ui/conversation-message.ui:723 msgid "The link appears to go to:" msgstr "Linket ser ud til at føre til:" -#: ../ui/conversation-message.ui.h:16 +#: ui/conversation-message.ui:735 msgid "Deceptive link found" msgstr "Bedragerisk link fundet" -#: ../ui/conversation-message.ui.h:17 +#: ui/conversation-message.ui:750 msgid "The email sender may be leading you to the wrong web site." msgstr "" "Afsenderen af denne e-mail forsøger måske at lede dig til det forkerte " "websted." -#: ../ui/conversation-message.ui.h:18 +#: ui/conversation-message.ui:763 msgid "If unsure, contact the sender and ask before continuing." msgstr "" "Hvis du er i tvivl, så kontakt afsenderen og spørg, inden du fortsætter." -#: ../ui/conversation-viewer.ui.h:1 +#: ui/conversation-viewer.ui:60 msgid "Find in conversation" msgstr "Find i samtale" -#: ../ui/conversation-viewer.ui.h:2 +#: ui/conversation-viewer.ui:74 msgid "Find the previous occurrence of the search string." msgstr "Find forrige forekomst af søgestrengen." -#: ../ui/conversation-viewer.ui.h:3 +#: ui/conversation-viewer.ui:95 msgid "Find the next occurrence of the search string." msgstr "Find næste forekomst af søgestrengen." -#: ../ui/edit_alternate_emails.glade.h:1 -msgid "Remove email address" -msgstr "Fjern e-mailadresse" - -#: ../ui/edit_alternate_emails.glade.h:2 -msgid "" -"Some email services require additional addresses be configured on the " -"server. Contact your email provider for more information." -msgstr "" -"Visse e-mailtjenester kræver at der konfigureres yderligere adresser på " -"serveren. Kontakt din e-mailudbyder for at få yderligere information." - -#: ../ui/edit_alternate_emails.glade.h:4 -msgid "_Update" -msgstr "_Opdatér" - -#: ../ui/find_bar.glade.h:1 +#: ui/find_bar.glade:66 msgid "Find:" msgstr "Find:" -#: ../ui/find_bar.glade.h:2 +#: ui/find_bar.glade:89 msgid "_Previous" msgstr "_Forrige" -#: ../ui/find_bar.glade.h:3 +#: ui/find_bar.glade:107 msgid "_Next" msgstr "_Næste" -#: ../ui/find_bar.glade.h:4 +#: ui/find_bar.glade:125 msgid "_Case sensitive" msgstr "Forskel på _store og små bogstaver" -#: ../ui/find_bar.glade.h:5 +#: ui/find_bar.glade:145 msgid "label" msgstr "etiket" -#: ../ui/gtk/help-overlay.ui.h:1 +#: ui/gtk/help-overlay.ui:9 msgid "Conversation Shortcuts" msgstr "Samtalegenveje" -#: ../ui/gtk/help-overlay.ui.h:2 +#: ui/gtk/help-overlay.ui:13 ui/gtk/help-overlay.ui:254 msgctxt "shortcut window" msgid "General" msgstr "Generelle" -#: ../ui/gtk/help-overlay.ui.h:3 +#: ui/gtk/help-overlay.ui:17 msgctxt "shortcut window" msgid "Move focus to the next/previous pane" msgstr "Flyt fokus til næste/forrige rude" -#: ../ui/gtk/help-overlay.ui.h:4 +#: ui/gtk/help-overlay.ui:24 msgctxt "shortcut window" msgid "Move focus to conversation list" msgstr "Flyt fokus til samtalelisten" -#: ../ui/gtk/help-overlay.ui.h:5 +#: ui/gtk/help-overlay.ui:31 msgctxt "shortcut window" msgid "Detach composer window" -msgstr "Frakobl redigeringsvindue" +msgstr "Løsriv redigeringsvindue" -#: ../ui/gtk/help-overlay.ui.h:6 +#: ui/gtk/help-overlay.ui:38 msgctxt "shortcut window" msgid "Close composer window" msgstr "Luk redigeringsvindue" -#: ../ui/gtk/help-overlay.ui.h:7 +#: ui/gtk/help-overlay.ui:45 msgctxt "shortcut window" msgid "Show keyboard shortcuts" msgstr "Vis tastaturgenveje" -#: ../ui/gtk/help-overlay.ui.h:8 +#: ui/gtk/help-overlay.ui:52 msgctxt "shortcut window" msgid "Show help" msgstr "Vis hjælp" -#: ../ui/gtk/help-overlay.ui.h:9 +#: ui/gtk/help-overlay.ui:59 msgctxt "shortcut window" msgid "Quit the application" msgstr "Afslut programmet" -#: ../ui/gtk/help-overlay.ui.h:10 +#: ui/gtk/help-overlay.ui:68 msgctxt "shortcut window" msgid "Search" msgstr "Søg" -#: ../ui/gtk/help-overlay.ui.h:11 +#: ui/gtk/help-overlay.ui:72 msgctxt "shortcut window" msgid "Jump to search box" msgstr "Hop til søgeboks" -#: ../ui/gtk/help-overlay.ui.h:12 +#: ui/gtk/help-overlay.ui:79 msgctxt "shortcut window" msgid "Find in current conversation" msgstr "Find i nuværende samtale" -#: ../ui/gtk/help-overlay.ui.h:13 +#: ui/gtk/help-overlay.ui:86 msgctxt "shortcut window" msgid "Find next/previous in current conversation" msgstr "Find næste/forrige i nuværende samtale" -#: ../ui/gtk/help-overlay.ui.h:14 +#: ui/gtk/help-overlay.ui:95 ui/gtk/help-overlay.ui:274 msgctxt "shortcut window" msgid "Actions" msgstr "Handlinger" -#: ../ui/gtk/help-overlay.ui.h:15 +#: ui/gtk/help-overlay.ui:99 msgctxt "shortcut window" msgid "Compose a new message" -msgstr "Skriv ny meddelelse" +msgstr "Skriv ny besked" -#: ../ui/gtk/help-overlay.ui.h:16 +#: ui/gtk/help-overlay.ui:106 msgctxt "shortcut window" msgid "Reply to sender " -msgstr "Svar til afsender" +msgstr "Svar til afsender " -#: ../ui/gtk/help-overlay.ui.h:17 +#: ui/gtk/help-overlay.ui:113 msgctxt "shortcut window" msgid "Reply to all" msgstr "Svar til alle" -#: ../ui/gtk/help-overlay.ui.h:18 +#: ui/gtk/help-overlay.ui:120 msgctxt "shortcut window" msgid "Forward" msgstr "Videresend" -#: ../ui/gtk/help-overlay.ui.h:19 +#: ui/gtk/help-overlay.ui:127 msgctxt "shortcut window" msgid "Archive" msgstr "Arkivér" -#: ../ui/gtk/help-overlay.ui.h:20 +#: ui/gtk/help-overlay.ui:134 msgctxt "shortcut window" msgid "Move to trash" msgstr "Flyt til papirkurv" -#: ../ui/gtk/help-overlay.ui.h:21 +#: ui/gtk/help-overlay.ui:141 msgctxt "shortcut window" msgid "Toggle spam" msgstr "Slå spam til/fra" -#: ../ui/gtk/help-overlay.ui.h:22 +#: ui/gtk/help-overlay.ui:148 msgctxt "shortcut window" msgid "Move the conversation" msgstr "Flyt samtalen" -#: ../ui/gtk/help-overlay.ui.h:23 +#: ui/gtk/help-overlay.ui:155 msgctxt "shortcut window" msgid "Label the conversation" msgstr "Tilføj etiket til samtalen" -#: ../ui/gtk/help-overlay.ui.h:24 +#: ui/gtk/help-overlay.ui:163 msgctxt "shortcut window" msgid "Mark read" msgstr "Markér som læst" -#: ../ui/gtk/help-overlay.ui.h:25 +#: ui/gtk/help-overlay.ui:170 msgctxt "shortcut window" msgid "Mark unread" msgstr "Markér som ulæst" -#: ../ui/gtk/help-overlay.ui.h:26 +#: ui/gtk/help-overlay.ui:179 msgctxt "shortcut window" msgid "View" msgstr "Vis" -#: ../ui/gtk/help-overlay.ui.h:27 +#: ui/gtk/help-overlay.ui:183 msgctxt "shortcut window" msgid "Zoom in" msgstr "Zoom ind" -#: ../ui/gtk/help-overlay.ui.h:28 +#: ui/gtk/help-overlay.ui:190 msgctxt "shortcut window" msgid "Zoom out" msgstr "Zoom ud" -#: ../ui/gtk/help-overlay.ui.h:29 +#: ui/gtk/help-overlay.ui:197 msgctxt "shortcut window" msgid "Reset zoom" msgstr "Gendan zoom" -#: ../ui/gtk/help-overlay.ui.h:30 +#: ui/gtk/help-overlay.ui:206 msgctxt "shortcut window" msgid "Additional Shortcuts" msgstr "Yderligere genveje" -#: ../ui/gtk/help-overlay.ui.h:31 +#: ui/gtk/help-overlay.ui:210 msgctxt "shortcut window" msgid "Star" msgstr "Tilføj stjerne" -#: ../ui/gtk/help-overlay.ui.h:32 +#: ui/gtk/help-overlay.ui:217 msgctxt "shortcut window" msgid "Unstar" msgstr "Fjern stjerne" -#: ../ui/gtk/help-overlay.ui.h:33 +#: ui/gtk/help-overlay.ui:224 msgctxt "shortcut window" msgid "Delete" msgstr "Slet" -#: ../ui/gtk/help-overlay.ui.h:34 +#: ui/gtk/help-overlay.ui:231 msgctxt "shortcut window" msgid "Jump to next (older) conversation" msgstr "Hop til næste (ældre) samtale" -#: ../ui/gtk/help-overlay.ui.h:35 +#: ui/gtk/help-overlay.ui:238 msgctxt "shortcut window" msgid "Jump to previous (newer) conversation" msgstr "Hop til forrige (nyere) samtale" -#: ../ui/gtk/help-overlay.ui.h:36 +#: ui/gtk/help-overlay.ui:250 msgid "Composer Shortcuts" msgstr "Genveje i redigering" -#: ../ui/gtk/help-overlay.ui.h:37 +#: ui/gtk/help-overlay.ui:258 msgctxt "shortcut window" msgid "Quote text" msgstr "Citér tekst" -#: ../ui/gtk/help-overlay.ui.h:38 +#: ui/gtk/help-overlay.ui:265 msgctxt "shortcut window" msgid "Unquote text" msgstr "Fjern citat" -#: ../ui/gtk/help-overlay.ui.h:39 +#: ui/gtk/help-overlay.ui:278 msgctxt "shortcut window" msgid "Send" msgstr "Send" -#: ../ui/gtk/help-overlay.ui.h:40 +#: ui/gtk/help-overlay.ui:285 msgctxt "shortcut window" msgid "Add attachment" msgstr "Vedhæft bilag" -#: ../ui/gtk/help-overlay.ui.h:41 +#: ui/gtk/help-overlay.ui:294 msgctxt "shortcut window" msgid "Rich text mode" msgstr "Formateret tekst" -#: ../ui/gtk/help-overlay.ui.h:42 +#: ui/gtk/help-overlay.ui:298 msgctxt "shortcut window" msgid "Bold text" msgstr "Fed tekst" -#: ../ui/gtk/help-overlay.ui.h:43 +#: ui/gtk/help-overlay.ui:305 msgctxt "shortcut window" msgid "Italicize text" msgstr "Kursiv tekst" -#: ../ui/gtk/help-overlay.ui.h:44 +#: ui/gtk/help-overlay.ui:312 msgctxt "shortcut window" msgid "Underline text" msgstr "Understreget tekst" -#: ../ui/gtk/help-overlay.ui.h:45 +#: ui/gtk/help-overlay.ui:319 msgctxt "shortcut window" msgid "Strike text" msgstr "Gennemgstreget tekst" -#: ../ui/gtk/help-overlay.ui.h:46 +#: ui/gtk/help-overlay.ui:326 msgctxt "shortcut window" msgid "Insert a link" msgstr "Indsæt link" -#: ../ui/gtk/help-overlay.ui.h:47 +#: ui/gtk/help-overlay.ui:333 msgctxt "shortcut window" msgid "Remove formatting" msgstr "Fjern formatering" -#: ../ui/gtk/menus.ui.h:2 +#: ui/gtk/menus.ui:13 msgid "A_ccounts" msgstr "_Konti" -#: ../ui/gtk/menus.ui.h:4 +#: ui/gtk/menus.ui:23 msgid "_Keyboard Shortcuts" msgstr "_Tastaturgenveje" -#: ../ui/login.glade.h:1 -msgid "email@example.com" -msgstr "e-mail@eksempel.dk" +#: ui/main-toolbar.ui:51 +msgid "Toggle search bar" +msgstr "Slå søgebjælke til/fra" -#: ../ui/login.glade.h:2 ../ui/password-dialog.glade.h:3 -msgid "Password" -msgstr "Adgangskode" +#: ui/main-toolbar.ui:72 +msgid "Empty Spam or Trash folders" +msgstr "Tøm spam- eller papirkurvsmapper" -#: ../ui/login.glade.h:3 -msgid "E_mail address" -msgstr "E-_mailadresse" - -#: ../ui/login.glade.h:4 -msgid "_Password" -msgstr "_Adgangskode" - -#: ../ui/login.glade.h:5 -msgid "S_ervice" -msgstr "_Tjeneste" - -#: ../ui/login.glade.h:6 -msgid "N_ame" -msgstr "Na_vn" - -#: ../ui/login.glade.h:8 -msgid "N_ickname" -msgstr "_Kaldenavn" - -#: ../ui/login.glade.h:9 -msgid "Work, Home, etc." -msgstr "Arbejde, hjem osv." - -#: ../ui/login.glade.h:10 -msgid "_Save sent mail" -msgstr "_Gem sendt mail" - -#: ../ui/login.glade.h:11 -msgid "Addi_tional email addresses…" -msgstr "_Yderligere e-mailadresser …" - -#: ../ui/login.glade.h:12 -msgid "IMAP settings" -msgstr "IMAP-indstillinger" - -#: ../ui/login.glade.h:13 -msgid "Se_rver" -msgstr "_Server" +#: ui/main-toolbar.ui:112 +msgid "Reply" +msgstr "Svar" -#: ../ui/login.glade.h:14 -msgid "imap.example.com" -msgstr "imap.eksempel.dk" +#: ui/main-toolbar.ui:135 +msgid "Reply All" +msgstr "Svar til alle" -#: ../ui/login.glade.h:15 -msgid "P_ort" -msgstr "_Port" +#: ui/main-toolbar.ui:158 +msgid "Forward" +msgstr "Videresend" -#: ../ui/login.glade.h:16 -msgid "smtp.example.com" -msgstr "smtp.eksempel.dk" +#: ui/main-toolbar.ui:264 +msgid "Toggle find bar" +msgstr "Slå find-bjælke til/fra" -#: ../ui/login.glade.h:17 -msgid "Ser_ver" -msgstr "Se_rver" - -#: ../ui/login.glade.h:18 -msgid "Por_t" -msgstr "Por_t" - -#: ../ui/login.glade.h:19 -msgid "SMTP settings" -msgstr "SMTP-indstillinger" - -#: ../ui/login.glade.h:20 -msgid "User_name" -msgstr "Bruger_navn" - -#: ../ui/login.glade.h:21 -msgid "Pass_word" -msgstr "A_dgangskode" - -#: ../ui/login.glade.h:22 -msgid "SMTP username" -msgstr "SMTP-brugernavn" - -#: ../ui/login.glade.h:23 -msgid "SMTP password" -msgstr "SMTP-adgangskode" - -#: ../ui/login.glade.h:24 -msgid "_Username" -msgstr "_Brugernavn" - -#: ../ui/login.glade.h:25 -msgid "IMAP username" -msgstr "IMAP-brugernavn" - -#: ../ui/login.glade.h:26 -msgid "IMAP password" -msgstr "IMAP-adgangskode" - -#: ../ui/login.glade.h:27 -msgid "Encr_yption" -msgstr "Kr_yptering" - -#: ../ui/login.glade.h:28 -msgid "Encrypt_ion" -msgstr "Krypter_ing" - -#: ../ui/login.glade.h:30 -msgid "SSL/TLS" -msgstr "SSL/TLS" - -#: ../ui/login.glade.h:31 -msgid "STARTTLS" -msgstr "STARTTLS" - -#: ../ui/login.glade.h:32 -msgid "No authentication re_quired" -msgstr "Ingen autentifikation kr_ævet" - -#: ../ui/login.glade.h:33 -msgid "Use IMAP cre_dentials" -msgstr "Br_ug IMAP-akkreditiver" - -#: ../ui/login.glade.h:34 -msgid "Composer" -msgstr "Beskedskriver" - -#: ../ui/login.glade.h:35 -msgid "Save dra_fts on server" -msgstr "Gem kla_dder på serveren" - -#: ../ui/login.glade.h:36 -msgid "Si_gn emails (HTML allowed):" -msgstr "Si_gnér e-mail (HTML er tilladt):" - -#: ../ui/login.glade.h:37 -msgid "Storage" -msgstr "Lager" - -#: ../ui/login.glade.h:38 -msgid "_Download mail" -msgstr "_Download mail" +#: ui/main-toolbar.ui:306 +msgid "_Archive" +msgstr "_Arkivér" -#: ../ui/main-toolbar.ui.h:1 -msgid "Empty Spam or Trash folders" -msgstr "Tøm spam- eller papirkurvsmapper" +#: ui/main-toolbar-menus.ui:5 +msgid "Empty _Spam…" +msgstr "Tøm _spam …" + +#: ui/main-toolbar-menus.ui:9 +msgid "Empty _Trash…" +msgstr "Tøm _papirkurv …" + +#: ui/main-toolbar-menus.ui:32 +msgid "Mark as S_pam" +msgstr "Markér som s_pam" + +#: ui/main-toolbar-menus.ui:36 +msgid "Mark as not S_pam" +msgstr "Marker som ikke s_pam" + +#. Infobar title when one or more accounts are offline +#: ui/main-window.ui:183 +msgid "Working offline" +msgstr "Arbejder offline" + +#. Label and tooltip for offline infobar +#: ui/main-window.ui:197 +msgid "" +"Your computer does not appear to be connected to the Internet.\n" +"You will not be able to send or receive email until it is re-connected." +msgstr "" +"Det lader ikke til, din computer er på internettet.\n" +"Du kan ikke sende eller modtage e-mails, indtil forbindelsen er " +"genoprettet." + +#. Label and tooltip for offline infobar +#: ui/main-window.ui:200 +msgid "You will not be able to send or receive email until re-connected." +msgstr "" +"Du kan ikke sende eller modtage e-mails, indtil forbindelsen er " +"genoprettet." + +#. Button label for displaying technical details about an account problem +#. Dialog title for displaying technical details of a problem. Same as the button that invokes it. +#: ui/main-window.ui:247 ui/problem-details-dialog.ui:13 +msgid "Details" +msgstr "Detaljer" + +#. Button label for retrying an account problem +#: ui/main-window.ui:261 +msgid "Retry" +msgstr "Prøv igen" + +#. Infobar title when one or more accounts have encounted an error +#: ui/main-window.ui:294 +msgid "Account problem" +msgstr "Kontoproblem" + +#. Label and tooltip for account service problem infobar +#: ui/main-window.ui:308 +msgid "" +"Geary encountered a problem connecting to an account.\n" +"Please check your Internet connection, the server configuration and try " +"again." +msgstr "" +"Geary kunne ikke forbinde til en konto.\n" +"Tjek din internetadgang og serverkonfiguration og prøv igen" + +#. Label and tooltip for account service problem infobar +#: ui/main-window.ui:311 +msgid "Geary encountered a problem connecting to an account." +msgstr "Geary kunne ikke forbinde til en konto." + +#. Button label for retrying TLS cert validation +#: ui/main-window.ui:358 +msgid "Check" +msgstr "Undersøg" + +#. Button tooltip for retrying TLS cert validation +#: ui/main-window.ui:362 +msgid "Check the security details for the connection" +msgstr "Undersøg forbindelsens sikkerhedsdetaljer" + +#. Infobar title when one or more accounts have a TLS cert validation error +#: ui/main-window.ui:391 +msgid "Security problem" +msgstr "Sikkerhedsproblem" + +#. Label and tooltip for TLS cert validation error infobar +#: ui/main-window.ui:405 +msgid "" +"An account has reported an untrusted server.\n" +"Please check the server configuration and try again." +msgstr "" +"En konto har rapporteret en ubetroet server.\n" +"Tjek serverkonfigurationen og prøv igen." + +#. Label and tooltip for TLS cert validation error infobar +#: ui/main-window.ui:408 +msgid "An account has reported an untrusted server." +msgstr "En konto har rapporteret en ubetroet server." + +#. Button tooltip for retrying when a login error has occurred +#: ui/main-window.ui:459 +msgid "Retry login, you will be prompted for your password" +msgstr "Prøv igen at logge på; du vil blive bedt om en adgangskode" + +#. Infobar title when one or more accounts have a login error +#: ui/main-window.ui:488 +msgid "Login problem" +msgstr "Loginproblem" -#: ../ui/password-dialog.glade.h:1 +#. Label and tooltip for authentication problem infobar +#: ui/main-window.ui:502 +msgid "" +"An account has reported an incorrect login or password.\n" +"Please check your login name and try again." +msgstr "" +"En konto har rapporteret et forkert login eller en forkert adgangskode.\n" +"Tjek dit loginnavn og prøv igen." + +#. Label and tooltip for authentication problem infobar +#: ui/main-window.ui:505 +msgid "An account has reported an incorrect login or password." +msgstr "" +"En konto har rapporteret et forkert login eller en forkert adgangskode." + +#: ui/password-dialog.glade:74 msgid "SMTP Credentials" msgstr "SMTP-legitimationsoplysninger" -#: ../ui/password-dialog.glade.h:2 +#: ui/password-dialog.glade:91 msgid "Username" msgstr "Brugernavn" -#: ../ui/password-dialog.glade.h:4 +#: ui/password-dialog.glade:152 msgid "_Remember password" msgstr "_Husk adgangskode" -#: ../ui/password-dialog.glade.h:6 +#: ui/password-dialog.glade:210 msgid "_Authenticate" msgstr "_Godkend" -#: ../ui/preferences-dialog.ui.h:1 +#: ui/preferences-dialog.ui:38 msgid "Reading" msgstr "Læsning" -#: ../ui/preferences-dialog.ui.h:2 +#: ui/preferences-dialog.ui:51 msgid "_Automatically select next message" msgstr "_Vælg automatisk næste besked" -#: ../ui/preferences-dialog.ui.h:3 +#: ui/preferences-dialog.ui:70 msgid "_Display conversation preview" msgstr "_Vis samtaleforudvisning" -#: ../ui/preferences-dialog.ui.h:4 +#: ui/preferences-dialog.ui:89 msgid "Use _three pane view" msgstr "Vis _tre ruder" -#: ../ui/preferences-dialog.ui.h:5 +#: ui/preferences-dialog.ui:113 msgid "Notifications" msgstr "Påmindelser" -#: ../ui/preferences-dialog.ui.h:6 +#: ui/preferences-dialog.ui:126 msgid "_Play notification sounds" msgstr "_Afspil påmindelseslyde" -#: ../ui/preferences-dialog.ui.h:7 +#: ui/preferences-dialog.ui:145 msgid "Show _notifications for new mail" msgstr "Vis _påmindelse om ny e-mail" # oversættelse med vilje indirekte (se forklaring nedenfor) -#: ../ui/preferences-dialog.ui.h:8 -msgid "Always _watch for new mail" -msgstr "_Tjek efter ny post i baggrunden" - -#: ../ui/preferences-dialog.ui.h:9 -msgid "Geary will run in the background and notify of new mail" -msgstr "Geary vil køre i baggrunden og vise, når der kommer ny post" +#: ui/preferences-dialog.ui:164 +msgid "_Watch for new mail when closed" +msgstr "_Tjek for ny mail når lukket" + +#: ui/preferences-dialog.ui:168 +msgid "Geary will keep running after all windows are closed" +msgstr "Geary fortsætter med at køre, efter alle vinduer er blevet lukket" -#: ../ui/preferences-dialog.ui.h:10 +#: ui/preferences-dialog.ui:195 msgid "Preferences" msgstr "Indstillinger" -#: ../ui/remove_confirm.glade.h:1 +#. Button label for copying technical information to the clipboard +#: ui/problem-details-dialog.ui:17 +msgid "Copy to Clipboard" +msgstr "Kopiér til udklipsholderen" + +#. Button tooltip for copying technical information to the clipboard +#: ui/problem-details-dialog.ui:21 msgid "" -"Are you sure you want to remove this " -"account? " +"Copy technical details to clipboard for pasting into an email or bug report" msgstr "" -"Er du sikker på, at du vil fjerne " -"denne konto? " +"Kopiér tekniske detaljer til udklipsholderen for at sætte dem ind i en e-" +"mail eller en fejlrapport" -#: ../ui/remove_confirm.glade.h:2 +#: ui/problem-details-dialog.ui:73 msgid "" -"All email associated with this account will be removed from your computer. " -"This will not affect email on the server." +"If the problem is serious or persists, please copy and send these details to " +"the mailing list " +"or file a new " +"bug report." msgstr "" -"Al e-mail, der tilhører denne konto vil blive fjernet fra din computer. " -"Dette vil ikke fjerne e-mailen på serveren." +"Hvis problemet er alvorligt eller vedbliver, så kopiér og send disse " +"detaljer til mailinglisten eller registrér en ny fejlrapport." -#: ../ui/remove_confirm.glade.h:3 -msgid "Nickname:" -msgstr "Kaldenavn:" - -#: ../ui/remove_confirm.glade.h:4 -msgid "Email address:" -msgstr "E-mailadresse:" +#: ui/problem-details-dialog.ui:89 +msgid "Details:" +msgstr "Detaljer:" -#: ../ui/upgrade_dialog.glade.h:1 +#: ui/upgrade_dialog.glade:60 msgid "Geary update in progress…" msgstr "Geary er ved at blive opgraderet …" +#~ msgid "Additional addresses for %s" +#~ msgstr "Yderligere adresser for %s" + +#~ msgid "First Last" +#~ msgstr "Fornavn Efternavn" + +#~ msgid "Enter your account information to get started." +#~ msgstr "Indtast kontoinformation for at starte." + +#~ msgid "Edit" +#~ msgstr "Redigér" + +#~ msgid "Preview" +#~ msgstr "Forhåndsvisning" + +#~ msgid "Remem_ber passwords" +#~ msgstr "Hus_k adgangskoder" + +#~ msgid "Remem_ber password" +#~ msgstr "Hu_sk adgangskode" + +#~ msgid "Unable to validate:\n" +#~ msgstr "Kunne ikke validere:\n" + +#~ msgid " • Invalid account nickname.\n" +#~ msgstr " • Ugyldigt kaldenavn til bruger.\n" + +#~ msgid " • Email address already added to Geary.\n" +#~ msgstr " • E-mailadressen er allerede tilføjet til Geary.\n" + +#~ msgid " • IMAP connection error.\n" +#~ msgstr " • IMAP-forbindelsesfejl.\n" + +#~ msgid " • IMAP username or password incorrect.\n" +#~ msgstr " • Forkert brugernavn eller adgangskode til IMAP.\n" + +#~ msgid " • SMTP connection error.\n" +#~ msgstr " • SMTP-forbindelsesfejl.\n" + +#~ msgid " • SMTP username or password incorrect.\n" +#~ msgstr " • Forkert brugernavn eller adgangskode til SMTP.\n" + +#~ msgid " • Connection error.\n" +#~ msgstr " • Forbindelsesfejl.\n" + +#~ msgid " • Username or password incorrect.\n" +#~ msgstr " • Forkert brugernavn eller adgangskode.\n" + +#~ msgid "Unable to store server trust exception" +#~ msgstr "Kan ikke gemme undtagelse for tillid til server" + +#~ msgid "Your settings are insecure" +#~ msgstr "Dine indstillinger er usikre" + +#~ msgid "" +#~ "Your IMAP and/or SMTP settings do not specify SSL or TLS. This means " +#~ "your username and password could be read by another person on the " +#~ "network. Are you sure you want to do this?" +#~ msgstr "" +#~ "Dine IMAP- og/eller SMTP-indstillinger anvender ikke SSL eller TLS. Dette " +#~ "betyder, at dit brugernavn og din adgangskode kan læses af andre på " +#~ "netværket. Er du sikker på, at du vil gære dette?" + +#~ msgid "Co_ntinue" +#~ msgstr "F_ortsæt" + +#~ msgid "" +#~ "Geary encountered an error sending an email. If the problem persists, " +#~ "please manually delete the email from your Outbox folder." +#~ msgstr "" +#~ "Geary stødte ind i en fejl under afsendelse af en e-mail. Hvis dette " +#~ "problem fortsætter, bør du manuelt slette beskeden fra din “Udbakke”-" +#~ "mappe." + +#~ msgid "" +#~ "Geary encountered an error saving a sent message to Sent Mail. The " +#~ "message will stay in your Outbox folder until you delete it." +#~ msgstr "" +#~ "Der opstod en fejl, da Geary forsøgte at gemme en sendt besked til mappen " +#~ "med afsendt e-mail. Beskeden vil blive i din Udbakke, indtil du sletter " +#~ "den." + +#~ msgid "Unable to open local mailbox for %s" +#~ msgstr "Kunne ikke åbne den lokale postkasse for %s" + +#~ msgid "" +#~ "There was an error opening the local mail database for this account. This " +#~ "is possibly due to a file permissions problem.\n" +#~ "\n" +#~ "Please check that you have read/write permissions for all files in this " +#~ "directory:\n" +#~ "\n" +#~ "%s" +#~ msgstr "" +#~ "Der opstod en fejl under åbningen af den lokale e-maildatabase til denne " +#~ "konto. Dette sker sikkert på grund af problemer med filrettigheder.\n" +#~ "\n" +#~ "Tjek at du har læse- og skriverettigheder for alle filer i denne mappe.\n" +#~ "\n" +#~ "%s" + +#~ msgid "" +#~ "The version number of the local mail database is formatted for a newer " +#~ "version of Geary. Unfortunately, the database cannot be “rolled back” to " +#~ "work with this version of Geary.\n" +#~ "\n" +#~ "Please install the latest version of Geary and try again." +#~ msgstr "" +#~ "Den lokale e-maildatabases versionsnummer er til en nyere version af " +#~ "Geary. Desværre kan databasen ikke blive “rullet tilbage”, så den virker " +#~ "med denne version of Geary.\n" +#~ "\n" +#~ "Installér venligst den nyeste version af Geary og prøv igen." + +#~ msgid "" +#~ "There was an error opening the local account. This is probably due to " +#~ "connectivity issues.\n" +#~ "\n" +#~ "Please check your network connection and restart Geary." +#~ msgstr "" +#~ "Der opstod en felj under åbning af den lokele konto. Dette sker sikkert " +#~ "på grund af forbindelsesproblemer.\n" +#~ "\n" +#~ "Tjek, at din internetforbindelse virker og genstart derefter Geary." + +#~ msgid "Geary will exit if you have no other open email accounts." +#~ msgstr "Geary vil afsluttes, hvis du ikke har nogen andre åbne e-mailkonti." + +#~ msgid "IMAP" +#~ msgstr "IMAP" + +#~ msgid "SMTP" +#~ msgstr "SMTP" + +#~ msgid "Other" +#~ msgstr "Andet" + +#~ msgid "Cannot remove account " +#~ msgstr "Kan ikke fjerne konto " + +#~ msgid "" +#~ "A composer window associated with this account is currently open. Send or " +#~ "discard the message and try again." +#~ msgstr "" +#~ "Et beskedskrivningsvindue associéret med denne bruger er i forvejen " +#~ "åbent. Send eller kassér beskeden og prøv igen." + +#~ msgid "Please wait while Geary validates your account." +#~ msgstr "Vent venligst mens Geary validérer din konto." + +#~ msgid "" +#~ "Some email services require additional addresses be configured on the " +#~ "server. Contact your email provider for more information." +#~ msgstr "" +#~ "Visse e-mailtjenester kræver at der konfigureres yderligere adresser på " +#~ "serveren. Kontakt din e-mailudbyder for at få yderligere information." + +#~ msgid "_Update" +#~ msgstr "_Opdatér" + +#~ msgid "E_mail address" +#~ msgstr "E-_mailadresse" + +#~ msgid "_Password" +#~ msgstr "_Adgangskode" + +#~ msgid "S_ervice" +#~ msgstr "_Tjeneste" + +#~ msgid "N_ame" +#~ msgstr "Na_vn" + +#~ msgid "N_ickname" +#~ msgstr "_Kaldenavn" + +#~ msgid "Work, Home, etc." +#~ msgstr "Arbejde, hjem osv." + +#~ msgid "_Save sent mail" +#~ msgstr "_Gem sendt mail" + +#~ msgid "Addi_tional email addresses…" +#~ msgstr "_Yderligere e-mailadresser …" + +#~ msgid "IMAP settings" +#~ msgstr "IMAP-indstillinger" + +#~ msgid "Se_rver" +#~ msgstr "_Server" + +#~ msgid "P_ort" +#~ msgstr "_Port" + +#~ msgid "Ser_ver" +#~ msgstr "Se_rver" + +#~ msgid "Por_t" +#~ msgstr "Por_t" + +#~ msgid "User_name" +#~ msgstr "Bruger_navn" + +#~ msgid "Pass_word" +#~ msgstr "A_dgangskode" + +#~ msgid "SMTP password" +#~ msgstr "SMTP-adgangskode" + +#~ msgid "_Username" +#~ msgstr "_Brugernavn" + +#~ msgid "IMAP password" +#~ msgstr "IMAP-adgangskode" + +#~ msgid "Encr_yption" +#~ msgstr "Kr_yptering" + +#~ msgid "Encrypt_ion" +#~ msgstr "Krypter_ing" + +#~ msgid "STARTTLS" +#~ msgstr "STARTTLS" + +#~ msgid "No authentication re_quired" +#~ msgstr "Ingen autentifikation kr_ævet" + +#~ msgid "Use IMAP cre_dentials" +#~ msgstr "Br_ug IMAP-akkreditiver" + +#~ msgid "Composer" +#~ msgstr "Beskedskriver" + +#~ msgid "Si_gn emails (HTML allowed):" +#~ msgstr "Si_gnér e-mail (HTML er tilladt):" + +#~ msgid "Storage" +#~ msgstr "Lager" + +#~ msgid "" +#~ "Are you sure you want to remove " +#~ "this account? " +#~ msgstr "" +#~ "Er du sikker på, at du vil fjerne " +#~ "denne konto? " + +#~ msgid "" +#~ "All email associated with this account will be removed from your " +#~ "computer. This will not affect email on the server." +#~ msgstr "" +#~ "Al e-mail, der tilhører denne konto vil blive fjernet fra din computer. " +#~ "Dette vil ikke fjerne e-mailen på serveren." + +#~ msgid "Nickname:" +#~ msgstr "Kaldenavn:" + +#~ msgid "Default attachments directory" +#~ msgstr "Standardmappe til vedhæftede filer" + +#~ msgid "Location used when opening and saving attachments." +#~ msgstr "Placering der bruges, når vedhæftede filer åbnes og gemmes." + +#~ msgid "Default print output directory" +#~ msgstr "Standardmappe til output ved udskrivning" + +#~ msgid "Location used when printing to a file." +#~ msgstr "Placering der bruges ved udskrivning til en fil." + +#, fuzzy +#~ msgid "geary" +#~ msgstr "Geary" + +#~ msgid "" +#~ "Geary encountered an error while connecting to the server. Please try " +#~ "again in a few moments." +#~ msgstr "" +#~ "Geary stødte på en fejl i forsøget på at forbinde til serveren. Prøv igen " +#~ "om lidt." + +#~ msgid "Geary will run in the background and notify of new mail" +#~ msgstr "Geary vil køre i baggrunden og vise, når der kommer ny post" + +#~ msgid "Geary Email" +#~ msgstr "Geary E-mail" + +#~ msgid "Geary Mail" +#~ msgstr "Geary mail" + +#~ msgid "Try Again" +#~ msgstr "Prøv igen" + +#~ msgid "_Mark as…" +#~ msgstr "_Markér som …" + +#~ msgid "Add label" +#~ msgstr "Tilføj etiket" + +#~ msgid "_Label" +#~ msgstr "_Etiket" + +#~ msgid "_Move" +#~ msgstr "_Flyt" + +#~ msgid "Compose new message (Ctrl+N, N)" +#~ msgstr "Skriv ny besked (Ctrl+N, N)" + +#~ msgid "Reply (Ctrl+R, R)" +#~ msgstr "Svar (Ctrl+R, R)" + +#~ msgid "Reply all (Ctrl+Shift+R, Shift+R)" +#~ msgstr "Svar til alle (Ctrl+Skift+R, Skift+R)" + +#~ msgid "Forward (Ctrl+L, F)" +#~ msgstr "Videresend (Ctrl+L, F)" + #~ msgid "Mail Client" #~ msgstr "E-mailklient" @@ -2708,12 +3708,6 @@ #~ msgid "Copyright 2011-2014 Yorba Foundation" #~ msgstr "Copyright 2011-2014 Yorba Foundation" -#~ msgid "_Delete" -#~ msgstr "_Slet" - -#~ msgid "_Trash" -#~ msgstr "Flyt til _Papirkurven" - #~ msgid "_Donate" #~ msgstr "_Donér" @@ -2733,15 +3727,9 @@ #~ msgid "Do you want to discard the unsaved message?" #~ msgstr "Vil du kassére denne ugemte besked?" -#~ msgid "From:" -#~ msgstr "Fra:" - #~ msgid "No search results found." #~ msgstr "Ingen søgeresultater blev fundet." -#~ msgid "Date:" -#~ msgstr "Dato:" - #~ msgid "Select _Message" #~ msgstr "Vælg _besked" @@ -2786,8 +3774,5 @@ #~ msgid "Fixed Width" #~ msgstr "Fast bredde" -#~ msgid "Detach" -#~ msgstr "Fjern" - #~ msgid "_Attach File" #~ msgstr "_Vedhæft fil" diff -Nru geary-0.12.4/po/de.po geary-3.32.0/po/de.po --- geary-0.12.4/po/de.po 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/po/de.po 2019-03-17 13:39:29.000000000 +0000 @@ -12,46 +12,74 @@ # noxan , 2013 # rageltus , 2012 # Benjamin Steinwender , 2014. -# Mario Blättermann , 2016-2017. # Wolfgang Stöggl , 2016. +# Tim Sabsch , 2019. +# Mario Blättermann , 2016-2019. # msgid "" msgstr "" -"Project-Id-Version: geary-0.4.1\n" -"Report-Msgid-Bugs-To: https://bugzilla.gnome.org/enter_bug.cgi?" -"product=geary&keywords=I18N+L10N&component=internationalization\n" -"POT-Creation-Date: 2017-10-05 05:43+0000\n" -"PO-Revision-Date: 2017-10-05 20:38+0200\n" -"Last-Translator: Mario Blättermann \n" -"Language-Team: Deutsch \n" +"Project-Id-Version: geary master\n" +"Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/geary/issues\n" +"POT-Creation-Date: 2019-03-11 08:07+0000\n" +"PO-Revision-Date: 2019-03-11 14:03+0100\n" +"Last-Translator: Tim Sabsch \n" +"Language-Team: Deutsch \n" "Language: de\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Poedit 2.0.3\n" +"X-Generator: Poedit 2.2.1\n" + +#: desktop/geary-attach.contract.desktop.in:3 +msgid "Send by email" +msgstr "Als E-Mail versenden" + +#. Translators: Do NOT translate or transliterate this text (this is an icon file name)! +#: desktop/geary-attach.contract.desktop.in:5 +msgid "mail-send" +msgstr "mail-send" + +#: desktop/geary-attach.contract.desktop.in:6 +msgid "Send files using Geary" +msgstr "Dateien mit Geary versenden" #. Translators: The application name -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:2 -#: ../desktop/org.gnome.Geary.desktop.in.h:1 -#: ../desktop/geary-autostart.desktop.in.h:1 +#: desktop/geary-autostart.desktop.in:3 +#: desktop/org.gnome.Geary.appdata.xml.in:11 +#: desktop/org.gnome.Geary.desktop.in:3 +#: src/client/accounts/accounts-editor-servers-pane.vala:555 msgid "Geary" msgstr "Geary" -#. Translators: The development team's name -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:4 -msgid "Geary Development Team" -msgstr "Geary-Entwicklerteam" +#: desktop/geary-autostart.desktop.in:4 desktop/org.gnome.Geary.desktop.in:4 +msgid "Email" +msgstr "E-Mail" #. Translators: The application's summary / tagline -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:6 -#: ../desktop/org.gnome.Geary.desktop.in.h:4 -#: ../desktop/geary-autostart.desktop.in.h:4 -#: ../src/client/application/geary-application.vala:21 +#: desktop/geary-autostart.desktop.in:5 +#: desktop/org.gnome.Geary.appdata.xml.in:15 +#: desktop/org.gnome.Geary.desktop.in:5 +#: src/client/application/geary-application.vala:23 msgid "Send and receive email" msgstr "Senden und empfangen" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:7 +#. Translators: These are desktop search terms. Do not translate semicolons, end line with a semicolon. +#: desktop/geary-autostart.desktop.in:7 +msgid "Email;E-mail;Mail;" +msgstr "Email;E-Mail;Mail;" + +#. Translators: Do NOT translate or transliterate this text (this is an icon file name)! +#: desktop/geary-autostart.desktop.in:9 desktop/org.gnome.Geary.desktop.in:9 +msgid "org.gnome.Geary" +msgstr "org.gnome.Geary" + +#. Translators: The development team's name +#: desktop/org.gnome.Geary.appdata.xml.in:13 +msgid "Geary Development Team" +msgstr "Geary-Entwicklerteam" + +#: desktop/org.gnome.Geary.appdata.xml.in:17 msgid "" "Geary is an email application built around conversations, for the GNOME 3 " "desktop. It allows you to read, find and send email with a straightforward, " @@ -61,7 +89,7 @@ "ermöglicht das einfache Lesen, Finden und Schreiben von E-Mails in einer " "übersichtlichen und modernen Benutzeroberfläche." -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:8 +#: desktop/org.gnome.Geary.appdata.xml.in:22 msgid "" "Conversations allow you to read a complete discussion without having to find " "and click from message to message." @@ -69,220 +97,695 @@ "Konversationen ermöglichen das Lesen eines gesamten Diskussionsstrangs, ohne " "Nachrichten einzeln suchen zu müssen." -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:9 +#: desktop/org.gnome.Geary.appdata.xml.in:26 msgid "Geary’s features include:" msgstr "Funktionen von Geary umfassen:" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:10 +#: desktop/org.gnome.Geary.appdata.xml.in:28 msgid "Quick email account setup" msgstr "Schnelle Konto-Einrichtung" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:11 +#: desktop/org.gnome.Geary.appdata.xml.in:29 msgid "Shows related messages together in conversations" msgstr "Verwandte Nachrichten gebündelt in Konversationen anzeigen" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:12 +#: desktop/org.gnome.Geary.appdata.xml.in:30 msgid "Fast, full text and keyword search" msgstr "Schnelle Volltext- und Schlüsselwortsuche" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:13 +#: desktop/org.gnome.Geary.appdata.xml.in:31 msgid "Full-featured HTML and plain text message composer" msgstr "Voll ausgestatteter HTML- und Klartext-Nachrichteneditor" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:14 +#: desktop/org.gnome.Geary.appdata.xml.in:32 msgid "Desktop notification of new mail" msgstr "Benachrichtigung bei neuer E-Mail" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:15 +#: desktop/org.gnome.Geary.appdata.xml.in:33 msgid "Compatible with GMail, Yahoo! Mail, Outlook.com and other IMAP servers" msgstr "" "Kompatibel mit GMail, Yahoo! Mail, Outlook.com und weiteren IMAP-Servern" #. Translators: A screenshot description. -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:17 +#: desktop/org.gnome.Geary.appdata.xml.in:47 msgid "Geary displaying a conversation" msgstr "Geary zeigt eine Konversationsvorschau an" #. Translators: A screenshot description. -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:19 +#: desktop/org.gnome.Geary.appdata.xml.in:52 msgid "Geary showing the rich text composer" msgstr "Geary zeigt den Editor für formatierten Text" -#: ../desktop/org.gnome.Geary.desktop.in.h:2 -#: ../desktop/geary-autostart.desktop.in.h:2 -msgid "Email" -msgstr "E-Mail" - -# Oder Geary E-Mail -#: ../desktop/org.gnome.Geary.desktop.in.h:3 -msgid "Geary Email" -msgstr "Geary E-Mail" - #. Translators: These are desktop search terms. Do not translate semicolons, end line with a semicolon. -#: ../desktop/org.gnome.Geary.desktop.in.h:6 +#: desktop/org.gnome.Geary.desktop.in:7 msgid "Mail;E-mail;IMAP;GMail;Yahoo;Hotmail;Outlook;" msgstr "Mail;E-Mail;IMAP;GMail;Yahoo;Hotmail;Outlook;" -#: ../desktop/org.gnome.Geary.desktop.in.h:7 ../ui/gtk/menus.ui.h:1 +#: desktop/org.gnome.Geary.desktop.in:21 msgid "Compose Message" msgstr "Nachricht verfassen" -#: ../desktop/geary-autostart.desktop.in.h:3 -msgid "Geary Mail" -msgstr "Geary Mail" - -#: ../desktop/geary-autostart.desktop.in.h:5 -msgid "Email;E-mail;Mail;" -msgstr "Email;E-Mail;Mail;" - -#: ../desktop/geary-attach.contract.in.h:1 -msgid "Send by email" -msgstr "Als E-Mail versenden" +#: desktop/org.gnome.Geary.gschema.xml:8 +msgid "Maximize window" +msgstr "Fenster maximieren" + +#: desktop/org.gnome.Geary.gschema.xml:9 +msgid "True if the application window is maximized, false otherwise." +msgstr "Legt fest, ob das Anwendungsfenster maximiert werden soll." + +#: desktop/org.gnome.Geary.gschema.xml:14 +msgid "Width of window" +msgstr "Breite des Fensters" + +#: desktop/org.gnome.Geary.gschema.xml:15 +msgid "The last recorded width of the application window." +msgstr "Die zuletzt gespeicherte Breite des Anwendungsfensters." + +#: desktop/org.gnome.Geary.gschema.xml:20 +msgid "Height of window" +msgstr "Höhe des Fensters" + +#: desktop/org.gnome.Geary.gschema.xml:21 +msgid "The last recorded height of the application window." +msgstr "Die zuletzt gespeicherte Höhe des Anwendungsfensters." + +#: desktop/org.gnome.Geary.gschema.xml:26 +msgid "Position of folder list pane" +msgstr "Position der Ordnerliste" + +#: desktop/org.gnome.Geary.gschema.xml:27 +msgid "Position of the folder list Paned grabber." +msgstr "Position des Teilfensters mit der Ordnerliste." + +#: desktop/org.gnome.Geary.gschema.xml:32 +msgid "Position of folder list pane when horizontal" +msgstr "Position der Ordnerliste im horizontalen Modus" + +#: desktop/org.gnome.Geary.gschema.xml:33 +msgid "" +"Position of the folder list Paned grabber in the horizontal orientation." +msgstr "" +"Position des Teilfensters mit der Ordnerliste in horizontaler Ausrichtung." + +#: desktop/org.gnome.Geary.gschema.xml:38 +msgid "Position of folder list pane when vertical" +msgstr "Position der Ordnerliste im vertikalen Modus" + +#: desktop/org.gnome.Geary.gschema.xml:39 +msgid "Position of the folder list Paned grabber in the vertical orientation." +msgstr "" +"Position des Teilfensters mit der Ordnerliste in vertikaler Ausrichtung." + +#: desktop/org.gnome.Geary.gschema.xml:44 +msgid "Orientation of the folder list pane" +msgstr "Ausrichtung der Ordnerliste" + +#: desktop/org.gnome.Geary.gschema.xml:45 +msgid "True if the folder list Paned is in the horizontal orientation." +msgstr "Horizontale Ausrichtung des Ordnerliste-Teilfensters." + +#: desktop/org.gnome.Geary.gschema.xml:50 +msgid "Position of message list pane" +msgstr "Position der Nachrichtenliste" + +#: desktop/org.gnome.Geary.gschema.xml:51 +msgid "Position of the message list Paned grabber." +msgstr "Position des Teilfensters mit der Nachrichtenliste." + +#: desktop/org.gnome.Geary.gschema.xml:56 +msgid "Autoselect next message" +msgstr "Automatisch die nächste Nachricht wählen" + +#: desktop/org.gnome.Geary.gschema.xml:57 +msgid "True if we should autoselect the next available conversation." +msgstr "" +"Legt fest, ob die nächste verfügbare Konversation automatisch ausgewählt " +"werden soll." + +#: desktop/org.gnome.Geary.gschema.xml:62 +msgid "Display message previews" +msgstr "Nachrichtenvorschauen anzeigen" + +#: desktop/org.gnome.Geary.gschema.xml:63 +msgid "True if we should display a short preview of each message." +msgstr "" +"Legt fest, ob eine kurze Vorschau jeder Nachricht angezeigt werden soll." + +#: desktop/org.gnome.Geary.gschema.xml:68 +msgid "Languages that shall be used in the spell checker" +msgstr "Sprachen, die in der Rechtschreibprüfung verwendet werden sollen" + +#: desktop/org.gnome.Geary.gschema.xml:69 +msgid "List of the languages to use in the spell checker." +msgstr "" +"Liste der Sprachen, die in der Rechtschreibprüfung verwendet werden sollen." + +#: desktop/org.gnome.Geary.gschema.xml:74 +msgid "Languages that are displayed in the spell checker popover" +msgstr "" +"Sprachen, die im Einblendmenü der Rechtschreibprüfung angezeigt werden sollen" + +#: desktop/org.gnome.Geary.gschema.xml:75 +msgid "" +"List of languages that are always displayed in the popover of the spell " +"checker." +msgstr "" +"Liste der Sprachen, die immer im Einblendmenü der Rechtschreibprüfung " +"angezeigt werden sollen." + +#: desktop/org.gnome.Geary.gschema.xml:80 +msgid "Enable notification sounds" +msgstr "Benachrichtigungstöne aktivieren" + +#: desktop/org.gnome.Geary.gschema.xml:81 +msgid "True to play sounds for notifications and sending." +msgstr "" +"Legt fest, ob Klänge für Benachrichtigungen und beim Senden abgespielt " +"werden sollen." + +#: desktop/org.gnome.Geary.gschema.xml:86 +msgid "Show notifications for new mail" +msgstr "Benachrichtigung bei neuen Nachrichten anzeigen" + +#: desktop/org.gnome.Geary.gschema.xml:87 +msgid "True to show notification bubbles." +msgstr "Legt fest, ob Benachrichtigungen angezeigt werden sollen." + +#: desktop/org.gnome.Geary.gschema.xml:92 +msgid "Notify of new mail at startup" +msgstr "Beim Start über neue Nachrichten benachrichtigen" + +#: desktop/org.gnome.Geary.gschema.xml:93 +msgid "True to notify of new mail at startup." +msgstr "" +"Legt fest, ob beim Start Benachrichtigungen bei neuen Nachrichten angezeigt " +"werden sollen." + +#: desktop/org.gnome.Geary.gschema.xml:98 +msgid "Ask when opening an attachment" +msgstr "Fragen, wenn ein Anhang geöffnet wird" + +#: desktop/org.gnome.Geary.gschema.xml:99 +msgid "True to ask when opening an attachment." +msgstr "Legt fest, ob nachgefragt werden soll, wenn ein Anhang geöffnet wird." + +#: desktop/org.gnome.Geary.gschema.xml:104 +msgid "Whether to compose emails in HTML" +msgstr "Nachrichten in HTML verfassen" + +#: desktop/org.gnome.Geary.gschema.xml:105 +msgid "True to compose emails in HTML; false for plain text." +msgstr "Legt fest, ob Nachrichten in HTML verfasst werden sollen." + +#: desktop/org.gnome.Geary.gschema.xml:110 +msgid "Advisory strategy for full-text searching" +msgstr "Strategie für Volltextsuche" + +#: desktop/org.gnome.Geary.gschema.xml:111 +msgid "" +"Acceptable values are “exact”, “conservative”, “aggressive”, and “horizon”." +msgstr "" +"Zulässige Werte sind »exact«, »conservative«, »aggressive« und »horizon«." + +#: desktop/org.gnome.Geary.gschema.xml:116 +msgid "Zoom of conversation viewer" +msgstr "Vergrößerungsstufe der Konversationsansicht" + +#: desktop/org.gnome.Geary.gschema.xml:117 +msgid "The zoom to apply on the conservation view." +msgstr "Der auf die Konversationsansicht anzuwendende Vergrößerungsstufe." + +#: desktop/org.gnome.Geary.gschema.xml:122 +msgid "Size of detached composer window" +msgstr "Größe des abgekoppelten Verfassen-Fensters" + +#: desktop/org.gnome.Geary.gschema.xml:123 +msgid "The last recorded size of the detached composer window." +msgstr "Die zuletzt gespeicherte Größe des abgekoppelten Verfassen-Fensters." + +#: desktop/org.gnome.Geary.gschema.xml:128 +msgid "Whether we migrated the old settings" +msgstr "Alte Einstellungen migrieren" + +#: desktop/org.gnome.Geary.gschema.xml:129 +msgid "" +"False to check for the old “org.yorba.geary”-schema and copy its values." +msgstr "" +"Wenn nicht aktiviert, wird nach dem alten Schema »org.yorba.geary« geschaut " +"und dessen Werte übernommen." + +#. Translators: In-app notification label, when +#. the app had a problem pinning an otherwise +#. untrusted TLS certificate +#: src/client/accounts/accounts-editor.vala:203 +msgid "Failed to store certificate" +msgstr "Zertifikat konnte nicht gespeichert werden" + +#. Translators: Label for adding an email account +#. account for a generic IMAP service provider. +#: src/client/accounts/accounts-editor-add-pane.vala:109 +msgid "All others" +msgstr "Alle übrigen" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:196 +#: src/client/accounts/accounts-editor-servers-pane.vala:316 +msgid "Check your receiving login and password" +msgstr "Prüfen Sie Ihren Benutzernamen und Passwort zum Empfangen" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:211 +#: src/client/accounts/accounts-editor-servers-pane.vala:329 +msgid "Check your receiving server details" +msgstr "Prüfen Sie die Serverdetails zum Empfangen" + +#. Translators: In-app notification label +#. There was an SMTP auth error, but IMAP already +#. succeeded, so the user probably needs to +#. specify custom creds here +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:233 +#: src/client/accounts/accounts-editor-servers-pane.vala:350 +msgid "Check your sending login and password" +msgstr "Prüfen Sie Ihren Benutzernamen und Passwort zum Senden" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:247 +#: src/client/accounts/accounts-editor-servers-pane.vala:363 +msgid "Check your sending server details" +msgstr "Prüfen Sie die Serverdetails zum Senden" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:262 +msgid "Check your email address and password" +msgstr "Prüfen Sie Ihre E-Mail-Adresse und Ihr Passwort" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:273 +msgid "Could not connect, check your network" +msgstr "Verbindung konnte nicht hergestellt werden, prüfen Sie Ihr Netzwerk" + +#. Translators: In-app notification label for a +#. generic error creating an account +#: src/client/accounts/accounts-editor-add-pane.vala:286 +msgid "An unexpected problem occurred" +msgstr "Ein unerwartetes Problem ist aufgetreten" + +#. Translators: In-app notification label, the +#. string substitution is a more detailed reason. +#: src/client/accounts/accounts-editor-add-pane.vala:304 +#, c-format +msgid "Account not created: %s" +msgstr "Konto wurde nicht erstellt: %s" + +#. Translators: Label for the person's actual name when adding +#. an account +#: src/client/accounts/accounts-editor-add-pane.vala:551 +msgid "Your name" +msgstr "Ihr Name" + +#. Translators: Label used for the address part of an +#. email address when editing a user's sender address +#. preferences for an account. +#: src/client/accounts/accounts-editor-add-pane.vala:568 +#: src/client/accounts/accounts-editor-edit-pane.vala:501 +msgid "Email address" +msgstr "E-Mail-Adresse" + +#. Translators: Placeholder for the default sender address +#. when adding an account +#. Translators: This is used as a placeholder for the +#. address part of an email address when editing a user's +#. sender address preferences for an account. +#: src/client/accounts/accounts-editor-add-pane.vala:571 +#: src/client/accounts/accounts-editor-edit-pane.vala:469 +msgid "person@example.com" +msgstr "name@beispiel.de" + +#. Translators: Label for an IMAP/SMTP service login/user name +#. when adding an account +#. Translators: Label for the user's login name for an +#. IMAP, SMTP, etc service +#: src/client/accounts/accounts-editor-add-pane.vala:585 +#: src/client/accounts/accounts-editor-servers-pane.vala:880 +msgid "Login name" +msgstr "Benutzername" -#: ../desktop/geary-attach.contract.in.h:2 -msgid "Send files using Geary" -msgstr "Dateien mit Geary versenden" +#. Translators: Label for the user's password for an IMAP, +#. SMTP, etc service +#: src/client/accounts/accounts-editor-add-pane.vala:599 +#: src/client/accounts/accounts-editor-servers-pane.vala:999 +#: ui/password-dialog.glade:108 +msgid "Password" +msgstr "Passwort" -#: ../src/client/accounts/account-dialog-add-edit-pane.vala:51 -#: ../src/client/components/stock.vala:31 -#: ../ui/conversation-email-menus.ui.h:10 -msgid "_Save" -msgstr "_Speichern" +#. Translators: Label for the IMAP server hostname when +#. adding an account. +#. Translators: This label describes the host name or IP +#. address and port used by an account's IMAP service. +#: src/client/accounts/accounts-editor-add-pane.vala:621 +#: src/client/accounts/accounts-editor-servers-pane.vala:727 +msgid "IMAP server" +msgstr "IMAP-Server" + +#. Translators: Placeholder for the IMAP server hostname +#. when adding an account. +#: src/client/accounts/accounts-editor-add-pane.vala:624 +msgid "imap.example.com" +msgstr "imap.example.com" -#: ../src/client/accounts/account-dialog-add-edit-pane.vala:51 -#: ../src/client/components/stock.vala:22 -msgid "_Add" -msgstr "_Hinzufügen" +#. Translators: Label for the SMTP server hostname when +#. adding an account. +#. Translators: This label describes the host name or IP +#. address and port used by an account's SMTP service. +#: src/client/accounts/accounts-editor-add-pane.vala:630 +#: src/client/accounts/accounts-editor-servers-pane.vala:733 +msgid "SMTP server" +msgstr "SMTP-Server" + +#. Translators: Placeholder for the SMTP server hostname +#. when adding an account. +#: src/client/accounts/accounts-editor-add-pane.vala:633 +msgid "smtp.example.com" +msgstr "smtp.example.com" -#. reset/clear widgets -#: ../src/client/accounts/account-dialog-edit-alternate-emails-pane.vala:120 +#. Translators: Label in the account editor for the user's +#. custom name for an account. +#: src/client/accounts/accounts-editor-edit-pane.vala:278 +#: ui/accounts_editor_remove_pane.ui:123 +msgid "Account name" +msgstr "Kontoname" + +#. Translators: Tooltip used to undo changing +#. the name of an account. The string +#. substitution is the old name of the +#. account. +#: src/client/accounts/accounts-editor-edit-pane.vala:312 +#, c-format +msgid "Change account name back to “%s”" +msgstr "Ändern Sie den Kontonamen zurück zu »%s«" + +#. Translators: Tooltip for adding a new email sender/from +#. address's address to an account +#: src/client/accounts/accounts-editor-edit-pane.vala:336 +msgid "Add a new sender email address" +msgstr "Eine neue E-Mail-Adresse zum Senden hinzufügen" + +#. Translators: Label used to indicate the user has +#. provided no display name for one of their sender +#. email addresses in their account settings. +#: src/client/accounts/accounts-editor-edit-pane.vala:417 +msgid "Name not set" +msgstr "Name ist nicht gesetzt" + +#. Translators: This is used as a placeholder for the +#. display name for an email address when editing a user's +#. sender address preferences for an account. +#: src/client/accounts/accounts-editor-edit-pane.vala:456 +msgid "Sender Name" +msgstr "Absendername" + +#: src/client/accounts/accounts-editor-edit-pane.vala:479 +msgid "Remove" +msgstr "Entfernen" + +#. Translators: Label used for the display name part of an +#. email address when editing a user's sender address +#. preferences for an account. +#: src/client/accounts/accounts-editor-edit-pane.vala:494 +msgid "Sender name" +msgstr "Absendername" + +#. Translators: Label used as the undo tooltip after adding an +#. new sender email address to an account. The string +#. substitution is the email address added. +#: src/client/accounts/accounts-editor-edit-pane.vala:561 +#, c-format +msgid "Remove “%s”" +msgstr "»%s« entfernen" + +#. Translators: Label used as the undo tooltip after editing a +#. sender address for an account. The string substitution is +#. the email address edited. +#: src/client/accounts/accounts-editor-edit-pane.vala:601 +#, c-format +msgid "Undo changes to “%s”" +msgstr "Änderungen rückgängig machen zu »%s«" + +#. Translators: Label used as the undo tooltip after removing +#. a sender address from an account. The string substitution +#. is the email address edited. +#: src/client/accounts/accounts-editor-edit-pane.vala:688 +#, c-format +msgid "Add “%s” back" +msgstr "»%s« wieder hinzufügen" + +#. Translators: Label used as the undo tooltip after removing +#. a sender address from an account. The string substitution +#. is the email address edited. +#: src/client/accounts/accounts-editor-edit-pane.vala:730 +msgid "Undo signature changes" +msgstr "Änderungen an der Signatur rückgängig machen" + +#. Translators: This label describes the account +#. preference for the length of time (weeks, months or +#. years) that past email should be downloaded. +#: src/client/accounts/accounts-editor-edit-pane.vala:778 +msgid "Download mail" +msgstr "E-Mail herunterladen" + +#. Translators: Tooltip for undoing a change +#. to the length of time that past email +#. should be downloaded for an account. The +#. string substitution is the duration, +#. e.g. "1 month back". +#: src/client/accounts/accounts-editor-edit-pane.vala:810 #, c-format -msgid "Additional addresses for %s" -msgstr "Zusätzliche Adressen für %s" +msgid "Change download period back to: %s" +msgstr "Download-Länge zurücksetzen auf: %s" -#. Sets min size. -#: ../src/client/accounts/account-dialog.vala:21 -msgid "Accounts" -msgstr "Konten" - -#. Copyright 2016 Software Freedom Conservancy Inc. -#. * -#. * This software is licensed under the GNU Lesser General Public License -#. * (version 2.1 or later). See the COPYING file in this distribution. -#. -#. Page for adding or editing an account. -#. / Placeholder text indicating that the user should type their first name and last name -#: ../src/client/accounts/add-edit-page.vala:10 -msgid "First Last" -msgstr "Vorname Nachname" - -#: ../src/client/accounts/add-edit-page.vala:235 -msgid "Welcome to Geary." -msgstr "Willkommen bei Geary." - -#: ../src/client/accounts/add-edit-page.vala:235 -msgid "Enter your account information to get started." -msgstr "Tragen Sie zu Beginn bitte Ihre Kontodaten ein." +#: src/client/accounts/accounts-editor-edit-pane.vala:831 +msgid "Everything" +msgstr "Alles" -#: ../src/client/accounts/add-edit-page.vala:255 +#: src/client/accounts/accounts-editor-edit-pane.vala:835 msgid "2 weeks back" msgstr "2 Wochen zurück" -#. IDs are # of days -#: ../src/client/accounts/add-edit-page.vala:256 +#: src/client/accounts/accounts-editor-edit-pane.vala:839 msgid "1 month back" msgstr "1 Monat zurück" -#: ../src/client/accounts/add-edit-page.vala:257 +#: src/client/accounts/accounts-editor-edit-pane.vala:843 msgid "3 months back" msgstr "3 Monate zurück" -#: ../src/client/accounts/add-edit-page.vala:258 +#: src/client/accounts/accounts-editor-edit-pane.vala:847 msgid "6 months back" msgstr "6 Monate zurück" -#: ../src/client/accounts/add-edit-page.vala:259 +#: src/client/accounts/accounts-editor-edit-pane.vala:851 msgid "1 year back" msgstr "1 Jahr zurück" -#: ../src/client/accounts/add-edit-page.vala:260 +#: src/client/accounts/accounts-editor-edit-pane.vala:855 msgid "2 years back" msgstr "2 Jahre zurück" -#: ../src/client/accounts/add-edit-page.vala:261 +#: src/client/accounts/accounts-editor-edit-pane.vala:859 msgid "4 years back" msgstr "4 Jahre zurück" -#. Separator -#: ../src/client/accounts/add-edit-page.vala:263 -msgid "Everything" -msgstr "Alles" +#: src/client/accounts/accounts-editor-edit-pane.vala:865 +#, c-format +msgid "%d day back" +msgid_plural "%d days back" +msgstr[0] "%d Tag zurück" +msgstr[1] "%d Tage zurück" + +#: src/client/accounts/accounts-editor-list-pane.vala:243 +msgid "Undo" +msgstr "Rückgängig machen" + +#: src/client/accounts/accounts-editor-list-pane.vala:251 +msgid "Redo" +msgstr "Wiederholen" + +#: src/client/accounts/accounts-editor-list-pane.vala:345 +#: src/client/accounts/accounts-editor-list-pane.vala:433 +#: src/client/accounts/accounts-editor-row.vala:278 +msgid "Gmail" +msgstr "Gmail" + +#: src/client/accounts/accounts-editor-list-pane.vala:349 +#: src/client/accounts/accounts-editor-list-pane.vala:437 +#: src/client/accounts/accounts-editor-row.vala:282 +msgid "Outlook.com" +msgstr "Outlook.com" + +#: src/client/accounts/accounts-editor-list-pane.vala:353 +#: src/client/accounts/accounts-editor-list-pane.vala:441 +#: src/client/accounts/accounts-editor-row.vala:286 +msgid "Yahoo" +msgstr "Yahoo" + +#. Translators: Tooltip for accounts that have been +#. loaded but disabled by the user. +#: src/client/accounts/accounts-editor-list-pane.vala:371 +msgid "This account has been disabled" +msgstr "Dieses Konto wurde deaktiviert" + +#. Translators: Tooltip for accounts that have been +#. loaded but because of some error are not able to be +#. used. +#: src/client/accounts/accounts-editor-list-pane.vala:380 +msgid "This account has encountered a problem and is unavailable" +msgstr "" +"Bei diesem Konto ist ein Fehler aufgetreten und es ist daher nicht verfügbar" + +#. Translators: Label for adding a generic email account +#: src/client/accounts/accounts-editor-list-pane.vala:430 +msgid "Other email providers" +msgstr "Andere E-Mail-Anbieter" + +#. Translators: Notification shown after removing an +#. account. The string substitution is the name of the +#. account. +#: src/client/accounts/accounts-editor-list-pane.vala:547 +#, c-format +msgid "Account “%s” removed" +msgstr "Konto »%s« wurde entfernt" + +#. Translators: Notification shown after removing an account +#. is undone. The string substitution is the name of the +#. account. +#: src/client/accounts/accounts-editor-list-pane.vala:554 +#, c-format +msgid "Account “%s” restored" +msgstr "Konto »%s« wurde wiederhergestellt" + +#. Translators: Tooltip for dragging list items +#: src/client/accounts/accounts-editor-row.vala:49 +msgid "Drag to move this item" +msgstr "Klicken Sie auf das Objekt und ziehen Sie es, um es zu bewegen" + +#. Translators: Label describes the service provider +#. hosting the email account, e.g. Gmail, Yahoo, or some +#. other generic IMAP service. +#: src/client/accounts/accounts-editor-row.vala:294 +msgid "Service provider" +msgstr "Anbieter" + +#. Translators: This label describes what form of transport +#. security (TLS, StartTLS, etc) used by an account's IMAP or SMTP +#. service. +#: src/client/accounts/accounts-editor-row.vala:467 +msgid "Connection security" +msgstr "Sicherheit der Verbindung" + +#. Translators: Label used when no auth scheme is used +#. by an account's IMAP or SMTP service. +#: src/client/accounts/accounts-editor-row.vala:478 +#: src/client/accounts/accounts-editor-servers-pane.vala:752 +#: src/client/accounts/accounts-editor-servers-pane.vala:964 +#: src/engine/api/geary-special-folder-type.vala:58 +msgid "None" +msgstr "Nichts" -#: ../src/client/accounts/add-edit-page.vala:283 -msgid "Edit" -msgstr "Bearbeiten" - -#: ../src/client/accounts/add-edit-page.vala:285 -msgid "Preview" -msgstr "Vorschau" - -#: ../src/client/accounts/add-edit-page.vala:751 -msgid "Remem_ber passwords" -msgstr "Passwörter _merken" - -#: ../src/client/accounts/add-edit-page.vala:758 ../ui/login.glade.h:7 -msgid "Remem_ber password" -msgstr "Passwort _merken" - -#: ../src/client/accounts/add-edit-page.vala:792 -msgid "Unable to validate:\n" -msgstr "Bestätigung nicht möglich:\n" - -#: ../src/client/accounts/add-edit-page.vala:794 -msgid " • Invalid account nickname.\n" -msgstr " • Ungültiger Konto-Nickname.\n" - -#: ../src/client/accounts/add-edit-page.vala:797 -msgid " • Email address already added to Geary.\n" -msgstr " • E-Mail-Adresse wurde bereits zu Geary hinzugefügt.\n" - -#: ../src/client/accounts/add-edit-page.vala:801 -msgid " • IMAP connection error.\n" -msgstr " • IMAP-Verbindungsfehler.\n" - -#: ../src/client/accounts/add-edit-page.vala:804 -msgid " • IMAP username or password incorrect.\n" -msgstr " • IMAP-Benutzername oder Passwort ungültig.\n" - -#: ../src/client/accounts/add-edit-page.vala:807 -msgid " • SMTP connection error.\n" -msgstr " • SMTP-Verbindungsfehler.\n" - -#: ../src/client/accounts/add-edit-page.vala:810 -msgid " • SMTP username or password incorrect.\n" -msgstr " • SMTP-Benutzername oder Passwort ungültig.\n" - -#: ../src/client/accounts/add-edit-page.vala:814 -msgid " • Connection error.\n" -msgstr " • Verbindungsfehler.\n" - -#: ../src/client/accounts/add-edit-page.vala:818 -msgid " • Username or password incorrect.\n" -msgstr " • IMAP-Benutzername oder Passwort ungültig.\n" +#: src/client/accounts/accounts-editor-row.vala:485 +msgid "StartTLS" +msgstr "StartTLS" + +#: src/client/accounts/accounts-editor-row.vala:492 +msgid "TLS" +msgstr "TLS" + +#. Translators: Label for source of SMTP authentication +#. credentials (none, use IMAP, custom) when adding a new +#. account +#. Button label for retrying when a login error has occurred +#: src/client/accounts/accounts-editor-row.vala:533 ui/main-window.ui:455 +msgid "Login" +msgstr "Anmelden" + +#. Translators: ComboBox value for source of SMTP +#. authentication credentials (none) when adding a new +#. account +#: src/client/accounts/accounts-editor-row.vala:540 +msgid "No login needed" +msgstr "Keine Anmeldung erforderlich" + +#. Translators: ComboBox value for source of SMTP +#. authentication credentials (use IMAP) when adding a new +#. account +#: src/client/accounts/accounts-editor-row.vala:548 +msgid "Use same login as receiving" +msgstr "Dieselben Anmeldedaten zum Empfangen verwenden" + +#. Translators: ComboBox value for source of SMTP +#. authentication credentials (custom) when adding a new +#. account +#: src/client/accounts/accounts-editor-row.vala:556 +msgid "Use a different login" +msgstr "Andere Anmeldedaten verwenden" + +#. Translators: In-app notification label, the +#. string substitution is a more detailed reason. +#: src/client/accounts/accounts-editor-servers-pane.vala:377 +#, c-format +msgid "Account not updated: %s" +msgstr "Konto wurde nicht aktualisiert: %s" + +#. Translators: This label describes the program that +#. created the account, e.g. an SSO service like GOA, or +#. locally by Geary. +#: src/client/accounts/accounts-editor-servers-pane.vala:540 +msgid "Account source" +msgstr "Kontoquelle" + +#: src/client/accounts/accounts-editor-servers-pane.vala:552 +msgid "GNOME Online Accounts" +msgstr "GNOME Online-Konten" + +#. Translators: This label describes an account +#. preference. +#: src/client/accounts/accounts-editor-servers-pane.vala:611 +msgid "Save drafts on server" +msgstr "Entwürfe auf dem Server speichern" + +#. Translators: This label describes an account +#. preference. +#: src/client/accounts/accounts-editor-servers-pane.vala:666 +msgid "Save sent email on server" +msgstr "Gesendete E-Mails auf dem Server speichern" + +#. Add a suffix for OAuth2 auth so people know they +#. shouldn't expect to be prompted for a password +#. Translators: Label used when an account's IMAP or +#. SMTP service uses OAuth2. The string replacement is +#. the service's login name. +#: src/client/accounts/accounts-editor-servers-pane.vala:950 +#, c-format +msgid "%s using OAuth2" +msgstr "%s mittels OAuth2" + +#: src/client/accounts/accounts-editor-servers-pane.vala:960 +msgid "Use receiving server login" +msgstr "Server-Anmeldedaten zum Empfangen verwenden" -#: ../src/client/application/geary-application.vala:22 +#: src/client/application/geary-application.vala:24 msgid "Copyright 2016 Software Freedom Conservancy Inc." msgstr "Copyright 2016 Software Freedom Conservancy Inc." -#: ../src/client/application/geary-application.vala:23 -msgid "Copyright 2016-2017 Geary Development Team." -msgstr "Copyright 2016-2017 Geary-Entwicklerteam." +#: src/client/application/geary-application.vala:25 +msgid "Copyright 2016-2019 Geary Development Team." +msgstr "Copyright 2016-2019 Geary-Entwicklerteam." -#: ../src/client/application/geary-application.vala:25 +#: src/client/application/geary-application.vala:27 msgid "Visit the Geary web site" msgstr "Geary-Webseite besuchen" -#: ../src/client/application/geary-application.vala:466 +#: src/client/application/geary-application.vala:454 #, c-format msgid "About %s" msgstr "Info zu %s" @@ -290,321 +793,116 @@ #. Translators: add your name and email address to receive #. credit in the About dialog For example: Yamada Taro #. -#: ../src/client/application/geary-application.vala:470 +#: src/client/application/geary-application.vala:458 msgid "translator-credits" msgstr "" "Benjamin Steinwender \n" "Wolfgang Stöggl \n" -"Mario Blättermann " +"Mario Blättermann \n" +"Tim Sabsch " -#: ../src/client/application/geary-args.vala:10 +#: src/client/application/geary-args.vala:10 msgid "Start Geary with hidden main window" msgstr "Geary mit verborgenem Hauptfenster starten" -#: ../src/client/application/geary-args.vala:11 +#: src/client/application/geary-args.vala:11 msgid "Output debugging information" msgstr "Debuginformationen ausgeben" -#: ../src/client/application/geary-args.vala:12 +#: src/client/application/geary-args.vala:12 msgid "Log conversation monitoring" msgstr "Gesprächsmonitoring protokollieren" -#: ../src/client/application/geary-args.vala:13 +#: src/client/application/geary-args.vala:13 msgid "Log network deserialization" msgstr "Netzwerkdeserialisierung protokollieren" -#: ../src/client/application/geary-args.vala:14 +#: src/client/application/geary-args.vala:14 msgid "Log network activity" msgstr "Netzwerkaktivitäten protokollieren" #. / The IMAP replay queue is how changes on the server are replicated on the client. #. / It could also be called the IMAP events queue. -#: ../src/client/application/geary-args.vala:17 +#: src/client/application/geary-args.vala:17 msgid "Log IMAP replay queue" msgstr "IMAP-Ereigniswarteschlange protokollieren" #. / Serialization is how commands and responses are converted into a stream of bytes for #. / network transmission -#: ../src/client/application/geary-args.vala:20 +#: src/client/application/geary-args.vala:20 msgid "Log network serialization" msgstr "Netzwerkserialisierung protokollieren" -#: ../src/client/application/geary-args.vala:21 +#: src/client/application/geary-args.vala:21 msgid "Log periodic activity" msgstr "Regelmäßige Aktivitäten protokollieren" -#: ../src/client/application/geary-args.vala:22 +#: src/client/application/geary-args.vala:22 msgid "Log database queries (generates lots of messages)" msgstr "" "Datenbankabfragen protokollieren (dabei werden sehr viele Einträge erzeugt)" #. / "Normalization" can also be called "synchronization" -#: ../src/client/application/geary-args.vala:24 +#: src/client/application/geary-args.vala:24 msgid "Log folder normalization" msgstr "Ordnerabgleich protokollieren" -#: ../src/client/application/geary-args.vala:25 +#: src/client/application/geary-args.vala:25 msgid "Allow inspection of WebView" msgstr "Untersuchung der Webansicht erlauben" -#: ../src/client/application/geary-args.vala:26 +#: src/client/application/geary-args.vala:26 msgid "Revoke all server certificates with TLS warnings" msgstr "Alle Server-Zertifikate mit TLS-Warnungen widerrufen" -#: ../src/client/application/geary-args.vala:27 +#: src/client/application/geary-args.vala:27 msgid "Perform a graceful quit" msgstr "Ordnungsgemäß beenden" -#: ../src/client/application/geary-args.vala:28 +#: src/client/application/geary-args.vala:28 msgid "Display program version" msgstr "Programmversion anzeigen" #. This gives a command-line hint on how to open new composer windows with mailto: -#: ../src/client/application/geary-args.vala:53 +#: src/client/application/geary-args.vala:53 #, c-format msgid "Use %s to open a new composer window" msgstr "Benutzen Sie %s, um ein neues »Verfassen«-Fenster zu öffnen" -#: ../src/client/application/geary-args.vala:56 +#: src/client/application/geary-args.vala:56 msgid "Please report comments, suggestions and bugs to:" msgstr "Bitte melden Sie Kommentare, Vorschläge und Fehler an:" #. i18n: Command line arguments are invalid -#: ../src/client/application/geary-args.vala:63 +#: src/client/application/geary-args.vala:63 #, c-format msgid "Failed to parse command line options: %s\n" msgstr "Die Befehlszeilenargumente konnten nicht interpretiert werden: %s\n" -#: ../src/client/application/geary-args.vala:74 +#: src/client/application/geary-args.vala:74 #, c-format msgid "Unrecognized command line option “%s”\n" msgstr "Unbekanntes Befehlszeilenargument »%s«\n" -#: ../src/client/application/geary-controller.vala:57 -msgid "Delete conversation" -msgstr "Konversation löschen" - -#: ../src/client/application/geary-controller.vala:58 -msgid "Delete conversation (Shift+Delete)" -msgstr "Konversation löschen (Umschalt+Entf)" - -#: ../src/client/application/geary-controller.vala:59 -msgid "Delete conversations (Shift+Delete)" -msgstr "Konversationen löschen (Umschalt+Entf)" - -#. This refers to the action ("move email to the trash"), not the Trash folder itself -#: ../src/client/application/geary-controller.vala:63 -msgid "Move conversation to Trash (Delete, Backspace)" -msgstr "Konversation in den Papierkorb verschieben (Entf, Rücktaste)" - -#: ../src/client/application/geary-controller.vala:64 -msgid "Move conversations to Trash (Delete, Backspace)" -msgstr "Konversationen in den Papierkorb verschieben (Entf, Rücktaste)" - -#. This refers to the action ("archive an email"), not the Archive folder itself -#: ../src/client/application/geary-controller.vala:68 -msgid "_Archive" -msgstr "_Archivieren" - -#: ../src/client/application/geary-controller.vala:69 -msgid "Archive conversation (A)" -msgstr "Konversation archivieren (A)" - -#: ../src/client/application/geary-controller.vala:70 -msgid "Archive conversations (A)" -msgstr "Konversationen archivieren (A)" - -#: ../src/client/application/geary-controller.vala:73 -msgid "Mark as S_pam" -msgstr "Als S_pam markieren" - -#: ../src/client/application/geary-controller.vala:74 -msgid "Mark as not S_pam" -msgstr "S_pam-Markierung entfernen" - -#: ../src/client/application/geary-controller.vala:76 -#: ../src/client/application/geary-controller.vala:448 -msgid "Mark conversation" -msgstr "Konversation markieren" - -#: ../src/client/application/geary-controller.vala:77 -msgid "Mark conversations" -msgstr "Konversationen markieren" - -#: ../src/client/application/geary-controller.vala:78 -msgid "Add label to conversation" -msgstr "Konversation eine Beschriftung zuweisen" - -#: ../src/client/application/geary-controller.vala:79 -msgid "Add label to conversations" -msgstr "Konversationen eine Beschriftung zuweisen" - -#: ../src/client/application/geary-controller.vala:80 -#: ../src/client/application/geary-controller.vala:487 -msgid "Move conversation" -msgstr "Konversation verschieben" - -#: ../src/client/application/geary-controller.vala:81 -msgid "Move conversations" -msgstr "Konversationen verschieben" - -#: ../src/client/application/geary-controller.vala:450 -msgid "_Mark as…" -msgstr "_Markieren als …" - -#: ../src/client/application/geary-controller.vala:456 -msgid "Mark as _Read" -msgstr "Als _gelesen markieren" - -#: ../src/client/application/geary-controller.vala:462 -msgid "Mark as _Unread" -msgstr "Als _ungelesen markieren" - -#: ../src/client/application/geary-controller.vala:468 -msgid "_Star" -msgstr "_Stern" - -#: ../src/client/application/geary-controller.vala:473 -msgid "U_nstar" -msgstr "_Stern entfernen" - -#: ../src/client/application/geary-controller.vala:483 -msgid "Add label" -msgstr "Beschriftung zuweisen" - -#: ../src/client/application/geary-controller.vala:484 -msgid "_Label" -msgstr "_Beschriftung" - -#: ../src/client/application/geary-controller.vala:488 -msgid "_Move" -msgstr "_Verschieben" - -#: ../src/client/application/geary-controller.vala:492 -msgid "Compose new message (Ctrl+N, N)" -msgstr "Neue Nachricht verfassen (Strg+N, N)" - -#: ../src/client/application/geary-controller.vala:496 -#: ../ui/conversation-email-menus.ui.h:1 -msgid "_Reply" -msgstr "_Antworten" - -#: ../src/client/application/geary-controller.vala:497 -msgid "Reply (Ctrl+R, R)" -msgstr "Antworten (Strg+R, R)" - -#: ../src/client/application/geary-controller.vala:501 -msgid "R_eply All" -msgstr "Allen _antworten" - -#: ../src/client/application/geary-controller.vala:502 -msgid "Reply all (Ctrl+Shift+R, Shift+R)" -msgstr "Allen antworten (Strg+Umschalt+R, Umschalt+R)" - -#: ../src/client/application/geary-controller.vala:507 -#: ../ui/conversation-email-menus.ui.h:3 -msgid "_Forward" -msgstr "_Weiterleiten" - -#: ../src/client/application/geary-controller.vala:508 -msgid "Forward (Ctrl+L, F)" -msgstr "Weiterleiten (Strg+L, F)" - -#: ../src/client/application/geary-controller.vala:538 -msgid "Empty _Spam…" -msgstr "_Spam leeren …" - -#: ../src/client/application/geary-controller.vala:542 -msgid "Empty _Trash…" -msgstr "_Papierkorb leeren …" - -#. No callback is connected, since we bind the toggle button to the search bar visibility -#: ../src/client/application/geary-controller.vala:574 -msgid "Toggle search bar" -msgstr "Suchleiste anzeigen/verbergen" - -#. No callback is connected, since we bind the toggle button to the find bar visibility -#: ../src/client/application/geary-controller.vala:579 -msgid "Toggle find bar" -msgstr "Suchleiste anzeigen/verbergen" - -#: ../src/client/application/geary-controller.vala:757 -msgid "Unable to store server trust exception" -msgstr "Ausnahme für Serververtrauen konnte nicht gespeichert werden" - -#: ../src/client/application/geary-controller.vala:992 -msgid "Your settings are insecure" -msgstr "Ihre Einstellungen sind unsicher" - -#: ../src/client/application/geary-controller.vala:993 -msgid "" -"Your IMAP and/or SMTP settings do not specify SSL or TLS. This means your " -"username and password could be read by another person on the network. Are " -"you sure you want to do this?" -msgstr "" -"In Ihren IMAP- und/oder SMTP-Einstellungen sind weder SSL noch TLS " -"festgelegt. Dies bedeutet, dass Ihr Benutzername und Ihr Passwort von " -"anderen Personen im selben Netzwerk gelesen werden können. Sind Sie sicher, " -"dass Sie dies tun möchten?" - -#: ../src/client/application/geary-controller.vala:994 -msgid "Co_ntinue" -msgstr "_Weiter" - -#: ../src/client/application/geary-controller.vala:1041 -msgid "Error connecting to the server" -msgstr "Fehler bei der Verbindung mit dem Server" - -#: ../src/client/application/geary-controller.vala:1042 -msgid "" -"Geary encountered an error while connecting to the server. Please try again " -"in a few moments." -msgstr "" -"Während des Verbindungsaufbaus zum Server ist ein Fehler aufgetreten. Bitte " -"versuchen Sie es in einigen Momenten erneut." - -#. / Displayed in the space-limited status bar when a message fails to be sent due to error. -#: ../src/client/application/geary-controller.vala:1079 -#: ../src/client/components/status-bar.vala:29 -msgid "Error sending email" -msgstr "Fehler beim Senden der E-Mail" - -#: ../src/client/application/geary-controller.vala:1080 -msgid "" -"Geary encountered an error sending an email. If the problem persists, " -"please manually delete the email from your Outbox folder." -msgstr "" -"Beim Senden einer E-Mail trat ein Fehler auf. Wenn dieses Problem auch " -"weiterhin besteht, löschen Sie bitte die E-Mail aus dem Postausgang." +#. Translators: File name used in save chooser when saving +#. attachments that do not otherwise have a name. +#: src/client/application/geary-controller.vala:58 +msgid "Untitled" +msgstr "Unbenannt" -#. Displayed in the space-limited status bar when a message fails to be uploaded -#. to Sent Mail after being sent. -#: ../src/client/application/geary-controller.vala:1084 -#: ../src/client/components/status-bar.vala:33 -msgid "Error saving sent mail" -msgstr "Fehler beim Speichern der gesendeten E-Mail" - -#: ../src/client/application/geary-controller.vala:1085 -msgid "" -"Geary encountered an error saving a sent message to Sent Mail. The message " -"will stay in your Outbox folder until you delete it." -msgstr "" -"Beim Speichern einer gesendeten E-Mail trat ein Fehler auf. Die Nachricht " -"bleibt im Postausgang, bis Sie sie löschen." - -#: ../src/client/application/geary-controller.vala:1154 +#: src/client/application/geary-controller.vala:954 msgid "Labels" msgstr "Beschriftungen" #. give the user two options: reset the Account local store, or exit Geary. A third #. could be done to leave the Account in an unopened state, but we don't currently #. have provisions for that. -#: ../src/client/application/geary-controller.vala:1166 +#: src/client/application/geary-controller.vala:967 #, c-format msgid "Unable to open the database for %s" msgstr "Die Datenbank für %s konnte nicht geöffnet werden" -#: ../src/client/application/geary-controller.vala:1167 +#: src/client/application/geary-controller.vala:968 #, c-format msgid "" "There was an error opening the local mail database for this account. This is " @@ -629,20 +927,20 @@ "dazugehörigen Anhänge verloren. E-Mails auf Ihrem Server werden nicht " "betroffen sein." -#: ../src/client/application/geary-controller.vala:1169 +#: src/client/application/geary-controller.vala:970 msgid "_Rebuild" msgstr "_Wiederherstellen" -#: ../src/client/application/geary-controller.vala:1169 +#: src/client/application/geary-controller.vala:970 msgid "E_xit" msgstr "B_eenden" -#: ../src/client/application/geary-controller.vala:1178 +#: src/client/application/geary-controller.vala:979 #, c-format msgid "Unable to rebuild database for “%s”" msgstr "Die Datenbank für »%s« konnte nicht wiederhergestellt werden" -#: ../src/client/application/geary-controller.vala:1179 +#: src/client/application/geary-controller.vala:980 #, c-format msgid "" "Error during rebuild:\n" @@ -653,70 +951,15 @@ "\n" "%s" -#. some other problem opening the account ... as with other flow path, can't run -#. Geary today with an account in unopened state, so have to exit -#: ../src/client/application/geary-controller.vala:1201 -#: ../src/client/application/geary-controller.vala:1211 -#: ../src/client/application/geary-controller.vala:1222 -#, c-format -msgid "Unable to open local mailbox for %s" -msgstr "Das lokale Postfach für %s konnte nicht geöffnet werden" - -#: ../src/client/application/geary-controller.vala:1202 -#, c-format -msgid "" -"There was an error opening the local mail database for this account. This is " -"possibly due to a file permissions problem.\n" -"\n" -"Please check that you have read/write permissions for all files in this " -"directory:\n" -"\n" -"%s" -msgstr "" -"Beim Öffnen der lokalen E-Mail-Datenbank trat ein Fehler auf. Die Ursache " -"ist möglicherweise ein Dateizugriffsproblem.\n" -"\n" -"Bitte überprüfen Sie die Lese-/Schreib-Rechte für alle Dateien in diesem " -"Ordner:\n" -"\n" -"%s" - -#: ../src/client/application/geary-controller.vala:1212 -msgid "" -"The version number of the local mail database is formatted for a newer " -"version of Geary. Unfortunately, the database cannot be “rolled back” to " -"work with this version of Geary.\n" -"\n" -"Please install the latest version of Geary and try again." -msgstr "" -"Die Versionsnummer der lokalen E-Mail-Datenbank ist für eine neuere Version " -"formatiert. Unglücklicherweise kann die Datenbank nicht »zurückgesetzt« " -"werden, damit sie mit dieser Version von Geary funktioniert.\n" -"\n" -"Bitte installieren Sie die neueste Version von Geary und versuchen Sie es " -"erneut." - -#: ../src/client/application/geary-controller.vala:1223 -msgid "" -"There was an error opening the local account. This is probably due to " -"connectivity issues.\n" -"\n" -"Please check your network connection and restart Geary." -msgstr "" -"Beim Öffnen des lokalen Kontos ist ein Fehler aufgetreten. Die Ursache ist " -"vermutlich ein Netzwerkfehler.\n" -"\n" -"Bitte überprüfen Sie Ihre Verbindung und starten Sie Geary neu." - -#: ../src/client/application/geary-controller.vala:1999 +#: src/client/application/geary-controller.vala:1835 msgid "Undo move (Ctrl+Z)" msgstr "Verschieben rückgängig machen (Strg+Z)" -#: ../src/client/application/geary-controller.vala:2009 +#: src/client/application/geary-controller.vala:1845 msgid "Are you sure you want to open these attachments?" msgstr "Möchten Sie wirklich diese Anhänge öffnen?" -#: ../src/client/application/geary-controller.vala:2010 +#: src/client/application/geary-controller.vala:1846 msgid "" "Attachments may cause damage to your system if opened. Only open files from " "trusted sources." @@ -724,16 +967,22 @@ "Anhänge können Ihr System beschädigen, wenn diese geöffnet werden. Öffnen " "Sie nur Dateien aus vertrauenswürdigen Quellen." -#: ../src/client/application/geary-controller.vala:2011 +#: src/client/application/geary-controller.vala:1847 msgid "Don’t _ask me again" msgstr "Nicht _noch einmal fragen" -#: ../src/client/application/geary-controller.vala:2121 +#. Translators: Dialog primary label when prompting to +#. overwrite a file. The string substitution is the file'sx +#. name. +#: src/client/application/geary-controller.vala:1976 #, c-format msgid "A file named “%s” already exists. Do you want to replace it?" msgstr "Die Datei »%s« existiert schon. Möchten Sie diese ersetzen?" -#: ../src/client/application/geary-controller.vala:2123 +#. Translators: Dialog secondary label when prompting to +#. overwrite a file. The string substitution is the parent +#. folder's name. +#: src/client/application/geary-controller.vala:1983 #, c-format msgid "" "The file already exists in “%s”. Replacing it will overwrite its contents." @@ -741,185 +990,469 @@ "Die Datei existiert bereits in »%s«. Beim Ersetzen wird die Datei " "überschrieben." -#: ../src/client/application/geary-controller.vala:2126 +#: src/client/application/geary-controller.vala:1987 msgid "_Replace" msgstr "_Ersetzen" -#. Find out what to do with the inline composers. -#. TODO: Remove this in favor of automatically saving drafts -#: ../src/client/application/geary-controller.vala:2374 -msgid "Close open draft messages?" -msgstr "Offene Nachrichtenentwürfe schließen?" +#: src/client/application/geary-controller.vala:2263 +msgid "Close the draft message?" +msgid_plural "Close all draft messages?" +msgstr[0] "Nachrichtenentwurf schließen?" +msgstr[1] "Alle Nachrichtenentwürfe schließen?" -#: ../src/client/application/geary-controller.vala:2496 +#: src/client/application/geary-controller.vala:2389 #, c-format msgid "Empty all email from your %s folder?" msgstr "Alle E-Mails aus dem Ordner %s löschen?" -#: ../src/client/application/geary-controller.vala:2497 +#: src/client/application/geary-controller.vala:2390 msgid "This removes the email from Geary and your email server." msgstr "Dies wird die E-Mail aus Geary und von Ihrem E-Mail-Server löschen." -#: ../src/client/application/geary-controller.vala:2498 +#: src/client/application/geary-controller.vala:2391 msgid "This cannot be undone." msgstr "Dies kann nicht rückgängig gemacht werden." -#: ../src/client/application/geary-controller.vala:2499 +#: src/client/application/geary-controller.vala:2392 #, c-format msgid "Empty %s" msgstr "%s leeren" -#: ../src/client/application/geary-controller.vala:2516 +#: src/client/application/geary-controller.vala:2409 #, c-format msgid "Error emptying %s" msgstr "Fehler beim Leeren von %s" -#: ../src/client/application/geary-controller.vala:2546 +#: src/client/application/geary-controller.vala:2441 msgid "Do you want to permanently delete this message?" msgid_plural "Do you want to permanently delete these messages?" msgstr[0] "Möchten Sie diese Nachricht dauerhaft löschen?" msgstr[1] "Möchten Sie diese Nachrichten dauerhaft löschen?" -#: ../src/client/application/geary-controller.vala:2548 +#: src/client/application/geary-controller.vala:2443 msgid "Delete" msgstr "Löschen" -#: ../src/client/application/geary-controller.vala:2580 -msgid "Undo archive (Ctrl+Z)" -msgstr "Archivieren rückgängig (Strg+Z)" - -#: ../src/client/application/geary-controller.vala:2595 +#: src/client/application/geary-controller.vala:2457 msgid "Undo trash (Ctrl+Z)" msgstr "Papierkorb rückgängig (Strg+Z)" -#: ../src/client/application/geary-controller.vala:2649 +#: src/client/application/geary-controller.vala:2507 +msgid "Undo archive (Ctrl+Z)" +msgstr "Archivieren rückgängig (Strg+Z)" + +#: src/client/application/geary-controller.vala:2552 msgid "Undo (Ctrl+Z)" msgstr "Rückgängig (Strg+Z)" -#: ../src/client/application/geary-controller.vala:2780 +#. Translators: The label for an in-app notification. The +#. string substitution is a list of recipients of the email. +#: src/client/application/geary-controller.vala:2633 +#, c-format +msgid "Successfully sent mail to %s." +msgstr "Nachricht an %s wurde erfolgreich gesendet." + +#: src/client/application/geary-controller.vala:2715 msgid "Failed to open default text editor." msgstr "Der Standard-Texteditor konnte nicht geöffnet werden." -#: ../src/client/components/main-window.vala:389 +#. Translators: Tooltip used when an entry requires a valid +#. email address to be entered, but one is not provided. +#: src/client/components/components-validator.vala:378 +msgid "An email address is required" +msgstr "Es wird eine E-Mail-Adresse benötigt" + +#. Translators: Tooltip used when an entry requires a valid +#. email address to be entered, but the address is invalid. +#: src/client/components/components-validator.vala:382 +msgid "Not a valid email address" +msgstr "Ungültige E-Mail-Adresse" + +#. Translators: Tooltip used when an entry requires a valid, +#. resolvable server name to be entered, but one is not +#. provided. +#: src/client/components/components-validator.vala:428 +msgid "A server name is required" +msgstr "Es wird ein Servername benötigt" + +#. Translators: Tooltip used when an entry requires a valid +#. server name to be entered, but it was unable to be +#. looked-up in the DNS. +#: src/client/components/components-validator.vala:433 +msgid "Could not look up server name" +msgstr "Servername konnte nicht abgefragt werden" + +#: src/client/components/main-toolbar.vala:151 +msgid "Mark conversation" +msgid_plural "Mark conversations" +msgstr[0] "Konversation markieren" +msgstr[1] "Konversationen markieren" + +#: src/client/components/main-toolbar.vala:156 +msgid "Add label to conversation" +msgid_plural "Add label to conversations" +msgstr[0] "Konversation eine Beschriftung zuweisen" +msgstr[1] "Konversationen eine Beschriftung zuweisen" + +#: src/client/components/main-toolbar.vala:161 +msgid "Move conversation" +msgid_plural "Move conversations" +msgstr[0] "Konversation verschieben" +msgstr[1] "Konversationen verschieben" + +#: src/client/components/main-toolbar.vala:166 +msgid "Archive conversation (A)" +msgid_plural "Archive conversations (A)" +msgstr[0] "Konversation archivieren (A)" +msgstr[1] "Konversationen archivieren (A)" + +#: src/client/components/main-toolbar.vala:175 +msgid "Move conversation to Trash (Delete, Backspace)" +msgid_plural "Move conversations to Trash (Delete, Backspace)" +msgstr[0] "Konversation in den Papierkorb verschieben (Entf, Rücktaste)" +msgstr[1] "Konversationen in den Papierkorb verschieben (Entf, Rücktaste)" + +#: src/client/components/main-toolbar.vala:183 +msgid "Delete conversation (Shift+Delete)" +msgid_plural "Delete conversations (Shift+Delete)" +msgstr[0] "Konversation löschen (Umschalt+Entf)" +msgstr[1] "Konversationen löschen (Umschalt+Entf)" + +#: src/client/components/main-window.vala:503 #, c-format msgid "%s (%d)" msgstr "%s (%d)" -#: ../src/client/components/search-bar.vala:8 -#: ../src/client/folder-list/folder-list-search-branch.vala:38 -#: ../src/engine/api/geary-special-folder-type.vala:51 +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:47 +#, c-format +msgid "Problem connecting to incoming server for %s" +msgstr "Fehler bei der Verbindung mit dem Eingangsserver für %s" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:49 +#: src/client/components/main-window-info-bar.vala:58 +#, c-format +msgid "" +"Could not connect to %s, check your Internet access and the server name and " +"try again" +msgstr "" +"Verbindung zu %s war nicht möglich, überprüfen Sie Ihre Internetverbindung " +"und den Servernamen und versuchen Sie es erneut" + +#. Translators: Tooltip label for Retry button +#. Button tooltip for retrying an account problem +#: src/client/components/main-window-info-bar.vala:51 +#: src/client/components/main-window-info-bar.vala:60 +#: src/client/components/main-window-info-bar.vala:69 +#: src/client/components/main-window-info-bar.vala:78 +#: src/client/components/main-window-info-bar.vala:87 +#: src/client/components/main-window-info-bar.vala:96 +#: src/client/components/main-window-info-bar.vala:136 ui/main-window.ui:265 +msgid "Try reconnecting" +msgstr "Erneuter Verbindungsversuch" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:56 +#, c-format +msgid "Problem connecting to outgoing server for %s" +msgstr "Fehler bei der Verbindung mit dem Ausgangsserver für %s" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:65 +#: src/client/components/main-window-info-bar.vala:83 +#, c-format +msgid "Problem communicating with incoming server for %s" +msgstr "Problem bei der Kommunikation mit dem Eingangsserver für %s" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:67 +#: src/client/components/main-window-info-bar.vala:76 +#, c-format +msgid "Network error talking to %s, check your Internet access and try again" +msgstr "" +"Netzwerkfehler bei der Kommunikation mit %s, bitte überprüfen Sie Ihre " +"Internetverbindung und versuchen Sie es erneut" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:74 +#: src/client/components/main-window-info-bar.vala:91 +msgid "Problem communicating with outgoing mail server" +msgstr "Problem bei der Kommunikation mit dem Ausgangsserver" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:85 +#, c-format +msgid "" +"Geary did not understand a message from %s or vice versa, please file a bug " +"report" +msgstr "" +"Geary verstand eine Meldung von %s nicht oder umgekehrt, bitte erstellen Sie " +"einen Fehlerbericht" + +#. Translators: First string substitution is the server +#. name, second is the account name +#: src/client/components/main-window-info-bar.vala:94 +#, c-format +msgid "" +"Could not communicate with %s for %s, check the server name and try again in " +"a moment" +msgstr "" +"Kommunikation mit %s für %s war nicht möglich, überprüfen Sie den " +"Servernamen und versuchen Sie es in Kürze erneut" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:101 +#, c-format +msgid "Incoming mail server password required for %s" +msgstr "Serverpasswort für %s ist für eingehende Nachrichten erforderlich" + +#: src/client/components/main-window-info-bar.vala:102 +msgid "Messages cannot be received without the correct password." +msgstr "Nachrichten können nicht ohne das korrekte Passwort abgerufen werden." + +#. Translators: Tooltip label for Retry button +#: src/client/components/main-window-info-bar.vala:104 +msgid "Retry receiving email, you will be prompted for a password" +msgstr "" +"Erneut versuchen, Nachrichten abzurufen, Sie werden nach einem Passwort " +"gefragt" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:109 +#, c-format +msgid "Outgoing mail server password required for %s" +msgstr "Serverpasswort für %s ist für ausgehende Nachrichten erforderlich" + +#: src/client/components/main-window-info-bar.vala:110 +msgid "Messages cannot be sent without the correct password." +msgstr "Nachrichten können nicht ohne das korrekte Passwort versendet werden." + +#. Translators: Tooltip label for Retry button +#: src/client/components/main-window-info-bar.vala:112 +msgid "Retry sending queued messages, you will be prompted for a password" +msgstr "" +"Erneut versuchen, in der Warteschlange befindliche Nachrichten zu versenden, " +"Sie werden nach einem Passwort gefragt" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:117 +#, c-format +msgid "Incoming mail server security is not trusted for %s" +msgstr "" +"Den Sicherheitseinstellungen des Servers für eingehende E-Mails von %s wird " +"nicht vertraut" + +#: src/client/components/main-window-info-bar.vala:118 +msgid "Messages will not be received until checked." +msgstr "Nachrichten werden nicht abgerufen, bis sie geprüft wurden." + +#. Translators: Tooltip label for Retry button +#: src/client/components/main-window-info-bar.vala:120 +#: src/client/components/main-window-info-bar.vala:128 +msgid "Check security details" +msgstr "Sicherheitsdetails prüfen" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:125 +#, c-format +msgid "Outgoing mail server security is not trusted for %s" +msgstr "" +"Den Sicherheitseinstellungen des Servers für ausgehende E-Mails von %s wird " +"nicht vertraut" + +#: src/client/components/main-window-info-bar.vala:126 +msgid "Messages cannot be sent until checked." +msgstr "Nachrichten können nicht versendet werden, bevor sie geprüft wurden." + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:133 +#, c-format +msgid "A problem occurred checking mail for %s" +msgstr "Ein Problem ist beim Prüfen auf neue Nachrichten für %s aufgetreten" + +#: src/client/components/main-window-info-bar.vala:134 +#: src/client/components/main-window-info-bar.vala:142 +msgid "Something went wrong, please file a bug report if the problem persists" +msgstr "" +"Etwas ging schief, bitte senden Sie einen Fehlerbericht, wenn das Problem " +"weiterhin besteht" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:141 +#, c-format +msgid "A problem occurred sending mail for %s" +msgstr "Ein Problem ist beim Senden von Nachrichten für %s aufgetreten" + +#. Translators: Tooltip label for Retry button +#: src/client/components/main-window-info-bar.vala:144 +msgid "Retry sending queued messages" +msgstr "Versuchen, Nachrichten in der Warteschlange erneut zu senden" + +#: src/client/components/main-window-info-bar.vala:155 +msgid "A database problem has occurred" +msgstr "Ein Datenbankproblem ist aufgetreten" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:157 +#, c-format +msgid "Messages for %s must be downloaded again." +msgstr "Nachrichten für %s müssen erneut heruntergeladen werden." + +#: src/client/components/main-window-info-bar.vala:170 +msgid "Geary has encountered a problem" +msgstr "Geary ist auf ein Problem gestoßen" + +#: src/client/components/main-window-info-bar.vala:171 +msgid "" +"Please check the technical details and report the problem if it persists." +msgstr "" +"Bitte überprüfen Sie die technischen Details und melden Sie den Fehler, " +"falls das Problem weiterhin besteht." + +#: src/client/components/main-window-info-bar.vala:179 +msgid "_Details" +msgstr "_Details" + +#. Button tooltip for displaying technical details about an account problem +#: src/client/components/main-window-info-bar.vala:180 ui/main-window.ui:250 +msgid "View technical details about the error" +msgstr "Technische Details zum Fehler anzeigen" + +#: src/client/components/main-window-info-bar.vala:184 +msgid "_Retry" +msgstr "_Wiederholen" + +#: src/client/components/search-bar.vala:8 +#: src/client/folder-list/folder-list-search-branch.vala:38 +#: src/engine/api/geary-special-folder-type.vala:51 msgid "Search" msgstr "Suchen" #. Search entry. -#: ../src/client/components/search-bar.vala:23 +#: src/client/components/search-bar.vala:23 msgid "Search all mail in account for keywords (Ctrl+S)" msgstr "Alle E-Mails dieses Kontos nach Stichworten durchsuchen (Strg+S)" -#: ../src/client/components/search-bar.vala:100 +#: src/client/components/search-bar.vala:101 #, c-format msgid "Indexing %s account" msgstr "%s-Konto wird indiziert" -#: ../src/client/components/search-bar.vala:111 -#: ../src/client/folder-list/folder-list-search-branch.vala:39 +#: src/client/components/search-bar.vala:112 +#: src/client/folder-list/folder-list-search-branch.vala:39 #, c-format msgid "Search %s account" msgstr "%s-Konto durchsuchen" #. / Displayed in the space-limited status bar while a message is in the process of being sent. -#: ../src/client/components/status-bar.vala:26 +#: src/client/components/status-bar.vala:26 msgid "Sending…" msgstr "Wird gesendet …" -#: ../src/client/components/stock.vala:18 ../ui/account_cannot_remove.glade.h:3 +#. / Displayed in the space-limited status bar when a message fails to be sent due to error. +#: src/client/components/status-bar.vala:29 +msgid "Error sending email" +msgstr "Fehler beim Senden der E-Mail" + +#. Displayed in the space-limited status bar when a message fails to be uploaded +#. to Sent Mail after being sent. +#: src/client/components/status-bar.vala:33 +msgid "Error saving sent mail" +msgstr "Fehler beim Speichern der gesendeten E-Mail" + +#: src/client/components/stock.vala:18 msgid "_OK" msgstr "_OK" -#: ../src/client/components/stock.vala:19 ../ui/edit_alternate_emails.glade.h:3 -#: ../ui/password-dialog.glade.h:5 ../ui/remove_confirm.glade.h:5 +#: src/client/components/stock.vala:19 ui/password-dialog.glade:196 msgid "_Cancel" msgstr "_Abbrechen" -#: ../src/client/components/stock.vala:21 ../ui/gtk/menus.ui.h:6 +#: src/client/components/stock.vala:21 msgid "_About" msgstr "_Info" -#: ../src/client/components/stock.vala:23 +#: src/client/components/stock.vala:22 +msgid "_Add" +msgstr "_Hinzufügen" + +#: src/client/components/stock.vala:23 msgid "_Close" msgstr "_Schließen" -#: ../src/client/components/stock.vala:24 +#: src/client/components/stock.vala:24 msgid "_Discard" msgstr "_Verwerfen" -#: ../src/client/components/stock.vala:25 ../ui/gtk/menus.ui.h:5 +#: src/client/components/stock.vala:25 ui/main-toolbar-menus.ui:56 msgid "_Help" msgstr "_Hilfe" -#: ../src/client/components/stock.vala:26 ../ui/conversation-email-menus.ui.h:9 +#: src/client/components/stock.vala:26 ui/conversation-email-menus.ui:77 msgid "_Open" msgstr "_Öffnen" -#: ../src/client/components/stock.vala:27 ../ui/gtk/menus.ui.h:3 +#: src/client/components/stock.vala:27 ui/main-toolbar-menus.ui:46 msgid "_Preferences" msgstr "_Einstellungen" -#: ../src/client/components/stock.vala:28 ../ui/conversation-email-menus.ui.h:7 +#. Translators: Menu item to print a single, specific message +#: src/client/components/stock.vala:28 ui/conversation-email-menus.ui:64 msgid "_Print…" msgstr "_Drucken …" -#: ../src/client/components/stock.vala:29 ../ui/gtk/menus.ui.h:7 +#: src/client/components/stock.vala:29 msgid "_Quit" msgstr "_Beenden" -#: ../src/client/components/stock.vala:30 ../ui/remove_confirm.glade.h:6 +#: src/client/components/stock.vala:30 msgid "_Remove" msgstr "_Entfernen" -#: ../src/client/components/stock.vala:32 +#: src/client/components/stock.vala:31 ui/conversation-email-menus.ui:83 +msgid "_Save" +msgstr "_Speichern" + +#: src/client/components/stock.vala:32 msgid "_Keep" msgstr "_Behalten" -#: ../src/client/composer/composer-link-popover.vala:150 +#: src/client/composer/composer-link-popover.vala:149 msgid "Link URL is not correctly formatted, e.g. http://example.com" msgstr "" "Die Adresse des Verweises ist nicht korrekt formatiert, z.B. http://example." "com" -#: ../src/client/composer/composer-link-popover.vala:157 +#: src/client/composer/composer-link-popover.vala:156 msgid "Invalid link URL" msgstr "Ungültige Verweisadresse" -#: ../src/client/composer/composer-link-popover.vala:157 +#: src/client/composer/composer-link-popover.vala:156 msgid "Invalid email address" msgstr "Ungültige E-Mail-Adresse" -#: ../src/client/composer/composer-widget.vala:154 +#: src/client/composer/composer-widget.vala:158 msgid "Saved" msgstr "Gespeichert" -#: ../src/client/composer/composer-widget.vala:155 +#: src/client/composer/composer-widget.vala:159 msgid "Saving" msgstr "Wird gespeichert" -#: ../src/client/composer/composer-widget.vala:156 +#: src/client/composer/composer-widget.vala:160 msgid "Error saving" msgstr "Fehler beim Speichern" -#: ../src/client/composer/composer-widget.vala:157 +#: src/client/composer/composer-widget.vala:161 msgid "Press Backspace to delete quote" msgstr "Rücktaste drücken, um die Einrückung zu entfernen" -#: ../src/client/composer/composer-widget.vala:158 -msgid "New Message" -msgstr "Neue Nachricht" - #. Translators: This is list of keywords, separated by pipe ("|") #. characters, that suggest an attachment; since this is full-word #. checking, include all variants of each word. No spaces are #. allowed. -#: ../src/client/composer/composer-widget.vala:167 +#: src/client/composer/composer-widget.vala:170 msgid "" "attach|attaching|attaches|attachment|attachments|attached|enclose|enclosed|" "enclosing|encloses|enclosure|enclosures" @@ -927,28 +1460,37 @@ "Anhang|Anhänge|angehängt|anhängen|Wiedervorlage|Beilage|beilegen|Anlage|" "anbei|beigefügt|anhängend" -#: ../src/client/composer/composer-widget.vala:1122 -#: ../src/client/composer/composer-widget.vala:1141 -msgid "Do you want to discard this message?" -msgstr "Möchten Sie diese Nachricht verwerfen?" +#. Translators: This dialog text is displayed to the +#. user when closing a composer where the options are +#. Keep, Discard or Cancel. +#: src/client/composer/composer-widget.vala:1130 +msgid "Do you want to keep or discard this draft message?" +msgstr "Möchten Sie diesen Nachrichtenentwurf behalten oder verwerfen?" + +#. Translators: This dialog text is displayed to the +#. user when closing a composer where the options are +#. only Discard or Cancel. +#: src/client/composer/composer-widget.vala:1158 +msgid "Do you want to discard this draft message?" +msgstr "Möchten Sie diesen Nachrichtenentwurf verwerfen?" -#: ../src/client/composer/composer-widget.vala:1245 +#: src/client/composer/composer-widget.vala:1275 msgid "Send message with an empty subject and body?" msgstr "Nachricht ohne Betreff und Inhalt senden?" -#: ../src/client/composer/composer-widget.vala:1247 +#: src/client/composer/composer-widget.vala:1277 msgid "Send message with an empty subject?" msgstr "Nachricht ohne Betreff senden?" -#: ../src/client/composer/composer-widget.vala:1249 +#: src/client/composer/composer-widget.vala:1279 msgid "Send message with an empty body?" msgstr "Nachricht ohne Inhalt senden?" -#: ../src/client/composer/composer-widget.vala:1253 +#: src/client/composer/composer-widget.vala:1283 msgid "Send message without an attachment?" msgstr "Nachricht ohne Anhang senden?" -#: ../src/client/composer/composer-widget.vala:1515 +#: src/client/composer/composer-widget.vala:1588 #, c-format msgid "“%s” already attached for delivery." msgstr "»%s« ist bereits als Anhang ausgewählt worden." @@ -958,173 +1500,265 @@ #. description of the document type, the second will #. be a human-friendly size string. For example: #. Document (100.9MB) -#: ../src/client/composer/composer-widget.vala:1523 -#: ../src/client/conversation-viewer/conversation-email.vala:138 +#: src/client/composer/composer-widget.vala:1596 +#: src/client/conversation-viewer/conversation-email.vala:173 #, c-format msgid "%s (%s)" msgstr "%s (%s)" -#: ../src/client/composer/composer-widget.vala:1560 +#: src/client/composer/composer-widget.vala:1633 #, c-format msgid "“%s” could not be found." msgstr "»%s« konnte nicht gefunden werden." -#: ../src/client/composer/composer-widget.vala:1566 +#: src/client/composer/composer-widget.vala:1639 #, c-format msgid "“%s” is a folder." msgstr "»%s« ist ein Ordner." -#: ../src/client/composer/composer-widget.vala:1572 +#: src/client/composer/composer-widget.vala:1645 #, c-format msgid "“%s” is an empty file." msgstr "»%s« ist eine leere Datei." -#: ../src/client/composer/composer-widget.vala:1585 +#: src/client/composer/composer-widget.vala:1658 #, c-format msgid "“%s” could not be opened for reading." msgstr "»%s« konnte nicht zum Lesen geöffnet werden." -#: ../src/client/composer/composer-widget.vala:1593 +#: src/client/composer/composer-widget.vala:1666 msgid "Cannot add attachment" msgstr "Anhang kann nicht hinzugefügt werden" -#: ../src/client/composer/composer-widget.vala:1645 -msgid "To: " -msgstr "An: " - -#: ../src/client/composer/composer-widget.vala:1648 -msgid "Cc: " -msgstr "Cc: " - -#: ../src/client/composer/composer-widget.vala:1651 -msgid "Bcc: " -msgstr "Bcc:" +#. Translators: Human-readable version of the RFC 822 To header +#: src/client/composer/composer-widget.vala:1716 +#: src/client/conversation-viewer/conversation-email.vala:981 +#: src/engine/rfc822/rfc822-utils.vala:298 ui/conversation-message.ui:312 +msgid "To:" +msgstr "An:" -#: ../src/client/composer/composer-widget.vala:1654 +#. Translators: Human-readable version of the RFC 822 CC header +#: src/client/composer/composer-widget.vala:1722 +#: src/client/conversation-viewer/conversation-email.vala:986 +#: src/engine/rfc822/rfc822-utils.vala:303 ui/conversation-message.ui:357 +msgid "Cc:" +msgstr "Kopie:" + +#. Translators: Human-readable version of the RFC 822 BCC header +#: src/client/composer/composer-widget.vala:1728 +#: src/client/conversation-viewer/conversation-email.vala:991 +#: ui/conversation-message.ui:402 +msgid "Bcc:" +msgstr "Blindkopie:" + +#. Translators: Human-readable version of the RFC 822 Reply-To header +#: src/client/composer/composer-widget.vala:1734 msgid "Reply-To: " msgstr "Antwort an: " -#: ../src/client/composer/composer-widget.vala:1786 +#: src/client/composer/composer-widget.vala:1874 msgid "Select Color" msgstr "Farbe auswählen" -#. Displayed in the From dropdown to indicate an "alternate email address" -#. for an account. The first printf argument will be the alternate email -#. address, and the second will be the account's primary email address. -#: ../src/client/composer/composer-widget.vala:1986 +#. Displayed in the From dropdown to indicate an +#. "alternate email address" for an account. The first +#. printf argument will be the alternate email address, +#. and the second will be the account's primary email +#. address. +#: src/client/composer/composer-widget.vala:2064 #, c-format msgid "%1$s via %2$s" msgstr "%1$s über %2$s" #. Composer label (with mnemonic underscore) for the account selector #. when choosing what address to send a message from. -#: ../src/client/composer/composer-widget.vala:2028 +#: src/client/composer/composer-widget.vala:2125 msgid "_From:" msgstr "_Von:" #. Translators: This is the name of the file chooser filter #. when inserting an image in the composer. -#: ../src/client/composer/composer-widget.vala:2252 +#: src/client/composer/composer-widget.vala:2357 msgid "Images" msgstr "Bilder" -#: ../src/client/composer/spell-check-popover.vala:117 +#: src/client/composer/composer-window.vala:14 +msgid "New Message" +msgstr "Neue Nachricht" + +#: src/client/composer/spell-check-popover.vala:117 msgid "Remove this language from the preferred list" msgstr "Diese Sprache aus der Liste der bevorzugten Sprachen entfernen" -#: ../src/client/composer/spell-check-popover.vala:121 +#: src/client/composer/spell-check-popover.vala:121 msgid "Add this language to the preferred list" msgstr "Diese Sprache zur Liste der bevorzugten Sprachen hinzufügen" -#: ../src/client/composer/spell-check-popover.vala:217 +#: src/client/composer/spell-check-popover.vala:217 msgid "Search for more languages" msgstr "Nach weiteren Sprachen suchen" -#: ../src/client/conversation-list/formatted-conversation-data.vala:11 +#: src/client/conversation-list/conversation-list-view.vala:283 +msgid "Delete conversation" +msgstr "Konversation löschen" + +#: src/client/conversation-list/conversation-list-view.vala:286 +#: ui/main-toolbar-menus.ui:5 +msgid "Mark as _Read" +msgstr "Als _gelesen markieren" + +#: src/client/conversation-list/conversation-list-view.vala:289 +#: ui/main-toolbar-menus.ui:9 +msgid "Mark as _Unread" +msgstr "Als _ungelesen markieren" + +#: src/client/conversation-list/conversation-list-view.vala:292 +#: ui/main-toolbar-menus.ui:17 +msgid "U_nstar" +msgstr "_Stern entfernen" + +#: src/client/conversation-list/conversation-list-view.vala:294 +#: ui/main-toolbar-menus.ui:13 +msgid "_Star" +msgstr "_Stern" + +#. Translators: Menu item to reply to a specific message. +#: src/client/conversation-list/conversation-list-view.vala:297 +#: ui/conversation-email-menus.ui:9 +msgid "_Reply" +msgstr "_Antworten" + +#: src/client/conversation-list/conversation-list-view.vala:298 +msgid "R_eply All" +msgstr "Allen _antworten" + +#. Translators: Menu item to forward a specific message. +#: src/client/conversation-list/conversation-list-view.vala:299 +#: ui/conversation-email-menus.ui:21 +msgid "_Forward" +msgstr "_Weiterleiten" + +#: src/client/conversation-list/formatted-conversation-data.vala:11 msgid "Me" -msgstr "Mir" +msgstr "Ich" #. Translators: This is the file type displayed for #. attachments with unknown file types. -#: ../src/client/conversation-viewer/conversation-email.vala:124 +#: src/client/conversation-viewer/conversation-email.vala:159 msgid "Unknown" msgstr "Unbekannt" -#. Preview headers +#. Translators: Human-readable version of the RFC 822 From header +#: src/client/conversation-viewer/conversation-email.vala:976 +#: src/engine/rfc822/rfc822-utils.vala:289 +msgid "From:" +msgstr "Von:" + +#. Translators: Human-readable version of the RFC 822 Date header +#: src/client/conversation-viewer/conversation-email.vala:996 +#: src/engine/rfc822/rfc822-utils.vala:294 +msgid "Date:" +msgstr "Datum:" + +#. Translators: Human-readable version of the RFC 822 Subject header +#: src/client/conversation-viewer/conversation-email.vala:1001 +#: src/engine/rfc822/rfc822-utils.vala:292 +msgid "Subject:" +msgstr "Betreff:" + +#: src/client/conversation-viewer/conversation-message.vala:65 +msgid "This email address may have been forged" +msgstr "Diese E-Mail-Adresse könnte gefälscht sein" + +#. Compact headers #. Translators: This is displayed in place of the from address #. when the message has no from address. -#: ../src/client/conversation-viewer/conversation-message.vala:331 +#: src/client/conversation-viewer/conversation-message.vala:394 msgid "No sender" msgstr "Kein Absender" #. Translators: This separates multiple 'from' -#. addresses in the header preview for a message. -#: ../src/client/conversation-viewer/conversation-message.vala:586 +#. addresses in the compact header for a message. +#: src/client/conversation-viewer/conversation-message.vala:766 msgid ", " msgstr ", " #. Translators: This string is used as the HTML IMG ALT #. attribute value when displaying an inline image in an email #. that did not specify a file name. E.g. ImageCannot remove account " -msgstr "" -"Konto konnte nicht entfernt werden " +#: ui/accounts_editor_edit_pane.ui:262 ui/accounts_editor_remove_pane.ui:27 +msgid "Remove this account from Geary" +msgstr "Konto aus Geary entfernen" -#: ../ui/account_cannot_remove.glade.h:2 -msgid "" -"A composer window associated with this account is currently open. Send or " -"discard the message and try again." -msgstr "" -"Ein Fenster, in dem eine mit diesem Konto verknüpfte E-Mail verfasst wird, " -"ist bereits offen.\n" -"Senden Sie diese Nachricht oder verwerfen Sie sie, und versuchen Sie es dann " -"erneut." +#: ui/accounts_editor_list_pane.ui:8 +msgid "Accounts" +msgstr "Konten" -#: ../ui/account_list.glade.h:1 -msgid "Add account" -msgstr "Konto hinzufügen" +#: ui/accounts_editor_list_pane.ui:63 +msgid "To get started, select an email provider below." +msgstr "Wählen Sie unten einen E-Mail-Anbieter aus, um zu beginnen." -#: ../ui/account_list.glade.h:2 -msgid "Edit account" -msgstr "Konto bearbeiten" +#: ui/accounts_editor_list_pane.ui:76 +msgid "Welcome to Geary" +msgstr "Willkommen bei Geary" -#: ../ui/account_list.glade.h:3 +#. This title is shown to users when confirming if they want to remove an account. The string substitution is replaced with the account's name. +#: ui/accounts_editor_remove_pane.ui:73 +#, c-format +msgid "Confirm removing: %s" +msgstr "Entfernen bestätigen: %s" + +#: ui/accounts_editor_remove_pane.ui:91 +msgid "" +"Removing an account will remove it from Geary and delete locally cached " +"email data from your computer, but not from your service provider." +msgstr "" +"Wenn Sie ein Konto entfernen, wird es aus Geary entfernt und lokal " +"zwischengespeicherte E-Mail-Daten werden von Ihrem Computer gelöscht. Sie " +"bleiben jedoch bei Ihrem Dienstleister gespeichert." + +#: ui/accounts_editor_remove_pane.ui:122 msgid "Remove account" msgstr "Konto entfernen" -#: ../ui/account_spinner.glade.h:1 -msgid "Please wait while Geary validates your account." -msgstr "Bitte warten, bis Geary Ihr Konto bestätigt hat." +#: ui/accounts_editor_servers_pane.ui:17 +msgid "Cancel" +msgstr "Abbrechen" + +#: ui/accounts_editor_servers_pane.ui:47 +msgid "Apply" +msgstr "Anwenden" -#: ../ui/certificate_warning_dialog.glade.h:1 +#: ui/certificate_warning_dialog.glade:7 msgid "Untrusted Connection" msgstr "Nicht vertrauenswürdige Verbindung" -#: ../ui/certificate_warning_dialog.glade.h:2 +#: ui/certificate_warning_dialog.glade:29 msgid "_Always Trust This Server" msgstr "Diesem Server _immer vertrauen" -#: ../ui/certificate_warning_dialog.glade.h:3 +#: ui/certificate_warning_dialog.glade:43 msgid "_Trust This Server" msgstr "Diesem Server _vertrauen" -#: ../ui/certificate_warning_dialog.glade.h:4 +#: ui/certificate_warning_dialog.glade:57 msgid "_Don’t Trust This Server" msgstr "Diesem Server _nicht vertrauen" -#: ../ui/composer-headerbar.ui.h:1 +#: ui/composer-headerbar.ui:17 ui/composer-headerbar.ui:174 msgid "Detach (Ctrl+D)" msgstr "Loslösen (Strg+D)" -#: ../ui/composer-headerbar.ui.h:2 +#: ui/composer-headerbar.ui:57 ui/composer-headerbar.ui:83 msgid "Attach File (Ctrl+T)" msgstr "Datei anhängen (Strg+T)" -#: ../ui/composer-headerbar.ui.h:3 +#: ui/composer-headerbar.ui:107 msgid "Include Original Attachments" msgstr "Ursprüngliche Anhänge übernehmen" -#: ../ui/composer-headerbar.ui.h:4 -msgid "Send (Ctrl+Enter)" -msgstr "Senden (Strg+Eingabetaste)" - -#: ../ui/composer-headerbar.ui.h:5 +#: ui/composer-headerbar.ui:202 msgid "_Send" msgstr "_Senden" -#: ../ui/composer-headerbar.ui.h:6 +#: ui/composer-headerbar.ui:207 +msgid "Send (Ctrl+Enter)" +msgstr "Senden (Strg+Eingabetaste)" + +#: ui/composer-headerbar.ui:230 msgid "Discard and Close" msgstr "Verwerfen und schließen" -#: ../ui/composer-headerbar.ui.h:7 +#: ui/composer-headerbar.ui:254 msgid "Save and Close" msgstr "Speichern und schließen" #. Note that this button and the Update button will never be shown at the same time to the user. -#: ../ui/composer-link-popover.ui.h:2 +#: ui/composer-link-popover.ui:41 msgid "Insert the new link with this URL" msgstr "Den neuen Verweis mit dieser Adresse einfügen" -#: ../ui/composer-link-popover.ui.h:3 +#: ui/composer-link-popover.ui:52 msgid "Link URL" msgstr "Verweisadresse" #. Note that this button and the Insert button will never be shown at the same time to the user. -#: ../ui/composer-link-popover.ui.h:5 +#: ui/composer-link-popover.ui:66 msgid "Update this link’s URL" msgstr "Die Adresse dieses Verweises aktualisieren" -#: ../ui/composer-link-popover.ui.h:6 +#: ui/composer-link-popover.ui:86 msgid "Delete this link" msgstr "Diesen Verweis löschen" -#: ../ui/composer-link-popover.ui.h:7 +#: ui/composer-link-popover.ui:106 msgid "Open this link" msgstr "Diesen Verweis öffnen" -#: ../ui/composer-menus.ui.h:1 +#: ui/composer-menus.ui:7 msgid "S_ans Serif" msgstr "S_ans Serif" -#: ../ui/composer-menus.ui.h:2 +#: ui/composer-menus.ui:12 msgid "S_erif" msgstr "S_erif" -#: ../ui/composer-menus.ui.h:3 +#: ui/composer-menus.ui:17 msgid "_Fixed Width" msgstr "_Feste Breite" -#: ../ui/composer-menus.ui.h:4 +#: ui/composer-menus.ui:24 msgid "_Small" msgstr "_Klein" -#: ../ui/composer-menus.ui.h:5 +#: ui/composer-menus.ui:29 msgid "_Medium" msgstr "_Normal" -#: ../ui/composer-menus.ui.h:6 +#: ui/composer-menus.ui:34 msgid "Lar_ge" msgstr "Gr_oß" -#: ../ui/composer-menus.ui.h:7 +#: ui/composer-menus.ui:41 msgid "C_olor" msgstr "F_arbe" -#: ../ui/composer-menus.ui.h:8 +#: ui/composer-menus.ui:47 ui/composer-menus.ui:62 msgid "_Rich Text" msgstr "_Formatierter Text" -#: ../ui/composer-menus.ui.h:9 +#: ui/composer-menus.ui:53 ui/composer-menus.ui:68 msgid "Show Extended Fields" msgstr "Erweiterte Felder anzeigen" -#: ../ui/composer-menus.ui.h:10 +#: ui/composer-menus.ui:78 msgid "_Undo" msgstr "_Rückgängig machen" -#: ../ui/composer-menus.ui.h:11 +#: ui/composer-menus.ui:82 msgid "_Redo" msgstr "_Wiederholen" -#: ../ui/composer-menus.ui.h:12 +#: ui/composer-menus.ui:88 ui/composer-menus.ui:106 msgid "Cu_t" msgstr "Aus_schneiden" -#: ../ui/composer-menus.ui.h:13 ../ui/conversation-message-menus.ui.h:7 +#: ui/composer-menus.ui:92 ui/composer-menus.ui:110 +#: ui/conversation-message-menus.ui:37 msgid "_Copy" msgstr "_Kopieren" -#: ../ui/composer-menus.ui.h:14 +#: ui/composer-menus.ui:96 ui/composer-menus.ui:114 msgid "_Paste" msgstr "_Einfügen" -#: ../ui/composer-menus.ui.h:15 -msgctxt "Clipboard paste with rich text" -msgid "Paste _With Formatting" -msgstr "Mit _Formatierung einfügen" +#: ui/composer-menus.ui:100 +msgctxt "Clipboard paste as plain text" +msgid "Paste _Without Formatting" +msgstr "_Ohne Formatierung einfügen" -#: ../ui/composer-menus.ui.h:16 +#: ui/composer-menus.ui:120 msgid "Select _All" msgstr "Alles _auswählen" -#: ../ui/composer-menus.ui.h:17 ../ui/conversation-message-menus.ui.h:9 +#: ui/composer-menus.ui:127 ui/conversation-message-menus.ui:49 msgid "_Inspect…" msgstr "_Untersuchen …" #. Address(es) e-mail is to be sent to -#: ../ui/composer-widget.ui.h:2 +#: ui/composer-widget.ui:56 msgid "_To" msgstr "_An" -#: ../ui/composer-widget.ui.h:3 +#: ui/composer-widget.ui:75 msgid "_Cc" -msgstr "_CC" +msgstr "_Kopie" -#: ../ui/composer-widget.ui.h:4 +#: ui/composer-widget.ui:130 msgid "_Subject" msgstr "_Betreff" -#: ../ui/composer-widget.ui.h:5 +#: ui/composer-widget.ui:149 msgid "_Bcc" -msgstr "_Bcc" +msgstr "_Blindkopie" -#: ../ui/composer-widget.ui.h:6 +#: ui/composer-widget.ui:179 msgid "_Reply-To" -msgstr "_Antworten" +msgstr "_Antwort an" #. Geary account mail will be sent from -#: ../ui/composer-widget.ui.h:8 +#: ui/composer-widget.ui:208 msgid "From" msgstr "Von" -#: ../ui/composer-widget.ui.h:9 +#: ui/composer-widget.ui:293 msgid "Drop files here" msgstr "Dateien hier ablegen" -#: ../ui/composer-widget.ui.h:10 +#: ui/composer-widget.ui:309 msgid "To add them as attachments" msgstr "Als Anhang einfügen" -#: ../ui/composer-widget.ui.h:11 +#: ui/composer-widget.ui:353 msgid "Undo last edit (Ctrl+Z)" msgstr "Letzte Bearbeitung rückgängig (Strg+Z)" -#: ../ui/composer-widget.ui.h:12 +#: ui/composer-widget.ui:377 msgid "Redo last edit (Ctrl+Shift+Z)" msgstr "Letzte Bearbeitung wiederholen (Umschalt+Strg+Z)" -#: ../ui/composer-widget.ui.h:13 +#: ui/composer-widget.ui:415 msgid "Bold (Ctrl+B)" msgstr "Fett (Strg+B)" -#: ../ui/composer-widget.ui.h:14 +#: ui/composer-widget.ui:439 msgid "Italic (Ctrl+I)" msgstr "Kursiv (Strg+I)" -#: ../ui/composer-widget.ui.h:15 +#: ui/composer-widget.ui:463 msgid "Underline (Ctrl+U)" msgstr "Unterstrichen (Strg+U)" -#: ../ui/composer-widget.ui.h:16 +#: ui/composer-widget.ui:487 msgid "Strikethrough (Ctrl+K)" msgstr "Durchgestrichen (Strg+K)" -#: ../ui/composer-widget.ui.h:17 +#: ui/composer-widget.ui:525 +msgid "Insert unordered list" +msgstr "Ungeordnete Liste einfügen" + +#: ui/composer-widget.ui:549 +msgid "Insert ordered list" +msgstr "Geordnete Liste einfügen" + +#: ui/composer-widget.ui:587 msgid "Quote text (Ctrl+])" msgstr "Text einrücken (Strg+])" -#: ../ui/composer-widget.ui.h:18 +#: ui/composer-widget.ui:611 msgid "Unquote text (Ctrl+[)" msgstr "Einrückung aufheben (Strg+[)" -#: ../ui/composer-widget.ui.h:19 +#: ui/composer-widget.ui:649 msgid "Insert or update selection link (Ctrl+L)" msgstr "Verweis in Auswahl einfügen oder aktualisieren (Strg+L)" -#: ../ui/composer-widget.ui.h:20 +#: ui/composer-widget.ui:673 msgid "Insert an image (Ctrl+G)" msgstr "Bild einfügen (Strg+G)" -#: ../ui/composer-widget.ui.h:21 +#: ui/composer-widget.ui:707 msgid "Remove selection formatting (Ctrl+Space)" msgstr "Formatierung der Auswahl löschen (Strg+Leertaste)" -#: ../ui/composer-widget.ui.h:22 +#: ui/composer-widget.ui:731 msgid "Select spell checking languages" msgstr "Sprachen für Rechtschreibprüfung auswählen" -#: ../ui/conversation-email.ui.h:1 +#: ui/conversation-email.ui:27 msgid "Save all attachments" msgstr "Alle Anhänge speichern" #. Note: The application will never show this button at the same time as unstar_button, one will always be hidden. -#: ../ui/conversation-email.ui.h:3 +#: ui/conversation-email.ui:50 msgid "Mark this message as starred" msgstr "Diese Nachricht mit einem Stern markieren" #. Note: The application will never show this button at the same time as star_button, one will always be hidden. -#: ../ui/conversation-email.ui.h:5 +#: ui/conversation-email.ui:72 msgid "Mark this message as not starred" msgstr "Sternmarkierung von dieser Nachricht entfernen" -#: ../ui/conversation-email.ui.h:6 +#: ui/conversation-email.ui:95 msgid "Display the message menu" msgstr "Das Nachrichtenmenü anzeigen" -#: ../ui/conversation-email.ui.h:7 +#: ui/conversation-email.ui:161 msgid "Open selected attachments" msgstr "Ausgewählte Anhänge öffnen" -#: ../ui/conversation-email.ui.h:8 +#: ui/conversation-email.ui:178 msgid "Save selected attachments" msgstr "Ausgewählte Anhänge speichern" -#: ../ui/conversation-email.ui.h:9 +#: ui/conversation-email.ui:195 msgid "Select all attachments" msgstr "Alle Anhänge auswählen" -#: ../ui/conversation-email.ui.h:10 +#: ui/conversation-email.ui:240 msgid "Edit Draft" msgstr "Entwurf bearbeiten" -#: ../ui/conversation-email.ui.h:11 +#: ui/conversation-email.ui:267 msgid "Draft message" msgstr "Nachrichtenentwurf" -#: ../ui/conversation-email.ui.h:12 +#: ui/conversation-email.ui:283 msgid "This message has not yet been sent." msgstr "Diese Nachricht wurde noch nicht versendet." -#: ../ui/conversation-email.ui.h:13 -msgid "Try Again" -msgstr "Erneut versuchen" - -#: ../ui/conversation-email.ui.h:14 +#: ui/conversation-email.ui:329 msgid "Message not saved" msgstr "Nachricht nicht gespeichert" -#: ../ui/conversation-email.ui.h:15 +#: ui/conversation-email.ui:345 msgid "This message was sent, but has not been saved to your account." msgstr "" "Diese Nachricht wurde gesendet, wurde aber nicht in Ihrem Konto gespeichert." -#: ../ui/conversation-email-menus.ui.h:2 +#. Translators: Menu item to reply to a specific message. +#: ui/conversation-email-menus.ui:15 msgid "Reply to _All" msgstr "_Allen antworten" -#: ../ui/conversation-email-menus.ui.h:4 +#. Translators: Menu item to mark a specific message as +#. read. +#: ui/conversation-email-menus.ui:30 msgid "_Mark Read" msgstr "Als _gelesen markieren" -#: ../ui/conversation-email-menus.ui.h:5 +#: ui/conversation-email-menus.ui:36 msgid "_Mark Unread" msgstr "Als _ungelesen markieren" -#: ../ui/conversation-email-menus.ui.h:6 +#. Translators: Menu item to mark all messages in a +#. conversation from this one as unread. +#: ui/conversation-email-menus.ui:42 msgid "Mark Unread From _Here" msgstr "Von _hier ab als ungelesen markieren" -#: ../ui/conversation-email-menus.ui.h:8 +#. Translators: Menu item to move a single, specific message +#. to the trash +#: ui/conversation-email-menus.ui:50 +msgid "_Trash" +msgstr "In den _Papierkorb" + +#. Translators: Menu item to delete a single, specific message +#: ui/conversation-email-menus.ui:57 +msgid "_Delete…" +msgstr "_Löschen …" + +#. Translators: Menu item to view the source for a message +#: ui/conversation-email-menus.ui:69 msgid "_View Source" msgstr "_Quelltext anzeigen" -#: ../ui/conversation-email-menus.ui.h:11 +#: ui/conversation-email-menus.ui:87 msgid "_Save All" msgstr "A_lle speichern" -#: ../ui/conversation-message-menus.ui.h:1 +#: ui/conversation-message-menus.ui:7 msgid "_Open Link" msgstr "_Verweis öffnen" -#: ../ui/conversation-message-menus.ui.h:2 +#: ui/conversation-message-menus.ui:11 msgid "Copy Link _Address" msgstr "Verweis_adresse kopieren" -#: ../ui/conversation-message-menus.ui.h:3 +#: ui/conversation-message-menus.ui:17 msgid "Send New _Message…" msgstr "Neue _Nachricht senden …" -#: ../ui/conversation-message-menus.ui.h:4 +#: ui/conversation-message-menus.ui:21 msgid "Copy Email _Address" msgstr "_E-Mail-Adresse kopieren" -#: ../ui/conversation-message-menus.ui.h:5 +#: ui/conversation-message-menus.ui:27 msgid "Save _Image As…" msgstr "_Bild speichern unter …" -#: ../ui/conversation-message-menus.ui.h:6 +#: ui/conversation-message-menus.ui:33 msgid "_Select All" msgstr "Alles _auswählen" -#: ../ui/conversation-message-menus.ui.h:8 +#: ui/conversation-message-menus.ui:43 msgid "Search for messages from" msgstr "Nach Nachrichten suchen von" -#: ../ui/conversation-message.ui.h:1 +#: ui/conversation-message.ui:63 msgid "From " msgstr "Von " -#: ../ui/conversation-message.ui.h:2 +#: ui/conversation-message.ui:79 ui/conversation-message.ui:178 msgid "1/1/1970\t" msgstr "1.1.1970\t" -#: ../ui/conversation-message.ui.h:3 +#: ui/conversation-message.ui:102 msgid "Preview body text." msgstr "Nachrichtenvorschau anzeigen." -#: ../ui/conversation-message.ui.h:4 +#: ui/conversation-message.ui:202 msgid "Sent by:" msgstr "Gesendet von:" -#: ../ui/conversation-message.ui.h:5 +#: ui/conversation-message.ui:247 msgid "Reply to:" -msgstr "Antwort an" +msgstr "Antwort an:" -#: ../ui/conversation-message.ui.h:6 +#: ui/conversation-message.ui:291 msgid "Subject" msgstr "Betreff" -#: ../ui/conversation-message.ui.h:7 -msgid "To:" -msgstr "An:" - -#: ../ui/conversation-message.ui.h:8 -msgid "Cc:" -msgstr "Cc:" - -#: ../ui/conversation-message.ui.h:9 -msgid "Bcc:" -msgstr "Bcc:" - -#: ../ui/conversation-message.ui.h:10 +#: ui/conversation-message.ui:501 msgid "Show Images" msgstr "Bilder anzeigen" -#: ../ui/conversation-message.ui.h:11 +#: ui/conversation-message.ui:514 msgid "Always Show From Sender" msgstr "Von diesem Absender immer anzeigen" # habe aus fuer remote aus "entfernt" "extern gemacht, da entfernt auch ein Synonym fuer geloescht, also von der Nachricht entfernte Bilder sein kann. -#: ../ui/conversation-message.ui.h:12 +#: ui/conversation-message.ui:542 msgid "Remote images not shown" msgstr "Externe Bilder werden nicht angezeigt" -#: ../ui/conversation-message.ui.h:13 +#: ui/conversation-message.ui:559 msgid "Only show remote images from senders you trust." msgstr "Externe Bilder nur von Absendern anzeigen, denen Sie vertrauen." -#: ../ui/conversation-message.ui.h:14 +#: ui/conversation-message.ui:692 msgid "But actually goes to:" -msgstr "aber führt tatsächlich zu:" +msgstr "Aber führt tatsächlich zu:" -#: ../ui/conversation-message.ui.h:15 +#: ui/conversation-message.ui:723 msgid "The link appears to go to:" msgstr "Dieser Verweis scheint zu folgender Seite zu führen:" -#: ../ui/conversation-message.ui.h:16 +#: ui/conversation-message.ui:735 msgid "Deceptive link found" msgstr "Trügerischer Verweis gefunden" -#: ../ui/conversation-message.ui.h:17 +#: ui/conversation-message.ui:750 msgid "The email sender may be leading you to the wrong web site." msgstr "Der Absender der E-Mail könnte Sie zur falschen Webseite leiten." -#: ../ui/conversation-message.ui.h:18 +#: ui/conversation-message.ui:763 msgid "If unsure, contact the sender and ask before continuing." msgstr "" "Falls Sie unsicher sind, kontaktieren Sie den Absender und fragen Sie, bevor " "Sie fortfahren." -#: ../ui/conversation-viewer.ui.h:1 +#: ui/conversation-viewer.ui:60 msgid "Find in conversation" msgstr "In Konversation suchen" -#: ../ui/conversation-viewer.ui.h:2 +#: ui/conversation-viewer.ui:74 msgid "Find the previous occurrence of the search string." -msgstr "Vorhergehende Übereinstimmungen mit dem Suchtext finden" +msgstr "Vorhergehende Übereinstimmungen mit dem Suchtext finden." -#: ../ui/conversation-viewer.ui.h:3 +#: ui/conversation-viewer.ui:95 msgid "Find the next occurrence of the search string." -msgstr "Nächste Übereinstimmung mit dem Suchtext finden" - -#: ../ui/edit_alternate_emails.glade.h:1 -msgid "Remove email address" -msgstr "E-Mail-Adresse entfernen" - -#: ../ui/edit_alternate_emails.glade.h:2 -msgid "" -"Some email services require additional addresses be configured on the " -"server. Contact your email provider for more information." -msgstr "" -"Einige E-Mail-Dienste verlangen das Einrichten von zusätzlichen Adressen auf " -"dem Server. Kontaktieren Sie Ihren E-Mail-Anbieter für weitere Informationen." - -#: ../ui/edit_alternate_emails.glade.h:4 -msgid "_Update" -msgstr "_Aktualisieren" +msgstr "Nächste Übereinstimmung mit dem Suchtext finden." -#: ../ui/find_bar.glade.h:1 +#: ui/find_bar.glade:66 msgid "Find:" msgstr "Suchen:" -#: ../ui/find_bar.glade.h:2 +#: ui/find_bar.glade:89 msgid "_Previous" msgstr "_Vorige" -#: ../ui/find_bar.glade.h:3 +#: ui/find_bar.glade:107 msgid "_Next" msgstr "_Nächste" -#: ../ui/find_bar.glade.h:4 +#: ui/find_bar.glade:125 msgid "_Case sensitive" msgstr "_Groß-/ Kleinschreibung" -#: ../ui/find_bar.glade.h:5 +#: ui/find_bar.glade:145 msgid "label" msgstr "Beschriftung" -#: ../ui/gtk/help-overlay.ui.h:1 +#: ui/gtk/help-overlay.ui:9 msgid "Conversation Shortcuts" msgstr "Tastenkombinationen für Konversationen" -#: ../ui/gtk/help-overlay.ui.h:2 +#: ui/gtk/help-overlay.ui:13 ui/gtk/help-overlay.ui:254 msgctxt "shortcut window" msgid "General" msgstr "Allgemein" -#: ../ui/gtk/help-overlay.ui.h:3 +#: ui/gtk/help-overlay.ui:17 msgctxt "shortcut window" msgid "Move focus to the next/previous pane" msgstr "Fokus auf die nächste/vorherige Leiste verlegen" -#: ../ui/gtk/help-overlay.ui.h:4 +#: ui/gtk/help-overlay.ui:24 msgctxt "shortcut window" msgid "Move focus to conversation list" msgstr "Fokus auf die Konversationsliste verschieben" -#: ../ui/gtk/help-overlay.ui.h:5 +#: ui/gtk/help-overlay.ui:31 msgctxt "shortcut window" msgid "Detach composer window" msgstr "»Verfassen«-Fenster abkoppeln" -#: ../ui/gtk/help-overlay.ui.h:6 +#: ui/gtk/help-overlay.ui:38 msgctxt "shortcut window" msgid "Close composer window" msgstr "»Verfassen«-Fenster schließen" -#: ../ui/gtk/help-overlay.ui.h:7 +#: ui/gtk/help-overlay.ui:45 msgctxt "shortcut window" msgid "Show keyboard shortcuts" msgstr "Tastenkombinationen anzeigen" -#: ../ui/gtk/help-overlay.ui.h:8 +#: ui/gtk/help-overlay.ui:52 msgctxt "shortcut window" msgid "Show help" msgstr "Hilfe anzeigen" -#: ../ui/gtk/help-overlay.ui.h:9 +#: ui/gtk/help-overlay.ui:59 msgctxt "shortcut window" msgid "Quit the application" msgstr "Die Anwendung beenden" -#: ../ui/gtk/help-overlay.ui.h:10 +#: ui/gtk/help-overlay.ui:68 msgctxt "shortcut window" msgid "Search" msgstr "Suchen" -#: ../ui/gtk/help-overlay.ui.h:11 +#: ui/gtk/help-overlay.ui:72 msgctxt "shortcut window" msgid "Jump to search box" msgstr "Zum Suchfeld springen" -#: ../ui/gtk/help-overlay.ui.h:12 +#: ui/gtk/help-overlay.ui:79 msgctxt "shortcut window" msgid "Find in current conversation" msgstr "In aktueller Konversation suchen" -#: ../ui/gtk/help-overlay.ui.h:13 +#: ui/gtk/help-overlay.ui:86 msgctxt "shortcut window" msgid "Find next/previous in current conversation" msgstr "Das nächste/vorherige Vorkommen in dieser Konversation finden" -#: ../ui/gtk/help-overlay.ui.h:14 +#: ui/gtk/help-overlay.ui:95 ui/gtk/help-overlay.ui:274 msgctxt "shortcut window" msgid "Actions" msgstr "Aktionen" -#: ../ui/gtk/help-overlay.ui.h:15 +#: ui/gtk/help-overlay.ui:99 msgctxt "shortcut window" msgid "Compose a new message" msgstr "Eine neue Nachricht verfassen" -#: ../ui/gtk/help-overlay.ui.h:16 +#: ui/gtk/help-overlay.ui:106 msgctxt "shortcut window" msgid "Reply to sender " msgstr "Absender antworten " -#: ../ui/gtk/help-overlay.ui.h:17 +#: ui/gtk/help-overlay.ui:113 msgctxt "shortcut window" msgid "Reply to all" msgstr "Allen antworten" -#: ../ui/gtk/help-overlay.ui.h:18 +#: ui/gtk/help-overlay.ui:120 msgctxt "shortcut window" msgid "Forward" msgstr "Weiterleiten" -#: ../ui/gtk/help-overlay.ui.h:19 +#: ui/gtk/help-overlay.ui:127 msgctxt "shortcut window" msgid "Archive" msgstr "Archivieren" -#: ../ui/gtk/help-overlay.ui.h:20 +#: ui/gtk/help-overlay.ui:134 msgctxt "shortcut window" msgid "Move to trash" msgstr "In den Papierkorb verschieben" -#: ../ui/gtk/help-overlay.ui.h:21 +#: ui/gtk/help-overlay.ui:141 msgctxt "shortcut window" msgid "Toggle spam" msgstr "Unerwünscht-Markierung umschalten" -#: ../ui/gtk/help-overlay.ui.h:22 +#: ui/gtk/help-overlay.ui:148 msgctxt "shortcut window" msgid "Move the conversation" msgstr "Konversation verschieben" -#: ../ui/gtk/help-overlay.ui.h:23 +#: ui/gtk/help-overlay.ui:155 msgctxt "shortcut window" msgid "Label the conversation" msgstr "Konversation eine Beschriftung zuweisen" -#: ../ui/gtk/help-overlay.ui.h:24 +#: ui/gtk/help-overlay.ui:163 msgctxt "shortcut window" msgid "Mark read" msgstr "Als gelesen markieren" -#: ../ui/gtk/help-overlay.ui.h:25 +#: ui/gtk/help-overlay.ui:170 msgctxt "shortcut window" msgid "Mark unread" msgstr "Als ungelesen markieren" -#: ../ui/gtk/help-overlay.ui.h:26 +#: ui/gtk/help-overlay.ui:179 msgctxt "shortcut window" msgid "View" msgstr "Betrachten" -#: ../ui/gtk/help-overlay.ui.h:27 +#: ui/gtk/help-overlay.ui:183 msgctxt "shortcut window" msgid "Zoom in" msgstr "Ansicht vergrößern" -#: ../ui/gtk/help-overlay.ui.h:28 +#: ui/gtk/help-overlay.ui:190 msgctxt "shortcut window" msgid "Zoom out" msgstr "Ansicht verkleinern" -#: ../ui/gtk/help-overlay.ui.h:29 +#: ui/gtk/help-overlay.ui:197 msgctxt "shortcut window" msgid "Reset zoom" msgstr "Vergrößerung zurücksetzen" -#: ../ui/gtk/help-overlay.ui.h:30 +#: ui/gtk/help-overlay.ui:206 msgctxt "shortcut window" msgid "Additional Shortcuts" msgstr "Weitere Tastenkombinationen" -#: ../ui/gtk/help-overlay.ui.h:31 +#: ui/gtk/help-overlay.ui:210 msgctxt "shortcut window" msgid "Star" msgstr "Stern" -#: ../ui/gtk/help-overlay.ui.h:32 +#: ui/gtk/help-overlay.ui:217 msgctxt "shortcut window" msgid "Unstar" msgstr "Stern entfernen" -#: ../ui/gtk/help-overlay.ui.h:33 +#: ui/gtk/help-overlay.ui:224 msgctxt "shortcut window" msgid "Delete" msgstr "Löschen" -#: ../ui/gtk/help-overlay.ui.h:34 +#: ui/gtk/help-overlay.ui:231 msgctxt "shortcut window" msgid "Jump to next (older) conversation" msgstr "Zur nächsten (älteren) Konversation springen" -#: ../ui/gtk/help-overlay.ui.h:35 +#: ui/gtk/help-overlay.ui:238 msgctxt "shortcut window" msgid "Jump to previous (newer) conversation" msgstr "Zur letzten (neueren) Konversation springen" -#: ../ui/gtk/help-overlay.ui.h:36 +#: ui/gtk/help-overlay.ui:250 msgid "Composer Shortcuts" msgstr "Tastenkombinationen beim Verfassen" -#: ../ui/gtk/help-overlay.ui.h:37 +#: ui/gtk/help-overlay.ui:258 msgctxt "shortcut window" msgid "Quote text" msgstr "Text einrücken" -#: ../ui/gtk/help-overlay.ui.h:38 +#: ui/gtk/help-overlay.ui:265 msgctxt "shortcut window" msgid "Unquote text" msgstr "Einrückung aufheben" -#: ../ui/gtk/help-overlay.ui.h:39 +#: ui/gtk/help-overlay.ui:278 msgctxt "shortcut window" msgid "Send" msgstr "Senden" -#: ../ui/gtk/help-overlay.ui.h:40 +#: ui/gtk/help-overlay.ui:285 msgctxt "shortcut window" msgid "Add attachment" msgstr "Anhang hinzufügen" -#: ../ui/gtk/help-overlay.ui.h:41 +#: ui/gtk/help-overlay.ui:294 msgctxt "shortcut window" msgid "Rich text mode" msgstr "Formatierter Text" -#: ../ui/gtk/help-overlay.ui.h:42 +#: ui/gtk/help-overlay.ui:298 msgctxt "shortcut window" msgid "Bold text" msgstr "Fetter Text" -#: ../ui/gtk/help-overlay.ui.h:43 +#: ui/gtk/help-overlay.ui:305 msgctxt "shortcut window" msgid "Italicize text" msgstr "Kursiver Text" -#: ../ui/gtk/help-overlay.ui.h:44 +#: ui/gtk/help-overlay.ui:312 msgctxt "shortcut window" msgid "Underline text" msgstr "Unterstrichener Text" -#: ../ui/gtk/help-overlay.ui.h:45 +#: ui/gtk/help-overlay.ui:319 msgctxt "shortcut window" msgid "Strike text" msgstr "Durchgestrichener Text" -#: ../ui/gtk/help-overlay.ui.h:46 +#: ui/gtk/help-overlay.ui:326 msgctxt "shortcut window" msgid "Insert a link" msgstr "Einen Verweis einfügen" -#: ../ui/gtk/help-overlay.ui.h:47 +#: ui/gtk/help-overlay.ui:333 msgctxt "shortcut window" msgid "Remove formatting" msgstr "Formatierung löschen" -#: ../ui/gtk/menus.ui.h:2 -msgid "A_ccounts" -msgstr "_Konten" +#: ui/main-toolbar.ui:23 +msgctxt "tooltip" +msgid "Compose Message" +msgstr "Nachricht verfassen" -#: ../ui/gtk/menus.ui.h:4 -msgid "_Keyboard Shortcuts" -msgstr "_Tastenkombinationen" +#: ui/main-toolbar.ui:52 +msgid "Toggle search bar" +msgstr "Suchleiste anzeigen/verbergen" -#: ../ui/login.glade.h:1 -msgid "email@example.com" -msgstr "email@example.com" +#: ui/main-toolbar.ui:112 +msgid "Reply" +msgstr "Antworten" -#: ../ui/login.glade.h:2 ../ui/password-dialog.glade.h:3 -msgid "Password" -msgstr "Passwort" +#: ui/main-toolbar.ui:135 +msgid "Reply All" +msgstr "Allen antworten" -#: ../ui/login.glade.h:3 -msgid "E_mail address" -msgstr "E-_Mail-Adresse" - -#: ../ui/login.glade.h:4 -msgid "_Password" -msgstr "_Passwort" - -#: ../ui/login.glade.h:5 -msgid "S_ervice" -msgstr "Di_enst" - -#: ../ui/login.glade.h:6 -msgid "N_ame" -msgstr "N_ame" - -#: ../ui/login.glade.h:8 -msgid "N_ickname" -msgstr "N_ickname" - -#: ../ui/login.glade.h:9 -msgid "Work, Home, etc." -msgstr "Arbeit, Zuhause, etc." - -#: ../ui/login.glade.h:10 -msgid "_Save sent mail" -msgstr "Gesendete E-Mail _speichern" - -#: ../ui/login.glade.h:11 -msgid "Addi_tional email addresses…" -msgstr "Zusätzliche _E-Mail-Adressen …" - -#: ../ui/login.glade.h:12 -msgid "IMAP settings" -msgstr "IMAP-Einstellungen" - -#: ../ui/login.glade.h:13 -msgid "Se_rver" -msgstr "Se_rver" +#: ui/main-toolbar.ui:158 +msgid "Forward" +msgstr "Weiterleiten" -#: ../ui/login.glade.h:14 -msgid "imap.example.com" -msgstr "imap.example.com" +#: ui/main-toolbar.ui:264 +msgid "Toggle find bar" +msgstr "Suchleiste anzeigen/verbergen" -#: ../ui/login.glade.h:15 -msgid "P_ort" -msgstr "P_ort" +#: ui/main-toolbar.ui:306 +msgid "_Archive" +msgstr "_Archivieren" -#: ../ui/login.glade.h:16 -msgid "smtp.example.com" -msgstr "smtp.example.com" +#: ui/main-toolbar-menus.ui:21 +msgid "Mark as S_pam" +msgstr "Als S_pam markieren" -#: ../ui/login.glade.h:17 -msgid "Ser_ver" -msgstr "Ser_ver" - -#: ../ui/login.glade.h:18 -msgid "Por_t" -msgstr "Por_t" +#: ui/main-toolbar-menus.ui:25 +msgid "Mark as not S_pam" +msgstr "S_pam-Markierung entfernen" -#: ../ui/login.glade.h:19 -msgid "SMTP settings" -msgstr "SMTP-Einstellungen" +#: ui/main-toolbar-menus.ui:32 +msgid "Empty _Spam…" +msgstr "_Spam leeren …" -#: ../ui/login.glade.h:20 -msgid "User_name" -msgstr "Be_nutzername" - -#: ../ui/login.glade.h:21 -msgid "Pass_word" -msgstr "Pass_wort" - -#: ../ui/login.glade.h:22 -msgid "SMTP username" -msgstr "SMTP-Benutzername" - -#: ../ui/login.glade.h:23 -msgid "SMTP password" -msgstr "SMTP-Passwort" - -#: ../ui/login.glade.h:24 -msgid "_Username" -msgstr "Ben_utzername" - -#: ../ui/login.glade.h:25 -msgid "IMAP username" -msgstr "IMAP-Benutzername" - -#: ../ui/login.glade.h:26 -msgid "IMAP password" -msgstr "IMAP-Passwort" - -#: ../ui/login.glade.h:27 -msgid "Encr_yption" -msgstr "_Verschlüsselung" - -#: ../ui/login.glade.h:28 -msgid "Encrypt_ion" -msgstr "_Verschlüsselung" - -#: ../ui/login.glade.h:30 -msgid "SSL/TLS" -msgstr "SSL/TLS" - -#: ../ui/login.glade.h:31 -msgid "STARTTLS" -msgstr "STARTTLS" - -#: ../ui/login.glade.h:32 -msgid "No authentication re_quired" -msgstr "Keine Authentifizierung nötig" - -#: ../ui/login.glade.h:33 -msgid "Use IMAP cre_dentials" -msgstr "_IMAP-Zugangsinformationen verwenden" - -#: ../ui/login.glade.h:34 -msgid "Composer" -msgstr "Verfassen" - -#: ../ui/login.glade.h:35 -msgid "Save dra_fts on server" -msgstr "_Entwürfe auf dem Server speichern" - -#: ../ui/login.glade.h:36 -msgid "Si_gn emails (HTML allowed):" -msgstr "E-Mails _unterzeichnen (HTML erlaubt):" - -#: ../ui/login.glade.h:37 -msgid "Storage" -msgstr "Speicher" - -#: ../ui/login.glade.h:38 -msgid "_Download mail" -msgstr "_Mail herunterladen" - -#: ../ui/main-toolbar.ui.h:1 -msgid "Empty Spam or Trash folders" -msgstr "Spam oder Papierkorb leeren" +#: ui/main-toolbar-menus.ui:36 +msgid "Empty _Trash…" +msgstr "_Papierkorb leeren …" + +#: ui/main-toolbar-menus.ui:42 +msgid "_Accounts" +msgstr "_Konten" -#: ../ui/password-dialog.glade.h:1 +#: ui/main-toolbar-menus.ui:50 +msgid "_Keyboard Shortcuts" +msgstr "_Tastenkombinationen" + +#: ui/main-toolbar-menus.ui:61 +msgid "_About Geary" +msgstr "_Info zu Geary" + +#. Infobar title when one or more accounts are offline +#: ui/main-window.ui:183 +msgid "Working offline" +msgstr "Abgemeldet arbeiten" + +#. Label and tooltip for offline infobar +#: ui/main-window.ui:197 +msgid "" +"Your computer does not appear to be connected to the Internet.\n" +"You will not be able to send or receive email until it is re-connected." +msgstr "" +"Ihr Rechner scheint nicht mit dem Internet verbunden zu sein.\n" +"Sie können keine E-Mails senden oder empfangen, bis er wieder verbunden ist." + +#. Label and tooltip for offline infobar +#: ui/main-window.ui:200 +msgid "You will not be able to send or receive email until re-connected." +msgstr "" +"Sie können keine E-Mails senden oder empfangen, bis wieder eine Verbindung " +"besteht." + +#. Button label for displaying technical details about an account problem +#. Dialog title for displaying technical details of a problem. Same as the button that invokes it. +#: ui/main-window.ui:247 ui/problem-details-dialog.ui:13 +msgid "Details" +msgstr "Details" + +#. Button label for retrying an account problem +#: ui/main-window.ui:261 +msgid "Retry" +msgstr "Wiederholen" + +#. Infobar title when one or more accounts have encounted an error +#: ui/main-window.ui:294 +msgid "Account problem" +msgstr "Kontoproblem" + +#. Label and tooltip for account service problem infobar +#: ui/main-window.ui:308 +msgid "" +"Geary encountered a problem connecting to an account.\n" +"Please check your Internet connection, the server configuration and try " +"again." +msgstr "" +"Geary ist beim Verbinden zu einem Konto auf ein Problem gestoßen.\n" +"Bitte überprüfen Sie Ihre Internetverbindung und die Servereinstellungen, " +"und versuchen Sie es erneut." + +#. Label and tooltip for account service problem infobar +#: ui/main-window.ui:311 +msgid "Geary encountered a problem connecting to an account." +msgstr "Geary ist beim Verbinden zu einem Konto auf ein Problem gestoßen." + +#. Button label for retrying TLS cert validation +#: ui/main-window.ui:358 +msgid "Check" +msgstr "Prüfen" + +#. Button tooltip for retrying TLS cert validation +#: ui/main-window.ui:362 +msgid "Check the security details for the connection" +msgstr "Sicherheitsdetails für die Verbindung prüfen" + +#. Infobar title when one or more accounts have a TLS cert validation error +#: ui/main-window.ui:391 +msgid "Security problem" +msgstr "Sicherheitsproblem" + +#. Label and tooltip for TLS cert validation error infobar +#: ui/main-window.ui:405 +msgid "" +"An account has reported an untrusted server.\n" +"Please check the server configuration and try again." +msgstr "" +"Ein Konto hat einen nicht vertrauenswürdigen Server gemeldet.\n" +"Bitte prüfen Sie die Servereinstellungen und versuchen Sie es erneut." + +#. Label and tooltip for TLS cert validation error infobar +#: ui/main-window.ui:408 +msgid "An account has reported an untrusted server." +msgstr "Ein Konto hat einen nicht-vertrauten Server gemeldet." + +#. Button tooltip for retrying when a login error has occurred +#: ui/main-window.ui:459 +msgid "Retry login, you will be prompted for your password" +msgstr "Anmeldung erneut versuchen, Sie werden nach einem Passwort gefragt" + +#. Infobar title when one or more accounts have a login error +#: ui/main-window.ui:488 +msgid "Login problem" +msgstr "Anmeldeproblem" + +# Das ist im Original schon irreführend: Es reicht nicht, den Benutzernamen +# zu prüfen, auch das Passwort könnte falsch sein. +#. Label and tooltip for authentication problem infobar +#: ui/main-window.ui:502 +msgid "" +"An account has reported an incorrect login or password.\n" +"Please check your login name and try again." +msgstr "" +"Ein Konto hat einen falschen Benutzernamen oder Passwort gemeldet.\n" +"Bitte prüfen Sie Ihre Anmeldedaten und versuchen Sie es erneut." + +#. Label and tooltip for authentication problem infobar +#: ui/main-window.ui:505 +msgid "An account has reported an incorrect login or password." +msgstr "Ein Konto hat einen falschen Benutzernamen oder Passwort gemeldet." + +#: ui/password-dialog.glade:74 msgid "SMTP Credentials" msgstr "SMTP-Einstellungen" -#: ../ui/password-dialog.glade.h:2 +#: ui/password-dialog.glade:91 msgid "Username" msgstr "Benutzername" -#: ../ui/password-dialog.glade.h:4 +#: ui/password-dialog.glade:152 msgid "_Remember password" msgstr "_Passwort speichern" -#: ../ui/password-dialog.glade.h:6 +#: ui/password-dialog.glade:210 msgid "_Authenticate" msgstr "_Legitimieren" -#: ../ui/preferences-dialog.ui.h:1 +#: ui/preferences-dialog.ui:38 msgid "Reading" msgstr "Lesen" -#: ../ui/preferences-dialog.ui.h:2 +#: ui/preferences-dialog.ui:51 msgid "_Automatically select next message" msgstr "Automatisch die nächste Nachricht _wählen" -#: ../ui/preferences-dialog.ui.h:3 +#: ui/preferences-dialog.ui:70 msgid "_Display conversation preview" msgstr "Konversations_vorschau anzeigen" -#: ../ui/preferences-dialog.ui.h:4 +#: ui/preferences-dialog.ui:89 msgid "Use _three pane view" msgstr "_Dreispaltige Ansicht verwenden" -#: ../ui/preferences-dialog.ui.h:5 +#: ui/preferences-dialog.ui:113 msgid "Notifications" msgstr "Benachrichtigungen" -#: ../ui/preferences-dialog.ui.h:6 +#: ui/preferences-dialog.ui:126 msgid "_Play notification sounds" msgstr "_Benachrichtigungstöne abspielen" -#: ../ui/preferences-dialog.ui.h:7 +#: ui/preferences-dialog.ui:145 msgid "Show _notifications for new mail" msgstr "Benachrichtigung bei _neuen E-Mails anzeigen" -#: ../ui/preferences-dialog.ui.h:8 -msgid "Always _watch for new mail" +#: ui/preferences-dialog.ui:164 +msgid "_Watch for new mail when closed" msgstr "_Ständig nach neuen E-Mails prüfen" -#: ../ui/preferences-dialog.ui.h:9 -msgid "Geary will run in the background and notify of new mail" -msgstr "Geary im Hintergrund ausführen und bei neuen E-Mails benachrichtigen" +#: ui/preferences-dialog.ui:168 +msgid "Geary will keep running after all windows are closed" +msgstr "Geary wird weiter ausgeführt, nachdem alle Fenster geschlossen wurden" -#: ../ui/preferences-dialog.ui.h:10 +#: ui/preferences-dialog.ui:195 msgid "Preferences" msgstr "Einstellungen" -#: ../ui/remove_confirm.glade.h:1 +#. Button label for copying technical information to the clipboard +#: ui/problem-details-dialog.ui:17 +msgid "Copy to Clipboard" +msgstr "In die Zwischenablage kopieren" + +#. Button tooltip for copying technical information to the clipboard +#: ui/problem-details-dialog.ui:21 msgid "" -"Are you sure you want to remove this " -"account? " +"Copy technical details to clipboard for pasting into an email or bug report" msgstr "" -"Sind Sie sicher, dass Sie dieses Konto " -"löschen wollen?" +"Technische Details zum Einfügen in eine E-Mail oder einen Fehlerbericht in " +"die Zwischenablage einfügen" -#: ../ui/remove_confirm.glade.h:2 +#: ui/problem-details-dialog.ui:73 msgid "" -"All email associated with this account will be removed from your computer. " -"This will not affect email on the server." +"If the problem is serious or persists, please copy and send these details to " +"the mailing list " +"or file a new " +"bug report." msgstr "" -"Alle E-Mails, die mit diesem Konto verknüpft sind, werden von Ihrem Rechner " -"entfernt.\n" -"Die auf dem Server gespeicherten E-Mails bleiben erhalten." - -#: ../ui/remove_confirm.glade.h:3 -msgid "Nickname:" -msgstr "Nickname:" +"Falls das Problem schwerwiegend ist oder weiterhin besteht, kopieren Sie " +"diese Details und senden Sie sie an die Mailingliste oder erstellen Sie einen neuen Fehlerbericht." -#: ../ui/remove_confirm.glade.h:4 -msgid "Email address:" -msgstr "E-Mail-Adresse:" +#: ui/problem-details-dialog.ui:89 +msgid "Details:" +msgstr "Details:" -#: ../ui/upgrade_dialog.glade.h:1 +#: ui/upgrade_dialog.glade:60 msgid "Geary update in progress…" msgstr "Aktualisierung von Geary wird durchgeführt …" + +#~ msgid "Base URL to look up contact avatars" +#~ msgstr "Basis-Adresse zum Abruf von Kontakt-Benutzerbildern" + +#~ msgid "" +#~ "A Gravatar or Libravatar compatible URL, set to the empty string to " +#~ "disable." +#~ msgstr "" +#~ "Eine mit Gravatar oder Libravatar kompatible Adresse (zum Deaktivieren " +#~ "auf leere Zeichenkette einstellen)." + +#~ msgid "Delete conversations (Shift+Delete)" +#~ msgstr "Konversationen löschen (Umschalt+Entf)" + +#~ msgid "Move conversations to Trash (Delete, Backspace)" +#~ msgstr "Konversationen in den Papierkorb verschieben (Entf, Rücktaste)" + +#~ msgid "Archive conversations (A)" +#~ msgstr "Konversationen archivieren (A)" + +#~ msgid "Mark conversations" +#~ msgstr "Konversationen markieren" + +#~ msgid "Add label to conversations" +#~ msgstr "Konversationen eine Beschriftung zuweisen" + +#~ msgid "Move conversations" +#~ msgstr "Konversationen verschieben" + +#~ msgid "A_ccounts" +#~ msgstr "_Konten" + +#~ msgid "Empty Spam or Trash folders" +#~ msgstr "Spam oder Papierkorb leeren" + +#~ msgid "Retry connecting now" +#~ msgstr "Jetzt neu verbinden" + +#~ msgid "Try reconnecting now" +#~ msgstr "Jetzt neu verbinden" + +#~ msgid "Problem with connection to incoming server for %s" +#~ msgstr "Fehler bei der Verbindung mit dem Eingangsserver für %s" + +#~ msgid "Problem with connection to outgoing server for %s" +#~ msgstr "Fehler bei der Verbindung mit dem Eingangsserver für %s" + +#~ msgid "To: " +#~ msgstr "An: " + +#~ msgid "Cc: " +#~ msgstr "Kopie: " + +#~ msgid "Bcc: " +#~ msgstr "Blindkopie: " + +#~ msgid "From: %s\n" +#~ msgstr "Von: %s\n" + +#~ msgid "Subject: %s\n" +#~ msgstr "Betreff: %s\n" + +#~ msgid "Date: %s\n" +#~ msgstr "Datum: %s\n" + +#~ msgid "To: %s\n" +#~ msgstr "An: %s\n" + +#~ msgid "Cc: %s\n" +#~ msgstr "Kopie: %s\n" diff -Nru geary-0.12.4/po/eo.po geary-3.32.0/po/eo.po --- geary-0.12.4/po/eo.po 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/po/eo.po 2019-03-17 13:39:29.000000000 +0000 @@ -2,1509 +2,3030 @@ # PO message string template file for Geary email client # Copyright 2016 Software Freedom Conservancy Inc. # This file is distributed under the GNU LGPL, version 2.1. -# # Translators: # Baptiste , 2012 # elopio , 2013 # R2D221 , 2012 +# Kristjan SCHMIDT , 2017. msgid "" msgstr "" "Project-Id-Version: geary-0.4.1\n" -"Report-Msgid-Bugs-To: http://redmine.yorba.org/projects/geary\n" -"POT-Creation-Date: 2013-09-20 12:16-0700\n" -"PO-Revision-Date: 2013-09-20 19:28+0000\n" -"Last-Translator: yorbajim \n" -"Language-Team: Esperanto (http://www.transifex.com/projects/p/geary/language/" -"eo/)\n" +"Report-Msgid-Bugs-To: https://bugzilla.gnome.org/enter_bug.cgi?product=geary&" +"keywords=I18N+L10N&component=internationalization\n" +"POT-Creation-Date: 2017-12-12 12:40+0000\n" +"PO-Revision-Date: 2017-12-23 02:47+0200\n" +"Last-Translator: Kristjan SCHMIDT \n" +"Language-Team: Esperanto \n" "Language: eo\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Virtaal 0.7.1\n" +"X-Project-Style: gnome\n" -#: ../../src/client/accounts/add-edit-page.vala:641 -msgid " • Connection error.\n" -msgstr "" +#: desktop/geary-attach.contract.desktop.in:3 +#, fuzzy +#| msgid "Send and receive email" +msgid "Send by email" +msgstr "Sendi kaj ricevi retpoŝton" -#: ../../src/client/accounts/add-edit-page.vala:624 -msgid " • Email address already added to Geary.\n" -msgstr "" +#. Translators: Do NOT translate or transliterate this text (this is an icon file name)! +#: desktop/geary-attach.contract.desktop.in:5 +msgid "mail-send" +msgstr "" + +#: desktop/geary-attach.contract.desktop.in:6 +msgid "Send files using Geary" +msgstr "" + +#. Translators: The application name +#: desktop/geary-autostart.desktop.in:3 +#: desktop/org.gnome.Geary.appdata.xml.in:11 +#: desktop/org.gnome.Geary.desktop.in:3 +#| msgid "Geary Mail" +msgid "Geary" +msgstr "Geary" + +#: desktop/geary-autostart.desktop.in:4 desktop/org.gnome.Geary.desktop.in:4 +#| msgid "Gmail" +msgid "Email" +msgstr "Retpoŝto" + +#. Translators: The application's summary / tagline +#: desktop/geary-autostart.desktop.in:5 +#: desktop/org.gnome.Geary.appdata.xml.in:15 +#: desktop/org.gnome.Geary.desktop.in:5 +#: src/client/application/geary-application.vala:21 +msgid "Send and receive email" +msgstr "Sendi kaj ricevi retpoŝton" -#: ../../src/client/accounts/add-edit-page.vala:628 -msgid " • IMAP connection error.\n" +#. Translators: These are desktop search terms. Do not translate semicolons, end line with a semicolon. +#: desktop/geary-autostart.desktop.in:7 +msgid "Email;E-mail;Mail;" msgstr "" -#: ../../src/client/accounts/add-edit-page.vala:631 -msgid " • IMAP username or password incorrect.\n" -msgstr "" +#. Translators: Do NOT translate or transliterate this text (this is an icon file name)! +#: desktop/geary-autostart.desktop.in:9 desktop/org.gnome.Geary.desktop.in:9 +msgid "org.gnome.Geary" +msgstr "org.gnome.Geary" -#: ../../src/client/accounts/add-edit-page.vala:621 -msgid " • Invalid account nickname.\n" +#. Translators: The development team's name +#: desktop/org.gnome.Geary.appdata.xml.in:13 +msgid "Geary Development Team" msgstr "" -#: ../../src/client/accounts/add-edit-page.vala:634 -msgid " • SMTP connection error.\n" +#: desktop/org.gnome.Geary.appdata.xml.in:17 +msgid "" +"Geary is an email application built around conversations, for the GNOME 3 " +"desktop. It allows you to read, find and send email with a straightforward, " +"modern interface." msgstr "" -#: ../../src/client/accounts/add-edit-page.vala:637 -msgid " • SMTP username or password incorrect.\n" +#: desktop/org.gnome.Geary.appdata.xml.in:22 +msgid "" +"Conversations allow you to read a complete discussion without having to find " +"and click from message to message." msgstr "" -#: ../../src/client/accounts/add-edit-page.vala:645 -msgid " • Username or password incorrect.\n" +#: desktop/org.gnome.Geary.appdata.xml.in:26 +msgid "Geary’s features include:" msgstr "" -#: ../../src/client/views/conversation-viewer.vala:1203 -msgid " (Invalid?)" +#: desktop/org.gnome.Geary.appdata.xml.in:28 +msgid "Quick email account setup" msgstr "" -#: ../../src/client/composer/composer-window.vala:972 -#, c-format -msgid "\"%s\" already attached for delivery." +#: desktop/org.gnome.Geary.appdata.xml.in:29 +msgid "Shows related messages together in conversations" msgstr "" -#: ../../src/client/composer/composer-window.vala:937 -#, c-format -msgid "\"%s\" could not be found." +#: desktop/org.gnome.Geary.appdata.xml.in:30 +msgid "Fast, full text and keyword search" msgstr "" -#: ../../src/client/composer/composer-window.vala:965 -#, c-format -msgid "\"%s\" could not be opened for reading." +#: desktop/org.gnome.Geary.appdata.xml.in:31 +msgid "Full-featured HTML and plain text message composer" msgstr "" -#: ../../src/client/composer/composer-window.vala:944 -#, c-format -msgid "\"%s\" is a folder." +#: desktop/org.gnome.Geary.appdata.xml.in:32 +msgid "Desktop notification of new mail" msgstr "" -#: ../../src/client/composer/composer-window.vala:951 -#, c-format -msgid "\"%s\" is an empty file." +#: desktop/org.gnome.Geary.appdata.xml.in:33 +msgid "Compatible with GMail, Yahoo! Mail, Outlook.com and other IMAP servers" msgstr "" -#. / Date format that shows the weekday (Monday, Tuesday, ...) -#. / See http://developer.gnome.org/glib/2.32/glib-GDateTime.html#g-date-time-format -#: ../../src/client/util/util-date.vala:182 -#, c-format -msgid "%A" +#. Translators: A screenshot description. +#: desktop/org.gnome.Geary.appdata.xml.in:47 +msgid "Geary displaying a conversation" msgstr "" -#. / Verbose datetime format for 24-hour time, i.e. November 8, 2010 16:35 -#. / See http://developer.gnome.org/glib/2.32/glib-GDateTime.html#g-date-time-format -#: ../../src/client/util/util-date.vala:91 -msgid "%B %-e, %Y %-H:%M" -msgstr "%B %-e, %Y %-H:%M" +#. Translators: A screenshot description. +#: desktop/org.gnome.Geary.appdata.xml.in:52 +msgid "Geary showing the rich text composer" +msgstr "" -#. / Verbose datetime format for 12-hour time, i.e. November 8, 2010 8:42 am -#. / See http://developer.gnome.org/glib/2.32/glib-GDateTime.html#g-date-time-format -#: ../../src/client/util/util-date.vala:88 -msgid "%B %-e, %Y %-l:%M %P" -msgstr "%B %-e, %Y %-l:%M %P" +#. Translators: These are desktop search terms. Do not translate semicolons, end line with a semicolon. +#: desktop/org.gnome.Geary.desktop.in:7 +msgid "Mail;E-mail;IMAP;GMail;Yahoo;Hotmail;Outlook;" +msgstr "" -#. / Verbose datetime format for the locale default (full month, day and time) -#. / See http://developer.gnome.org/glib/2.32/glib-GDateTime.html#g-date-time-format -#: ../../src/client/util/util-date.vala:94 -msgctxt "Default full date" -msgid "%B %-e, %Y %-l:%M %P" -msgstr "%B %-e, %Y %-l:%M %P" +#: desktop/org.gnome.Geary.desktop.in:20 ui/gtk/menus.ui:7 +msgid "Compose Message" +msgstr "Verki mesaĝon" -#. / Datetime format for 24-hour time, i.e. 16:35 -#. / See http://developer.gnome.org/glib/2.32/glib-GDateTime.html#g-date-time-format -#: ../../src/client/util/util-date.vala:71 -msgid "%H:%M" -msgstr "%H:%M" +#: desktop/org.gnome.Geary.gschema.xml:8 +msgid "Default attachments directory" +msgstr "" -#. / Format for the datetime that a message being replied to was received -#. / See http://developer.gnome.org/glib/2.32/glib-GDateTime.html#g-date-time-format -#: ../../src/engine/rfc822/rfc822-utils.vala:165 -msgid "%a, %b %-e, %Y at %-l:%M %p" -msgstr "%a, %b %-e, %Y at %-l:%M %p" +#: desktop/org.gnome.Geary.gschema.xml:9 +msgid "Location used when opening and saving attachments." +msgstr "" -#. / Date format for dates within the current year, i.e. Nov 8 -#. / See http://developer.gnome.org/glib/2.32/glib-GDateTime.html#g-date-time-format -#: ../../src/client/util/util-date.vala:78 -msgid "%b %-e" -msgstr "%b %-e" +#: desktop/org.gnome.Geary.gschema.xml:14 +msgid "Default print output directory" +msgstr "" -#: ../../src/client/folder-list/folder-list-folder-entry.vala:30 -#, c-format -msgid "%d message" -msgid_plural "%d messages" -msgstr[0] "" -msgstr[1] "" +#: desktop/org.gnome.Geary.gschema.xml:15 +msgid "Location used when printing to a file." +msgstr "" -#: ../../src/client/notification/libnotify.vala:72 -#, c-format -msgid "%d new message" -msgid_plural "%d new messages" -msgstr[0] "%d nova mesaĝo" -msgstr[1] "%d novaj mesaĝoj" +#: desktop/org.gnome.Geary.gschema.xml:20 +msgid "Maximize window" +msgstr "" -#: ../../src/client/folder-list/folder-list-search-branch.vala:43 -#, c-format -msgid "%d results" +#: desktop/org.gnome.Geary.gschema.xml:21 +msgid "True if library application is maximized, false otherwise." msgstr "" -#. / Label displaying number of unread email messages in a folder -#: ../../src/client/folder-list/folder-list-folder-entry.vala:37 -#, c-format -msgid "%d unread" -msgid_plural "%d unread" -msgstr[0] "" -msgstr[1] "" +#: desktop/org.gnome.Geary.gschema.xml:26 +msgid "Width of window" +msgstr "" -#: ../../src/client/util/util-date.vala:170 -#, c-format -msgid "%dh ago" +#: desktop/org.gnome.Geary.gschema.xml:27 +msgid "The last recorded width of the application window." msgstr "" -#: ../../src/client/util/util-date.vala:167 -#, c-format -msgid "%dm ago" +#: desktop/org.gnome.Geary.gschema.xml:32 +msgid "Height of window" msgstr "" -#: ../../src/client/views/conversation-find-bar.vala:222 -#, c-format -msgid "%i matches" +#: desktop/org.gnome.Geary.gschema.xml:33 +msgid "The last recorded height of the application window." msgstr "" -#: ../../src/client/views/conversation-find-bar.vala:224 -#, c-format -msgid "%i matches (wrapped)" +#: desktop/org.gnome.Geary.gschema.xml:38 +msgid "Position of folder list pane" msgstr "" -#. / Datetime format for 12-hour time, i.e. 8:31 am -#. / See http://developer.gnome.org/glib/2.32/glib-GDateTime.html#g-date-time-format -#: ../../src/client/util/util-date.vala:68 -msgid "%l:%M %P" -msgstr "%l:%M %P" +#: desktop/org.gnome.Geary.gschema.xml:39 +msgid "Position of the folder list Paned grabber." +msgstr "" -#. / Datetime format for the locale default, i.e. 8:31 am or 16:35, -#. / See http://developer.gnome.org/glib/2.32/glib-GDateTime.html#g-date-time-format -#: ../../src/client/util/util-date.vala:74 -msgctxt "Default clock format" -msgid "%l:%M %P" -msgstr "%l:%M %P" +#: desktop/org.gnome.Geary.gschema.xml:44 +msgid "Position of folder list pane when horizontal" +msgstr "" -#: ../../src/client/notification/libnotify.vala:107 -#, c-format +#: desktop/org.gnome.Geary.gschema.xml:45 msgid "" -"%s\n" -"(%d other new message for %s)" -msgid_plural "" -"%s\n" -"(%d other new messages for %s)" -msgstr[0] "" -msgstr[1] "" - -#. / In the composer, the filename followed by its filesize, i.e. "notes.txt (1.12KB)" -#: ../../src/client/composer/composer-window.vala:981 -#, c-format -msgid "%s (%s)" +"Position of the folder list Paned grabber in the horizontal orientation." msgstr "" -#: ../../src/client/views/conversation-web-view.vala:289 -#, c-format -msgid "%s - Conversation Inspector" +#: desktop/org.gnome.Geary.gschema.xml:50 +msgid "Position of folder list pane when vertical" msgstr "" -#: ../../src/client/notification/libmessagingmenu.vala:75 -#, c-format -msgid "%s - New Messages" +#: desktop/org.gnome.Geary.gschema.xml:51 +msgid "Position of the folder list Paned grabber in the vertical orientation." msgstr "" -#. / The quoted header for a message being replied to (in case the date is not known). -#. / %s will be replaced by the original sender. -#: ../../src/engine/rfc822/rfc822-utils.vala:178 -#, c-format -msgid "%s wrote:" -msgstr "%s skribis:" - -#: ../../src/client/notification/libnotify.vala:75 -#, c-format -msgid "%s, %d new message total" -msgid_plural "%s, %d new messages total" -msgstr[0] "" -msgstr[1] "" - -#. / This string represents the divider between two messages: "n messages" and "n unread", -#. / shown in the folder list as a tooltip. Please use your languages conventions for -#. / combining the two, i.e. a comma (",") for English; "6 messages, 3 unread" -#: ../../src/client/folder-list/folder-list-folder-entry.vala:43 -#, c-format -msgid "%s, %s" +#: desktop/org.gnome.Geary.gschema.xml:56 +msgid "Orientation of the folder list pane" msgstr "" -#: ../../src/client/views/conversation-viewer.vala:246 -#, c-format -msgid "%u conversations selected." -msgstr "%u konversacioj elektitaj." +#: desktop/org.gnome.Geary.gschema.xml:57 +msgid "True if the folder list Paned is in the horizontal orientation." +msgstr "" -#: ../../src/client/views/conversation-viewer.vala:743 -#, c-format -msgid "%u read messages" +#: desktop/org.gnome.Geary.gschema.xml:62 +msgid "Position of message list pane" msgstr "" -#. / Date format for dates within a different year, i.e. 02/04/10 -#. / See http://developer.gnome.org/glib/2.32/glib-GDateTime.html#g-date-time-format -#: ../../src/client/util/util-date.vala:83 -#, no-c-format -msgid "%x" -msgstr "%x" +#: desktop/org.gnome.Geary.gschema.xml:63 +msgid "Position of the message list Paned grabber." +msgstr "" -#: ../../src/client/util/util-email.vala:30 -msgid "(no subject)" -msgstr "(neniu temo)" +#: desktop/org.gnome.Geary.gschema.xml:68 +msgid "Autoselect next message" +msgstr "" -#: ../../src/engine/rfc822/rfc822-utils.vala:211 -msgid "---------- Forwarded message ----------" -msgstr "---------- Plusendita mesaĝo ----------" +#: desktop/org.gnome.Geary.gschema.xml:69 +msgid "True if we should autoselect the next available conversation." +msgstr "" -#: ../../src/client/accounts/add-edit-page.vala:211 -msgid "1 month back" +#: desktop/org.gnome.Geary.gschema.xml:74 +msgid "Display message previews" msgstr "" -#: ../../src/client/accounts/add-edit-page.vala:214 -msgid "1 year back" +#: desktop/org.gnome.Geary.gschema.xml:75 +msgid "True if we should display a short preview of each message." msgstr "" -#: ../../src/client/accounts/add-edit-page.vala:210 -msgid "2 weeks back" +#: desktop/org.gnome.Geary.gschema.xml:80 +msgid "Languages that shall be used in the spell checker" msgstr "" -#: ../../src/client/accounts/add-edit-page.vala:212 -msgid "3 months back" +#: desktop/org.gnome.Geary.gschema.xml:81 +msgid "List of the languages to use in the spell checker." msgstr "" -#: ../../src/client/accounts/add-edit-page.vala:213 -msgid "6 months back" +#: desktop/org.gnome.Geary.gschema.xml:86 +msgid "Languages that are displayed in the spell checker popover" msgstr "" -#: ../../ui/remove_confirm.glade:43 +#: desktop/org.gnome.Geary.gschema.xml:87 msgid "" -"Are you sure you want to remove this " -"account? " +"List of languages that are always displayed in the popover of the spell " +"checker." msgstr "" -#: ../../ui/account_cannot_remove.glade:40 -msgid "Cannot remove account " +#: desktop/org.gnome.Geary.gschema.xml:92 +msgid "Enable notification sounds" msgstr "" -#: ../../ui/account_cannot_remove.glade:56 -msgid "" -"A composer window associated with this account is currently open. Send or " -"discard the message and try again." +#: desktop/org.gnome.Geary.gschema.xml:93 +msgid "True to play sounds for notifications and sending." msgstr "" -#: ../../src/client/geary-controller.vala:1491 -#, c-format -msgid "A file named \"%s\" already exists. Do you want to replace it?" -msgstr "Dosiero nomita \"%s\" jam ekzistas. Ĉu vi volas anstataŭigi ĝin?" - -#: ../../src/client/geary-controller.vala:245 -msgid "A_ccounts" +#: desktop/org.gnome.Geary.gschema.xml:98 +msgid "Show notifications for new mail" msgstr "" -#: ../../src/client/geary-controller.vala:1227 -#, c-format -msgid "About %s" -msgstr "Pri %s" +#: desktop/org.gnome.Geary.gschema.xml:99 +msgid "True to show notification bubbles." +msgstr "" -#: ../../src/client/accounts/account-dialog.vala:21 -msgid "Accounts" +#: desktop/org.gnome.Geary.gschema.xml:104 +msgid "Notify of new mail at startup" msgstr "" -#: ../../ui/account_list.glade:71 -msgid "Add account" +#: desktop/org.gnome.Geary.gschema.xml:105 +msgid "True to notify of new mail at startup." msgstr "" -#: ../../src/client/geary-controller.vala:304 -msgid "Add label" +#: desktop/org.gnome.Geary.gschema.xml:110 +msgid "Ask when opening an attachment" msgstr "" -#: ../../src/client/geary-controller.vala:57 -msgid "Add label to conversation" +#: desktop/org.gnome.Geary.gschema.xml:111 +#, fuzzy +#| msgid "To add them as attachments" +msgid "True to ask when opening an attachment." +msgstr "Por aldoni ilin kiel kunsendaĵoj." + +#: desktop/org.gnome.Geary.gschema.xml:116 +msgid "Whether to compose emails in HTML" msgstr "" -#: ../../src/client/geary-controller.vala:58 -msgid "Add label to conversations" +#: desktop/org.gnome.Geary.gschema.xml:117 +msgid "True to compose emails in HTML; false for plain text." msgstr "" -#: ../../src/engine/api/geary-special-folder-type.vala:39 -msgid "All Mail" -msgstr "Ĉiuj mesaĝoj" +#: desktop/org.gnome.Geary.gschema.xml:122 +msgid "Advisory strategy for full-text searching" +msgstr "" -#: ../../ui/remove_confirm.glade:58 +#: desktop/org.gnome.Geary.gschema.xml:123 msgid "" -"All email associated with this account will be removed from your computer. " -"This will not affect email on the server." +"Acceptable values are “exact”, “conservative”, “aggressive”, and “horizon”." msgstr "" -#: ../../src/client/geary-args.vala:24 -msgid "Allow inspection of WebView" -msgstr "" +#: desktop/org.gnome.Geary.gschema.xml:128 +#, fuzzy +#| msgid "No conversations selected." +msgid "Zoom of conversation viewer" +msgstr "Neniu konversacio elektita" -#: ../../src/client/views/conversation-viewer.vala:509 -msgid "Always Show From Sender" +#: desktop/org.gnome.Geary.gschema.xml:129 +msgid "The zoom to apply on the conservation view." msgstr "" -#: ../../src/engine/api/geary-special-folder-type.vala:54 -msgid "Archive" +#: desktop/org.gnome.Geary.gschema.xml:134 +msgid "Size of detached composer window" msgstr "" -#: ../../src/client/geary-controller.vala:48 -msgid "Archive conversation (Delete, Backspace, A)" +#: desktop/org.gnome.Geary.gschema.xml:135 +msgid "The last recorded size of the detached composer window." msgstr "" -#: ../../src/client/geary-controller.vala:49 -msgid "Archive conversations (Delete, Backspace, A)" +#: desktop/org.gnome.Geary.gschema.xml:140 +msgid "Whether we migrated the old settings" msgstr "" -#: ../../src/client/geary-controller.vala:1477 -#, c-format -msgid "Are you sure you want to open \"%s\"?" -msgstr "Ĉu vi vere volas malfermi je \"%s\"?" - -#: ../../src/client/geary-controller.vala:1478 +#: desktop/org.gnome.Geary.gschema.xml:141 msgid "" -"Attachments may cause damage to your system if opened. Only open files from " -"trusted sources." +"False to check for the old “org.yorba.geary”-schema and copy its values." msgstr "" -"Kunsendaĵoj povas kaŭzi damaĝojn al via sistemo. Nur malfermu dosierojn el " -"fidindaj fontoj." - -#: ../../src/client/views/conversation-viewer.vala:614 -msgid "Bcc:" -msgstr "Kaŝkopio:" -#: ../../ui/composer.glade:113 -msgid "Bold (Ctrl+B)" +#: src/client/accounts/account-dialog-add-edit-pane.vala:51 +#: src/client/components/stock.vala:31 ui/conversation-email-menus.ui:57 +msgid "_Save" msgstr "" -#: ../../ui/composer.glade:69 -msgid "C_olor" +#: src/client/accounts/account-dialog-add-edit-pane.vala:51 +#: src/client/components/stock.vala:22 +msgid "_Add" msgstr "" -#: ../../src/client/composer/composer-window.vala:926 -msgid "Cannot add attachment" -msgstr "Ne aldoneblas kunsendaĵon" - -#: ../../src/client/views/conversation-viewer.vala:611 -msgid "Cc:" -msgstr "Kopio:" - -#: ../../src/engine/rfc822/rfc822-utils.vala:223 +#. reset/clear widgets +#: src/client/accounts/account-dialog-edit-alternate-emails-pane.vala:120 #, c-format -msgid "Cc: %s\n" +msgid "Additional addresses for %s" msgstr "" -#: ../../src/client/dialogs/attachment-dialog.vala:18 -msgid "Choose a file" -msgstr "Elektu dosieron" +#. Sets min size. +#: src/client/accounts/account-dialog.vala:21 +msgid "Accounts" +msgstr "" -#: ../../src/client/geary-controller.vala:553 -msgid "Co_ntinue" -msgstr "A_ntaŭiri" +#. Copyright 2016 Software Freedom Conservancy Inc. +#. * +#. * This software is licensed under the GNU Lesser General Public License +#. * (version 2.1 or later). See the COPYING file in this distribution. +#. +#. Page for adding or editing an account. +#. / Placeholder text indicating that the user should type their first name and last name +#: src/client/accounts/add-edit-page.vala:10 +msgid "First Last" +msgstr "" -#: ../../src/client/geary-application.vala:29 -msgid "Compose Message" -msgstr "Verki mesaĝon" +#: src/client/accounts/add-edit-page.vala:235 +msgid "Welcome to Geary." +msgstr "Bonvenon en Geary." -#: ../../src/client/geary-controller.vala:313 -msgid "Compose new message (Ctrl+N, N)" -msgstr "" +#: src/client/accounts/add-edit-page.vala:235 +msgid "Enter your account information to get started." +msgstr "Enigu vian kontan informon por komenciĝi." -#: ../../ui/preferences.glade:117 -msgid "Composer" +#: src/client/accounts/add-edit-page.vala:255 +msgid "2 weeks back" msgstr "" -#: ../../src/client/views/conversation-viewer.vala:897 -msgid "Copy _Email Address" +#. IDs are # of days +#: src/client/accounts/add-edit-page.vala:256 +msgid "1 month back" msgstr "" -#: ../../src/client/views/conversation-viewer.vala:902 -msgid "Copy _Link" +#: src/client/accounts/add-edit-page.vala:257 +msgid "3 months back" msgstr "" -#: ../../src/client/geary-application.vala:17 -msgid "Copyright 2011-2013 Yorba Foundation" +#: src/client/accounts/add-edit-page.vala:258 +msgid "6 months back" msgstr "" -#: ../../ui/composer.glade:21 -msgid "Cu_t" +#: src/client/accounts/add-edit-page.vala:259 +msgid "1 year back" msgstr "" -#: ../../src/client/views/conversation-viewer.vala:620 -msgid "Date:" -msgstr "Dato:" - -#: ../../src/engine/rfc822/rfc822-utils.vala:217 -#, c-format -msgid "Date: %s\n" -msgstr "Dato: %s\n" - -#: ../../src/client/geary-controller.vala:43 -msgid "Delete conversation (Delete, Backspace, A)" +#: src/client/accounts/add-edit-page.vala:260 +msgid "2 years back" msgstr "" -#: ../../src/client/geary-controller.vala:44 -msgid "Delete conversations (Delete, Backspace, A)" +#: src/client/accounts/add-edit-page.vala:261 +msgid "4 years back" msgstr "" -#: ../../src/client/geary-args.vala:25 -msgid "Display program version" -msgstr "Montri programversion" - -#: ../../src/client/composer/composer-window.vala:663 -msgid "Do you want to discard the unsaved message?" -msgstr "Ĉu vi volas forigi la nekonservitan dosieron?" - -#: ../../src/client/composer/composer-window.vala:666 -msgid "Do you want to discard this message?" +#. Separator +#: src/client/accounts/add-edit-page.vala:263 +msgid "Everything" msgstr "" -#: ../../src/client/geary-controller.vala:1479 -msgid "Don't _ask me again" -msgstr "Ne demandi min denove" - -#: ../../src/engine/api/geary-special-folder-type.vala:27 -msgid "Drafts" -msgstr "Malnetoj" - -#: ../../ui/composer.glade:419 -msgid "Drop files here" +#: src/client/accounts/add-edit-page.vala:283 +msgid "Edit" msgstr "" -#: ../../ui/login.glade:115 -msgid "E_mail address:" +#: src/client/accounts/add-edit-page.vala:285 +msgid "Preview" msgstr "" -#: ../../src/client/geary-controller.vala:704 -msgid "E_xit" +#: src/client/accounts/add-edit-page.vala:751 +msgid "Remem_ber passwords" msgstr "" -#: ../../src/client/views/conversation-viewer.vala:533 -msgid "Edit Draft" +#: src/client/accounts/add-edit-page.vala:758 ui/login.glade:233 +msgid "Remem_ber password" msgstr "" -#: ../../ui/account_list.glade:84 -msgid "Edit account" +#: src/client/accounts/add-edit-page.vala:792 +msgid "Unable to validate:\n" msgstr "" -#: ../../ui/remove_confirm.glade:94 -msgid "Email address:" +#: src/client/accounts/add-edit-page.vala:794 +msgid " • Invalid account nickname.\n" msgstr "" -#: ../../src/client/geary-application.vala:28 -msgid "Email;E-mail;Mail;" +#: src/client/accounts/add-edit-page.vala:797 +msgid " • Email address already added to Geary.\n" msgstr "" -#: ../../ui/preferences.glade:131 -msgid "Enable _spell checking" +#: src/client/accounts/add-edit-page.vala:801 +msgid " • IMAP connection error.\n" msgstr "" -#: ../../ui/login.glade:588 -msgid "Encr_yption:" +#: src/client/accounts/add-edit-page.vala:804 +msgid " • IMAP username or password incorrect.\n" msgstr "" -#: ../../ui/login.glade:607 -msgid "Encrypt_ion:" +#: src/client/accounts/add-edit-page.vala:807 +msgid " • SMTP connection error.\n" msgstr "" -#: ../../src/client/accounts/add-edit-page.vala:190 -msgid "Enter your account information to get started." -msgstr "Enigu vian kontan informon por komenciĝi." +#: src/client/accounts/add-edit-page.vala:810 +msgid " • SMTP username or password incorrect.\n" +msgstr "" -#: ../../src/client/geary-controller.vala:714 -#, c-format -msgid "" -"Error during rebuild:\n" -"\n" -"%s" +#: src/client/accounts/add-edit-page.vala:814 +msgid " • Connection error.\n" msgstr "" -#: ../../src/client/composer/composer-window.vala:43 -msgid "Error saving" +#: src/client/accounts/add-edit-page.vala:818 +msgid " • Username or password incorrect.\n" msgstr "" -#. / Displayed in the space-limited status bar when a message fails to be sent due to error. -#: ../../src/client/geary-controller.vala:625 -#: ../../src/client/ui/status-bar.vala:28 -msgid "Error sending email" +#: src/client/application/geary-application.vala:22 +msgid "Copyright 2016 Software Freedom Conservancy Inc." msgstr "" -#: ../../src/client/accounts/add-edit-page.vala:216 -msgid "Everything" +#: src/client/application/geary-application.vala:23 +msgid "Copyright 2016-2017 Geary Development Team." msgstr "" -#: ../../src/client/views/conversation-viewer.vala:1897 -msgid "Failed to open default text editor." -msgstr "Malsukcesis malfermi aprioran tekstredaktilon." +#: src/client/application/geary-application.vala:25 +msgid "Visit the Geary web site" +msgstr "" -#: ../../src/client/geary-args.vala:54 +#: src/client/application/geary-application.vala:423 #, c-format -msgid "Failed to parse command line options: %s\n" -msgstr "Malsukcesis analizi komandliniajn opciojn: \"%s\"\n" +msgid "About %s" +msgstr "Pri %s" -#. / Placeholder text indicating that the user should type their first name and last name -#: ../../src/client/accounts/add-edit-page.vala:10 -msgid "First Last" +#. Translators: add your name and email address to receive +#. credit in the About dialog For example: Yamada Taro +#. +#: src/client/application/geary-application.vala:427 +msgid "translator-credits" +msgstr "Baptiste, elopio, R2D221, Kristjan SCHMIDT" + +#: src/client/application/geary-args.vala:10 +msgid "Start Geary with hidden main window" msgstr "" -#: ../../ui/composer.glade:182 -msgid "Fixed Width" +#: src/client/application/geary-args.vala:11 +msgid "Output debugging information" msgstr "" -#: ../../src/client/geary-controller.vala:329 -msgid "Forward (Ctrl+L, F)" +#: src/client/application/geary-args.vala:12 +msgid "Log conversation monitoring" msgstr "" -#: ../../src/client/views/conversation-viewer.vala:605 -msgid "From:" -msgstr "El:" +#: src/client/application/geary-args.vala:13 +msgid "Log network deserialization" +msgstr "" -#: ../../src/engine/rfc822/rfc822-utils.vala:215 +#: src/client/application/geary-args.vala:14 +msgid "Log network activity" +msgstr "" + +#. / The IMAP replay queue is how changes on the server are replicated on the client. +#. / It could also be called the IMAP events queue. +#: src/client/application/geary-args.vala:17 +msgid "Log IMAP replay queue" +msgstr "" + +#. / Serialization is how commands and responses are converted into a stream of bytes for +#. / network transmission +#: src/client/application/geary-args.vala:20 +msgid "Log network serialization" +msgstr "" + +#: src/client/application/geary-args.vala:21 +msgid "Log periodic activity" +msgstr "" + +#: src/client/application/geary-args.vala:22 +msgid "Log database queries (generates lots of messages)" +msgstr "" + +#. / "Normalization" can also be called "synchronization" +#: src/client/application/geary-args.vala:24 +msgid "Log folder normalization" +msgstr "" + +#: src/client/application/geary-args.vala:25 +msgid "Allow inspection of WebView" +msgstr "" + +#: src/client/application/geary-args.vala:26 +msgid "Revoke all server certificates with TLS warnings" +msgstr "" + +#: src/client/application/geary-args.vala:27 +msgid "Perform a graceful quit" +msgstr "" + +#: src/client/application/geary-args.vala:28 +msgid "Display program version" +msgstr "Montri programversion" + +#. This gives a command-line hint on how to open new composer windows with mailto: +#: src/client/application/geary-args.vala:53 #, c-format -msgid "From: %s\n" -msgstr "El: %s\n" +msgid "Use %s to open a new composer window" +msgstr "" -#: ../../src/client/util/util-files.vala:22 -msgctxt "Abbreviation for gigabyte" -msgid "GB" -msgstr "GB" +#: src/client/application/geary-args.vala:56 +msgid "Please report comments, suggestions and bugs to:" +msgstr "" + +#. i18n: Command line arguments are invalid +#: src/client/application/geary-args.vala:63 +#, c-format +msgid "Failed to parse command line options: %s\n" +msgstr "Malsukcesis analizi komandliniajn opciojn: %s\n" + +#: src/client/application/geary-args.vala:74 +#, c-format +#| msgid "Unrecognized command line option \"%s\"\n" +msgid "Unrecognized command line option “%s”\n" +msgstr "Nekonata komandlinia opcio “%s”\n" + +#: src/client/application/geary-controller.vala:627 +msgid "Unable to store server trust exception" +msgstr "" -#: ../../src/client/geary-application.vala:25 -msgid "Geary Mail" -msgstr "Geary Mail" +#: src/client/application/geary-controller.vala:862 +msgid "Your settings are insecure" +msgstr "Viaj agordoj estas malsekuraj" + +#: src/client/application/geary-controller.vala:863 +msgid "" +"Your IMAP and/or SMTP settings do not specify SSL or TLS. This means your " +"username and password could be read by another person on the network. Are " +"you sure you want to do this?" +msgstr "" +"Viaj agordoj por IMAP kaj/aŭ SMTP ne specifas je SSL nek je TLS. Tio " +"signifas, ke viaj uzantonomo kaj pasvorto povas esti legitaj de alia persono " +"en la reto. Ĉu vi vere volas fari tion?" + +#: src/client/application/geary-controller.vala:864 +msgid "Co_ntinue" +msgstr "A_ntaŭiri" + +#. / Displayed in the space-limited status bar when a message fails to be sent due to error. +#: src/client/application/geary-controller.vala:977 +#: src/client/components/status-bar.vala:29 +msgid "Error sending email" +msgstr "" -#: ../../src/client/geary-controller.vala:626 +#: src/client/application/geary-controller.vala:978 msgid "" "Geary encountered an error sending an email. If the problem persists, " "please manually delete the email from your Outbox folder." msgstr "" -#: ../../ui/password-dialog.glade:315 -msgid "General" +#. Displayed in the space-limited status bar when a message fails to be uploaded +#. to Sent Mail after being sent. +#: src/client/application/geary-controller.vala:982 +#: src/client/components/status-bar.vala:33 +msgid "Error saving sent mail" msgstr "" -#: ../../src/engine/api/geary-service-provider.vala:52 -msgid "Gmail" -msgstr "Gmail" - -#: ../../ui/password-dialog.glade:133 -msgid "IMAP Credentials" +#: src/client/application/geary-controller.vala:983 +msgid "" +"Geary encountered an error saving a sent message to Sent Mail. The message " +"will stay in your Outbox folder until you delete it." msgstr "" -#: ../../ui/login.glade:572 -msgid "IMAP password" +#: src/client/application/geary-controller.vala:1056 +msgid "Labels" +msgstr "Etikedoj" + +#. give the user two options: reset the Account local store, or exit Geary. A third +#. could be done to leave the Account in an unopened state, but we don't currently +#. have provisions for that. +#: src/client/application/geary-controller.vala:1068 +#, c-format +msgid "Unable to open the database for %s" msgstr "" -#: ../../ui/login.glade:286 ../../ui/password-dialog.glade:334 -msgid "IMAP settings" +#: src/client/application/geary-controller.vala:1069 +#, c-format +msgid "" +"There was an error opening the local mail database for this account. This is " +"possibly due to corruption of the database file in this directory:\n" +"\n" +"%s\n" +"\n" +"Geary can rebuild the database and re-synchronize with the server or exit.\n" +"\n" +"Rebuilding the database will destroy all local email and its attachments. " +"The mail on the your server will not be affected." msgstr "" -#: ../../ui/login.glade:556 -msgid "IMAP username" +#: src/client/application/geary-controller.vala:1071 +msgid "_Rebuild" msgstr "" -#: ../../src/engine/api/geary-special-folder-type.vala:36 -msgid "Important" +#: src/client/application/geary-controller.vala:1071 +msgid "E_xit" msgstr "" -#: ../../src/engine/api/geary-special-folder-type.vala:24 -msgid "Inbox" -msgstr "Enirkesto" +#: src/client/application/geary-controller.vala:1080 +#, c-format +msgid "Unable to rebuild database for “%s”" +msgstr "" -#: ../../src/client/folder-list/folder-list-inboxes-branch.vala:14 -msgid "Inboxes" +#: src/client/application/geary-controller.vala:1081 +#, c-format +msgid "" +"Error during rebuild:\n" +"\n" +"%s" msgstr "" -#: ../../src/client/ui/main-toolbar.vala:163 +#. some other problem opening the account ... as with other flow path, can't run +#. Geary today with an account in unopened state, so have to exit +#: src/client/application/geary-controller.vala:1103 +#: src/client/application/geary-controller.vala:1113 +#: src/client/application/geary-controller.vala:1124 #, c-format -msgid "Indexing %s account" +msgid "Unable to open local mailbox for %s" msgstr "" -#: ../../ui/composer.glade:120 -msgid "Italic (Ctrl+I)" +#: src/client/application/geary-controller.vala:1104 +#, c-format +msgid "" +"There was an error opening the local mail database for this account. This is " +"possibly due to a file permissions problem.\n" +"\n" +"Please check that you have read/write permissions for all files in this " +"directory:\n" +"\n" +"%s" msgstr "" -#: ../../src/client/util/util-files.vala:28 -msgctxt "Abbreviation for kilobyte" -msgid "KB" -msgstr "KB" +#: src/client/application/geary-controller.vala:1114 +msgid "" +"The version number of the local mail database is formatted for a newer " +"version of Geary. Unfortunately, the database cannot be “rolled back” to " +"work with this version of Geary.\n" +"\n" +"Please install the latest version of Geary and try again." +msgstr "" -#: ../../src/client/geary-controller.vala:689 -msgid "Labels" -msgstr "Etikedoj" +#: src/client/application/geary-controller.vala:1125 +msgid "" +"There was an error opening the local account. This is probably due to " +"connectivity issues.\n" +"\n" +"Please check your network connection and restart Geary." +msgstr "" -#: ../../ui/composer.glade:151 -msgid "Lar_ge" +#: src/client/application/geary-controller.vala:1914 +msgid "Undo move (Ctrl+Z)" msgstr "" -#: ../../ui/composer.glade:152 -msgid "Large" -msgstr "Granda" +#: src/client/application/geary-controller.vala:1924 +#| msgid "Are you sure you want to open \"%s\"?" +msgid "Are you sure you want to open these attachments?" +msgstr "Ĉu vi vere volas malfermi tiun alkroĉaĵon?" -#: ../../ui/composer.glade:62 -msgid "Link (Ctrl+L)" +#: src/client/application/geary-controller.vala:1925 +msgid "" +"Attachments may cause damage to your system if opened. Only open files from " +"trusted sources." msgstr "" +"Kunsendaĵoj povas kaŭzi damaĝojn al via sistemo. Nur malfermu dosierojn el " +"fidindaj fontoj." -#. / The IMAP replay queue is how changes on the server are replicated on the client. -#. / It could also be called the IMAP events queue. -#: ../../src/client/geary-args.vala:16 -msgid "Log IMAP replay queue" +#: src/client/application/geary-controller.vala:1926 +#| msgid "Don't _ask me again" +msgid "Don’t _ask me again" +msgstr "Ne _demandi min denove" + +#: src/client/application/geary-controller.vala:2022 +#, c-format +#| msgid "A file named \"%s\" already exists. Do you want to replace it?" +msgid "A file named “%s” already exists. Do you want to replace it?" +msgstr "Dosiero kun la nomo “%s” jam ekzistas. Ĉu vi volas anstataŭigi ĝin?" + +#: src/client/application/geary-controller.vala:2024 +#, c-format +msgid "" +"The file already exists in “%s”. Replacing it will overwrite its contents." msgstr "" -#: ../../src/client/geary-args.vala:11 -msgid "Log conversation monitoring" +#: src/client/application/geary-controller.vala:2027 +msgid "_Replace" msgstr "" -#: ../../src/client/geary-args.vala:21 -msgid "Log database queries (generates lots of messages)" +#. Find out what to do with the inline composers. +#. TODO: Remove this in favor of automatically saving drafts +#: src/client/application/geary-controller.vala:2262 +msgid "Close open draft messages?" msgstr "" -#. / "Normalization" can also be called "synchronization" -#: ../../src/client/geary-args.vala:23 -msgid "Log folder normalization" +#: src/client/application/geary-controller.vala:2388 +#, c-format +msgid "Empty all email from your %s folder?" msgstr "" -#: ../../src/client/geary-args.vala:13 -msgid "Log network activity" +#: src/client/application/geary-controller.vala:2389 +msgid "This removes the email from Geary and your email server." msgstr "" -#: ../../src/client/geary-args.vala:12 -msgid "Log network deserialization" +#: src/client/application/geary-controller.vala:2390 +msgid "This cannot be undone." msgstr "" -#. / Serialization is how commands and responses are converted into a stream of bytes for -#. / network transmission -#: ../../src/client/geary-args.vala:19 -msgid "Log network serialization" +#: src/client/application/geary-controller.vala:2391 +#, c-format +msgid "Empty %s" msgstr "" -#: ../../src/client/geary-args.vala:20 -msgid "Log periodic activity" +#: src/client/application/geary-controller.vala:2408 +#, c-format +msgid "Error emptying %s" msgstr "" -#: ../../src/client/util/util-files.vala:25 -msgctxt "Abbreviation for megabyte" -msgid "MB" -msgstr "MB" +#: src/client/application/geary-controller.vala:2438 +#, fuzzy +#| msgid "Do you want to discard the unsaved message?" +msgid "Do you want to permanently delete this message?" +msgid_plural "Do you want to permanently delete these messages?" +msgstr[0] "Ĉu vi volas forigi la nekonservitan dosieron?" +msgstr[1] "Ĉu vi volas forigi la nekonservitan dosieron?" -#: ../../src/client/geary-application.vala:26 -msgid "Mail Client" -msgstr "Retpoŝtilo" +#: src/client/application/geary-controller.vala:2440 +#| msgid "_Delete" +msgid "Delete" +msgstr "Forigi" -#: ../../src/client/views/conversation-viewer.vala:1479 -msgid "Mark Unread From _Here" +#: src/client/application/geary-controller.vala:2472 +msgid "Undo archive (Ctrl+Z)" msgstr "" -#: ../../src/client/geary-controller.vala:52 -msgid "Mark as S_pam" +#: src/client/application/geary-controller.vala:2487 +msgid "Undo trash (Ctrl+Z)" msgstr "" -#: ../../src/client/geary-controller.vala:277 -msgid "Mark as _Read" +#: src/client/application/geary-controller.vala:2543 +msgid "Undo (Ctrl+Z)" msgstr "" -#: ../../src/client/geary-controller.vala:283 -msgid "Mark as _Unread" +#: src/client/application/geary-controller.vala:2661 +msgid "Failed to open default text editor." +msgstr "Malsukcesis malfermi defaŭltan tekstredaktilon." + +#. Tooltips +#: src/client/components/main-toolbar.vala:69 +#, fuzzy +#| msgid "%u conversations selected." +msgid "Delete conversation (Shift+Delete)" +msgstr "Forigi konversacion" + +#: src/client/components/main-toolbar.vala:70 +#, fuzzy +#| msgid "%u conversations selected." +msgid "Delete conversations (Shift+Delete)" +msgstr "Forigi konversaciojn" + +#: src/client/components/main-toolbar.vala:71 +msgid "Move conversation to Trash (Delete, Backspace)" msgstr "" -#: ../../src/client/geary-controller.vala:53 -msgid "Mark as not S_pam" +#: src/client/components/main-toolbar.vala:72 +#, fuzzy +#| msgid "No conversations selected." +msgid "Move conversations to Trash (Delete, Backspace)" +msgstr "Movi konversacion rubujen" + +#: src/client/components/main-toolbar.vala:73 +msgid "Archive conversation (A)" +msgstr "" + +#: src/client/components/main-toolbar.vala:74 +msgid "Archive conversations (A)" msgstr "" -#: ../../src/client/geary-controller.vala:55 -#: ../../src/client/geary-controller.vala:269 +#: src/client/components/main-toolbar.vala:75 msgid "Mark conversation" msgstr "" -#: ../../src/client/geary-controller.vala:56 +#: src/client/components/main-toolbar.vala:76 msgid "Mark conversations" msgstr "" -#: ../../src/client/views/formatted-conversation-data.vala:11 -msgid "Me" -msgstr "Mi" - -#: ../../ui/composer.glade:158 -msgid "Medium" +#: src/client/components/main-toolbar.vala:77 +msgid "Add label to conversation" msgstr "" -#: ../../ui/composer.glade:75 -msgid "More options" +#: src/client/components/main-toolbar.vala:78 +msgid "Add label to conversations" msgstr "" -#: ../../src/client/geary-controller.vala:59 -#: ../../src/client/geary-controller.vala:308 +#: src/client/components/main-toolbar.vala:79 msgid "Move conversation" msgstr "" -#: ../../src/client/geary-controller.vala:60 +#: src/client/components/main-toolbar.vala:80 msgid "Move conversations" msgstr "" -#: ../../ui/login.glade:176 -msgid "N_ame:" +#: src/client/components/main-window.vala:395 +#, c-format +msgid "%s (%d)" msgstr "" -#: ../../ui/login.glade:230 -msgid "N_ickname:" +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:54 +#, c-format +msgid "Problem connecting to incoming server for %s" msgstr "" -#: ../../src/client/composer/composer-window.vala:40 -msgid "New Message" -msgstr "Nova mesaĝo" +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:56 +#: src/client/components/main-window-info-bar.vala:64 +#, c-format +msgid "" +"Could not connect to %s, check your Internet access and the server name and " +"try again" +msgstr "" -#: ../../ui/remove_confirm.glade:80 -msgid "Nickname:" +#: src/client/components/main-window-info-bar.vala:57 +#: src/client/components/main-window-info-bar.vala:66 +msgid "Retry connecting now" msgstr "" -#: ../../ui/login.glade:661 -msgid "No authentication re_quired" +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:62 +#, c-format +msgid "Problem connecting to outgoing server for %s" msgstr "" -#: ../../src/client/views/conversation-viewer.vala:275 -msgid "No conversations in folder." +#: src/client/components/main-window-info-bar.vala:65 +msgid "Try reconnecting now" msgstr "" -#: ../../src/client/views/conversation-viewer.vala:244 -msgid "No conversations selected." -msgstr "Neniu konversacio elektita." +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:71 +#, c-format +msgid "Problem with connection to incoming server for %s" +msgstr "" -#: ../../src/client/views/conversation-viewer.vala:273 -msgid "No search results found." +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:73 +#: src/client/components/main-window-info-bar.vala:81 +#, c-format +msgid "Network error talking to %s, check your Internet access try again" msgstr "" -#: ../../src/client/dialogs/password-dialog.vala:128 -#: ../../src/engine/api/geary-special-folder-type.vala:58 -msgid "None" -msgstr "Neniu nomo" +#: src/client/components/main-window-info-bar.vala:74 +#: src/client/components/main-window-info-bar.vala:82 +#: src/client/components/main-window-info-bar.vala:90 +#: src/client/components/main-window-info-bar.vala:98 +#: src/client/components/main-window-info-bar.vala:119 +msgid "Try reconnecting" +msgstr "" -#: ../../ui/preferences.glade:158 -msgid "Notifications" +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:79 +#, c-format +msgid "Problem with connection to outgoing server for %s" msgstr "" -#: ../../src/client/util/util-date.vala:164 -msgid "Now" +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:87 +#, c-format +msgid "Problem communicating with incoming server for %s" msgstr "" -#. / The quoted header for a message being replied to. -#. / %1$s will be substituted for the date, and %2$s will be substituted for -#. / the original sender. -#: ../../src/engine/rfc822/rfc822-utils.vala:171 +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:89 #, c-format -msgid "On %1$s, %2$s wrote:" +msgid "" +"Geary did not understand a message from %s or vice versa, please file a bug " +"report" msgstr "" -#. / The quoted header for a message being replied to (in case the sender is not known). -#. / %s will be replaced by the original date -#: ../../src/engine/rfc822/rfc822-utils.vala:184 +#: src/client/components/main-window-info-bar.vala:94 +msgid "Problem communicating with outgoing mail server" +msgstr "" + +#. Translators: First string substitution is the server +#. name, second is the account name +#: src/client/components/main-window-info-bar.vala:97 #, c-format -msgid "On %s:" +msgid "" +"Could now communicate with %s for %s, server name and try again in a moment" msgstr "" -#: ../../src/client/notification/libnotify.vala:160 -msgid "Open" -msgstr "Malfermi" +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:103 +#, c-format +msgid "Incoming mail server password required for %s" +msgstr "" -#: ../../src/engine/api/geary-service-provider.vala:61 -msgid "Other" -msgstr "Alia" +#: src/client/components/main-window-info-bar.vala:104 +msgid "Messages cannot be received without the correct password." +msgstr "" -#: ../../src/engine/api/geary-special-folder-type.vala:48 -msgid "Outbox" +#: src/client/components/main-window-info-bar.vala:105 +msgid "Retry receiving email, you will be prompted for a password" +msgstr "" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:110 +#, c-format +msgid "Outgoing mail server password required for %s" +msgstr "" + +#: src/client/components/main-window-info-bar.vala:111 +msgid "Messages cannot be sent without the correct password." +msgstr "" + +#: src/client/components/main-window-info-bar.vala:112 +msgid "Retry sending queued messages, you will be prompted for a password" +msgstr "" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:117 +#, c-format +msgid "A problem occurred checking mail for %s" +msgstr "" + +#: src/client/components/main-window-info-bar.vala:118 +#: src/client/components/main-window-info-bar.vala:125 +msgid "Something went wrong, please file a bug report if the problem persists" +msgstr "" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:124 +#, c-format +msgid "A problem occurred sending mail for %s" +msgstr "" + +#: src/client/components/main-window-info-bar.vala:126 +msgid "Retry sending queued messages" +msgstr "" + +#: src/client/components/main-window-info-bar.vala:137 +msgid "A database problem has occurred" +msgstr "" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:139 +#, c-format +msgid "Messages for %s must be downloaded again." +msgstr "" + +#: src/client/components/main-window-info-bar.vala:152 +msgid "Geary has encountered a problem" +msgstr "" + +#: src/client/components/main-window-info-bar.vala:153 +msgid "" +"Please check the technical details and report the problem if it persists." +msgstr "" + +#: src/client/components/main-window-info-bar.vala:161 +msgid "_Details" +msgstr "" + +#: src/client/components/main-window-info-bar.vala:162 +msgid "View technical details about the error" +msgstr "" + +#: src/client/components/main-window-info-bar.vala:166 +msgid "_Retry" +msgstr "" + +#: src/client/components/main-window-info-bar.vala:253 +msgid "Details" +msgstr "" + +#: src/client/components/main-window-info-bar.vala:266 +#: src/client/components/stock.vala:23 +msgid "_Close" +msgstr "" + +#: src/client/components/main-window-info-bar.vala:270 +msgid "Copy to Clipboard" msgstr "" -#: ../../src/engine/api/geary-service-provider.vala:58 -msgid "Outlook.com" +#: src/client/components/main-window-info-bar.vala:273 +msgid "" +"Copy technical details to clipboard for pasting into an email or bug report" +msgstr "" + +#: src/client/components/search-bar.vala:8 +#: src/client/folder-list/folder-list-search-branch.vala:38 +#: src/engine/api/geary-special-folder-type.vala:51 +msgid "Search" +msgstr "" + +#. Search entry. +#: src/client/components/search-bar.vala:23 +msgid "Search all mail in account for keywords (Ctrl+S)" +msgstr "" + +#: src/client/components/search-bar.vala:100 +#, c-format +msgid "Indexing %s account" +msgstr "" + +#: src/client/components/search-bar.vala:111 +#: src/client/folder-list/folder-list-search-branch.vala:39 +#, c-format +msgid "Search %s account" +msgstr "" + +#. / Displayed in the space-limited status bar while a message is in the process of being sent. +#: src/client/components/status-bar.vala:26 +msgid "Sending…" +msgstr "" + +#: src/client/components/stock.vala:18 ui/account_cannot_remove.glade:74 +msgid "_OK" +msgstr "_Bone" + +#: src/client/components/stock.vala:19 ui/edit_alternate_emails.glade:160 +#: ui/password-dialog.glade:196 ui/remove_confirm.glade:155 +msgid "_Cancel" +msgstr "_Rezigni" + +#: src/client/components/stock.vala:21 ui/gtk/menus.ui:34 +msgid "_About" +msgstr "_Pri" + +#: src/client/components/stock.vala:24 +msgid "_Discard" +msgstr "_Rifuzi" + +#: src/client/components/stock.vala:25 ui/gtk/menus.ui:29 +msgid "_Help" +msgstr "_Helpo" + +#: src/client/components/stock.vala:26 ui/conversation-email-menus.ui:51 +msgid "_Open" +msgstr "_Malfermi" + +#: src/client/components/stock.vala:27 ui/gtk/menus.ui:17 +msgid "_Preferences" +msgstr "_Agordoj" + +#: src/client/components/stock.vala:28 ui/conversation-email-menus.ui:39 +msgid "_Print…" +msgstr "_Presi…" + +#: src/client/components/stock.vala:29 ui/gtk/menus.ui:38 +msgid "_Quit" +msgstr "Ĉ_esi" + +#: src/client/components/stock.vala:30 ui/remove_confirm.glade:170 +msgid "_Remove" +msgstr "Fo_rigi" + +#: src/client/components/stock.vala:32 +msgid "_Keep" +msgstr "" + +#: src/client/composer/composer-link-popover.vala:149 +msgid "Link URL is not correctly formatted, e.g. http://example.com" +msgstr "" + +#: src/client/composer/composer-link-popover.vala:156 +msgid "Invalid link URL" +msgstr "" + +#: src/client/composer/composer-link-popover.vala:156 +msgid "Invalid email address" +msgstr "" + +#: src/client/composer/composer-widget.vala:159 +msgid "Saved" +msgstr "Konservite" + +#: src/client/composer/composer-widget.vala:160 +msgid "Saving" +msgstr "Konservante" + +#: src/client/composer/composer-widget.vala:161 +msgid "Error saving" +msgstr "Eraro dum konservo" + +#: src/client/composer/composer-widget.vala:162 +msgid "Press Backspace to delete quote" +msgstr "" + +#: src/client/composer/composer-widget.vala:163 +msgid "New Message" +msgstr "Nova mesaĝo" + +#. Translators: This is list of keywords, separated by pipe ("|") +#. characters, that suggest an attachment; since this is full-word +#. checking, include all variants of each word. No spaces are +#. allowed. +#: src/client/composer/composer-widget.vala:172 +msgid "" +"attach|attaching|attaches|attachment|attachments|attached|enclose|enclosed|" +"enclosing|encloses|enclosure|enclosures" +msgstr "" + +#: src/client/composer/composer-widget.vala:1138 +#: src/client/composer/composer-widget.vala:1157 +msgid "Do you want to discard this message?" +msgstr "" + +#: src/client/composer/composer-widget.vala:1261 +msgid "Send message with an empty subject and body?" +msgstr "" + +#: src/client/composer/composer-widget.vala:1263 +msgid "Send message with an empty subject?" +msgstr "" + +#: src/client/composer/composer-widget.vala:1265 +msgid "Send message with an empty body?" +msgstr "" + +#: src/client/composer/composer-widget.vala:1269 +msgid "Send message without an attachment?" +msgstr "" + +#: src/client/composer/composer-widget.vala:1531 +#, c-format +msgid "“%s” already attached for delivery." +msgstr "" + +#. / In the composer, the filename followed by its filesize, i.e. "notes.txt (1.12KB)" +#. Translators: The first argument will be a +#. description of the document type, the second will +#. be a human-friendly size string. For example: +#. Document (100.9MB) +#: src/client/composer/composer-widget.vala:1539 +#: src/client/conversation-viewer/conversation-email.vala:138 +#, c-format +msgid "%s (%s)" +msgstr "" + +#: src/client/composer/composer-widget.vala:1576 +#, c-format +msgid "“%s” could not be found." +msgstr "" + +#: src/client/composer/composer-widget.vala:1582 +#, c-format +msgid "“%s” is a folder." +msgstr "" + +#: src/client/composer/composer-widget.vala:1588 +#, c-format +msgid "“%s” is an empty file." +msgstr "" + +#: src/client/composer/composer-widget.vala:1601 +#, c-format +msgid "“%s” could not be opened for reading." +msgstr "" + +#: src/client/composer/composer-widget.vala:1609 +msgid "Cannot add attachment" +msgstr "Ne aldoneblas kunsendaĵon" + +#: src/client/composer/composer-widget.vala:1661 +#| msgid "To: %s\n" +msgid "To: " +msgstr "Al: " + +#: src/client/composer/composer-widget.vala:1664 +msgid "Cc: " +msgstr "Kopio: " + +#: src/client/composer/composer-widget.vala:1667 +#| msgid "Bcc:" +msgid "Bcc: " +msgstr "Kaŝkopio: " + +#: src/client/composer/composer-widget.vala:1670 +msgid "Reply-To: " +msgstr "" + +#: src/client/composer/composer-widget.vala:1803 +msgid "Select Color" +msgstr "" + +#. Displayed in the From dropdown to indicate an "alternate email address" +#. for an account. The first printf argument will be the alternate email +#. address, and the second will be the account's primary email address. +#: src/client/composer/composer-widget.vala:2011 +#, c-format +msgid "%1$s via %2$s" +msgstr "" + +#. Composer label (with mnemonic underscore) for the account selector +#. when choosing what address to send a message from. +#: src/client/composer/composer-widget.vala:2053 +#| msgid "From:" +msgid "_From:" +msgstr "_De:" + +#. Translators: This is the name of the file chooser filter +#. when inserting an image in the composer. +#: src/client/composer/composer-widget.vala:2278 +msgid "Images" +msgstr "Bildoj" + +#: src/client/composer/spell-check-popover.vala:117 +msgid "Remove this language from the preferred list" +msgstr "" + +#: src/client/composer/spell-check-popover.vala:121 +msgid "Add this language to the preferred list" +msgstr "" + +#: src/client/composer/spell-check-popover.vala:217 +msgid "Search for more languages" +msgstr "" + +#: src/client/conversation-list/conversation-list-view.vala:289 +msgid "Delete conversation" +msgstr "" + +#: src/client/conversation-list/conversation-list-view.vala:292 +#: ui/main-toolbar-menus.ui:16 +msgid "Mark as _Read" +msgstr "" + +#: src/client/conversation-list/conversation-list-view.vala:295 +#: ui/main-toolbar-menus.ui:20 +msgid "Mark as _Unread" +msgstr "" + +#: src/client/conversation-list/conversation-list-view.vala:298 +#: ui/main-toolbar-menus.ui:28 +msgid "U_nstar" +msgstr "Forigi stelo_n" + +#: src/client/conversation-list/conversation-list-view.vala:300 +#: ui/main-toolbar-menus.ui:24 +msgid "_Star" +msgstr "" + +#: src/client/conversation-list/conversation-list-view.vala:303 +#: ui/conversation-email-menus.ui:8 +msgid "_Reply" +msgstr "" + +#: src/client/conversation-list/conversation-list-view.vala:304 +msgid "R_eply All" +msgstr "" + +#: src/client/conversation-list/conversation-list-view.vala:305 +#: ui/conversation-email-menus.ui:18 +msgid "_Forward" +msgstr "" + +#: src/client/conversation-list/formatted-conversation-data.vala:11 +msgid "Me" +msgstr "Mi" + +#. Translators: This is the file type displayed for +#. attachments with unknown file types. +#: src/client/conversation-viewer/conversation-email.vala:124 +msgid "Unknown" +msgstr "Nekonate" + +#. Preview headers +#. Translators: This is displayed in place of the from address +#. when the message has no from address. +#: src/client/conversation-viewer/conversation-message.vala:325 +msgid "No sender" +msgstr "" + +#. Translators: This separates multiple 'from' +#. addresses in the header preview for a message. +#: src/client/conversation-viewer/conversation-message.vala:579 +msgid ", " +msgstr ", " + +#. Translators: This string is used as the HTML IMG ALT +#. attribute value when displaying an inline image in an email +#. that did not specify a file name. E.g. ImageCannot remove account " +msgstr "" + +#: ui/account_cannot_remove.glade:56 +msgid "" +"A composer window associated with this account is currently open. Send or " +"discard the message and try again." +msgstr "" + +#: ui/account_list.glade:69 +msgid "Add account" +msgstr "" + +#: ui/account_list.glade:82 +msgid "Edit account" +msgstr "" + +#: ui/account_list.glade:95 +msgid "Remove account" +msgstr "" + +#: ui/account_spinner.glade:41 +msgid "Please wait while Geary validates your account." +msgstr "" + +#: ui/certificate_warning_dialog.glade:7 +msgid "Untrusted Connection" +msgstr "" + +#: ui/certificate_warning_dialog.glade:29 +msgid "_Always Trust This Server" +msgstr "" + +#: ui/certificate_warning_dialog.glade:43 +msgid "_Trust This Server" +msgstr "" + +#: ui/certificate_warning_dialog.glade:57 +msgid "_Don’t Trust This Server" +msgstr "" + +#: ui/composer-headerbar.ui:26 ui/composer-headerbar.ui:138 +msgid "Detach (Ctrl+D)" +msgstr "" + +#: ui/composer-headerbar.ui:51 ui/composer-headerbar.ui:75 +msgid "Attach File (Ctrl+T)" +msgstr "" + +#: ui/composer-headerbar.ui:92 +msgid "Include Original Attachments" +msgstr "" + +#: ui/composer-headerbar.ui:162 +msgid "Send (Ctrl+Enter)" +msgstr "" + +#: ui/composer-headerbar.ui:163 +msgid "_Send" +msgstr "_Sendi" + +#: ui/composer-headerbar.ui:186 +msgid "Discard and Close" +msgstr "" + +#: ui/composer-headerbar.ui:203 +msgid "Save and Close" +msgstr "" + +#. Note that this button and the Update button will never be shown at the same time to the user. +#: ui/composer-link-popover.ui:41 +msgid "Insert the new link with this URL" +msgstr "" + +#: ui/composer-link-popover.ui:52 +msgid "Link URL" +msgstr "" + +#. Note that this button and the Insert button will never be shown at the same time to the user. +#: ui/composer-link-popover.ui:66 +msgid "Update this link’s URL" +msgstr "" + +#: ui/composer-link-popover.ui:86 +msgid "Delete this link" +msgstr "" + +#: ui/composer-link-popover.ui:106 +msgid "Open this link" +msgstr "" + +#: ui/composer-menus.ui:7 +msgid "S_ans Serif" +msgstr "" + +#: ui/composer-menus.ui:12 +msgid "S_erif" +msgstr "" + +#: ui/composer-menus.ui:17 +msgid "_Fixed Width" +msgstr "" + +#: ui/composer-menus.ui:24 +msgid "_Small" +msgstr "_Malgrande" + +#: ui/composer-menus.ui:29 +msgid "_Medium" +msgstr "" + +#: ui/composer-menus.ui:34 +msgid "Lar_ge" +msgstr "_Grande" + +#: ui/composer-menus.ui:41 +msgid "C_olor" +msgstr "" + +#: ui/composer-menus.ui:47 ui/composer-menus.ui:62 +msgid "_Rich Text" +msgstr "" + +#: ui/composer-menus.ui:53 ui/composer-menus.ui:68 +msgid "Show Extended Fields" +msgstr "" + +#: ui/composer-menus.ui:78 +msgid "_Undo" +msgstr "" + +#: ui/composer-menus.ui:82 +msgid "_Redo" +msgstr "" + +#: ui/composer-menus.ui:88 ui/composer-menus.ui:106 +msgid "Cu_t" +msgstr "" + +#: ui/composer-menus.ui:92 ui/composer-menus.ui:110 +#: ui/conversation-message-menus.ui:37 +msgid "_Copy" +msgstr "_Copii" + +#: ui/composer-menus.ui:96 ui/composer-menus.ui:114 +msgid "_Paste" +msgstr "" + +#: ui/composer-menus.ui:100 +msgctxt "Clipboard paste with rich text" +msgid "Paste _With Formatting" +msgstr "" + +#: ui/composer-menus.ui:120 +msgid "Select _All" +msgstr "Elekti ĉi_ujn" + +#: ui/composer-menus.ui:127 ui/conversation-message-menus.ui:49 +msgid "_Inspect…" +msgstr "" + +#. Address(es) e-mail is to be sent to +#: ui/composer-widget.ui:56 +#| msgid "To:" +msgid "_To" +msgstr "_Al" + +#: ui/composer-widget.ui:75 +#, fuzzy +#| msgid "Cc:" +msgid "_Cc" +msgstr "_Kopio" + +#: ui/composer-widget.ui:130 +#| msgid "Subject:" +msgid "_Subject" +msgstr "_Temo" + +#: ui/composer-widget.ui:149 +msgid "_Bcc" +msgstr "" + +#: ui/composer-widget.ui:179 +msgid "_Reply-To" +msgstr "" + +#. Geary account mail will be sent from +#: ui/composer-widget.ui:208 +#| msgid "From:" +msgid "From" +msgstr "De" + +#: ui/composer-widget.ui:293 +msgid "Drop files here" +msgstr "" + +#: ui/composer-widget.ui:309 +msgid "To add them as attachments" +msgstr "Por aldoni ilin kiel kunsendaĵoj" + +#: ui/composer-widget.ui:348 +msgid "Undo last edit (Ctrl+Z)" +msgstr "" + +#: ui/composer-widget.ui:372 +msgid "Redo last edit (Ctrl+Shift+Z)" +msgstr "" + +#: ui/composer-widget.ui:410 +msgid "Bold (Ctrl+B)" +msgstr "" + +#: ui/composer-widget.ui:434 +msgid "Italic (Ctrl+I)" +msgstr "" + +#: ui/composer-widget.ui:458 +msgid "Underline (Ctrl+U)" +msgstr "" + +#: ui/composer-widget.ui:482 +msgid "Strikethrough (Ctrl+K)" +msgstr "" + +#: ui/composer-widget.ui:520 +msgid "Insert unordered list" +msgstr "" + +#: ui/composer-widget.ui:544 +msgid "Insert ordered list" +msgstr "" + +#: ui/composer-widget.ui:582 +msgid "Quote text (Ctrl+])" +msgstr "" + +#: ui/composer-widget.ui:606 +msgid "Unquote text (Ctrl+[)" +msgstr "" + +#: ui/composer-widget.ui:644 +msgid "Insert or update selection link (Ctrl+L)" +msgstr "" + +#: ui/composer-widget.ui:668 +msgid "Insert an image (Ctrl+G)" +msgstr "" + +#: ui/composer-widget.ui:702 +msgid "Remove selection formatting (Ctrl+Space)" +msgstr "" + +#: ui/composer-widget.ui:726 +msgid "Select spell checking languages" +msgstr "" + +#: ui/conversation-email.ui:27 +#| msgid "Save All A_ttachments..." +msgid "Save all attachments" +msgstr "Konservi ĉiujn kunsendaĵojn" + +#. Note: The application will never show this button at the same time as unstar_button, one will always be hidden. +#: ui/conversation-email.ui:50 +msgid "Mark this message as starred" +msgstr "" + +#. Note: The application will never show this button at the same time as star_button, one will always be hidden. +#: ui/conversation-email.ui:72 +msgid "Mark this message as not starred" +msgstr "" + +#: ui/conversation-email.ui:95 +msgid "Display the message menu" +msgstr "" + +#: ui/conversation-email.ui:161 +#, fuzzy +#| msgid "Cannot add attachment" +msgid "Open selected attachments" +msgstr "Ne aldoneblas kunsendaĵon" + +#: ui/conversation-email.ui:178 +#| msgid "Save All A_ttachments..." +msgid "Save selected attachments" +msgstr "Konservi elektitajn kunsendaĵojn" + +#: ui/conversation-email.ui:195 +#| msgid "Save All A_ttachments..." +msgid "Select all attachments" +msgstr "Konservi ĉiujn kunsendaĵojn" + +#: ui/conversation-email.ui:240 +msgid "Edit Draft" msgstr "" -#: ../../src/client/geary-args.vala:10 -msgid "Output debugging information" -msgstr "" +#: ui/conversation-email.ui:267 +#, fuzzy +#| msgid "Drafts" +msgid "Draft message" +msgstr "Malnetoj" -#: ../../ui/login.glade:337 -msgid "P_ort:" +#: ui/conversation-email.ui:283 +msgid "This message has not yet been sent." msgstr "" -#: ../../ui/login.glade:474 -msgid "Pass_word:" +#: ui/conversation-email.ui:329 +msgid "Message not saved" msgstr "" -#: ../../ui/login.glade:101 -msgid "Password" +#: ui/conversation-email.ui:345 +msgid "This message was sent, but has not been saved to your account." msgstr "" -#: ../../ui/password-dialog.glade:196 -msgid "Password:" -msgstr "Pasvorto:" - -#: ../../ui/composer.glade:102 -msgid "Paste _With Formatting" +#: ui/conversation-email-menus.ui:13 +msgid "Reply to _All" msgstr "" -#: ../../src/client/dialogs/password-dialog.vala:16 -msgid "Please enter your email password" -msgstr "Bonvole enigu vian retpoŝtan pasvorton" - -#: ../../src/client/geary-args.vala:47 -msgid "Please report comments, suggestions and bugs to:" +#: ui/conversation-email-menus.ui:25 +msgid "_Mark Read" msgstr "" -#: ../../ui/account_spinner.glade:41 -msgid "Please wait while Geary validates your account." +#: ui/conversation-email-menus.ui:29 +msgid "_Mark Unread" msgstr "" -#: ../../ui/login.glade:421 -msgid "Por_t:" +#: ui/conversation-email-menus.ui:33 +msgid "Mark Unread From _Here" msgstr "" -#: ../../ui/password-dialog.glade:367 ../../ui/password-dialog.glade:470 -msgid "Port:" +#: ui/conversation-email-menus.ui:43 +msgid "_View Source" msgstr "" -#: ../../ui/composer.glade:81 -msgid "Quote text (Ctrl+])" +#: ui/conversation-email-menus.ui:61 +msgid "_Save All" msgstr "" -#: ../../src/client/geary-controller.vala:322 -msgid "R_eply All" +#: ui/conversation-message-menus.ui:7 +msgid "_Open Link" msgstr "" -#: ../../ui/preferences.glade:55 -msgid "Reading" +#: ui/conversation-message-menus.ui:11 +msgid "Copy Link _Address" msgstr "" -#: ../../ui/password-dialog.glade:274 -msgid "Real name:" -msgstr "" +#: ui/conversation-message-menus.ui:17 +#| msgid "New Message" +msgid "Send New _Message…" +msgstr "Sendi novan _mesaĝon…" -#: ../../src/client/accounts/add-edit-page.vala:590 -msgid "Remem_ber password" +#: ui/conversation-message-menus.ui:21 +msgid "Copy Email _Address" msgstr "" -#: ../../src/client/accounts/add-edit-page.vala:583 -msgid "Remem_ber passwords" +#: ui/conversation-message-menus.ui:27 +msgid "Save _Image As…" msgstr "" -#: ../../ui/account_list.glade:97 -msgid "Remove account" +#: ui/conversation-message-menus.ui:33 +#| msgid "Select _All" +msgid "_Select All" +msgstr "_Elekti ĉiujn" + +#: ui/conversation-message-menus.ui:43 +msgid "Search for messages from" msgstr "" -#: ../../ui/composer.glade:95 -msgid "Remove formatting (Ctrl+Space)" +#: ui/conversation-message.ui:64 +msgid "From " msgstr "" -#: ../../src/client/geary-controller.vala:318 -msgid "Reply (Ctrl+R, R)" +#: ui/conversation-message.ui:80 ui/conversation-message.ui:179 +msgid "1/1/1970\t" msgstr "" -#: ../../src/client/geary-controller.vala:323 -msgid "Reply all (Ctrl+Shift+R, Shift+R)" +#: ui/conversation-message.ui:103 +msgid "Preview body text." msgstr "" -#: ../../src/client/views/conversation-viewer.vala:1452 -msgid "Reply to _All" +#: ui/conversation-message.ui:203 +msgid "Sent by:" msgstr "" -#: ../../ui/password-dialog.glade:163 -msgid "SMTP Credentials" +#: ui/conversation-message.ui:248 +msgid "Reply to:" msgstr "" -#: ../../ui/login.glade:507 -msgid "SMTP password" +#: ui/conversation-message.ui:292 +#| msgid "Subject:" +msgid "Subject" +msgstr "Temo" + +#: ui/conversation-message.ui:313 +msgid "To:" +msgstr "Al:" + +#: ui/conversation-message.ui:358 +msgid "Cc:" +msgstr "Kopio:" + +#: ui/conversation-message.ui:403 +msgid "Bcc:" +msgstr "Kaŝkopio:" + +#: ui/conversation-message.ui:502 +msgid "Show Images" msgstr "" -#: ../../ui/login.glade:439 ../../ui/password-dialog.glade:437 -msgid "SMTP settings" +#: ui/conversation-message.ui:515 +msgid "Always Show From Sender" msgstr "" -#: ../../ui/login.glade:491 -msgid "SMTP username" +#: ui/conversation-message.ui:543 +msgid "Remote images not shown" msgstr "" -#: ../../src/client/dialogs/password-dialog.vala:124 -msgid "SSL" +#: ui/conversation-message.ui:560 +msgid "Only show remote images from senders you trust." msgstr "" -#: ../../ui/password-dialog.glade:382 ../../ui/password-dialog.glade:485 -msgid "SSL/TLS encryption:" +#: ui/conversation-message.ui:692 +msgid "But actually goes to:" msgstr "" -#: ../../src/client/dialogs/password-dialog.vala:126 -msgid "STARTTLS" +#: ui/conversation-message.ui:723 +msgid "The link appears to go to:" msgstr "" -#: ../../ui/composer.glade:169 -msgid "S_ans Serif" +#: ui/conversation-message.ui:735 +msgid "Deceptive link found" msgstr "" -#: ../../ui/composer.glade:175 -msgid "S_erif" +#: ui/conversation-message.ui:750 +msgid "The email sender may be leading you to the wrong web site." msgstr "" -#: ../../ui/login.glade:160 -msgid "S_ervice:" +#: ui/conversation-message.ui:763 +msgid "If unsure, contact the sender and ask before continuing." msgstr "" -#: ../../ui/composer.glade:170 -msgid "Sans Serif" -msgstr "Sans Serif" +#: ui/conversation-viewer.ui:61 +msgid "Find in conversation" +msgstr "" -#: ../../src/client/views/conversation-viewer.vala:1437 -msgid "Save A_ttachment..." -msgid_plural "Save All A_ttachments..." -msgstr[0] "Konservi kunsendaĵon..." -msgstr[1] "Konservi ĉiujn kunsendaĵojn..." +#: ui/conversation-viewer.ui:75 +msgid "Find the previous occurrence of the search string." +msgstr "" -#: ../../src/client/views/conversation-viewer.vala:1400 -msgid "Save All A_ttachments..." -msgstr "Konservi ĉiujn kunsendaĵojn..." +#: ui/conversation-viewer.ui:96 +msgid "Find the next occurrence of the search string." +msgstr "" -#: ../../src/client/composer/composer-window.vala:41 -msgid "Saved" +#: ui/edit_alternate_emails.glade:112 +msgid "Remove email address" msgstr "" -#: ../../src/client/composer/composer-window.vala:42 -msgid "Saving" +#: ui/edit_alternate_emails.glade:136 +msgid "" +"Some email services require additional addresses be configured on the " +"server. Contact your email provider for more information." msgstr "" -#: ../../ui/login.glade:304 -msgid "Se_rver:" +#: ui/edit_alternate_emails.glade:175 +msgid "_Update" msgstr "" -#: ../../src/client/folder-list/folder-list-search-branch.vala:38 -#: ../../src/client/ui/main-toolbar.vala:10 -#: ../../src/engine/api/geary-special-folder-type.vala:51 -msgid "Search" +#: ui/find_bar.glade:66 +msgid "Find:" msgstr "" -#: ../../src/client/folder-list/folder-list-search-branch.vala:39 -#: ../../src/client/ui/main-toolbar.vala:170 -#, c-format -msgid "Search %s account" +#: ui/find_bar.glade:89 +msgid "_Previous" msgstr "" -#: ../../src/client/ui/main-toolbar.vala:76 -msgid "Search all mail in account for keywords (Ctrl+S)" +#: ui/find_bar.glade:107 +msgid "_Next" msgstr "" -#: ../../src/client/composer/composer-window.vala:1243 -msgid "Select Color" +#: ui/find_bar.glade:125 +msgid "_Case sensitive" msgstr "" -#: ../../src/client/ui/stock.vala:32 -#: ../../src/client/views/conversation-viewer.vala:916 -msgid "Select _All" -msgstr "Elekti ĉion" +#: ui/find_bar.glade:145 +#| msgid "Labels" +msgid "label" +msgstr "etikedo" -#: ../../src/client/views/conversation-viewer.vala:910 -msgid "Select _Message" +#: ui/gtk/help-overlay.ui:9 +msgid "Conversation Shortcuts" msgstr "" -#: ../../src/client/geary-application.vala:27 -msgid "Send and receive email" -msgstr "Sendi kaj ricevi retpoŝton" - -#: ../../src/client/composer/composer-window.vala:747 -msgid "Send message with an empty body?" +#: ui/gtk/help-overlay.ui:13 ui/gtk/help-overlay.ui:254 +msgctxt "shortcut window" +msgid "General" msgstr "" -#: ../../src/client/composer/composer-window.vala:743 -msgid "Send message with an empty subject and body?" +#: ui/gtk/help-overlay.ui:17 +msgctxt "shortcut window" +msgid "Move focus to the next/previous pane" msgstr "" -#: ../../src/client/composer/composer-window.vala:745 -msgid "Send message with an empty subject?" +#: ui/gtk/help-overlay.ui:24 +msgctxt "shortcut window" +msgid "Move focus to conversation list" msgstr "" -#: ../../src/client/composer/composer-window.vala:749 -msgid "Send message without an attachment?" +#: ui/gtk/help-overlay.ui:31 +msgctxt "shortcut window" +msgid "Detach composer window" msgstr "" -#. / Displayed in the space-limited status bar while a message is in the process of being sent. -#: ../../src/client/ui/status-bar.vala:25 -msgid "Sending..." +#: ui/gtk/help-overlay.ui:38 +msgctxt "shortcut window" +msgid "Close composer window" msgstr "" -#: ../../src/engine/api/geary-special-folder-type.vala:30 -msgid "Sent Mail" -msgstr "Mesaĝo sendita" +#: ui/gtk/help-overlay.ui:45 +msgctxt "shortcut window" +msgid "Show keyboard shortcuts" +msgstr "" -#: ../../ui/login.glade:404 -msgid "Ser_ver:" +#: ui/gtk/help-overlay.ui:52 +msgctxt "shortcut window" +msgid "Show help" msgstr "" -#: ../../ui/composer.glade:176 -msgid "Serif" +#: ui/gtk/help-overlay.ui:59 +msgctxt "shortcut window" +msgid "Quit the application" msgstr "" -#: ../../ui/password-dialog.glade:352 ../../ui/password-dialog.glade:455 -msgid "Server:" +#: ui/gtk/help-overlay.ui:68 +msgctxt "shortcut window" +msgid "Search" msgstr "" -#: ../../ui/password-dialog.glade:259 -msgid "Service:" +#: ui/gtk/help-overlay.ui:72 +msgctxt "shortcut window" +msgid "Jump to search box" msgstr "" -#: ../../src/client/views/conversation-viewer.vala:508 -msgid "Show Images" +#: ui/gtk/help-overlay.ui:79 +msgctxt "shortcut window" +msgid "Find in current conversation" msgstr "" -#: ../../ui/preferences.glade:193 -msgid "Show _notifications for new mail" +#: ui/gtk/help-overlay.ui:86 +msgctxt "shortcut window" +msgid "Find next/previous in current conversation" msgstr "" -#: ../../ui/composer.glade:164 -msgid "Small" -msgstr "Malgranda" +#: ui/gtk/help-overlay.ui:95 ui/gtk/help-overlay.ui:274 +msgctxt "shortcut window" +msgid "Actions" +msgstr "" -#: ../../src/engine/api/geary-special-folder-type.vala:42 -msgid "Spam" -msgstr "Trudmesaĝoj" +#: ui/gtk/help-overlay.ui:99 +#, fuzzy +#| msgid "Compose Message" +msgctxt "shortcut window" +msgid "Compose a new message" +msgstr "Verki mesaĝon" -#: ../../src/engine/api/geary-special-folder-type.vala:33 -msgid "Starred" -msgstr "Kun stelo" +#: ui/gtk/help-overlay.ui:106 +msgctxt "shortcut window" +msgid "Reply to sender " +msgstr "" -#: ../../ui/login.glade:761 -msgid "Storage" +#: ui/gtk/help-overlay.ui:113 +msgctxt "shortcut window" +msgid "Reply to all" msgstr "" -#: ../../ui/composer.glade:134 -msgid "Strikethrough (Ctrl+K)" +#: ui/gtk/help-overlay.ui:120 +msgctxt "shortcut window" +msgid "Forward" msgstr "" -#: ../../src/client/views/conversation-viewer.vala:617 -msgid "Subject:" -msgstr "Temo:" +#: ui/gtk/help-overlay.ui:127 +#, fuzzy +#| msgid "_Archive" +msgctxt "shortcut window" +msgid "Archive" +msgstr "Enarkivigi" -#: ../../src/engine/rfc822/rfc822-utils.vala:216 -#, c-format -msgid "Subject: %s\n" +#: ui/gtk/help-overlay.ui:134 +msgctxt "shortcut window" +msgid "Move to trash" msgstr "" -#: ../../src/client/util/util-files.vala:19 -msgctxt "Abbreviation for terabyte" -msgid "TB" -msgstr "TB" +#: ui/gtk/help-overlay.ui:141 +msgctxt "shortcut window" +msgid "Toggle spam" +msgstr "" -#: ../../src/client/geary-controller.vala:1493 -#, c-format -msgid "" -"The file already exists in \"%s\". Replacing it will overwrite its contents." +#: ui/gtk/help-overlay.ui:148 +msgctxt "shortcut window" +msgid "Move the conversation" msgstr "" -#: ../../src/client/geary-controller.vala:747 -msgid "" -"The version number of the local mail database is formatted for a newer " -"version of Geary. Unfortunately, the database cannot be \"rolled back\" to " -"work with this version of Geary.\n" -"\n" -"Please install the latest version of Geary and try again." +#: ui/gtk/help-overlay.ui:155 +msgctxt "shortcut window" +msgid "Label the conversation" msgstr "" -#: ../../src/client/geary-controller.vala:758 -msgid "" -"There was an error opening the local account. This is probably due to " -"connectivity issues.\n" -"\n" -"Please check your network connection and restart Geary." +#: ui/gtk/help-overlay.ui:163 +msgctxt "shortcut window" +msgid "Mark read" msgstr "" -#: ../../src/client/geary-controller.vala:737 -#, c-format -msgid "" -"There was an error opening the local mail database for this account. This is " -"possibly due to a file permissions problem.\n" -"\n" -"Please check that you have read/write permissions for all files in this " -"directory:\n" -"\n" -"%s" +#: ui/gtk/help-overlay.ui:170 +msgctxt "shortcut window" +msgid "Mark unread" msgstr "" -#: ../../src/client/geary-controller.vala:702 -#, c-format -msgid "" -"There was an error opening the local mail database for this account. This is " -"possibly due to corruption of the database file in this directory:\n" -"\n" -"%s\n" -"\n" -"Geary can rebuild the database and re-synchronize with the server or exit.\n" -"\n" -"Rebuilding the database will destroy all local email and its attachments. " -"The mail on the your server will not be affected." +#: ui/gtk/help-overlay.ui:179 +msgctxt "shortcut window" +msgid "View" msgstr "" -#: ../../src/client/views/conversation-viewer.vala:1147 -msgid "This link appears to go to" +#: ui/gtk/help-overlay.ui:183 +msgctxt "shortcut window" +msgid "Zoom in" msgstr "" -#: ../../src/client/views/conversation-viewer.vala:508 -msgid "This message contains remote images." +#: ui/gtk/help-overlay.ui:190 +msgctxt "shortcut window" +msgid "Zoom out" msgstr "" -#: ../../ui/composer.glade:435 -msgid "To add them as attachments" -msgstr "Por aldoni ilin kiel kunsendaĵoj" +#: ui/gtk/help-overlay.ui:197 +msgctxt "shortcut window" +msgid "Reset zoom" +msgstr "" -#: ../../src/client/views/conversation-viewer.vala:608 -msgid "To:" -msgstr "Al:" +#: ui/gtk/help-overlay.ui:206 +msgctxt "shortcut window" +msgid "Additional Shortcuts" +msgstr "" -#: ../../src/engine/rfc822/rfc822-utils.vala:220 -#, c-format -msgid "To: %s\n" -msgstr "Al: %s\n" +#: ui/gtk/help-overlay.ui:210 +#, fuzzy +#| msgid "Starred" +msgctxt "shortcut window" +msgid "Star" +msgstr "Kun stelo" -#: ../../src/engine/api/geary-special-folder-type.vala:45 -msgid "Trash" -msgstr "Rubujo" +#: ui/gtk/help-overlay.ui:217 +#| msgid "U_nstar" +msgctxt "shortcut window" +msgid "Unstar" +msgstr "" -#: ../../src/client/geary-controller.vala:294 -msgid "U_nstar" -msgstr "Forigi stelo_n" +#: ui/gtk/help-overlay.ui:224 +#| msgid "_Delete" +msgctxt "shortcut window" +msgid "Delete" +msgstr "Forigi" -#: ../../src/client/dialogs/password-dialog.vala:17 -msgid "Unable to login to email server" -msgstr "Ne konekteblas al retpoŝta servilo" - -#: ../../src/client/geary-controller.vala:736 -#: ../../src/client/geary-controller.vala:746 -#: ../../src/client/geary-controller.vala:757 -#, c-format -msgid "Unable to open local mailbox for %s" +#: ui/gtk/help-overlay.ui:231 +msgctxt "shortcut window" +msgid "Jump to next (older) conversation" msgstr "" -#: ../../src/client/geary-controller.vala:701 -#, c-format -msgid "Unable to open the database for %s" +#: ui/gtk/help-overlay.ui:238 +msgctxt "shortcut window" +msgid "Jump to previous (newer) conversation" msgstr "" -#: ../../src/client/geary-controller.vala:713 -#, c-format -msgid "Unable to rebuild database for \"%s\"" +#: ui/gtk/help-overlay.ui:250 +msgid "Composer Shortcuts" msgstr "" -#: ../../src/client/accounts/add-edit-page.vala:619 -msgid "Unable to validate:\n" +#: ui/gtk/help-overlay.ui:258 +msgctxt "shortcut window" +msgid "Quote text" msgstr "" -#: ../../ui/composer.glade:127 -msgid "Underline (Ctrl+U)" +#: ui/gtk/help-overlay.ui:265 +msgctxt "shortcut window" +msgid "Unquote text" msgstr "" -#: ../../ui/composer.glade:88 -msgid "Unquote text (Ctrl+[)" +#: ui/gtk/help-overlay.ui:278 +msgctxt "shortcut window" +msgid "Send" msgstr "" -#: ../../src/client/geary-args.vala:65 -#, c-format -msgid "Unrecognized command line option \"%s\"\n" -msgstr "Nekonata komandlinia opcio \"%s\"\n" +#: ui/gtk/help-overlay.ui:285 +#, fuzzy +#| msgid "Cannot add attachment" +msgctxt "shortcut window" +msgid "Add attachment" +msgstr "Ne aldoneblas kunsendaĵon" -#: ../../ui/login.glade:457 -msgid "User_name:" +#: ui/gtk/help-overlay.ui:294 +msgctxt "shortcut window" +msgid "Rich text mode" msgstr "" -#: ../../ui/password-dialog.glade:70 ../../ui/password-dialog.glade:181 -msgid "Username:" +#: ui/gtk/help-overlay.ui:298 +msgctxt "shortcut window" +msgid "Bold text" msgstr "" -#: ../../src/client/geary-application.vala:19 -msgid "Visit the Yorba web site" +#: ui/gtk/help-overlay.ui:305 +msgctxt "shortcut window" +msgid "Italicize text" msgstr "" -#: ../../src/client/accounts/add-edit-page.vala:190 -#, c-format -msgid "Welcome to Geary." -msgstr "Bonvenon en Geary." +#: ui/gtk/help-overlay.ui:312 +msgctxt "shortcut window" +msgid "Underline text" +msgstr "" -#: ../../ui/login.glade:252 -msgid "Work, Home, etc." +#: ui/gtk/help-overlay.ui:319 +msgctxt "shortcut window" +msgid "Strike text" msgstr "" -#: ../../src/engine/api/geary-service-provider.vala:55 -msgid "Yahoo! Mail" -msgstr "Yahoo! Mail" +#: ui/gtk/help-overlay.ui:326 +msgctxt "shortcut window" +msgid "Insert a link" +msgstr "" -#: ../../src/client/util/util-date.vala:177 -msgid "Yesterday" +#: ui/gtk/help-overlay.ui:333 +msgctxt "shortcut window" +msgid "Remove formatting" msgstr "" -#: ../../src/client/geary-controller.vala:552 -msgid "" -"Your IMAP and/or SMTP settings do not specify SSL or TLS. This means your " -"username and password could be read by another person on the network. Are " -"you sure you want to do this?" +#: ui/gtk/menus.ui:13 +msgid "A_ccounts" msgstr "" -"Viaj agordoj por IMAP kaj/aŭ SMTP ne specifas je SSL nek je TLS. Tio " -"signifas, ke viaj uzantnomo kaj pasvorto povas esti legitaj de alia persono " -"en la reto. Ĉu vi vere volas fari tion?" -#: ../../src/client/geary-controller.vala:551 -msgid "Your settings are insecure" -msgstr "Viaj agordoj estas malsekuraj." +#: ui/gtk/menus.ui:23 +msgid "_Keyboard Shortcuts" +msgstr "" -#: ../../src/client/geary-controller.vala:258 -#: ../../src/client/ui/stock.vala:21 -msgid "_About" -msgstr "Pri" +#: ui/login.glade:88 +msgid "email@example.com" +msgstr "" -#: ../../src/client/accounts/account-dialog-add-edit-pane.vala:48 -#: ../../src/client/ui/stock.vala:22 -msgid "_Add" +#: ui/login.glade:107 ui/password-dialog.glade:108 +msgid "Password" msgstr "" -#: ../../src/client/geary-controller.vala:47 -msgid "_Archive" -msgstr "Enarkivigi" +#: ui/login.glade:123 +msgid "E_mail address" +msgstr "" -#: ../../src/client/dialogs/attachment-dialog.vala:23 -msgid "_Attach" -msgstr "Kunsendi" +#: ui/login.glade:144 ui/login.glade:635 +#| msgid "_Password:" +msgid "_Password" +msgstr "_Pasvorto" -#: ../../ui/composer.glade:525 -msgid "_Attach File" +#: ui/login.glade:178 +msgid "S_ervice" msgstr "" -#: ../../ui/preferences.glade:69 -msgid "_Automatically select next message" +#: ui/login.glade:199 +msgid "N_ame" msgstr "" -#: ../../src/client/ui/stock.vala:19 -msgid "_Cancel" +#: ui/login.glade:256 +msgid "N_ickname" msgstr "" -#: ../../ui/composer.glade:52 -msgid "_Center" +#: ui/login.glade:280 +msgid "Work, Home, etc." msgstr "" -#: ../../src/client/ui/stock.vala:23 -msgid "_Close" +#: ui/login.glade:291 +#, fuzzy +#| msgid "Sent Mail" +msgid "_Save sent mail" +msgstr "Kon_servi senditan mesaĝon" + +#: ui/login.glade:309 +msgid "Addi_tional email addresses…" msgstr "" -#: ../../src/client/views/conversation-viewer.vala:889 -msgid "_Copy" -msgstr "_Copii" +#: ui/login.glade:353 +msgid "IMAP settings" +msgstr "" -#: ../../src/client/geary-controller.vala:42 -msgid "_Delete" -msgstr "_Forigi" +#: ui/login.glade:372 +msgid "Se_rver" +msgstr "" -#: ../../ui/password-dialog.glade:548 -msgid "_Details" +#: ui/login.glade:393 +msgid "imap.example.com" msgstr "" -#: ../../src/client/ui/stock.vala:24 -msgid "_Discard" +#: ui/login.glade:409 +msgid "P_ort" msgstr "" -#: ../../ui/preferences.glade:90 -msgid "_Display conversation preview" +#: ui/login.glade:448 +msgid "smtp.example.com" msgstr "" -#: ../../src/client/geary-controller.vala:262 -msgid "_Donate" +#: ui/login.glade:480 +msgid "Ser_ver" msgstr "" -#: ../../ui/login.glade:782 -msgid "_Download mail:" +#: ui/login.glade:501 +msgid "Por_t" msgstr "" -#: ../../ui/composer.glade:181 -msgid "_Fixed Width" +#: ui/login.glade:522 +msgid "SMTP settings" msgstr "" -#: ../../src/client/geary-controller.vala:328 -#: ../../src/client/views/conversation-viewer.vala:1457 -msgid "_Forward" +#: ui/login.glade:541 +msgid "User_name" msgstr "" -#: ../../src/client/geary-controller.vala:254 -#: ../../src/client/ui/stock.vala:25 -msgid "_Help" -msgstr "_Helpo" +#: ui/login.glade:562 +#| msgid "Password:" +msgid "Pass_word" +msgstr "Pas_vorto" -#: ../../ui/composer.glade:542 -msgid "_Include Original Attachments" +#: ui/login.glade:582 +msgid "SMTP username" msgstr "" -#: ../../src/client/views/conversation-viewer.vala:922 -msgid "_Inspect" +#: ui/login.glade:598 +msgid "SMTP password" msgstr "" -#: ../../ui/composer.glade:57 -msgid "_Justify" +#: ui/login.glade:614 +msgid "_Username" msgstr "" -#: ../../src/client/ui/stock.vala:33 -msgid "_Keep" +#: ui/login.glade:655 +msgid "IMAP username" msgstr "" -#: ../../src/client/geary-controller.vala:305 -msgid "_Label" -msgstr "Etikedi" - -#: ../../ui/composer.glade:42 -msgid "_Left" +#: ui/login.glade:671 +msgid "IMAP password" msgstr "" -#: ../../src/client/views/conversation-viewer.vala:1469 -msgid "_Mark as Read" +#: ui/login.glade:688 +msgid "Encr_yption" msgstr "" -#: ../../src/client/views/conversation-viewer.vala:1473 -msgid "_Mark as Unread" +#: ui/login.glade:711 +msgid "Encrypt_ion" msgstr "" -#: ../../src/client/geary-controller.vala:271 -msgid "_Mark as..." +#: ui/login.glade:733 ui/login.glade:751 +msgid "SSL/TLS" msgstr "" -#: ../../ui/composer.glade:157 -msgid "_Medium" +#: ui/login.glade:734 ui/login.glade:752 +msgid "STARTTLS" msgstr "" -#: ../../src/client/geary-controller.vala:309 -msgid "_Move" +#: ui/login.glade:764 +msgid "No authentication re_quired" msgstr "" -#: ../../src/client/ui/stock.vala:18 -msgid "_OK" +#: ui/login.glade:781 +msgid "Use IMAP cre_dentials" msgstr "" -#: ../../src/client/ui/stock.vala:26 -msgid "_Open" +#: ui/login.glade:888 +msgid "Composer" msgstr "" -#: ../../ui/login.glade:131 ../../ui/login.glade:539 -#: ../../ui/password-dialog.glade:85 -msgid "_Password:" -msgstr "_Pasvorto:" - -#: ../../ui/composer.glade:35 -msgid "_Paste" +#: ui/login.glade:901 +msgid "Save dra_fts on server" msgstr "" -#: ../../ui/preferences.glade:172 -msgid "_Play notification sounds" +#: ui/login.glade:918 +msgid "Si_gn emails (HTML allowed):" msgstr "" -#: ../../src/client/geary-controller.vala:250 -#: ../../src/client/ui/stock.vala:27 -msgid "_Preferences" -msgstr "" +#: ui/login.glade:976 +msgid "Storage" +msgstr "Konservado" -#: ../../src/client/ui/stock.vala:28 -msgid "_Print..." +#: ui/login.glade:998 +msgid "_Download mail" msgstr "" -#: ../../src/client/geary-controller.vala:266 -#: ../../src/client/ui/stock.vala:29 -msgid "_Quit" +#: ui/main-toolbar.ui:50 +msgid "Toggle search bar" msgstr "" -#: ../../src/client/geary-controller.vala:704 -msgid "_Rebuild" +#: ui/main-toolbar.ui:70 +msgid "Empty Spam or Trash folders" msgstr "" -#: ../../ui/composer.glade:14 -msgid "_Redo" -msgstr "" +#: ui/main-toolbar.ui:109 +msgid "Reply" +msgstr "Respondi" -#: ../../ui/password-dialog.glade:561 -msgid "_Remember passwords" -msgstr "" +#: ui/main-toolbar.ui:131 +msgid "Reply All" +msgstr "Respondi al" -#: ../../src/client/ui/stock.vala:30 -msgid "_Remove" -msgstr "" +#: ui/main-toolbar.ui:153 +msgid "Forward" +msgstr "Antaŭen" -#: ../../src/client/geary-controller.vala:1496 -msgid "_Replace" +#: ui/main-toolbar.ui:255 +msgid "Toggle find bar" msgstr "" -#: ../../src/client/geary-controller.vala:317 -#: ../../src/client/views/conversation-viewer.vala:1447 -msgid "_Reply" -msgstr "" +#: ui/main-toolbar.ui:295 +msgid "_Archive" +msgstr "En_arkivigi" -#: ../../ui/composer.glade:141 -msgid "_Rich Text" +#: ui/main-toolbar-menus.ui:5 +msgid "Empty _Spam…" msgstr "" -#: ../../ui/composer.glade:47 -msgid "_Right" +#: ui/main-toolbar-menus.ui:9 +msgid "Empty _Trash…" msgstr "" -#: ../../src/client/accounts/account-dialog-add-edit-pane.vala:48 -#: ../../src/client/ui/stock.vala:31 -msgid "_Save" +#: ui/main-toolbar-menus.ui:32 +msgid "Mark as S_pam" msgstr "" -#: ../../src/client/views/conversation-viewer.vala:1395 -msgid "_Save As..." +#: ui/main-toolbar-menus.ui:36 +msgid "Mark as not S_pam" msgstr "" -#: ../../src/client/views/conversation-viewer.vala:1414 -msgid "_Save Image As..." +#: ui/main-window-info-bar.ui:97 +msgid "" +"If the problem is serious or persists, please copy and send these details to " +"the mailing list " +"or file a new " +"bug report." msgstr "" -#: ../../ui/composer.glade:594 -msgid "_Send" +#: ui/main-window-info-bar.ui:113 +msgid "Details:" +msgstr "Detaloj:" + +#: ui/password-dialog.glade:74 +msgid "SMTP Credentials" msgstr "" -#: ../../ui/composer.glade:163 -msgid "_Small" +#: ui/password-dialog.glade:91 +msgid "Username" +msgstr "Uzantonomo" + +#: ui/password-dialog.glade:152 +msgid "_Remember password" msgstr "" -#: ../../src/client/geary-controller.vala:289 -msgid "_Star" +#: ui/password-dialog.glade:210 +msgid "_Authenticate" +msgstr "_Aŭtentigi" + +#: ui/preferences-dialog.ui:38 +msgid "Reading" +msgstr "Legante" + +#: ui/preferences-dialog.ui:51 +msgid "_Automatically select next message" msgstr "" -#: ../../ui/composer.glade:7 -msgid "_Undo" +#: ui/preferences-dialog.ui:70 +msgid "_Display conversation preview" msgstr "" -#: ../../ui/login.glade:522 -msgid "_Username:" +#: ui/preferences-dialog.ui:89 +msgid "Use _three pane view" msgstr "" -#: ../../src/client/views/conversation-viewer.vala:1494 -msgid "_View Source" +#: ui/preferences-dialog.ui:113 +msgid "Notifications" +msgstr "Atentigoj" + +#: ui/preferences-dialog.ui:126 +msgid "_Play notification sounds" msgstr "" -#. / A list of keywords, separated by pipe ("|") characters, that suggest an attachment -#: ../../src/client/composer/composer-window.vala:90 -msgid "attach|enclosed|enclosing|cover letter" +#: ui/preferences-dialog.ui:145 +msgid "Show _notifications for new mail" msgstr "" -#: ../../src/client/views/conversation-viewer.vala:1148 -msgid "but actually goes to" +#: ui/preferences-dialog.ui:164 +msgid "Always _watch for new mail" msgstr "" -#: ../../src/client/util/util-files.vala:16 -msgid "bytes" +#: ui/preferences-dialog.ui:168 +msgid "Geary will run in the background and notify of new mail" msgstr "" -#. / Placeholder filename for attachments with no filename. -#: ../../src/client/views/conversation-viewer.vala:1799 -#: ../../src/engine/rfc822/rfc822-utils.vala:327 -msgid "none" +#: ui/preferences-dialog.ui:195 +msgid "Preferences" +msgstr "Agordoj" + +#: ui/remove_confirm.glade:43 +msgid "" +"Are you sure you want to remove this " +"account? " msgstr "" -#: ../../src/client/views/conversation-find-bar.vala:226 -msgid "not found" +#: ui/remove_confirm.glade:58 +msgid "" +"All email associated with this account will be removed from your computer. " +"This will not affect email on the server." msgstr "" -#. / Translators: add your name and email address to receive credit in the About dialog -#. / For example: Yamada Taro -#: ../../src/client/geary-controller.vala:1230 -msgid "translator-credits" +#: ui/remove_confirm.glade:80 +msgid "Nickname:" +msgstr "Kromnomo:" + +#: ui/remove_confirm.glade:94 +msgid "Email address:" +msgstr "Retpoŝtadreso:" + +#: ui/upgrade_dialog.glade:60 +msgid "Geary update in progress…" msgstr "" + +#~ msgid "Date:" +#~ msgstr "Dato:" + +#~ msgid "Large" +#~ msgstr "Granda" + +#~ msgid "Mail Client" +#~ msgstr "Retpoŝtilo" + +#~ msgid "Sans Serif" +#~ msgstr "Sans Serif" + +#~ msgid "Small" +#~ msgstr "Malgranda" + +#~ msgid "Unable to login to email server" +#~ msgstr "Ne konekteblas al retpoŝta servilo" + +#~ msgid "_Label" +#~ msgstr "Etikedi" diff -Nru geary-0.12.4/po/es.po geary-3.32.0/po/es.po --- geary-0.12.4/po/es.po 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/po/es.po 2019-03-17 13:39:29.000000000 +0000 @@ -10,15 +10,15 @@ # pakitochus , 2012 # Revo , 2013 # Rodrigo Cares , 2012-2013 -# Daniel Mustieles , 2014-2015, 2015, 2016, 2017, 2018. +# Carlos Córdova , 2017. +# Daniel Mustieles , 2014-2019. # msgid "" msgstr "" "Project-Id-Version: geary-0.4.1\n" -"Report-Msgid-Bugs-To: https://bugzilla.gnome.org/enter_bug.cgi?" -"product=geary&keywords=I18N+L10N&component=internationalization\n" -"POT-Creation-Date: 2018-02-12 22:46+0000\n" -"PO-Revision-Date: 2018-02-26 11:36+0100\n" +"Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/geary/issues\n" +"POT-Creation-Date: 2019-02-24 16:03+0000\n" +"PO-Revision-Date: 2019-02-26 09:02+0100\n" "Last-Translator: Daniel Mustieles \n" "Language-Team: es \n" "Language: es\n" @@ -26,29 +26,57 @@ "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Gtranslator 2.91.6\n" +"X-Generator: Gtranslator 3.31.90\n" + +#: desktop/geary-attach.contract.desktop.in:3 +msgid "Send by email" +msgstr "Enviar por correo electrónico" + +#. Translators: Do NOT translate or transliterate this text (this is an icon file name)! +#: desktop/geary-attach.contract.desktop.in:5 +msgid "mail-send" +msgstr "mail-send" + +#: desktop/geary-attach.contract.desktop.in:6 +msgid "Send files using Geary" +msgstr "Enviar archivos mediante Geary" #. Translators: The application name -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:2 -#: ../desktop/org.gnome.Geary.desktop.in.h:1 -#: ../desktop/geary-autostart.desktop.in.h:1 +#: desktop/geary-autostart.desktop.in:3 +#: desktop/org.gnome.Geary.appdata.xml.in:11 +#: desktop/org.gnome.Geary.desktop.in:3 +#: src/client/accounts/accounts-editor-servers-pane.vala:555 msgid "Geary" msgstr "Geary" -#. Translators: The development team's name -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:4 -msgid "Geary Development Team" -msgstr "Equipo de desarrollo de Geary" +#: desktop/geary-autostart.desktop.in:4 desktop/org.gnome.Geary.desktop.in:4 +msgid "Email" +msgstr "Correo-e" #. Translators: The application's summary / tagline -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:6 -#: ../desktop/org.gnome.Geary.desktop.in.h:4 -#: ../desktop/geary-autostart.desktop.in.h:4 -#: ../src/client/application/geary-application.vala:21 +#: desktop/geary-autostart.desktop.in:5 +#: desktop/org.gnome.Geary.appdata.xml.in:15 +#: desktop/org.gnome.Geary.desktop.in:5 +#: src/client/application/geary-application.vala:23 msgid "Send and receive email" msgstr "Envíe y reciba mensajes de correo electrónico" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:7 +#. Translators: These are desktop search terms. Do not translate semicolons, end line with a semicolon. +#: desktop/geary-autostart.desktop.in:7 +msgid "Email;E-mail;Mail;" +msgstr "email;e-mail;correo electrónico;correo-e;" + +#. Translators: Do NOT translate or transliterate this text (this is an icon file name)! +#: desktop/geary-autostart.desktop.in:9 desktop/org.gnome.Geary.desktop.in:9 +msgid "org.gnome.Geary" +msgstr "org.gnome.Geary" + +#. Translators: The development team's name +#: desktop/org.gnome.Geary.appdata.xml.in:13 +msgid "Geary Development Team" +msgstr "Equipo de desarrollo de Geary" + +#: desktop/org.gnome.Geary.appdata.xml.in:17 msgid "" "Geary is an email application built around conversations, for the GNOME 3 " "desktop. It allows you to read, find and send email with a straightforward, " @@ -58,7 +86,7 @@ "escritorio GNOME 3. Le permite leer, buscar y enviar correos con una " "interfaz sencilla y moderna." -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:8 +#: desktop/org.gnome.Geary.appdata.xml.in:22 msgid "" "Conversations allow you to read a complete discussion without having to find " "and click from message to message." @@ -66,219 +94,699 @@ "Las conversaciones le permiten leer un hilo completo si tener que buscar y " "pulsa cada mensaje individual." -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:9 +#: desktop/org.gnome.Geary.appdata.xml.in:26 msgid "Geary’s features include:" msgstr "Entre las características de Geary se incluyen:" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:10 +#: desktop/org.gnome.Geary.appdata.xml.in:28 msgid "Quick email account setup" msgstr "Configuración rápida de la cuenta de correo" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:11 +#: desktop/org.gnome.Geary.appdata.xml.in:29 msgid "Shows related messages together in conversations" msgstr "Mostrar mensajes relacionados entre ellos en las conversaciones" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:12 +#: desktop/org.gnome.Geary.appdata.xml.in:30 msgid "Fast, full text and keyword search" msgstr "Búsqueda de palabras clave rápida y por texto completo" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:13 +#: desktop/org.gnome.Geary.appdata.xml.in:31 msgid "Full-featured HTML and plain text message composer" msgstr "Editor completo de HYML y texto plano" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:14 +#: desktop/org.gnome.Geary.appdata.xml.in:32 msgid "Desktop notification of new mail" msgstr "Notificaciones de escritorio cuando llegue correo nuevo" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:15 +#: desktop/org.gnome.Geary.appdata.xml.in:33 msgid "Compatible with GMail, Yahoo! Mail, Outlook.com and other IMAP servers" msgstr "Compatible con GMail, Yahoo! Mail, Outlook.com y otros servidores IMAP" #. Translators: A screenshot description. -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:17 +#: desktop/org.gnome.Geary.appdata.xml.in:47 msgid "Geary displaying a conversation" msgstr "Geary mostrando una conversación" #. Translators: A screenshot description. -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:19 +#: desktop/org.gnome.Geary.appdata.xml.in:52 msgid "Geary showing the rich text composer" msgstr "Geary mostrando el editor de texto enriquecido" -#: ../desktop/org.gnome.Geary.desktop.in.h:2 -#: ../desktop/geary-autostart.desktop.in.h:2 -msgid "Email" -msgstr "Correo-e" - -#: ../desktop/org.gnome.Geary.desktop.in.h:3 -msgid "Geary Email" -msgstr "Correo Geary" - #. Translators: These are desktop search terms. Do not translate semicolons, end line with a semicolon. -#: ../desktop/org.gnome.Geary.desktop.in.h:6 +#: desktop/org.gnome.Geary.desktop.in:7 msgid "Mail;E-mail;IMAP;GMail;Yahoo;Hotmail;Outlook;" msgstr "correo;correo-e;E-mail;IMAP;GMail;Yahoo;Hotmail;Outlook;" -#: ../desktop/org.gnome.Geary.desktop.in.h:7 ../ui/gtk/menus.ui.h:1 +#: desktop/org.gnome.Geary.desktop.in:21 msgid "Compose Message" msgstr "Redactar mensaje" -#: ../desktop/geary-autostart.desktop.in.h:3 -msgid "Geary Mail" -msgstr "Correo Geary" - -#: ../desktop/geary-autostart.desktop.in.h:5 -msgid "Email;E-mail;Mail;" -msgstr "email;e-mail;correo electrónico;correo-e;" +#: desktop/org.gnome.Geary.gschema.xml:8 +msgid "Maximize window" +msgstr "Maximizar la ventana" + +#: desktop/org.gnome.Geary.gschema.xml:9 +msgid "True if the application window is maximized, false otherwise." +msgstr "" +"Cierto si la ventana de la aplicación está maximizada, falso en otro caso." + +#: desktop/org.gnome.Geary.gschema.xml:14 +msgid "Width of window" +msgstr "Anchura de la ventana" + +#: desktop/org.gnome.Geary.gschema.xml:15 +msgid "The last recorded width of the application window." +msgstr "La última anchura guardada de la ventana de la aplicación." + +#: desktop/org.gnome.Geary.gschema.xml:20 +msgid "Height of window" +msgstr "Altura de la ventana" + +#: desktop/org.gnome.Geary.gschema.xml:21 +msgid "The last recorded height of the application window." +msgstr "La última altura guardada de la ventana de la aplicación." + +#: desktop/org.gnome.Geary.gschema.xml:26 +msgid "Position of folder list pane" +msgstr "Posición del panel de lista de carpetas" + +#: desktop/org.gnome.Geary.gschema.xml:27 +msgid "Position of the folder list Paned grabber." +msgstr "Posición del panel de exploración de carpetas." + +#: desktop/org.gnome.Geary.gschema.xml:32 +msgid "Position of folder list pane when horizontal" +msgstr "Posición del panel de lista de carpetas cuando está en horizontal." + +#: desktop/org.gnome.Geary.gschema.xml:33 +msgid "" +"Position of the folder list Paned grabber in the horizontal orientation." +msgstr "" +"Posición del panel de exploración de carpetas en orientación horizontal." + +#: desktop/org.gnome.Geary.gschema.xml:38 +msgid "Position of folder list pane when vertical" +msgstr "Posición del panel de lista de carpetas cuando está en vertical." + +#: desktop/org.gnome.Geary.gschema.xml:39 +msgid "Position of the folder list Paned grabber in the vertical orientation." +msgstr "Posición del panel de exploración de carpetas en orientación vertical." + +#: desktop/org.gnome.Geary.gschema.xml:44 +msgid "Orientation of the folder list pane" +msgstr "Orientación del panel de lista de carpetas" + +#: desktop/org.gnome.Geary.gschema.xml:45 +msgid "True if the folder list Paned is in the horizontal orientation." +msgstr "Cierto si el panel de lista de carpetas está en horizontal." + +#: desktop/org.gnome.Geary.gschema.xml:50 +msgid "Position of message list pane" +msgstr "Posición del panel de lista de mensajes" + +#: desktop/org.gnome.Geary.gschema.xml:51 +msgid "Position of the message list Paned grabber." +msgstr "Posición del panel de exploración en la lista de mensajes." + +#: desktop/org.gnome.Geary.gschema.xml:56 +msgid "Autoselect next message" +msgstr "Seleccionar el siguiente mensaje automáticamente" + +#: desktop/org.gnome.Geary.gschema.xml:57 +msgid "True if we should autoselect the next available conversation." +msgstr "" +"Cierto si se debe seleccionar automáticamente la siguiente conversación " +"disponible." + +#: desktop/org.gnome.Geary.gschema.xml:62 +msgid "Display message previews" +msgstr "Mostrar la vista previa de los mensajes" + +#: desktop/org.gnome.Geary.gschema.xml:63 +msgid "True if we should display a short preview of each message." +msgstr "Cierto si se debe mostrar una breve vista previa de cada mensaje." + +#: desktop/org.gnome.Geary.gschema.xml:68 +msgid "Languages that shall be used in the spell checker" +msgstr "Idiomas que se deben usar en el corrector ortográfico" + +#: desktop/org.gnome.Geary.gschema.xml:69 +msgid "List of the languages to use in the spell checker." +msgstr "Lista de idiomas que usar en el corrector ortográfico." + +#: desktop/org.gnome.Geary.gschema.xml:74 +msgid "Languages that are displayed in the spell checker popover" +msgstr "Idiomas mostrados en la ventana emergente del corrector ortográfico" + +#: desktop/org.gnome.Geary.gschema.xml:75 +msgid "" +"List of languages that are always displayed in the popover of the spell " +"checker." +msgstr "" +"Lista de idiomas que se muestra siempre en la ventana emergente del " +"corrector ortográfico." + +#: desktop/org.gnome.Geary.gschema.xml:80 +msgid "Enable notification sounds" +msgstr "Activar sonidos para las notificaciones" + +#: desktop/org.gnome.Geary.gschema.xml:81 +msgid "True to play sounds for notifications and sending." +msgstr "Cierto para reproducir sonidos en las notificaciones y al enviar." + +#: desktop/org.gnome.Geary.gschema.xml:86 +msgid "Show notifications for new mail" +msgstr "Mostrar notificaciones de mensajes nuevos" + +#: desktop/org.gnome.Geary.gschema.xml:87 +msgid "True to show notification bubbles." +msgstr "Cierto para mostrar burbujas de notificación." + +#: desktop/org.gnome.Geary.gschema.xml:92 +msgid "Notify of new mail at startup" +msgstr "Notificar si hay correo nuevo al iniciar" + +#: desktop/org.gnome.Geary.gschema.xml:93 +msgid "True to notify of new mail at startup." +msgstr "Cierto para notificar cuando llegue correo nuevo al inicio." + +#: desktop/org.gnome.Geary.gschema.xml:98 +msgid "Ask when opening an attachment" +msgstr "Preguntar al abrir un adjunto" + +#: desktop/org.gnome.Geary.gschema.xml:99 +msgid "True to ask when opening an attachment." +msgstr "Cierto para preguntar al abrir un adjunto." + +#: desktop/org.gnome.Geary.gschema.xml:104 +msgid "Whether to compose emails in HTML" +msgstr "Indica si se deben redactar los correos en HTML" + +#: desktop/org.gnome.Geary.gschema.xml:105 +msgid "True to compose emails in HTML; false for plain text." +msgstr "" +"Cierto para redactar los correos en HTML; falso para redactarlos en texto " +"plano." + +#: desktop/org.gnome.Geary.gschema.xml:110 +msgid "Advisory strategy for full-text searching" +msgstr "Estrategia de asesoriamento para la búsqueda de textos completos." + +#: desktop/org.gnome.Geary.gschema.xml:111 +msgid "" +"Acceptable values are “exact”, “conservative”, “aggressive”, and “horizon”." +msgstr "" +"Los valores posibles son “exact”, “conservative”, “aggressive”, y “horizon”." + +#: desktop/org.gnome.Geary.gschema.xml:116 +msgid "Zoom of conversation viewer" +msgstr "Ampliación del visor de conversaciones" + +#: desktop/org.gnome.Geary.gschema.xml:117 +msgid "The zoom to apply on the conservation view." +msgstr "El nivel de ampliación que usar para la vista de conversación." + +#: desktop/org.gnome.Geary.gschema.xml:122 +msgid "Size of detached composer window" +msgstr "Tamaño de la ventana desacoplada del editor" + +#: desktop/org.gnome.Geary.gschema.xml:123 +msgid "The last recorded size of the detached composer window." +msgstr "El último tamaño guardado de la ventana desacoplada del editor." + +#: desktop/org.gnome.Geary.gschema.xml:128 +msgid "Base URL to look up contact avatars" +msgstr "URL base para buscar avatares de contactos" + +#: desktop/org.gnome.Geary.gschema.xml:129 +msgid "" +"A Gravatar or Libravatar compatible URL, set to the empty string to disable." +msgstr "" +"Un URL compatible con Gravatar o Libravatar, dejar vacío para desactivar." + +#: desktop/org.gnome.Geary.gschema.xml:134 +msgid "Whether we migrated the old settings" +msgstr "Indica si se ha migrado la configuración antigua" + +#: desktop/org.gnome.Geary.gschema.xml:135 +msgid "" +"False to check for the old “org.yorba.geary”-schema and copy its values." +msgstr "" +"Falso para verificar en el antiguo esquema \"org.yorba.geary\" y copiar sus " +"valores." + +#. Translators: In-app notification label, when +#. the app had a problem pinning an otherwise +#. untrusted TLS certificate +#: src/client/accounts/accounts-editor.vala:203 +msgid "Failed to store certificate" +msgstr "Falló al almacenar el certificado" + +#. Translators: Label for adding an email account +#. account for a generic IMAP service provider. +#: src/client/accounts/accounts-editor-add-pane.vala:109 +msgid "All others" +msgstr "Todos los demás" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:196 +#: src/client/accounts/accounts-editor-servers-pane.vala:316 +msgid "Check your receiving login and password" +msgstr "Compruebe el usuario y la contraseña del servidor de envío" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:211 +#: src/client/accounts/accounts-editor-servers-pane.vala:329 +msgid "Check your receiving server details" +msgstr "Compruebe los detalles del servidor de recepción" + +#. Translators: In-app notification label +#. There was an SMTP auth error, but IMAP already +#. succeeded, so the user probably needs to +#. specify custom creds here +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:233 +#: src/client/accounts/accounts-editor-servers-pane.vala:350 +msgid "Check your sending login and password" +msgstr "Comprobar el usuario y la contraseña para envío de correo" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:247 +#: src/client/accounts/accounts-editor-servers-pane.vala:363 +msgid "Check your sending server details" +msgstr "Compruebe los detalles del servidor de envío" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:262 +msgid "Check your email address and password" +msgstr "Compruebe la dirección de correo y la contraseña" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:273 +msgid "Could not connect, check your network" +msgstr "No se ha podido conectar, compruebe su conexión" + +#. Translators: In-app notification label for a +#. generic error creating an account +#: src/client/accounts/accounts-editor-add-pane.vala:286 +msgid "An unexpected problem occurred" +msgstr "Ha ocurrido un error inesperado" + +#. Translators: In-app notification label, the +#. string substitution is a more detailed reason. +#: src/client/accounts/accounts-editor-add-pane.vala:304 +#, c-format +msgid "Account not created: %s" +msgstr "Cuenta no creada: %s" + +#. Translators: Label for the person's actual name when adding +#. an account +#: src/client/accounts/accounts-editor-add-pane.vala:551 +msgid "Your name" +msgstr "Su nombre" + +#. Translators: Label used for the address part of an +#. email address when editing a user's sender address +#. preferences for an account. +#: src/client/accounts/accounts-editor-add-pane.vala:568 +#: src/client/accounts/accounts-editor-edit-pane.vala:501 +msgid "Email address" +msgstr "Dirección de corre-e:" + +#. Translators: Placeholder for the default sender address +#. when adding an account +#. Translators: This is used as a placeholder for the +#. address part of an email address when editing a user's +#. sender address preferences for an account. +#: src/client/accounts/accounts-editor-add-pane.vala:571 +#: src/client/accounts/accounts-editor-edit-pane.vala:469 +msgid "person@example.com" +msgstr "correo@ejemplo.com" -#: ../desktop/geary-attach.contract.in.h:1 -msgid "Send by email" -msgstr "Enviar por correo electrónico" +#. Translators: Label for an IMAP/SMTP service login/user name +#. when adding an account +#. Translators: Label for the user's login name for an +#. IMAP, SMTP, etc service +#: src/client/accounts/accounts-editor-add-pane.vala:585 +#: src/client/accounts/accounts-editor-servers-pane.vala:880 +msgid "Login name" +msgstr "Nombre de usuario" -#: ../desktop/geary-attach.contract.in.h:2 -msgid "Send files using Geary" -msgstr "Enviar archivos mediante Geary" +#. Translators: Label for the user's password for an IMAP, +#. SMTP, etc service +#: src/client/accounts/accounts-editor-add-pane.vala:599 +#: src/client/accounts/accounts-editor-servers-pane.vala:999 +#: ui/password-dialog.glade:108 +msgid "Password" +msgstr "Contraseña" -#: ../src/client/accounts/account-dialog-add-edit-pane.vala:51 -#: ../src/client/components/stock.vala:31 -#: ../ui/conversation-email-menus.ui.h:10 -msgid "_Save" -msgstr "_Guardar" +#. Translators: Label for the IMAP server hostname when +#. adding an account. +#. Translators: This label describes the host name or IP +#. address and port used by an account's IMAP service. +#: src/client/accounts/accounts-editor-add-pane.vala:621 +#: src/client/accounts/accounts-editor-servers-pane.vala:727 +msgid "IMAP server" +msgstr "Servidor IMAP" + +#. Translators: Placeholder for the IMAP server hostname +#. when adding an account. +#: src/client/accounts/accounts-editor-add-pane.vala:624 +msgid "imap.example.com" +msgstr "imap.ejemplo.com" -#: ../src/client/accounts/account-dialog-add-edit-pane.vala:51 -#: ../src/client/components/stock.vala:22 -msgid "_Add" -msgstr "_Añadir" +#. Translators: Label for the SMTP server hostname when +#. adding an account. +#. Translators: This label describes the host name or IP +#. address and port used by an account's SMTP service. +#: src/client/accounts/accounts-editor-add-pane.vala:630 +#: src/client/accounts/accounts-editor-servers-pane.vala:733 +msgid "SMTP server" +msgstr "Servidor SMTP" + +#. Translators: Placeholder for the SMTP server hostname +#. when adding an account. +#: src/client/accounts/accounts-editor-add-pane.vala:633 +msgid "smtp.example.com" +msgstr "smtp.ejemplo.com" -#. reset/clear widgets -#: ../src/client/accounts/account-dialog-edit-alternate-emails-pane.vala:120 +#. Translators: Label in the account editor for the user's +#. custom name for an account. +#: src/client/accounts/accounts-editor-edit-pane.vala:278 +#: ui/accounts_editor_remove_pane.ui:123 +msgid "Account name" +msgstr "Nombre de la cuenta" + +#. Translators: Tooltip used to undo changing +#. the name of an account. The string +#. substitution is the old name of the +#. account. +#: src/client/accounts/accounts-editor-edit-pane.vala:312 +#, c-format +msgid "Change account name back to “%s”" +msgstr "Volver a cambiar el nombre de la cuenta a «%s»" + +#. Translators: Tooltip for adding a new email sender/from +#. address's address to an account +#: src/client/accounts/accounts-editor-edit-pane.vala:336 +msgid "Add a new sender email address" +msgstr "Añadir una dirección de correo-e nueva" + +#. Translators: Label used to indicate the user has +#. provided no display name for one of their sender +#. email addresses in their account settings. +#: src/client/accounts/accounts-editor-edit-pane.vala:417 +msgid "Name not set" +msgstr "Nombre no establecido" + +#. Translators: This is used as a placeholder for the +#. display name for an email address when editing a user's +#. sender address preferences for an account. +#: src/client/accounts/accounts-editor-edit-pane.vala:456 +msgid "Sender Name" +msgstr "Nombre del remitente" + +#: src/client/accounts/accounts-editor-edit-pane.vala:479 +msgid "Remove" +msgstr "Quitar" + +#. Translators: Label used for the display name part of an +#. email address when editing a user's sender address +#. preferences for an account. +#: src/client/accounts/accounts-editor-edit-pane.vala:494 +msgid "Sender name" +msgstr "Nombre del remitente" + +#. Translators: Label used as the undo tooltip after adding an +#. new sender email address to an account. The string +#. substitution is the email address added. +#: src/client/accounts/accounts-editor-edit-pane.vala:561 +#, c-format +msgid "Remove “%s”" +msgstr "Quitar «%s»" + +#. Translators: Label used as the undo tooltip after editing a +#. sender address for an account. The string substitution is +#. the email address edited. +#: src/client/accounts/accounts-editor-edit-pane.vala:601 +#, c-format +msgid "Undo changes to “%s”" +msgstr "Deshacer cambios en «%s»" + +#. Translators: Label used as the undo tooltip after removing +#. a sender address from an account. The string substitution +#. is the email address edited. +#: src/client/accounts/accounts-editor-edit-pane.vala:688 +#, c-format +msgid "Add “%s” back" +msgstr "Añadir «%s» de nuevo" + +#. Translators: Label used as the undo tooltip after removing +#. a sender address from an account. The string substitution +#. is the email address edited. +#: src/client/accounts/accounts-editor-edit-pane.vala:730 +msgid "Undo signature changes" +msgstr "Deshacer cambios en la firma" + +#. Translators: This label describes the account +#. preference for the length of time (weeks, months or +#. years) that past email should be downloaded. +#: src/client/accounts/accounts-editor-edit-pane.vala:778 +msgid "Download mail" +msgstr "Descargar correo" + +#. Translators: Tooltip for undoing a change +#. to the length of time that past email +#. should be downloaded for an account. The +#. string substitution is the duration, +#. e.g. "1 month back". +#: src/client/accounts/accounts-editor-edit-pane.vala:810 #, c-format -msgid "Additional addresses for %s" -msgstr "Direcciones adicionales para %s" +msgid "Change download period back to: %s" +msgstr "Volver a cambiar el período de descarga a: %s" -#. Sets min size. -#: ../src/client/accounts/account-dialog.vala:21 -msgid "Accounts" -msgstr "Cuentas" - -#. Copyright 2016 Software Freedom Conservancy Inc. -#. * -#. * This software is licensed under the GNU Lesser General Public License -#. * (version 2.1 or later). See the COPYING file in this distribution. -#. -#. Page for adding or editing an account. -#. / Placeholder text indicating that the user should type their first name and last name -#: ../src/client/accounts/add-edit-page.vala:10 -msgid "First Last" -msgstr "Nombre Apellido" - -#: ../src/client/accounts/add-edit-page.vala:235 -msgid "Welcome to Geary." -msgstr "Bienvenido a Geary." - -#: ../src/client/accounts/add-edit-page.vala:235 -msgid "Enter your account information to get started." -msgstr "Proporcione la información de su cuenta para empezar." +#: src/client/accounts/accounts-editor-edit-pane.vala:831 +msgid "Everything" +msgstr "Todo" -#: ../src/client/accounts/add-edit-page.vala:255 +#: src/client/accounts/accounts-editor-edit-pane.vala:835 msgid "2 weeks back" msgstr "hace 2 semanas" -#. IDs are # of days -#: ../src/client/accounts/add-edit-page.vala:256 +#: src/client/accounts/accounts-editor-edit-pane.vala:839 msgid "1 month back" msgstr "hace 1 mes" -#: ../src/client/accounts/add-edit-page.vala:257 +#: src/client/accounts/accounts-editor-edit-pane.vala:843 msgid "3 months back" msgstr "hace 3 meses" -#: ../src/client/accounts/add-edit-page.vala:258 +#: src/client/accounts/accounts-editor-edit-pane.vala:847 msgid "6 months back" msgstr "hace 6 meses" -#: ../src/client/accounts/add-edit-page.vala:259 +#: src/client/accounts/accounts-editor-edit-pane.vala:851 msgid "1 year back" msgstr "hace 1 año" -#: ../src/client/accounts/add-edit-page.vala:260 +#: src/client/accounts/accounts-editor-edit-pane.vala:855 msgid "2 years back" msgstr "hace 2 años" -#: ../src/client/accounts/add-edit-page.vala:261 +#: src/client/accounts/accounts-editor-edit-pane.vala:859 msgid "4 years back" msgstr "hace 4 años" -#. Separator -#: ../src/client/accounts/add-edit-page.vala:263 -msgid "Everything" -msgstr "Todo" +#: src/client/accounts/accounts-editor-edit-pane.vala:865 +#, c-format +msgid "%d day back" +msgid_plural "%d days back" +msgstr[0] "hace %d día" +msgstr[1] "hace %d días" + +#: src/client/accounts/accounts-editor-list-pane.vala:243 +msgid "Undo" +msgstr "Deshacer" + +#: src/client/accounts/accounts-editor-list-pane.vala:251 +msgid "Redo" +msgstr "Rehacer" + +#: src/client/accounts/accounts-editor-list-pane.vala:345 +#: src/client/accounts/accounts-editor-list-pane.vala:433 +#: src/client/accounts/accounts-editor-row.vala:278 +msgid "Gmail" +msgstr "Gmail" -#: ../src/client/accounts/add-edit-page.vala:283 -msgid "Edit" -msgstr "Editar" - -#: ../src/client/accounts/add-edit-page.vala:285 -msgid "Preview" -msgstr "Previsualizar" - -#: ../src/client/accounts/add-edit-page.vala:751 -msgid "Remem_ber passwords" -msgstr "Recor_dar las contraseñas" - -#: ../src/client/accounts/add-edit-page.vala:758 ../ui/login.glade.h:7 -msgid "Remem_ber password" -msgstr "Recor_dar la contraseña" - -#: ../src/client/accounts/add-edit-page.vala:792 -msgid "Unable to validate:\n" -msgstr "No se puede validar:\n" - -#: ../src/client/accounts/add-edit-page.vala:794 -msgid " • Invalid account nickname.\n" -msgstr " • Alias de cuenta no válido.\n" - -#: ../src/client/accounts/add-edit-page.vala:797 -msgid " • Email address already added to Geary.\n" -msgstr " • Ya se había añadido la dirección de correo a Geary.\n" - -#: ../src/client/accounts/add-edit-page.vala:801 -msgid " • IMAP connection error.\n" -msgstr " • Error de conexión IMAP.\n" - -#: ../src/client/accounts/add-edit-page.vala:804 -msgid " • IMAP username or password incorrect.\n" -msgstr " • Nombre de usuario o contraseña IMAP incorrectos.\n" - -#: ../src/client/accounts/add-edit-page.vala:807 -msgid " • SMTP connection error.\n" -msgstr " • Error de conexión SMTP.\n" - -#: ../src/client/accounts/add-edit-page.vala:810 -msgid " • SMTP username or password incorrect.\n" -msgstr " • El nombre de usuario o contraseña SMTP incorrectos.\n" - -#: ../src/client/accounts/add-edit-page.vala:814 -msgid " • Connection error.\n" -msgstr " • Error de conexión.\n" - -#: ../src/client/accounts/add-edit-page.vala:818 -msgid " • Username or password incorrect.\n" -msgstr " • Nombre de usuario o contraseña incorrectos.\n" +#: src/client/accounts/accounts-editor-list-pane.vala:349 +#: src/client/accounts/accounts-editor-list-pane.vala:437 +#: src/client/accounts/accounts-editor-row.vala:282 +msgid "Outlook.com" +msgstr "Outlook.com" + +#: src/client/accounts/accounts-editor-list-pane.vala:353 +#: src/client/accounts/accounts-editor-list-pane.vala:441 +#: src/client/accounts/accounts-editor-row.vala:286 +msgid "Yahoo" +msgstr "Yahoo" + +#. Translators: Tooltip for accounts that have been +#. loaded but disabled by the user. +#: src/client/accounts/accounts-editor-list-pane.vala:371 +msgid "This account has been disabled" +msgstr "Esta cuenta se ha desactivado" + +#. Translators: Tooltip for accounts that have been +#. loaded but because of some error are not able to be +#. used. +#: src/client/accounts/accounts-editor-list-pane.vala:380 +msgid "This account has encountered a problem and is unavailable" +msgstr "Esta cuenta ha encontrado un problema y no está disponible" + +#. Translators: Label for adding a generic email account +#: src/client/accounts/accounts-editor-list-pane.vala:430 +msgid "Other email providers" +msgstr "Otros proveedores de correo-e" + +#. Translators: Notification shown after removing an +#. account. The string substitution is the name of the +#. account. +#: src/client/accounts/accounts-editor-list-pane.vala:547 +#, c-format +msgid "Account “%s” removed" +msgstr "Cuenta «%s» eliminada" + +#. Translators: Notification shown after removing an account +#. is undone. The string substitution is the name of the +#. account. +#: src/client/accounts/accounts-editor-list-pane.vala:554 +#, c-format +msgid "Account “%s” restored" +msgstr "Cuenta «%s» restaurada" + +#. Translators: Tooltip for dragging list items +#: src/client/accounts/accounts-editor-row.vala:49 +msgid "Drag to move this item" +msgstr "Arrastre para mover este elemento" + +#. Translators: Label describes the service provider +#. hosting the email account, e.g. Gmail, Yahoo, or some +#. other generic IMAP service. +#: src/client/accounts/accounts-editor-row.vala:294 +msgid "Service provider" +msgstr "Proveedor del servicio" + +#. Translators: This label describes what form of transport +#. security (TLS, StartTLS, etc) used by an account's IMAP or SMTP +#. service. +#: src/client/accounts/accounts-editor-row.vala:467 +msgid "Connection security" +msgstr "Seguridad de la conexión" + +#. Translators: Label used when no auth scheme is used +#. by an account's IMAP or SMTP service. +#: src/client/accounts/accounts-editor-row.vala:478 +#: src/client/accounts/accounts-editor-servers-pane.vala:752 +#: src/client/accounts/accounts-editor-servers-pane.vala:964 +#: src/engine/api/geary-special-folder-type.vala:58 +msgid "None" +msgstr "Ninguno" -#: ../src/client/application/geary-application.vala:22 +#: src/client/accounts/accounts-editor-row.vala:485 +msgid "StartTLS" +msgstr "StartTLS" + +#: src/client/accounts/accounts-editor-row.vala:492 +msgid "TLS" +msgstr "TLS" + +#. Translators: Label for source of SMTP authentication +#. credentials (none, use IMAP, custom) when adding a new +#. account +#. Button label for retrying when a login error has occurred +#: src/client/accounts/accounts-editor-row.vala:533 ui/main-window.ui:455 +msgid "Login" +msgstr "Inicio de sesión" + +#. Translators: ComboBox value for source of SMTP +#. authentication credentials (none) when adding a new +#. account +#: src/client/accounts/accounts-editor-row.vala:540 +msgid "No login needed" +msgstr "No se necesita inicio de sesión" + +#. Translators: ComboBox value for source of SMTP +#. authentication credentials (use IMAP) when adding a new +#. account +#: src/client/accounts/accounts-editor-row.vala:548 +msgid "Use same login as receiving" +msgstr "Usar el mismo inicio de sesión para recibir" + +#. Translators: ComboBox value for source of SMTP +#. authentication credentials (custom) when adding a new +#. account +#: src/client/accounts/accounts-editor-row.vala:556 +msgid "Use a different login" +msgstr "Usar un inicio de sesión diferente" + +#. Translators: In-app notification label, the +#. string substitution is a more detailed reason. +#: src/client/accounts/accounts-editor-servers-pane.vala:377 +#, c-format +msgid "Account not updated: %s" +msgstr "Cuenta no actualizada: %s" + +#. Translators: This label describes the program that +#. created the account, e.g. an SSO service like GOA, or +#. locally by Geary. +#: src/client/accounts/accounts-editor-servers-pane.vala:540 +msgid "Account source" +msgstr "Origen de la cuenta" + +#: src/client/accounts/accounts-editor-servers-pane.vala:552 +msgid "GNOME Online Accounts" +msgstr "Cuentas en línea de GNOME" + +#. Translators: This label describes an account +#. preference. +#: src/client/accounts/accounts-editor-servers-pane.vala:611 +msgid "Save drafts on server" +msgstr "Guardar borradores en el servidor" + +#. Translators: This label describes an account +#. preference. +#: src/client/accounts/accounts-editor-servers-pane.vala:666 +#| msgid "_Save sent mail" +msgid "Save sent email on server" +msgstr "Guardar mensajes enviados en el servidor" + +#. Add a suffix for OAuth2 auth so people know they +#. shouldn't expect to be prompted for a password +#. Translators: Label used when an account's IMAP or +#. SMTP service uses OAuth2. The string replacement is +#. the service's login name. +#: src/client/accounts/accounts-editor-servers-pane.vala:950 +#, c-format +msgid "%s using OAuth2" +msgstr "%s usa OAuth2" + +#: src/client/accounts/accounts-editor-servers-pane.vala:960 +msgid "Use receiving server login" +msgstr "Usar inicio de sesión del servidor para la recepción" + +#: src/client/application/geary-application.vala:24 msgid "Copyright 2016 Software Freedom Conservancy Inc." msgstr "Copyright 2016 Software Freedom Conservancy Inc." -#: ../src/client/application/geary-application.vala:23 -#| msgid "Geary Development Team" -msgid "Copyright 2016-2017 Geary Development Team." -msgstr "Copyright 2016-2017 Equipo de desarrollo de Geary." +#: src/client/application/geary-application.vala:25 +msgid "Copyright 2016-2019 Geary Development Team." +msgstr "Copyright 2016-2019 Equipo de desarrollo de Geary." -#: ../src/client/application/geary-application.vala:25 +#: src/client/application/geary-application.vala:27 msgid "Visit the Geary web site" msgstr "Visite el sitio web de Geary" -#: ../src/client/application/geary-application.vala:466 +#: src/client/application/geary-application.vala:454 #, c-format msgid "About %s" msgstr "Acerca de %s" @@ -286,318 +794,113 @@ #. Translators: add your name and email address to receive #. credit in the About dialog For example: Yamada Taro #. -#: ../src/client/application/geary-application.vala:470 +#: src/client/application/geary-application.vala:458 msgid "translator-credits" msgstr "" "Daniel Mustieles , 2015 - 2017\n" "Adolfo Jayme Barrientos , 2012–2015" -#: ../src/client/application/geary-args.vala:10 +#: src/client/application/geary-args.vala:10 msgid "Start Geary with hidden main window" msgstr "Iniciar Geary con la ventana principal oculta" -#: ../src/client/application/geary-args.vala:11 +#: src/client/application/geary-args.vala:11 msgid "Output debugging information" msgstr "Salida de información de depuración" -#: ../src/client/application/geary-args.vala:12 +#: src/client/application/geary-args.vala:12 msgid "Log conversation monitoring" msgstr "Registrar la monitorización de conversaciones" -#: ../src/client/application/geary-args.vala:13 +#: src/client/application/geary-args.vala:13 msgid "Log network deserialization" msgstr "Registrar la deserialización de red" -#: ../src/client/application/geary-args.vala:14 +#: src/client/application/geary-args.vala:14 msgid "Log network activity" msgstr "Registrar la actividad de red" #. / The IMAP replay queue is how changes on the server are replicated on the client. #. / It could also be called the IMAP events queue. -#: ../src/client/application/geary-args.vala:17 +#: src/client/application/geary-args.vala:17 msgid "Log IMAP replay queue" msgstr "Registrar la cola de eventos IMAP" #. / Serialization is how commands and responses are converted into a stream of bytes for #. / network transmission -#: ../src/client/application/geary-args.vala:20 +#: src/client/application/geary-args.vala:20 msgid "Log network serialization" msgstr "Registrar la serialización de red" -#: ../src/client/application/geary-args.vala:21 +#: src/client/application/geary-args.vala:21 msgid "Log periodic activity" msgstr "Registrar la actividad periódica" -#: ../src/client/application/geary-args.vala:22 +#: src/client/application/geary-args.vala:22 msgid "Log database queries (generates lots of messages)" msgstr "Registrar las consultas a la base de datos (genera muchos mensajes)" #. / "Normalization" can also be called "synchronization" -#: ../src/client/application/geary-args.vala:24 +#: src/client/application/geary-args.vala:24 msgid "Log folder normalization" msgstr "Registrar la normalización de carpetas" -#: ../src/client/application/geary-args.vala:25 +#: src/client/application/geary-args.vala:25 msgid "Allow inspection of WebView" msgstr "Permitir la inspección de WebView" -#: ../src/client/application/geary-args.vala:26 +#: src/client/application/geary-args.vala:26 msgid "Revoke all server certificates with TLS warnings" msgstr "Revocar todos los certificados de servidores con advertencias de TLS" -#: ../src/client/application/geary-args.vala:27 +#: src/client/application/geary-args.vala:27 msgid "Perform a graceful quit" msgstr "Salir elegantemente" -#: ../src/client/application/geary-args.vala:28 +#: src/client/application/geary-args.vala:28 msgid "Display program version" msgstr "Mostrar la versión del programa" #. This gives a command-line hint on how to open new composer windows with mailto: -#: ../src/client/application/geary-args.vala:53 +#: src/client/application/geary-args.vala:53 #, c-format msgid "Use %s to open a new composer window" msgstr "Usar %s para abrir una ventana nueva del editor" -#: ../src/client/application/geary-args.vala:56 +#: src/client/application/geary-args.vala:56 msgid "Please report comments, suggestions and bugs to:" msgstr "Envíe comentarios, sugerencias y errores a:" #. i18n: Command line arguments are invalid -#: ../src/client/application/geary-args.vala:63 +#: src/client/application/geary-args.vala:63 #, c-format msgid "Failed to parse command line options: %s\n" msgstr "Falló al analizar las opciones de la línea de comandos: %s\n" -#: ../src/client/application/geary-args.vala:74 +#: src/client/application/geary-args.vala:74 #, c-format msgid "Unrecognized command line option “%s”\n" msgstr "Opción de la línea de comandos «%s» no reconocida\n" -#: ../src/client/application/geary-controller.vala:57 -msgid "Delete conversation" -msgstr "Eliminar conversación" - -#: ../src/client/application/geary-controller.vala:58 -msgid "Delete conversation (Shift+Delete)" -msgstr "Eliminar conversación (Mayús+Supr)" - -#: ../src/client/application/geary-controller.vala:59 -msgid "Delete conversations (Shift+Delete)" -msgstr "Eliminar conversaciones (Mayús+Supr)" - -#. This refers to the action ("move email to the trash"), not the Trash folder itself -#: ../src/client/application/geary-controller.vala:63 -msgid "Move conversation to Trash (Delete, Backspace)" -msgstr "Mover conversación a la papelera (Supr, Retroceso)" - -#: ../src/client/application/geary-controller.vala:64 -msgid "Move conversations to Trash (Delete, Backspace)" -msgstr "Mover conversaciones a la papelera (Supr, Retroceso)" - -#. This refers to the action ("archive an email"), not the Archive folder itself -#: ../src/client/application/geary-controller.vala:68 -msgid "_Archive" -msgstr "_Archivar" - -#: ../src/client/application/geary-controller.vala:69 -msgid "Archive conversation (A)" -msgstr "Archivar conversación (A)" - -#: ../src/client/application/geary-controller.vala:70 -msgid "Archive conversations (A)" -msgstr "Archivar conversaciones (A)" - -#: ../src/client/application/geary-controller.vala:73 -msgid "Mark as S_pam" -msgstr "Marcar como _spam" - -#: ../src/client/application/geary-controller.vala:74 -msgid "Mark as not S_pam" -msgstr "Marcar como no _spam" - -#: ../src/client/application/geary-controller.vala:76 -#: ../src/client/application/geary-controller.vala:448 -msgid "Mark conversation" -msgstr "Marcar conversación" - -#: ../src/client/application/geary-controller.vala:77 -msgid "Mark conversations" -msgstr "Marcar conversaciones" - -#: ../src/client/application/geary-controller.vala:78 -msgid "Add label to conversation" -msgstr "Añadir etiqueta a la conversación" - -#: ../src/client/application/geary-controller.vala:79 -msgid "Add label to conversations" -msgstr "Añadir etiqueta a las conversaciones" - -#: ../src/client/application/geary-controller.vala:80 -#: ../src/client/application/geary-controller.vala:487 -msgid "Move conversation" -msgstr "Mover conversación" - -#: ../src/client/application/geary-controller.vala:81 -msgid "Move conversations" -msgstr "Mover conversaciones" - -#: ../src/client/application/geary-controller.vala:450 -msgid "_Mark as…" -msgstr "_Marcar como…" - -#: ../src/client/application/geary-controller.vala:456 -msgid "Mark as _Read" -msgstr "Marcar como _leído" - -#: ../src/client/application/geary-controller.vala:462 -msgid "Mark as _Unread" -msgstr "Marcar como _no leído" - -#: ../src/client/application/geary-controller.vala:468 -msgid "_Star" -msgstr "_Destacar" - -#: ../src/client/application/geary-controller.vala:473 -msgid "U_nstar" -msgstr "_No destacar" - -#: ../src/client/application/geary-controller.vala:483 -msgid "Add label" -msgstr "Añadir etiqueta" - -#: ../src/client/application/geary-controller.vala:484 -msgid "_Label" -msgstr "_Etiquetar" - -#: ../src/client/application/geary-controller.vala:488 -msgid "_Move" -msgstr "_Mover" - -#: ../src/client/application/geary-controller.vala:492 -msgid "Compose new message (Ctrl+N, N)" -msgstr "Redactar un mensaje nuevo (Ctrl+N, N)" - -#: ../src/client/application/geary-controller.vala:496 -#: ../ui/conversation-email-menus.ui.h:1 -msgid "_Reply" -msgstr "_Responder" - -#: ../src/client/application/geary-controller.vala:497 -msgid "Reply (Ctrl+R, R)" -msgstr "Responder (Ctrl+R, R)" - -#: ../src/client/application/geary-controller.vala:501 -msgid "R_eply All" -msgstr "R_esponder a todos" - -#: ../src/client/application/geary-controller.vala:502 -msgid "Reply all (Ctrl+Shift+R, Shift+R)" -msgstr "Responder a todos (Ctrl+Mayús+R, Mayús+R)" - -#: ../src/client/application/geary-controller.vala:507 -#: ../ui/conversation-email-menus.ui.h:3 -msgid "_Forward" -msgstr "_Reenviar" - -#: ../src/client/application/geary-controller.vala:508 -msgid "Forward (Ctrl+L, F)" -msgstr "Reenviar (Ctrl+L, F)" - -#: ../src/client/application/geary-controller.vala:538 -msgid "Empty _Spam…" -msgstr "Vaciar _Spam…" - -#: ../src/client/application/geary-controller.vala:542 -msgid "Empty _Trash…" -msgstr "Vaciar la _papelera…" - -#. No callback is connected, since we bind the toggle button to the search bar visibility -#: ../src/client/application/geary-controller.vala:574 -msgid "Toggle search bar" -msgstr "Conmutar la barra de búsqueda" - -#. No callback is connected, since we bind the toggle button to the find bar visibility -#: ../src/client/application/geary-controller.vala:579 -msgid "Toggle find bar" -msgstr "Conmutar la barra de búsqueda" - -#: ../src/client/application/geary-controller.vala:757 -msgid "Unable to store server trust exception" -msgstr "No se pudo almacenar la excepción de seguridad para el servidor" - -#: ../src/client/application/geary-controller.vala:992 -msgid "Your settings are insecure" -msgstr "Su configuración no es segura" - -#: ../src/client/application/geary-controller.vala:993 -msgid "" -"Your IMAP and/or SMTP settings do not specify SSL or TLS. This means your " -"username and password could be read by another person on the network. Are " -"you sure you want to do this?" -msgstr "" -"Los parámetros IMAP y/o SMTP no especifican SSL o TLS. Esto quiere decir que " -"otra persona podría leer su nombre de usuario y contraseña en la misma red. " -"¿Está seguro de que quiere hacer esto?" - -#: ../src/client/application/geary-controller.vala:994 -msgid "Co_ntinue" -msgstr "Co_ntinuar" - -#: ../src/client/application/geary-controller.vala:1041 -msgid "Error connecting to the server" -msgstr "Error al conectar al servidor" - -#: ../src/client/application/geary-controller.vala:1042 -msgid "" -"Geary encountered an error while connecting to the server. Please try again " -"in a few moments." -msgstr "" -"Geary ha encontrado un error al conectar al servidor. Inténtelo de nuevo " -"pasados unos momentos." - -#. / Displayed in the space-limited status bar when a message fails to be sent due to error. -#: ../src/client/application/geary-controller.vala:1079 -#: ../src/client/components/status-bar.vala:29 -msgid "Error sending email" -msgstr "Error al enviar el mensaje" - -#: ../src/client/application/geary-controller.vala:1080 -msgid "" -"Geary encountered an error sending an email. If the problem persists, " -"please manually delete the email from your Outbox folder." -msgstr "" -"Geary encontró un error al enviar un mensaje. Si el problema persiste, " -"elimine manualmente el mensaje de la bandeja de salida." - -#. Displayed in the space-limited status bar when a message fails to be uploaded -#. to Sent Mail after being sent. -#: ../src/client/application/geary-controller.vala:1084 -#: ../src/client/components/status-bar.vala:33 -msgid "Error saving sent mail" -msgstr "Error al guardar los mensajes enviados" - -#: ../src/client/application/geary-controller.vala:1085 -msgid "" -"Geary encountered an error saving a sent message to Sent Mail. The message " -"will stay in your Outbox folder until you delete it." -msgstr "" -"Geary encontró un error al guardar un mensaje enviado en Enviados. El " -"mensaje permanecerá en la Bandeja de salida hasta que lo elimine." +#. Translators: File name used in save chooser when saving +#. attachments that do not otherwise have a name. +#: src/client/application/geary-controller.vala:58 +msgid "Untitled" +msgstr "Sin título" -#: ../src/client/application/geary-controller.vala:1154 +#: src/client/application/geary-controller.vala:919 msgid "Labels" msgstr "Etiquetas" #. give the user two options: reset the Account local store, or exit Geary. A third #. could be done to leave the Account in an unopened state, but we don't currently #. have provisions for that. -#: ../src/client/application/geary-controller.vala:1166 +#: src/client/application/geary-controller.vala:932 #, c-format msgid "Unable to open the database for %s" msgstr "No se pudo abrir la base de datos local para %s" -#: ../src/client/application/geary-controller.vala:1167 +#: src/client/application/geary-controller.vala:933 #, c-format msgid "" "There was an error opening the local mail database for this account. This is " @@ -622,20 +925,20 @@ "Reconstruir la base de datos destruirá todo el correo guardado de forma " "local y sus adjuntos. El correo del servidor permanecerá intacto." -#: ../src/client/application/geary-controller.vala:1169 +#: src/client/application/geary-controller.vala:935 msgid "_Rebuild" msgstr "_Reconstruir" -#: ../src/client/application/geary-controller.vala:1169 +#: src/client/application/geary-controller.vala:935 msgid "E_xit" msgstr "_Salir" -#: ../src/client/application/geary-controller.vala:1178 +#: src/client/application/geary-controller.vala:944 #, c-format msgid "Unable to rebuild database for “%s”" msgstr "No se puede reconstruir la base de datos para «%s»" -#: ../src/client/application/geary-controller.vala:1179 +#: src/client/application/geary-controller.vala:945 #, c-format msgid "" "Error during rebuild:\n" @@ -646,69 +949,15 @@ "\n" "%s" -#. some other problem opening the account ... as with other flow path, can't run -#. Geary today with an account in unopened state, so have to exit -#: ../src/client/application/geary-controller.vala:1201 -#: ../src/client/application/geary-controller.vala:1211 -#: ../src/client/application/geary-controller.vala:1222 -#, c-format -msgid "Unable to open local mailbox for %s" -msgstr "No se puede abrir el buzón local para %s" - -#: ../src/client/application/geary-controller.vala:1202 -#, c-format -msgid "" -"There was an error opening the local mail database for this account. This is " -"possibly due to a file permissions problem.\n" -"\n" -"Please check that you have read/write permissions for all files in this " -"directory:\n" -"\n" -"%s" -msgstr "" -"Hubo un error al abrir la base de datos de correo local para esta cuenta. " -"Esto se debe posiblemente a un problema de permisos de archivo.\n" -"\n" -"Revise que cuentas con permisos de lectura y escritura en la siguiente " -"carpeta:\n" -"\n" -"%s" - -#: ../src/client/application/geary-controller.vala:1212 -msgid "" -"The version number of the local mail database is formatted for a newer " -"version of Geary. Unfortunately, the database cannot be “rolled back” to " -"work with this version of Geary.\n" -"\n" -"Please install the latest version of Geary and try again." -msgstr "" -"La versión de la base de datos de correo local tiene el formato de una " -"versión más reciente de Geary. Desafortunadamente, no se puede volver a una " -"versión anterior para que funcione con esta versión de Geary.\n" -"\n" -"Instale la última versión de Geary e inténtelo de nuevo." - -#: ../src/client/application/geary-controller.vala:1223 -msgid "" -"There was an error opening the local account. This is probably due to " -"connectivity issues.\n" -"\n" -"Please check your network connection and restart Geary." -msgstr "" -"Hubo un error al abrir la cuenta local. Esto puede ser debido a problemas de " -"conectividad.\n" -"\n" -"Revise su conexión de red y reinicie Geary." - -#: ../src/client/application/geary-controller.vala:2010 +#: src/client/application/geary-controller.vala:1800 msgid "Undo move (Ctrl+Z)" msgstr "Deshacer mover (Ctrl+Z)" -#: ../src/client/application/geary-controller.vala:2020 +#: src/client/application/geary-controller.vala:1810 msgid "Are you sure you want to open these attachments?" msgstr "¿Está seguro de que quiere abrir estos adjuntos?" -#: ../src/client/application/geary-controller.vala:2021 +#: src/client/application/geary-controller.vala:1811 msgid "" "Attachments may cause damage to your system if opened. Only open files from " "trusted sources." @@ -716,204 +965,480 @@ "Los archivos adjuntos podrían causar daños a su sistema. Abra solo los " "archivos que provengan de fuentes fiables." -#: ../src/client/application/geary-controller.vala:2022 +#: src/client/application/geary-controller.vala:1812 msgid "Don’t _ask me again" msgstr "No volver a _preguntarme" -#: ../src/client/application/geary-controller.vala:2126 +#. Translators: Dialog primary label when prompting to +#. overwrite a file. The string substitution is the file'sx +#. name. +#: src/client/application/geary-controller.vala:1941 #, c-format msgid "A file named “%s” already exists. Do you want to replace it?" msgstr "Ya existe un archivo llamado «%s». ¿Quiere reemplazarlo?" -#: ../src/client/application/geary-controller.vala:2128 +#. Translators: Dialog secondary label when prompting to +#. overwrite a file. The string substitution is the parent +#. folder's name. +#: src/client/application/geary-controller.vala:1948 #, c-format msgid "" "The file already exists in “%s”. Replacing it will overwrite its contents." msgstr "" "El archivo ya existe en «%s». Si lo reemplaza sobrescribirá su contenido." -#: ../src/client/application/geary-controller.vala:2131 +#: src/client/application/geary-controller.vala:1952 msgid "_Replace" msgstr "_Reemplazar" -#. Find out what to do with the inline composers. -#. TODO: Remove this in favor of automatically saving drafts -#: ../src/client/application/geary-controller.vala:2379 -msgid "Close open draft messages?" -msgstr "¿Quiere cerrar los mensajes en borrador abiertos?" +#: src/client/application/geary-controller.vala:2228 +msgid "Close the draft message?" +msgid_plural "Close all draft messages?" +msgstr[0] "¿Quiere cerrar el mensaje en borrador?" +msgstr[1] "¿Quiere cerrar los mensajes en borrador?" -#: ../src/client/application/geary-controller.vala:2501 +#: src/client/application/geary-controller.vala:2354 #, c-format msgid "Empty all email from your %s folder?" msgstr "¿Quiere eliminar todos los mensajes de la carpeta «%s»?" -#: ../src/client/application/geary-controller.vala:2502 +#: src/client/application/geary-controller.vala:2355 msgid "This removes the email from Geary and your email server." msgstr "" "Esto eliminará el mensaje de Geary y del servidor de correo electrónico." -#: ../src/client/application/geary-controller.vala:2503 +#: src/client/application/geary-controller.vala:2356 msgid "This cannot be undone." msgstr "Esto no se puede deshacer." -#: ../src/client/application/geary-controller.vala:2504 +#: src/client/application/geary-controller.vala:2357 #, c-format msgid "Empty %s" msgstr "Vaciar %s" -#: ../src/client/application/geary-controller.vala:2521 +#: src/client/application/geary-controller.vala:2374 #, c-format msgid "Error emptying %s" msgstr "Error al vaciar «%s»" -#: ../src/client/application/geary-controller.vala:2551 +#: src/client/application/geary-controller.vala:2406 msgid "Do you want to permanently delete this message?" msgid_plural "Do you want to permanently delete these messages?" msgstr[0] "¿Quiere eliminar permanentemente este mensaje?" msgstr[1] "¿Quiere eliminar permanentemente estos mensajes?" -#: ../src/client/application/geary-controller.vala:2553 +#: src/client/application/geary-controller.vala:2408 msgid "Delete" msgstr "Eliminar" -#: ../src/client/application/geary-controller.vala:2585 -msgid "Undo archive (Ctrl+Z)" -msgstr "Deshacer archivado (Ctrl+Z)" - -#: ../src/client/application/geary-controller.vala:2600 +#: src/client/application/geary-controller.vala:2422 msgid "Undo trash (Ctrl+Z)" msgstr "Deshacer envío a la papelera (Ctrl+Z)" -#: ../src/client/application/geary-controller.vala:2654 +#: src/client/application/geary-controller.vala:2472 +msgid "Undo archive (Ctrl+Z)" +msgstr "Deshacer archivado (Ctrl+Z)" + +#: src/client/application/geary-controller.vala:2517 msgid "Undo (Ctrl+Z)" msgstr "Deshacer (Ctrl+Z)" -#: ../src/client/application/geary-controller.vala:2779 +#. Translators: The label for an in-app notification. The +#. string substitution is a list of recipients of the email. +#: src/client/application/geary-controller.vala:2598 +#, c-format +msgid "Successfully sent mail to %s." +msgstr "Correo enviado correctamente a %s." + +#: src/client/application/geary-controller.vala:2680 msgid "Failed to open default text editor." msgstr "Falló al abrir el editor de textos predeterminado." -#: ../src/client/components/main-window.vala:389 +#. Translators: Tooltip used when an entry requires a valid +#. email address to be entered, but one is not provided. +#: src/client/components/components-validator.vala:341 +msgid "An email address is required" +msgstr "Se necesita una dirección correo-e" + +#. Translators: Tooltip used when an entry requires a valid +#. email address to be entered, but the address is invalid. +#: src/client/components/components-validator.vala:345 +msgid "Not a valid email address" +msgstr "Dirección correo-e no válida" + +#. Translators: Tooltip used when an entry requires a valid, +#. resolvable server name to be entered, but one is not +#. provided. +#: src/client/components/components-validator.vala:391 +msgid "A server name is required" +msgstr "Se necesita un nombre de servidor" + +#. Translators: Tooltip used when an entry requires a valid +#. server name to be entered, but it was unable to be +#. looked-up in the DNS. +#: src/client/components/components-validator.vala:396 +msgid "Could not look up server name" +msgstr "No se pudo encontrar el nombre del servidor" + +#: src/client/components/main-toolbar.vala:151 +msgid "Mark conversation" +msgid_plural "Mark conversations" +msgstr[0] "Marcar conversación" +msgstr[1] "Marcar conversaciones" + +#: src/client/components/main-toolbar.vala:156 +msgid "Add label to conversation" +msgid_plural "Add label to conversations" +msgstr[0] "Añadir etiqueta a la conversación" +msgstr[1] "Añadir etiqueta a las conversaciones" + +#: src/client/components/main-toolbar.vala:161 +msgid "Move conversation" +msgid_plural "Move conversations" +msgstr[0] "Mover conversación" +msgstr[1] "Mover conversaciones" + +#: src/client/components/main-toolbar.vala:166 +msgid "Archive conversation (A)" +msgid_plural "Archive conversations (A)" +msgstr[0] "Archivar conversación (A)" +msgstr[1] "Archivar conversaciones (A)" + +#: src/client/components/main-toolbar.vala:175 +msgid "Move conversation to Trash (Delete, Backspace)" +msgid_plural "Move conversations to Trash (Delete, Backspace)" +msgstr[0] "Mover conversación a la papelera (Supr, Retroceso)" +msgstr[1] "Mover conversaciones a la papelera (Supr, Retroceso)" + +#: src/client/components/main-toolbar.vala:183 +msgid "Delete conversation (Shift+Delete)" +msgid_plural "Delete conversations (Shift+Delete)" +msgstr[0] "Eliminar conversación (Mayús+Supr)" +msgstr[1] "Eliminar conversaciones (Mayús+Supr)" + +#: src/client/components/main-window.vala:503 #, c-format msgid "%s (%d)" msgstr "%s (%d)" -#: ../src/client/components/search-bar.vala:8 -#: ../src/client/folder-list/folder-list-search-branch.vala:38 -#: ../src/engine/api/geary-special-folder-type.vala:51 +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:47 +#, c-format +msgid "Problem connecting to incoming server for %s" +msgstr "Problema al conectar al servidor de entrada para %s" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:49 +#: src/client/components/main-window-info-bar.vala:58 +#, c-format +msgid "" +"Could not connect to %s, check your Internet access and the server name and " +"try again" +msgstr "" +"No se pudo conectar a %s, revise su conexión a Internet y el nombre del " +"servidor e inténtelo de nuevo." + +#. Translators: Tooltip label for Retry button +#. Button tooltip for retrying an account problem +#: src/client/components/main-window-info-bar.vala:51 +#: src/client/components/main-window-info-bar.vala:60 +#: src/client/components/main-window-info-bar.vala:69 +#: src/client/components/main-window-info-bar.vala:78 +#: src/client/components/main-window-info-bar.vala:87 +#: src/client/components/main-window-info-bar.vala:96 +#: src/client/components/main-window-info-bar.vala:136 ui/main-window.ui:265 +msgid "Try reconnecting" +msgstr "Vuelva a intentar conectar" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:56 +#, c-format +msgid "Problem connecting to outgoing server for %s" +msgstr "Problema al conectar al servidor de salida para %s" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:65 +#: src/client/components/main-window-info-bar.vala:83 +#, c-format +msgid "Problem communicating with incoming server for %s" +msgstr "Problema al comunicarse con el servidor de correo entrante para %s" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:67 +#: src/client/components/main-window-info-bar.vala:76 +#, c-format +msgid "Network error talking to %s, check your Internet access and try again" +msgstr "" +"Problema de red mientras se comunicaba con %s, revise su conexión a Internet " +"e inténtelo nuevamente." + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:74 +#: src/client/components/main-window-info-bar.vala:91 +msgid "Problem communicating with outgoing mail server" +msgstr "Problema al comunicar con el servidor de correo saliente" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:85 +#, c-format +msgid "" +"Geary did not understand a message from %s or vice versa, please file a bug " +"report" +msgstr "" +"Geary no pudo entender un mensaje de %s o viceversa, porfavor envíe un " +"informe de error." + +#. Translators: First string substitution is the server +#. name, second is the account name +#: src/client/components/main-window-info-bar.vala:94 +#, c-format +msgid "" +"Could not communicate with %s for %s, check the server name and try again in " +"a moment" +msgstr "" +"No se pudo conectar a %s para %s, revise el nombre del servidor e inténtelo " +"de nuevo." + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:101 +#, c-format +msgid "Incoming mail server password required for %s" +msgstr "Contraseña necesaria para el servidor de correo entrante para %s" + +#: src/client/components/main-window-info-bar.vala:102 +msgid "Messages cannot be received without the correct password." +msgstr "No se pueden recibir los mensajes sin la contraseña correcta." + +#. Translators: Tooltip label for Retry button +#: src/client/components/main-window-info-bar.vala:104 +msgid "Retry receiving email, you will be prompted for a password" +msgstr "Reintentar recibir correo, se le pedirá una contraseña" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:109 +#, c-format +msgid "Outgoing mail server password required for %s" +msgstr "Contraseña necesaria para el servidor de correo saliente para %s" + +#: src/client/components/main-window-info-bar.vala:110 +msgid "Messages cannot be sent without the correct password." +msgstr "No se pueden enviar los mensajes sin la contraseña correcta." + +#. Translators: Tooltip label for Retry button +#: src/client/components/main-window-info-bar.vala:112 +msgid "Retry sending queued messages, you will be prompted for a password" +msgstr "Reintentar enviar los correos encolados, se le pedirá una contraseña" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:117 +#, c-format +msgid "Incoming mail server security is not trusted for %s" +msgstr "" +"La seguridad del servidor de correo entrante no es de confianza para %s" + +#: src/client/components/main-window-info-bar.vala:118 +msgid "Messages will not be received until checked." +msgstr "Los mensajes no se recibirán hasta que se comprueben." + +#. Translators: Tooltip label for Retry button +#: src/client/components/main-window-info-bar.vala:120 +#: src/client/components/main-window-info-bar.vala:128 +msgid "Check security details" +msgstr "Comprobar los detalles de seguridad" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:125 +#, c-format +msgid "Outgoing mail server security is not trusted for %s" +msgstr "" +"La seguridad del servidor de correo saliente no es de confianza para %s" + +#: src/client/components/main-window-info-bar.vala:126 +msgid "Messages cannot be sent until checked." +msgstr "No se podrán enviar los mensajes hasta que se comprueben." + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:133 +#, c-format +msgid "A problem occurred checking mail for %s" +msgstr "ha ocurrido un problema al comprobar el correo para %s" + +#: src/client/components/main-window-info-bar.vala:134 +#: src/client/components/main-window-info-bar.vala:142 +msgid "Something went wrong, please file a bug report if the problem persists" +msgstr "Algo ha fallado, envíe un informe de error si el problema persiste" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:141 +#, c-format +msgid "A problem occurred sending mail for %s" +msgstr "Ha ocurrido un error al enviar el correo para %s" + +#. Translators: Tooltip label for Retry button +#: src/client/components/main-window-info-bar.vala:144 +msgid "Retry sending queued messages" +msgstr "Reintentar enviar los correos encolados" + +#: src/client/components/main-window-info-bar.vala:155 +msgid "A database problem has occurred" +msgstr "Ha ocurrido un problema de base de datos" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:157 +#, c-format +msgid "Messages for %s must be downloaded again." +msgstr "Los mensajes para %s se deben descargar de nuevo." + +#: src/client/components/main-window-info-bar.vala:170 +msgid "Geary has encountered a problem" +msgstr "Geary ha encontrado un problema" + +#: src/client/components/main-window-info-bar.vala:171 +msgid "" +"Please check the technical details and report the problem if it persists." +msgstr "Compruebe los detalles técnicos e informe del problema si persiste." + +#: src/client/components/main-window-info-bar.vala:179 +msgid "_Details" +msgstr "_Detalles" + +#. Button tooltip for displaying technical details about an account problem +#: src/client/components/main-window-info-bar.vala:180 ui/main-window.ui:250 +msgid "View technical details about the error" +msgstr "Ver detalles técnicos sobre el error" + +#: src/client/components/main-window-info-bar.vala:184 +msgid "_Retry" +msgstr "_Reintentar" + +#: src/client/components/search-bar.vala:8 +#: src/client/folder-list/folder-list-search-branch.vala:38 +#: src/engine/api/geary-special-folder-type.vala:51 msgid "Search" msgstr "Buscar" #. Search entry. -#: ../src/client/components/search-bar.vala:23 +#: src/client/components/search-bar.vala:23 msgid "Search all mail in account for keywords (Ctrl+S)" msgstr "Buscar palabras en todo el correo de la cuenta (Ctrl+S)" -#: ../src/client/components/search-bar.vala:100 +#: src/client/components/search-bar.vala:101 #, c-format msgid "Indexing %s account" msgstr "Indexando la cuenta %s" -#: ../src/client/components/search-bar.vala:111 -#: ../src/client/folder-list/folder-list-search-branch.vala:39 +#: src/client/components/search-bar.vala:112 +#: src/client/folder-list/folder-list-search-branch.vala:39 #, c-format msgid "Search %s account" msgstr "Buscar la cuenta %s" #. / Displayed in the space-limited status bar while a message is in the process of being sent. -#: ../src/client/components/status-bar.vala:26 +#: src/client/components/status-bar.vala:26 msgid "Sending…" msgstr "Enviando…" -#: ../src/client/components/stock.vala:18 -#: ../ui/account_cannot_remove.glade.h:3 +#. / Displayed in the space-limited status bar when a message fails to be sent due to error. +#: src/client/components/status-bar.vala:29 +msgid "Error sending email" +msgstr "Error al enviar el mensaje" + +#. Displayed in the space-limited status bar when a message fails to be uploaded +#. to Sent Mail after being sent. +#: src/client/components/status-bar.vala:33 +msgid "Error saving sent mail" +msgstr "Error al guardar los mensajes enviados" + +#: src/client/components/stock.vala:18 msgid "_OK" msgstr "_Aceptar" -#: ../src/client/components/stock.vala:19 -#: ../ui/edit_alternate_emails.glade.h:3 ../ui/password-dialog.glade.h:5 -#: ../ui/remove_confirm.glade.h:5 +#: src/client/components/stock.vala:19 ui/password-dialog.glade:196 msgid "_Cancel" msgstr "_Cancelar" -#: ../src/client/components/stock.vala:21 ../ui/gtk/menus.ui.h:6 +#: src/client/components/stock.vala:21 msgid "_About" msgstr "_Acerca de" -#: ../src/client/components/stock.vala:23 +#: src/client/components/stock.vala:22 +msgid "_Add" +msgstr "_Añadir" + +#: src/client/components/stock.vala:23 msgid "_Close" msgstr "_Cerrar" -#: ../src/client/components/stock.vala:24 +#: src/client/components/stock.vala:24 msgid "_Discard" msgstr "_Descartar" -#: ../src/client/components/stock.vala:25 ../ui/gtk/menus.ui.h:5 +#: src/client/components/stock.vala:25 ui/main-toolbar-menus.ui:56 msgid "_Help" msgstr "Ay_uda" -#: ../src/client/components/stock.vala:26 -#: ../ui/conversation-email-menus.ui.h:9 +#: src/client/components/stock.vala:26 ui/conversation-email-menus.ui:77 msgid "_Open" msgstr "_Abrir" -#: ../src/client/components/stock.vala:27 ../ui/gtk/menus.ui.h:3 +#: src/client/components/stock.vala:27 ui/main-toolbar-menus.ui:46 msgid "_Preferences" msgstr "_Preferencias" -#: ../src/client/components/stock.vala:28 -#: ../ui/conversation-email-menus.ui.h:7 +#. Translators: Menu item to print a single, specific message +#: src/client/components/stock.vala:28 ui/conversation-email-menus.ui:64 msgid "_Print…" msgstr "_Imprimir…" -#: ../src/client/components/stock.vala:29 ../ui/gtk/menus.ui.h:7 +#: src/client/components/stock.vala:29 msgid "_Quit" msgstr "_Salir" -#: ../src/client/components/stock.vala:30 ../ui/remove_confirm.glade.h:6 +#: src/client/components/stock.vala:30 msgid "_Remove" msgstr "_Eliminar" -#: ../src/client/components/stock.vala:32 +#: src/client/components/stock.vala:31 ui/conversation-email-menus.ui:83 +msgid "_Save" +msgstr "_Guardar" + +#: src/client/components/stock.vala:32 msgid "_Keep" msgstr "_Mantener" -#: ../src/client/composer/composer-link-popover.vala:150 +#: src/client/composer/composer-link-popover.vala:149 msgid "Link URL is not correctly formatted, e.g. http://example.com" msgstr "El URL del enlace no tiene el formato correcto, ej. http://ejemplo.com" -#: ../src/client/composer/composer-link-popover.vala:157 +#: src/client/composer/composer-link-popover.vala:156 msgid "Invalid link URL" msgstr "Enlace a URL no válido" -#: ../src/client/composer/composer-link-popover.vala:157 +#: src/client/composer/composer-link-popover.vala:156 msgid "Invalid email address" msgstr "Dirección correo-e no válida" -#: ../src/client/composer/composer-widget.vala:154 +#: src/client/composer/composer-widget.vala:150 msgid "Saved" msgstr "Guardado" -#: ../src/client/composer/composer-widget.vala:155 +#: src/client/composer/composer-widget.vala:151 msgid "Saving" msgstr "Guardando" -#: ../src/client/composer/composer-widget.vala:156 +#: src/client/composer/composer-widget.vala:152 msgid "Error saving" msgstr "Error al guardar" -#: ../src/client/composer/composer-widget.vala:157 +#: src/client/composer/composer-widget.vala:153 msgid "Press Backspace to delete quote" msgstr "Pulse Retroceso para eliminar la cita" -#: ../src/client/composer/composer-widget.vala:158 -msgid "New Message" -msgstr "Mensaje nuevo" - #. Translators: This is list of keywords, separated by pipe ("|") #. characters, that suggest an attachment; since this is full-word #. checking, include all variants of each word. No spaces are #. allowed. -#: ../src/client/composer/composer-widget.vala:167 +#: src/client/composer/composer-widget.vala:162 msgid "" "attach|attaching|attaches|attachment|attachments|attached|enclose|enclosed|" "enclosing|encloses|enclosure|enclosures" @@ -921,28 +1446,37 @@ "adjuntar|adjuntando|adjunta|anexo|anexar|archivo|archivos|fichero|ficheros|" "adjunto|adjuntos|" -#: ../src/client/composer/composer-widget.vala:1129 -#: ../src/client/composer/composer-widget.vala:1148 -msgid "Do you want to discard this message?" -msgstr "¿Quiere descartar este mensaje?" +#. Translators: This dialog text is displayed to the +#. user when closing a composer where the options are +#. Keep, Discard or Cancel. +#: src/client/composer/composer-widget.vala:1121 +msgid "Do you want to keep or discard this draft message?" +msgstr "¿Quiere mantener o descartar este mensaje en borrador?" + +#. Translators: This dialog text is displayed to the +#. user when closing a composer where the options are +#. only Discard or Cancel. +#: src/client/composer/composer-widget.vala:1149 +msgid "Do you want to discard this draft message?" +msgstr "¿Quiere descartar este mensaje en borrador?" -#: ../src/client/composer/composer-widget.vala:1252 +#: src/client/composer/composer-widget.vala:1266 msgid "Send message with an empty subject and body?" msgstr "¿Quiere enviar el mensaje sin asunto ni cuerpo?" -#: ../src/client/composer/composer-widget.vala:1254 +#: src/client/composer/composer-widget.vala:1268 msgid "Send message with an empty subject?" msgstr "¿Quiere enviar el mensaje sin asunto?" -#: ../src/client/composer/composer-widget.vala:1256 +#: src/client/composer/composer-widget.vala:1270 msgid "Send message with an empty body?" msgstr "¿Quiere enviar el mensaje sin cuerpo?" -#: ../src/client/composer/composer-widget.vala:1260 +#: src/client/composer/composer-widget.vala:1274 msgid "Send message without an attachment?" msgstr "¿Quiere enviar el mensaje sin el archivo adjunto?" -#: ../src/client/composer/composer-widget.vala:1522 +#: src/client/composer/composer-widget.vala:1579 #, c-format msgid "“%s” already attached for delivery." msgstr "Ya se ha adjuntado «%s» para enviarlo." @@ -952,172 +1486,264 @@ #. description of the document type, the second will #. be a human-friendly size string. For example: #. Document (100.9MB) -#: ../src/client/composer/composer-widget.vala:1530 -#: ../src/client/conversation-viewer/conversation-email.vala:138 +#: src/client/composer/composer-widget.vala:1587 +#: src/client/conversation-viewer/conversation-email.vala:173 #, c-format msgid "%s (%s)" msgstr "%s (%s)" -#: ../src/client/composer/composer-widget.vala:1567 +#: src/client/composer/composer-widget.vala:1624 #, c-format msgid "“%s” could not be found." msgstr "No se pudo encontrar «%s»." -#: ../src/client/composer/composer-widget.vala:1573 +#: src/client/composer/composer-widget.vala:1630 #, c-format msgid "“%s” is a folder." msgstr "«%s» es una carpeta." -#: ../src/client/composer/composer-widget.vala:1579 +#: src/client/composer/composer-widget.vala:1636 #, c-format msgid "“%s” is an empty file." msgstr "«%s» es un archivo vacío." -#: ../src/client/composer/composer-widget.vala:1592 +#: src/client/composer/composer-widget.vala:1649 #, c-format msgid "“%s” could not be opened for reading." msgstr "No se pudo abrir «%s» para lectura." -#: ../src/client/composer/composer-widget.vala:1600 +#: src/client/composer/composer-widget.vala:1657 msgid "Cannot add attachment" msgstr "No se puede adjuntar el archivo" -#: ../src/client/composer/composer-widget.vala:1652 -msgid "To: " -msgstr "Para: " - -#: ../src/client/composer/composer-widget.vala:1655 -msgid "Cc: " -msgstr "Cc: " - -#: ../src/client/composer/composer-widget.vala:1658 -msgid "Bcc: " -msgstr "Cco: " +#. Translators: Human-readable version of the RFC 822 To header +#: src/client/composer/composer-widget.vala:1707 +#: src/client/conversation-viewer/conversation-email.vala:975 +#: src/engine/rfc822/rfc822-utils.vala:298 ui/conversation-message.ui:313 +msgid "To:" +msgstr "Para:" + +#. Translators: Human-readable version of the RFC 822 CC header +#: src/client/composer/composer-widget.vala:1713 +#: src/client/conversation-viewer/conversation-email.vala:980 +#: src/engine/rfc822/rfc822-utils.vala:303 ui/conversation-message.ui:358 +msgid "Cc:" +msgstr "Cc:" + +#. Translators: Human-readable version of the RFC 822 BCC header +#: src/client/composer/composer-widget.vala:1719 +#: src/client/conversation-viewer/conversation-email.vala:985 +#: ui/conversation-message.ui:403 +msgid "Bcc:" +msgstr "Cco:" -#: ../src/client/composer/composer-widget.vala:1661 +#. Translators: Human-readable version of the RFC 822 Reply-To header +#: src/client/composer/composer-widget.vala:1725 msgid "Reply-To: " msgstr "Responder a:" -#: ../src/client/composer/composer-widget.vala:1793 +#: src/client/composer/composer-widget.vala:1865 msgid "Select Color" msgstr "Seleccionar color" -#. Displayed in the From dropdown to indicate an "alternate email address" -#. for an account. The first printf argument will be the alternate email -#. address, and the second will be the account's primary email address. -#: ../src/client/composer/composer-widget.vala:1993 +#. Displayed in the From dropdown to indicate an +#. "alternate email address" for an account. The first +#. printf argument will be the alternate email address, +#. and the second will be the account's primary email +#. address. +#: src/client/composer/composer-widget.vala:2055 #, c-format msgid "%1$s via %2$s" msgstr "%1$s mediante %2$s" #. Composer label (with mnemonic underscore) for the account selector #. when choosing what address to send a message from. -#: ../src/client/composer/composer-widget.vala:2035 +#: src/client/composer/composer-widget.vala:2116 msgid "_From:" msgstr "_De:" #. Translators: This is the name of the file chooser filter #. when inserting an image in the composer. -#: ../src/client/composer/composer-widget.vala:2259 +#: src/client/composer/composer-widget.vala:2344 msgid "Images" msgstr "Imágenes" -#: ../src/client/composer/spell-check-popover.vala:117 +#: src/client/composer/composer-window.vala:14 +msgid "New Message" +msgstr "Mensaje nuevo" + +#: src/client/composer/spell-check-popover.vala:117 msgid "Remove this language from the preferred list" msgstr "Quitar este idioma de la lista de favoritos" -#: ../src/client/composer/spell-check-popover.vala:121 +#: src/client/composer/spell-check-popover.vala:121 msgid "Add this language to the preferred list" msgstr "Añadir este idioma de la lista de favoritos" -#: ../src/client/composer/spell-check-popover.vala:217 +#: src/client/composer/spell-check-popover.vala:217 msgid "Search for more languages" msgstr "Buscar más idiomas" -#: ../src/client/conversation-list/formatted-conversation-data.vala:11 +#: src/client/conversation-list/conversation-list-view.vala:283 +msgid "Delete conversation" +msgstr "Eliminar conversación" + +#: src/client/conversation-list/conversation-list-view.vala:286 +#: ui/main-toolbar-menus.ui:5 +msgid "Mark as _Read" +msgstr "Marcar como _leído" + +#: src/client/conversation-list/conversation-list-view.vala:289 +#: ui/main-toolbar-menus.ui:9 +msgid "Mark as _Unread" +msgstr "Marcar como _no leído" + +#: src/client/conversation-list/conversation-list-view.vala:292 +#: ui/main-toolbar-menus.ui:17 +msgid "U_nstar" +msgstr "_No destacar" + +#: src/client/conversation-list/conversation-list-view.vala:294 +#: ui/main-toolbar-menus.ui:13 +msgid "_Star" +msgstr "_Destacar" + +#. Translators: Menu item to reply to a specific message. +#: src/client/conversation-list/conversation-list-view.vala:297 +#: ui/conversation-email-menus.ui:9 +msgid "_Reply" +msgstr "_Responder" + +#: src/client/conversation-list/conversation-list-view.vala:298 +msgid "R_eply All" +msgstr "R_esponder a todos" + +#. Translators: Menu item to forward a specific message. +#: src/client/conversation-list/conversation-list-view.vala:299 +#: ui/conversation-email-menus.ui:21 +msgid "_Forward" +msgstr "_Reenviar" + +#: src/client/conversation-list/formatted-conversation-data.vala:11 msgid "Me" msgstr "Yo" #. Translators: This is the file type displayed for #. attachments with unknown file types. -#: ../src/client/conversation-viewer/conversation-email.vala:124 +#: src/client/conversation-viewer/conversation-email.vala:159 msgid "Unknown" msgstr "Desconocido" -#. Preview headers +#. Translators: Human-readable version of the RFC 822 From header +#: src/client/conversation-viewer/conversation-email.vala:970 +#: src/engine/rfc822/rfc822-utils.vala:289 +msgid "From:" +msgstr "De:" + +#. Translators: Human-readable version of the RFC 822 Date header +#: src/client/conversation-viewer/conversation-email.vala:990 +#: src/engine/rfc822/rfc822-utils.vala:294 +msgid "Date:" +msgstr "Fecha:" + +#. Translators: Human-readable version of the RFC 822 Subject header +#: src/client/conversation-viewer/conversation-email.vala:995 +#: src/engine/rfc822/rfc822-utils.vala:292 +msgid "Subject:" +msgstr "Asunto:" + +#: src/client/conversation-viewer/conversation-message.vala:65 +msgid "This email address may have been forged" +msgstr "Es posible que esta dirección de correo se haya falsificado" + +#. Compact headers #. Translators: This is displayed in place of the from address #. when the message has no from address. -#: ../src/client/conversation-viewer/conversation-message.vala:331 +#: src/client/conversation-viewer/conversation-message.vala:394 msgid "No sender" msgstr "Sin remitente" #. Translators: This separates multiple 'from' -#. addresses in the header preview for a message. -#: ../src/client/conversation-viewer/conversation-message.vala:585 +#. addresses in the compact header for a message. +#: src/client/conversation-viewer/conversation-message.vala:767 msgid ", " msgstr ", " #. Translators: This string is used as the HTML IMG ALT #. attribute value when displaying an inline image in an email #. that did not specify a file name. E.g. ImageCannot remove account " -msgstr "" -"No se puede eliminar la cuenta " +#: ui/accounts_editor_list_pane.ui:76 +msgid "Welcome to Geary" +msgstr "Bienvenido a Geary" + +#. This title is shown to users when confirming if they want to remove an account. The string substitution is replaced with the account's name. +#: ui/accounts_editor_remove_pane.ui:73 +#, c-format +msgid "Confirm removing: %s" +msgstr "Confirmar eliminar: %s" -#: ../ui/account_cannot_remove.glade.h:2 +#: ui/accounts_editor_remove_pane.ui:91 msgid "" -"A composer window associated with this account is currently open. Send or " -"discard the message and try again." +"Removing an account will remove it from Geary and delete locally cached " +"email data from your computer, but not from your service provider." msgstr "" -"Hay una ventana de redacción abierta asociada a esta cuenta. Envíe o " -"descarte el mensaje e inténtelo de nuevo." - -#: ../ui/account_list.glade.h:1 -msgid "Add account" -msgstr "Añadir cuenta" +"Quitar una cuenta la eliminará de Geary y también eliminará los datos en " +"caché de su equipo, pero no de su proveedor de servicios." -#: ../ui/account_list.glade.h:2 -msgid "Edit account" -msgstr "Editar cuenta" - -#: ../ui/account_list.glade.h:3 +#: ui/accounts_editor_remove_pane.ui:122 msgid "Remove account" msgstr "Eliminar cuenta" -#: ../ui/account_spinner.glade.h:1 -msgid "Please wait while Geary validates your account." -msgstr "Espere mientras Geary valida su cuenta" +#: ui/accounts_editor_servers_pane.ui:17 +msgid "Cancel" +msgstr "Cancelar" + +#: ui/accounts_editor_servers_pane.ui:47 +msgid "Apply" +msgstr "Aplicar" -#: ../ui/certificate_warning_dialog.glade.h:1 +#: ui/certificate_warning_dialog.glade:7 msgid "Untrusted Connection" msgstr "Conexión no confiable" -#: ../ui/certificate_warning_dialog.glade.h:2 +#: ui/certificate_warning_dialog.glade:29 msgid "_Always Trust This Server" msgstr "Confiar _siempre en este servidor" -#: ../ui/certificate_warning_dialog.glade.h:3 +#: ui/certificate_warning_dialog.glade:43 msgid "_Trust This Server" msgstr "_Confiar en este servidor" -#: ../ui/certificate_warning_dialog.glade.h:4 +#: ui/certificate_warning_dialog.glade:57 msgid "_Don’t Trust This Server" msgstr "_No confiar en este servidor" -#: ../ui/composer-headerbar.ui.h:1 +#: ui/composer-headerbar.ui:17 ui/composer-headerbar.ui:174 msgid "Detach (Ctrl+D)" msgstr "Desacoplar (Ctrl+D)" -#: ../ui/composer-headerbar.ui.h:2 +#: ui/composer-headerbar.ui:57 ui/composer-headerbar.ui:83 msgid "Attach File (Ctrl+T)" msgstr "Adjuntar archivo (Ctrl+T)" -#: ../ui/composer-headerbar.ui.h:3 +#: ui/composer-headerbar.ui:107 msgid "Include Original Attachments" msgstr "Incluir adjuntos originales" -#: ../ui/composer-headerbar.ui.h:4 -msgid "Send (Ctrl+Enter)" -msgstr "Enviar (Ctrl+Intro)" - -#: ../ui/composer-headerbar.ui.h:5 +#: ui/composer-headerbar.ui:202 msgid "_Send" msgstr "_Enviar" -#: ../ui/composer-headerbar.ui.h:6 +#: ui/composer-headerbar.ui:207 +msgid "Send (Ctrl+Enter)" +msgstr "Enviar (Ctrl+Intro)" + +#: ui/composer-headerbar.ui:230 msgid "Discard and Close" msgstr "Descartar y cerrar" -#: ../ui/composer-headerbar.ui.h:7 +#: ui/composer-headerbar.ui:254 msgid "Save and Close" msgstr "Guardar y cerrar" #. Note that this button and the Update button will never be shown at the same time to the user. -#: ../ui/composer-link-popover.ui.h:2 +#: ui/composer-link-popover.ui:41 msgid "Insert the new link with this URL" msgstr "Insertar el enlace nuevo con este URL" -#: ../ui/composer-link-popover.ui.h:3 +#: ui/composer-link-popover.ui:52 msgid "Link URL" msgstr "Enlazar URL" #. Note that this button and the Insert button will never be shown at the same time to the user. -#: ../ui/composer-link-popover.ui.h:5 +#: ui/composer-link-popover.ui:66 msgid "Update this link’s URL" msgstr "Actualizar el URL de este enlace" -#: ../ui/composer-link-popover.ui.h:6 +#: ui/composer-link-popover.ui:86 msgid "Delete this link" msgstr "Eliminar este enlace" -#: ../ui/composer-link-popover.ui.h:7 +#: ui/composer-link-popover.ui:106 msgid "Open this link" msgstr "Abrir este enlace" -#: ../ui/composer-menus.ui.h:1 +#: ui/composer-menus.ui:7 msgid "S_ans Serif" msgstr "S_ans serif" -#: ../ui/composer-menus.ui.h:2 +#: ui/composer-menus.ui:12 msgid "S_erif" msgstr "S_erif" -#: ../ui/composer-menus.ui.h:3 +#: ui/composer-menus.ui:17 msgid "_Fixed Width" msgstr "_Anchura fija" -#: ../ui/composer-menus.ui.h:4 +#: ui/composer-menus.ui:24 msgid "_Small" msgstr "_Pequeño" -#: ../ui/composer-menus.ui.h:5 +#: ui/composer-menus.ui:29 msgid "_Medium" msgstr "_Mediano" -#: ../ui/composer-menus.ui.h:6 +#: ui/composer-menus.ui:34 msgid "Lar_ge" msgstr "_Grande" -#: ../ui/composer-menus.ui.h:7 +#: ui/composer-menus.ui:41 msgid "C_olor" msgstr "C_olor" -#: ../ui/composer-menus.ui.h:8 +#: ui/composer-menus.ui:47 ui/composer-menus.ui:62 msgid "_Rich Text" msgstr "Texto en_riquecido" -#: ../ui/composer-menus.ui.h:9 +#: ui/composer-menus.ui:53 ui/composer-menus.ui:68 msgid "Show Extended Fields" msgstr "Mostrar campos ampliados" -#: ../ui/composer-menus.ui.h:10 +#: ui/composer-menus.ui:78 msgid "_Undo" msgstr "_Deshacer" -#: ../ui/composer-menus.ui.h:11 +#: ui/composer-menus.ui:82 msgid "_Redo" msgstr "_Rehacer" -#: ../ui/composer-menus.ui.h:12 +#: ui/composer-menus.ui:88 ui/composer-menus.ui:106 msgid "Cu_t" msgstr "Cor_tar" -#: ../ui/composer-menus.ui.h:13 ../ui/conversation-message-menus.ui.h:7 +#: ui/composer-menus.ui:92 ui/composer-menus.ui:110 +#: ui/conversation-message-menus.ui:37 msgid "_Copy" msgstr "_Copiar" -#: ../ui/composer-menus.ui.h:14 +#: ui/composer-menus.ui:96 ui/composer-menus.ui:114 msgid "_Paste" msgstr "_Pegar" -#: ../ui/composer-menus.ui.h:15 -msgctxt "Clipboard paste with rich text" -msgid "Paste _With Formatting" -msgstr "Pegar con _formato" +#: ui/composer-menus.ui:100 +msgctxt "Clipboard paste as plain text" +msgid "Paste _Without Formatting" +msgstr "Pegar _sin formato" -#: ../ui/composer-menus.ui.h:16 +#: ui/composer-menus.ui:120 msgid "Select _All" msgstr "Seleccionar _todo" -#: ../ui/composer-menus.ui.h:17 ../ui/conversation-message-menus.ui.h:9 +#: ui/composer-menus.ui:127 ui/conversation-message-menus.ui:49 msgid "_Inspect…" msgstr "_Inspeccionar…" #. Address(es) e-mail is to be sent to -#: ../ui/composer-widget.ui.h:2 +#: ui/composer-widget.ui:56 msgid "_To" msgstr "_Para" -#: ../ui/composer-widget.ui.h:3 +#: ui/composer-widget.ui:75 msgid "_Cc" msgstr "_Cc" -#: ../ui/composer-widget.ui.h:4 +#: ui/composer-widget.ui:130 msgid "_Subject" msgstr "A_sunto" -#: ../ui/composer-widget.ui.h:5 +#: ui/composer-widget.ui:149 msgid "_Bcc" msgstr "Cc_o" -#: ../ui/composer-widget.ui.h:6 +#: ui/composer-widget.ui:179 msgid "_Reply-To" msgstr "_Responder a:" #. Geary account mail will be sent from -#: ../ui/composer-widget.ui.h:8 +#: ui/composer-widget.ui:208 msgid "From" msgstr "De" -#: ../ui/composer-widget.ui.h:9 +#: ui/composer-widget.ui:293 msgid "Drop files here" msgstr "Suelte los archivos aquí" -#: ../ui/composer-widget.ui.h:10 +#: ui/composer-widget.ui:309 msgid "To add them as attachments" msgstr "Para añadirlos como adjuntos" -#: ../ui/composer-widget.ui.h:11 +#: ui/composer-widget.ui:353 msgid "Undo last edit (Ctrl+Z)" msgstr "Deshacer la última edición (Ctrl+Z)" -#: ../ui/composer-widget.ui.h:12 +#: ui/composer-widget.ui:377 msgid "Redo last edit (Ctrl+Shift+Z)" msgstr "Rehacer la última edición (Ctrl+Mayús+Z)" -#: ../ui/composer-widget.ui.h:13 +#: ui/composer-widget.ui:415 msgid "Bold (Ctrl+B)" msgstr "Negrita (Ctrl+B)" -#: ../ui/composer-widget.ui.h:14 +#: ui/composer-widget.ui:439 msgid "Italic (Ctrl+I)" msgstr "Cursiva (Ctrl+I)" -#: ../ui/composer-widget.ui.h:15 +#: ui/composer-widget.ui:463 msgid "Underline (Ctrl+U)" msgstr "Subrayado (Ctrl+U)" -#: ../ui/composer-widget.ui.h:16 +#: ui/composer-widget.ui:487 msgid "Strikethrough (Ctrl+K)" msgstr "Tachado (Ctrl+K)" -#: ../ui/composer-widget.ui.h:17 +#: ui/composer-widget.ui:525 +msgid "Insert unordered list" +msgstr "Insertar lista no ordenada" + +#: ui/composer-widget.ui:549 +msgid "Insert ordered list" +msgstr "Insertar lista ordenada" + +#: ui/composer-widget.ui:587 msgid "Quote text (Ctrl+])" msgstr "Citar el texto (Ctrl+])" -#: ../ui/composer-widget.ui.h:18 +#: ui/composer-widget.ui:611 msgid "Unquote text (Ctrl+[)" msgstr "Eliminar cita del texto (Ctrl+[)" -#: ../ui/composer-widget.ui.h:19 +#: ui/composer-widget.ui:649 msgid "Insert or update selection link (Ctrl+L)" msgstr "Insertar o actualizar el enlace seleccionado (Ctrl+L)" -#: ../ui/composer-widget.ui.h:20 +#: ui/composer-widget.ui:673 msgid "Insert an image (Ctrl+G)" msgstr "Insertar una imagen (Ctrl+G)" -#: ../ui/composer-widget.ui.h:21 +#: ui/composer-widget.ui:707 msgid "Remove selection formatting (Ctrl+Space)" msgstr "Quitar formato de la selección (Ctrl+Espacio)" -#: ../ui/composer-widget.ui.h:22 +#: ui/composer-widget.ui:731 msgid "Select spell checking languages" msgstr "Seleccione el idioma de la revisión ortográfica" -#: ../ui/conversation-email.ui.h:1 +#: ui/conversation-email.ui:27 msgid "Save all attachments" msgstr "Guardar todos los adjuntos" #. Note: The application will never show this button at the same time as unstar_button, one will always be hidden. -#: ../ui/conversation-email.ui.h:3 +#: ui/conversation-email.ui:50 msgid "Mark this message as starred" msgstr "Marcar este mensaje como favorito" #. Note: The application will never show this button at the same time as star_button, one will always be hidden. -#: ../ui/conversation-email.ui.h:5 +#: ui/conversation-email.ui:72 msgid "Mark this message as not starred" msgstr "Marcar este mensaje como no favorito" -#: ../ui/conversation-email.ui.h:6 +#: ui/conversation-email.ui:95 msgid "Display the message menu" msgstr "Mostrar el menú de mensajes" -#: ../ui/conversation-email.ui.h:7 +#: ui/conversation-email.ui:161 msgid "Open selected attachments" msgstr "Abrir los adjuntos seleccionados" -#: ../ui/conversation-email.ui.h:8 +#: ui/conversation-email.ui:178 msgid "Save selected attachments" msgstr "Guardar los adjuntos seleccionados" -#: ../ui/conversation-email.ui.h:9 +#: ui/conversation-email.ui:195 msgid "Select all attachments" msgstr "Guardar todos los adjuntos" -#: ../ui/conversation-email.ui.h:10 +#: ui/conversation-email.ui:240 msgid "Edit Draft" msgstr "Editar borrador" -#: ../ui/conversation-email.ui.h:11 +#: ui/conversation-email.ui:267 msgid "Draft message" msgstr "Borrador" -#: ../ui/conversation-email.ui.h:12 +#: ui/conversation-email.ui:283 msgid "This message has not yet been sent." msgstr "Este mensaje no se ha enviado todavía." -#: ../ui/conversation-email.ui.h:13 +#: ui/conversation-email.ui:329 msgid "Message not saved" msgstr "Mensaje no guardado" -#: ../ui/conversation-email.ui.h:14 +#: ui/conversation-email.ui:345 msgid "This message was sent, but has not been saved to your account." msgstr "" "Este mensaje se envió correctamente pero no se ha guardado en su cuenta." -#: ../ui/conversation-email-menus.ui.h:2 +#. Translators: Menu item to reply to a specific message. +#: ui/conversation-email-menus.ui:15 msgid "Reply to _All" msgstr "Responder a _todos" -#: ../ui/conversation-email-menus.ui.h:4 +#. Translators: Menu item to mark a specific message as +#. read. +#: ui/conversation-email-menus.ui:30 msgid "_Mark Read" msgstr "_Marcar como leído" -#: ../ui/conversation-email-menus.ui.h:5 +#: ui/conversation-email-menus.ui:36 msgid "_Mark Unread" msgstr "_Marcar como no leído" -#: ../ui/conversation-email-menus.ui.h:6 +#. Translators: Menu item to mark all messages in a +#. conversation from this one as unread. +#: ui/conversation-email-menus.ui:42 msgid "Mark Unread From _Here" msgstr "Marcar como no leído desde _aquí" -#: ../ui/conversation-email-menus.ui.h:8 +#. Translators: Menu item to move a single, specific message +#. to the trash +#: ui/conversation-email-menus.ui:50 +msgid "_Trash" +msgstr "_Papelera" + +#. Translators: Menu item to delete a single, specific message +#: ui/conversation-email-menus.ui:57 +msgid "_Delete…" +msgstr "E_liminar…" + +#. Translators: Menu item to view the source for a message +#: ui/conversation-email-menus.ui:69 msgid "_View Source" msgstr "_Ver código fuente" -#: ../ui/conversation-email-menus.ui.h:11 +#: ui/conversation-email-menus.ui:87 msgid "_Save All" msgstr "Guardar _todo" -#: ../ui/conversation-message-menus.ui.h:1 +#: ui/conversation-message-menus.ui:7 msgid "_Open Link" msgstr "A_brir enlace" -#: ../ui/conversation-message-menus.ui.h:2 +#: ui/conversation-message-menus.ui:11 msgid "Copy Link _Address" msgstr "Copiar enlace a la _dirección" -#: ../ui/conversation-message-menus.ui.h:3 +#: ui/conversation-message-menus.ui:17 msgid "Send New _Message…" msgstr "Enviar _mensaje nuevo…" -#: ../ui/conversation-message-menus.ui.h:4 +#: ui/conversation-message-menus.ui:21 msgid "Copy Email _Address" msgstr "Copiar dirección de _correo" -#: ../ui/conversation-message-menus.ui.h:5 +#: ui/conversation-message-menus.ui:27 msgid "Save _Image As…" msgstr "_Guardar imagen como…" -#: ../ui/conversation-message-menus.ui.h:6 +#: ui/conversation-message-menus.ui:33 msgid "_Select All" msgstr "Seleccionar _todo" -#: ../ui/conversation-message-menus.ui.h:8 +#: ui/conversation-message-menus.ui:43 msgid "Search for messages from" msgstr "Buscar mensajes de" -#: ../ui/conversation-message.ui.h:1 +#: ui/conversation-message.ui:64 msgid "From " msgstr "De " -#: ../ui/conversation-message.ui.h:2 +#: ui/conversation-message.ui:80 ui/conversation-message.ui:179 msgid "1/1/1970\t" msgstr "1/1/1970\t" -#: ../ui/conversation-message.ui.h:3 +#: ui/conversation-message.ui:103 msgid "Preview body text." msgstr "Vista previa del texto del cuerpo" -#: ../ui/conversation-message.ui.h:4 +#: ui/conversation-message.ui:203 msgid "Sent by:" msgstr "Enviado por:" -#: ../ui/conversation-message.ui.h:5 +#: ui/conversation-message.ui:248 msgid "Reply to:" msgstr "Responder a:" -#: ../ui/conversation-message.ui.h:6 +#: ui/conversation-message.ui:292 msgid "Subject" msgstr "Asunto" -#: ../ui/conversation-message.ui.h:7 -msgid "To:" -msgstr "Para:" - -#: ../ui/conversation-message.ui.h:8 -msgid "Cc:" -msgstr "Cc:" - -#: ../ui/conversation-message.ui.h:9 -msgid "Bcc:" -msgstr "Cco:" - -#: ../ui/conversation-message.ui.h:10 +#: ui/conversation-message.ui:502 msgid "Show Images" msgstr "Mostrar imágenes" -#: ../ui/conversation-message.ui.h:11 +#: ui/conversation-message.ui:515 msgid "Always Show From Sender" msgstr "Mostrar siempre el remitente" -#: ../ui/conversation-message.ui.h:12 +#: ui/conversation-message.ui:543 msgid "Remote images not shown" msgstr "Imágenes remotas no mostradas" -#: ../ui/conversation-message.ui.h:13 +#: ui/conversation-message.ui:560 msgid "Only show remote images from senders you trust." msgstr "Mostrar sólo las imágenes remotas de los remitentes en los que confíe" -#: ../ui/conversation-message.ui.h:14 +#: ui/conversation-message.ui:693 msgid "But actually goes to:" msgstr "Pero en realidad apunta a:" -#: ../ui/conversation-message.ui.h:15 +#: ui/conversation-message.ui:724 msgid "The link appears to go to:" msgstr "Parece que este enlace apunta a:" -#: ../ui/conversation-message.ui.h:16 +#: ui/conversation-message.ui:736 msgid "Deceptive link found" msgstr "Enlace engañoso encontrado" -#: ../ui/conversation-message.ui.h:17 +#: ui/conversation-message.ui:751 msgid "The email sender may be leading you to the wrong web site." msgstr "" "El remitente del correo puede estar llevándole a un sitio web incorrecto." -#: ../ui/conversation-message.ui.h:18 +#: ui/conversation-message.ui:764 msgid "If unsure, contact the sender and ask before continuing." msgstr "Si no está seguro, pregunte con el remitente antes de continuar." -#: ../ui/conversation-viewer.ui.h:1 +#: ui/conversation-viewer.ui:60 msgid "Find in conversation" msgstr "Buscar en la conversación" -#: ../ui/conversation-viewer.ui.h:2 +#: ui/conversation-viewer.ui:74 msgid "Find the previous occurrence of the search string." msgstr "Busca la aparición anterior de la cadena de búsqueda." -#: ../ui/conversation-viewer.ui.h:3 +#: ui/conversation-viewer.ui:95 msgid "Find the next occurrence of the search string." msgstr "Busca la siguiente aparición de la cadena de búsqueda." -#: ../ui/edit_alternate_emails.glade.h:1 -msgid "Remove email address" -msgstr "Eliminar dirección de correo-e" - -#: ../ui/edit_alternate_emails.glade.h:2 -msgid "" -"Some email services require additional addresses be configured on the " -"server. Contact your email provider for more information." -msgstr "" -"Algunos servicios de correo necesitan que se configuren direcciones de " -"correo adicionales en el servidor. Consulte con su proveedor de correo para " -"obtener más información." - -#: ../ui/edit_alternate_emails.glade.h:4 -msgid "_Update" -msgstr "_Actualizar" - -#: ../ui/find_bar.glade.h:1 +#: ui/find_bar.glade:66 msgid "Find:" msgstr "Buscar:" -#: ../ui/find_bar.glade.h:2 +#: ui/find_bar.glade:89 msgid "_Previous" msgstr "_Anterior" -#: ../ui/find_bar.glade.h:3 +#: ui/find_bar.glade:107 msgid "_Next" msgstr "_Siguiente" -#: ../ui/find_bar.glade.h:4 +#: ui/find_bar.glade:125 msgid "_Case sensitive" msgstr "_Distinguir mayúsculas y minúsculas" -#: ../ui/find_bar.glade.h:5 +#: ui/find_bar.glade:145 msgid "label" msgstr "etiqueta" -#: ../ui/gtk/help-overlay.ui.h:1 +#: ui/gtk/help-overlay.ui:9 msgid "Conversation Shortcuts" msgstr "Atajos de conversaciones" -#: ../ui/gtk/help-overlay.ui.h:2 +#: ui/gtk/help-overlay.ui:13 ui/gtk/help-overlay.ui:254 msgctxt "shortcut window" msgid "General" msgstr "General" -#: ../ui/gtk/help-overlay.ui.h:3 +#: ui/gtk/help-overlay.ui:17 msgctxt "shortcut window" msgid "Move focus to the next/previous pane" msgstr "Mover el foco al panel siguiente/anterior" -#: ../ui/gtk/help-overlay.ui.h:4 +#: ui/gtk/help-overlay.ui:24 msgctxt "shortcut window" msgid "Move focus to conversation list" msgstr "Mover el foco a lista de conversaciones" -#: ../ui/gtk/help-overlay.ui.h:5 +#: ui/gtk/help-overlay.ui:31 msgctxt "shortcut window" msgid "Detach composer window" msgstr "Desacoplar la ventana del editor" -#: ../ui/gtk/help-overlay.ui.h:6 +#: ui/gtk/help-overlay.ui:38 msgctxt "shortcut window" msgid "Close composer window" msgstr "Cerrar la ventana del editor" -#: ../ui/gtk/help-overlay.ui.h:7 +#: ui/gtk/help-overlay.ui:45 msgctxt "shortcut window" msgid "Show keyboard shortcuts" msgstr "Mostrar los atajos de teclado" -#: ../ui/gtk/help-overlay.ui.h:8 +#: ui/gtk/help-overlay.ui:52 msgctxt "shortcut window" msgid "Show help" msgstr "Mostrar la ayuda" -#: ../ui/gtk/help-overlay.ui.h:9 +#: ui/gtk/help-overlay.ui:59 msgctxt "shortcut window" msgid "Quit the application" msgstr "Salir de la aplicación" -#: ../ui/gtk/help-overlay.ui.h:10 +#: ui/gtk/help-overlay.ui:68 msgctxt "shortcut window" msgid "Search" msgstr "Buscar" -#: ../ui/gtk/help-overlay.ui.h:11 +#: ui/gtk/help-overlay.ui:72 msgctxt "shortcut window" msgid "Jump to search box" msgstr "Enfocar el cuadro de búsqueda" -#: ../ui/gtk/help-overlay.ui.h:12 +#: ui/gtk/help-overlay.ui:79 msgctxt "shortcut window" msgid "Find in current conversation" msgstr "Buscar en la conversación actual" -#: ../ui/gtk/help-overlay.ui.h:13 +#: ui/gtk/help-overlay.ui:86 msgctxt "shortcut window" msgid "Find next/previous in current conversation" msgstr "Buscar siguiente/anterior en la conversación actual" -#: ../ui/gtk/help-overlay.ui.h:14 +#: ui/gtk/help-overlay.ui:95 ui/gtk/help-overlay.ui:274 msgctxt "shortcut window" msgid "Actions" msgstr "Acciones" -#: ../ui/gtk/help-overlay.ui.h:15 +#: ui/gtk/help-overlay.ui:99 msgctxt "shortcut window" msgid "Compose a new message" msgstr "Redactar un mensaje nuevo" -#: ../ui/gtk/help-overlay.ui.h:16 +#: ui/gtk/help-overlay.ui:106 msgctxt "shortcut window" msgid "Reply to sender " msgstr "Responder al remitente " -#: ../ui/gtk/help-overlay.ui.h:17 +#: ui/gtk/help-overlay.ui:113 msgctxt "shortcut window" msgid "Reply to all" msgstr "Responder a todos" -#: ../ui/gtk/help-overlay.ui.h:18 +#: ui/gtk/help-overlay.ui:120 msgctxt "shortcut window" msgid "Forward" msgstr "Reenviar" -#: ../ui/gtk/help-overlay.ui.h:19 +#: ui/gtk/help-overlay.ui:127 msgctxt "shortcut window" msgid "Archive" msgstr "Archivar" -#: ../ui/gtk/help-overlay.ui.h:20 +#: ui/gtk/help-overlay.ui:134 msgctxt "shortcut window" msgid "Move to trash" msgstr "Mover a la papelera" -#: ../ui/gtk/help-overlay.ui.h:21 +#: ui/gtk/help-overlay.ui:141 msgctxt "shortcut window" msgid "Toggle spam" msgstr "Marcar o desmarcar como «spam»" -#: ../ui/gtk/help-overlay.ui.h:22 +#: ui/gtk/help-overlay.ui:148 msgctxt "shortcut window" msgid "Move the conversation" msgstr "Mover la conversación" -#: ../ui/gtk/help-overlay.ui.h:23 +#: ui/gtk/help-overlay.ui:155 msgctxt "shortcut window" msgid "Label the conversation" msgstr "Etiquetar la conversación" -#: ../ui/gtk/help-overlay.ui.h:24 +#: ui/gtk/help-overlay.ui:163 msgctxt "shortcut window" msgid "Mark read" msgstr "Marcar como leído" -#: ../ui/gtk/help-overlay.ui.h:25 +#: ui/gtk/help-overlay.ui:170 msgctxt "shortcut window" msgid "Mark unread" msgstr "Marcar como no leído" -#: ../ui/gtk/help-overlay.ui.h:26 +#: ui/gtk/help-overlay.ui:179 msgctxt "shortcut window" msgid "View" msgstr "Ver" -#: ../ui/gtk/help-overlay.ui.h:27 +#: ui/gtk/help-overlay.ui:183 msgctxt "shortcut window" msgid "Zoom in" msgstr "Acercar" -#: ../ui/gtk/help-overlay.ui.h:28 +#: ui/gtk/help-overlay.ui:190 msgctxt "shortcut window" msgid "Zoom out" msgstr "Alejar" -#: ../ui/gtk/help-overlay.ui.h:29 +#: ui/gtk/help-overlay.ui:197 msgctxt "shortcut window" msgid "Reset zoom" msgstr "Restablecer ampliación" -#: ../ui/gtk/help-overlay.ui.h:30 +#: ui/gtk/help-overlay.ui:206 msgctxt "shortcut window" msgid "Additional Shortcuts" msgstr "Atajos adicionales" -#: ../ui/gtk/help-overlay.ui.h:31 +#: ui/gtk/help-overlay.ui:210 msgctxt "shortcut window" msgid "Star" msgstr "Destacar" -#: ../ui/gtk/help-overlay.ui.h:32 +#: ui/gtk/help-overlay.ui:217 msgctxt "shortcut window" msgid "Unstar" msgstr "Quitar el destaque" -#: ../ui/gtk/help-overlay.ui.h:33 +#: ui/gtk/help-overlay.ui:224 msgctxt "shortcut window" msgid "Delete" msgstr "Eliminar" -#: ../ui/gtk/help-overlay.ui.h:34 +#: ui/gtk/help-overlay.ui:231 msgctxt "shortcut window" msgid "Jump to next (older) conversation" msgstr "Ir a la siguiente conversación (más antigua)" -#: ../ui/gtk/help-overlay.ui.h:35 +#: ui/gtk/help-overlay.ui:238 msgctxt "shortcut window" msgid "Jump to previous (newer) conversation" msgstr "Ir a la conversación anterior (más reciente)" -#: ../ui/gtk/help-overlay.ui.h:36 +#: ui/gtk/help-overlay.ui:250 msgid "Composer Shortcuts" msgstr "Atajos del editor" -#: ../ui/gtk/help-overlay.ui.h:37 +#: ui/gtk/help-overlay.ui:258 msgctxt "shortcut window" msgid "Quote text" msgstr "Citar el texto" -#: ../ui/gtk/help-overlay.ui.h:38 +#: ui/gtk/help-overlay.ui:265 msgctxt "shortcut window" msgid "Unquote text" msgstr "Eliminar cita del texto" -#: ../ui/gtk/help-overlay.ui.h:39 +#: ui/gtk/help-overlay.ui:278 msgctxt "shortcut window" msgid "Send" msgstr "Enviar" -#: ../ui/gtk/help-overlay.ui.h:40 +#: ui/gtk/help-overlay.ui:285 msgctxt "shortcut window" msgid "Add attachment" msgstr "Añadir un adjunto" -#: ../ui/gtk/help-overlay.ui.h:41 +#: ui/gtk/help-overlay.ui:294 msgctxt "shortcut window" msgid "Rich text mode" msgstr "Texto enriquecido" -#: ../ui/gtk/help-overlay.ui.h:42 +#: ui/gtk/help-overlay.ui:298 msgctxt "shortcut window" msgid "Bold text" msgstr "Texto en negrita" -#: ../ui/gtk/help-overlay.ui.h:43 +#: ui/gtk/help-overlay.ui:305 msgctxt "shortcut window" msgid "Italicize text" msgstr "Texto en cursiva" -#: ../ui/gtk/help-overlay.ui.h:44 +#: ui/gtk/help-overlay.ui:312 msgctxt "shortcut window" msgid "Underline text" msgstr "Subrayar el texto" -#: ../ui/gtk/help-overlay.ui.h:45 +#: ui/gtk/help-overlay.ui:319 msgctxt "shortcut window" msgid "Strike text" msgstr "Tachar el texto" -#: ../ui/gtk/help-overlay.ui.h:46 +#: ui/gtk/help-overlay.ui:326 msgctxt "shortcut window" msgid "Insert a link" msgstr "Insertar un enlace" -#: ../ui/gtk/help-overlay.ui.h:47 +#: ui/gtk/help-overlay.ui:333 msgctxt "shortcut window" msgid "Remove formatting" msgstr "Eliminar el formato" -#: ../ui/gtk/menus.ui.h:2 -msgid "A_ccounts" -msgstr "_Cuentas" +#: ui/main-toolbar.ui:23 +#| msgid "Compose Message" +msgctxt "tooltip" +msgid "Compose Message" +msgstr "Redactar mensaje" -#: ../ui/gtk/menus.ui.h:4 -msgid "_Keyboard Shortcuts" -msgstr "Atajos del _teclado" +#: ui/main-toolbar.ui:52 +msgid "Toggle search bar" +msgstr "Conmutar la barra de búsqueda" -#: ../ui/login.glade.h:1 -msgid "email@example.com" -msgstr "correo@ejemplo.com" +#: ui/main-toolbar.ui:112 +msgid "Reply" +msgstr "Responder" -#: ../ui/login.glade.h:2 ../ui/password-dialog.glade.h:3 -msgid "Password" -msgstr "Contraseña" +#: ui/main-toolbar.ui:135 +msgid "Reply All" +msgstr "Responder a todos" -#: ../ui/login.glade.h:3 -msgid "E_mail address" -msgstr "_Dirección de correo electrónico" - -#: ../ui/login.glade.h:4 -msgid "_Password" -msgstr "_Contraseña" - -#: ../ui/login.glade.h:5 -msgid "S_ervice" -msgstr "S_ervicio" - -#: ../ui/login.glade.h:6 -msgid "N_ame" -msgstr "N_ombre" - -#: ../ui/login.glade.h:8 -msgid "N_ickname" -msgstr "_Apodo" - -#: ../ui/login.glade.h:9 -msgid "Work, Home, etc." -msgstr "Trabajo, casa, etc." - -#: ../ui/login.glade.h:10 -msgid "_Save sent mail" -msgstr "_Guardar mensajes enviados" - -#: ../ui/login.glade.h:11 -msgid "Addi_tional email addresses…" -msgstr "Direcciones de correo-e adicionales…" - -#: ../ui/login.glade.h:12 -msgid "IMAP settings" -msgstr "Configuración IMAP" - -#: ../ui/login.glade.h:13 -msgid "Se_rver" -msgstr "Se_rvidor" +#: ui/main-toolbar.ui:158 +msgid "Forward" +msgstr "Reenviar" -#: ../ui/login.glade.h:14 -msgid "imap.example.com" -msgstr "imap.ejemplo.com" +#: ui/main-toolbar.ui:264 +msgid "Toggle find bar" +msgstr "Conmutar la barra de búsqueda" + +#: ui/main-toolbar.ui:306 +msgid "_Archive" +msgstr "_Archivar" -#: ../ui/login.glade.h:15 -msgid "P_ort" -msgstr "P_uerto" +#: ui/main-toolbar-menus.ui:21 +msgid "Mark as S_pam" +msgstr "Marcar como _spam" -#: ../ui/login.glade.h:16 -msgid "smtp.example.com" -msgstr "smtp.ejemplo.com" +#: ui/main-toolbar-menus.ui:25 +msgid "Mark as not S_pam" +msgstr "Marcar como no _spam" + +#: ui/main-toolbar-menus.ui:32 +msgid "Empty _Spam…" +msgstr "Vaciar _Spam…" + +#: ui/main-toolbar-menus.ui:36 +msgid "Empty _Trash…" +msgstr "Vaciar la _papelera…" + +#: ui/main-toolbar-menus.ui:42 +#| msgid "Accounts" +msgid "_Accounts" +msgstr "_Cuentas" -#: ../ui/login.glade.h:17 -msgid "Ser_ver" -msgstr "Ser_vidor" - -#: ../ui/login.glade.h:18 -msgid "Por_t" -msgstr "Pue_rto" - -#: ../ui/login.glade.h:19 -msgid "SMTP settings" -msgstr "Configuración de SMTP" - -#: ../ui/login.glade.h:20 -msgid "User_name" -msgstr "_Nombre de usuario" - -#: ../ui/login.glade.h:21 -msgid "Pass_word" -msgstr "_Contraseña" - -#: ../ui/login.glade.h:22 -msgid "SMTP username" -msgstr "Nombre de usuario de SMTP" - -#: ../ui/login.glade.h:23 -msgid "SMTP password" -msgstr "Contraseña de SMTP" - -#: ../ui/login.glade.h:24 -msgid "_Username" -msgstr "Nombre de _usuario" - -#: ../ui/login.glade.h:25 -msgid "IMAP username" -msgstr "Nombre de usuario de IMAP" - -#: ../ui/login.glade.h:26 -msgid "IMAP password" -msgstr "Contraseña de IMAP" - -#: ../ui/login.glade.h:27 -msgid "Encr_yption" -msgstr "Ci_frado" - -#: ../ui/login.glade.h:28 -msgid "Encrypt_ion" -msgstr "Cifra_do" - -#: ../ui/login.glade.h:30 -msgid "SSL/TLS" -msgstr "SSL/TLS" - -#: ../ui/login.glade.h:31 -msgid "STARTTLS" -msgstr "STARTTLS" - -#: ../ui/login.glade.h:32 -msgid "No authentication re_quired" -msgstr "No se _requiere autenticación" - -#: ../ui/login.glade.h:33 -msgid "Use IMAP cre_dentials" -msgstr "Utilizar cre_denciales IMAP" - -#: ../ui/login.glade.h:34 -msgid "Composer" -msgstr "Redactor" - -#: ../ui/login.glade.h:35 -msgid "Save dra_fts on server" -msgstr "Guardar _borradores en el servidor" - -#: ../ui/login.glade.h:36 -msgid "Si_gn emails (HTML allowed):" -msgstr "_Firmar los mensajes (HTML permitido):" - -#: ../ui/login.glade.h:37 -msgid "Storage" -msgstr "Almacenamiento" - -#: ../ui/login.glade.h:38 -msgid "_Download mail" -msgstr "_Descargar correo" - -#: ../ui/main-toolbar.ui.h:1 -msgid "Empty Spam or Trash folders" -msgstr "Vaciar las carpetas Spam o Papelera" +#: ui/main-toolbar-menus.ui:50 +msgid "_Keyboard Shortcuts" +msgstr "Atajos del _teclado" + +#: ui/main-toolbar-menus.ui:61 +#| msgid "_About" +msgid "_About Geary" +msgstr "_Acerca de Geary" + +#. Infobar title when one or more accounts are offline +#: ui/main-window.ui:183 +msgid "Working offline" +msgstr "Trabajando en modo desconectado" + +#. Label and tooltip for offline infobar +#: ui/main-window.ui:197 +msgid "" +"Your computer does not appear to be connected to the Internet.\n" +"You will not be able to send or receive email until it is re-connected." +msgstr "" +"Su equipo no parece estar conectado a Internet.\n" +"No podrá enviar o recibir correos hasta que vuelva a conectarse." + +#. Label and tooltip for offline infobar +#: ui/main-window.ui:200 +msgid "You will not be able to send or receive email until re-connected." +msgstr "No podrá enviar ni recibir correo hasta que se vuelva a conectar." + +#. Button label for displaying technical details about an account problem +#. Dialog title for displaying technical details of a problem. Same as the button that invokes it. +#: ui/main-window.ui:247 ui/problem-details-dialog.ui:13 +msgid "Details" +msgstr "Detalles" + +#. Button label for retrying an account problem +#: ui/main-window.ui:261 +msgid "Retry" +msgstr "Reintentar" + +#. Infobar title when one or more accounts have encounted an error +#: ui/main-window.ui:294 +msgid "Account problem" +msgstr "Problema con la cuenta" + +#. Label and tooltip for account service problem infobar +#: ui/main-window.ui:308 +msgid "" +"Geary encountered a problem connecting to an account.\n" +"Please check your Internet connection, the server configuration and try " +"again." +msgstr "" +"Geary ha encontrado un problema al conectarse a la cuenta.\n" +"Revise su conexión a Internet, la configuración del servidor e inténtelo de " +"nuevo." + +#. Label and tooltip for account service problem infobar +#: ui/main-window.ui:311 +msgid "Geary encountered a problem connecting to an account." +msgstr "Geary ha encontrado un problema al conectarse al a cuenta." + +#. Button label for retrying TLS cert validation +#: ui/main-window.ui:358 +msgid "Check" +msgstr "Comprobar" + +#. Button tooltip for retrying TLS cert validation +#: ui/main-window.ui:362 +msgid "Check the security details for the connection" +msgstr "Compruebe los detalles de seguridad de la conexión" + +#. Infobar title when one or more accounts have a TLS cert validation error +#: ui/main-window.ui:391 +msgid "Security problem" +msgstr "Problema de seguridad" + +#. Label and tooltip for TLS cert validation error infobar +#: ui/main-window.ui:405 +msgid "" +"An account has reported an untrusted server.\n" +"Please check the server configuration and try again." +msgstr "" +"Una cuenta ha informado de un servidor que no es de confianza.\n" +"Revise la configuración del servidor e inténtelo de nuevo." + +#. Label and tooltip for TLS cert validation error infobar +#: ui/main-window.ui:408 +msgid "An account has reported an untrusted server." +msgstr "Una cuenta ha informado de un servidor que no es de confianza." + +#. Button tooltip for retrying when a login error has occurred +#: ui/main-window.ui:459 +msgid "Retry login, you will be prompted for your password" +msgstr "Reintentar iniciar sesión, se le pedirá su contraseña" + +#. Infobar title when one or more accounts have a login error +#: ui/main-window.ui:488 +msgid "Login problem" +msgstr "Problema de inicio de sesión" + +#. Label and tooltip for authentication problem infobar +#: ui/main-window.ui:502 +msgid "" +"An account has reported an incorrect login or password.\n" +"Please check your login name and try again." +msgstr "" +"Una cuenta ha informado de un nombre de usuario o contraseña incorrectos.\n" +"Compruébelos e inténtelo de nuevo." + +#. Label and tooltip for authentication problem infobar +#: ui/main-window.ui:505 +msgid "An account has reported an incorrect login or password." +msgstr "" +"Una cuenta ha informado de un nombre de usuario o contraseña no válidos." -#: ../ui/password-dialog.glade.h:1 +#: ui/password-dialog.glade:74 msgid "SMTP Credentials" msgstr "Credenciales SMTP" -#: ../ui/password-dialog.glade.h:2 +#: ui/password-dialog.glade:91 msgid "Username" msgstr "Nombre de usuario" -#: ../ui/password-dialog.glade.h:4 +#: ui/password-dialog.glade:152 msgid "_Remember password" msgstr "_Recordar la contraseña" -#: ../ui/password-dialog.glade.h:6 +#: ui/password-dialog.glade:210 msgid "_Authenticate" msgstr "_Autenticar" -#: ../ui/preferences-dialog.ui.h:1 +#: ui/preferences-dialog.ui:38 msgid "Reading" msgstr "Lectura" -#: ../ui/preferences-dialog.ui.h:2 +#: ui/preferences-dialog.ui:51 msgid "_Automatically select next message" msgstr "Seleccionar el siguiente mensaje _automáticamente" -#: ../ui/preferences-dialog.ui.h:3 +#: ui/preferences-dialog.ui:70 msgid "_Display conversation preview" msgstr "_Mostrar la vista previa de la conversación" -#: ../ui/preferences-dialog.ui.h:4 +#: ui/preferences-dialog.ui:89 msgid "Use _three pane view" msgstr "Usar vista de _tres paneles" -#: ../ui/preferences-dialog.ui.h:5 +#: ui/preferences-dialog.ui:113 msgid "Notifications" msgstr "Notificaciones" -#: ../ui/preferences-dialog.ui.h:6 +#: ui/preferences-dialog.ui:126 msgid "_Play notification sounds" msgstr "_Reproducir un sonido para las notificaciones" -#: ../ui/preferences-dialog.ui.h:7 +#: ui/preferences-dialog.ui:145 msgid "Show _notifications for new mail" msgstr "Mostrar _notificaciones cuando llegue correo nuevo" -#: ../ui/preferences-dialog.ui.h:8 -msgid "Always _watch for new mail" -msgstr "_Revisar siempre si hay mensajes nuevos" - -#: ../ui/preferences-dialog.ui.h:9 -msgid "Geary will run in the background and notify of new mail" -msgstr "Geary se ejecutará en segundo plano y le notificará de mensajes nuevos" +#: ui/preferences-dialog.ui:164 +msgid "_Watch for new mail when closed" +msgstr "_Revisar si hay mensajes nuevos al cerrar" + +#: ui/preferences-dialog.ui:168 +msgid "Geary will keep running after all windows are closed" +msgstr "Geary seguirá en ejecución después de haber cerrado todas la ventanas" -#: ../ui/preferences-dialog.ui.h:10 +#: ui/preferences-dialog.ui:195 msgid "Preferences" msgstr "Preferencias" -#: ../ui/remove_confirm.glade.h:1 +#. Button label for copying technical information to the clipboard +#: ui/problem-details-dialog.ui:17 +msgid "Copy to Clipboard" +msgstr "Copiar al portapapeles" + +#. Button tooltip for copying technical information to the clipboard +#: ui/problem-details-dialog.ui:21 msgid "" -"Are you sure you want to remove this " -"account? " +"Copy technical details to clipboard for pasting into an email or bug report" msgstr "" -"¿Está seguro de que quiere eliminar " -"esta cuenta? " +"Copiar los detalles técnicos al portapapeles para pegarlos en un correo o un " +"informe de error" -#: ../ui/remove_confirm.glade.h:2 +#: ui/problem-details-dialog.ui:73 msgid "" -"All email associated with this account will be removed from your computer. " -"This will not affect email on the server." +"If the problem is serious or persists, please copy and send these details to " +"the mailing list " +"or file a new " +"bug report." msgstr "" -"Todos los correos electrónicos asociados con esta cuenta se eliminarán del " -"equipo. Esto no afectará los correos en el servidor." - -#: ../ui/remove_confirm.glade.h:3 -msgid "Nickname:" -msgstr "Apodo:" +"Si el problema es grave o persiste, por favor copie y envíe estos detalles a " +"la lista de correo " +"o envíe un nuevo " +"informe de error." -#: ../ui/remove_confirm.glade.h:4 -msgid "Email address:" -msgstr "Dirección de correo:" +#: ui/problem-details-dialog.ui:89 +msgid "Details:" +msgstr "Detalles:" -#: ../ui/upgrade_dialog.glade.h:1 +#: ui/upgrade_dialog.glade:60 msgid "Geary update in progress…" msgstr "Actualización de Geary en proceso…" +#~ msgid "A_ccounts" +#~ msgstr "_Cuentas" + +#~ msgid "Empty Spam or Trash folders" +#~ msgstr "Vaciar las carpetas Spam o Papelera" + +#~ msgid "Delete conversations (Shift+Delete)" +#~ msgstr "Eliminar conversaciones (Mayús+Supr)" + +#~ msgid "Move conversations to Trash (Delete, Backspace)" +#~ msgstr "Mover conversaciones a la papelera (Supr, Retroceso)" + +#~ msgid "Archive conversations (A)" +#~ msgstr "Archivar conversaciones (A)" + +#~ msgid "Mark conversations" +#~ msgstr "Marcar conversaciones" + +#~ msgid "Add label to conversations" +#~ msgstr "Añadir etiqueta a las conversaciones" + +#~ msgid "Move conversations" +#~ msgstr "Mover conversaciones" + +#~ msgid "Retry connecting now" +#~ msgstr "Vuelva a intentar conectar ahora" + +#~ msgid "Try reconnecting now" +#~ msgstr "Vuelva a intentar conectar ahora" + +#~ msgid "Problem with connection to incoming server for %s" +#~ msgstr "Problema al conectar al servidor de correo entrante %s" + +#~ msgid "Problem with connection to outgoing server for %s" +#~ msgstr "Problema al conectar al servidor de salida para %s" + +#~ msgid "To: " +#~ msgstr "Para: " + +#~ msgid "Cc: " +#~ msgstr "Cc: " + +#~ msgid "Bcc: " +#~ msgstr "Cco: " + +#~ msgid "From: %s\n" +#~ msgstr "De: %s\n" + +#~ msgid "Subject: %s\n" +#~ msgstr "Asunto: %s\n" + +#~ msgid "Date: %s\n" +#~ msgstr "Fecha: %s\n" + +#~ msgid "To: %s\n" +#~ msgstr "Para: %s\n" + +#~ msgid "Cc: %s\n" +#~ msgstr "Cc: %s\n" + +#~ msgid "Email address:" +#~ msgstr "Dirección de correo:" + +#~ msgid "Additional addresses for %s" +#~ msgstr "Direcciones adicionales para %s" + +#~ msgid "First Last" +#~ msgstr "Nombre Apellido" + +#~ msgid "Enter your account information to get started." +#~ msgstr "Proporcione la información de su cuenta para empezar." + +#~ msgid "Edit" +#~ msgstr "Editar" + +#~ msgid "Preview" +#~ msgstr "Previsualizar" + +#~ msgid "Remem_ber passwords" +#~ msgstr "Recor_dar las contraseñas" + +#~ msgid "Remem_ber password" +#~ msgstr "Recor_dar la contraseña" + +#~ msgid "Unable to validate:\n" +#~ msgstr "No se puede validar:\n" + +#~ msgid " • Invalid account nickname.\n" +#~ msgstr " • Alias de cuenta no válido.\n" + +#~ msgid " • Email address already added to Geary.\n" +#~ msgstr " • Ya se había añadido la dirección de correo a Geary.\n" + +#~ msgid " • IMAP connection error.\n" +#~ msgstr " • Error de conexión IMAP.\n" + +#~ msgid " • IMAP username or password incorrect.\n" +#~ msgstr " • Nombre de usuario o contraseña IMAP incorrectos.\n" + +#~ msgid " • SMTP connection error.\n" +#~ msgstr " • Error de conexión SMTP.\n" + +#~ msgid " • SMTP username or password incorrect.\n" +#~ msgstr " • El nombre de usuario o contraseña SMTP incorrectos.\n" + +#~ msgid " • Connection error.\n" +#~ msgstr " • Error de conexión.\n" + +#~ msgid " • Username or password incorrect.\n" +#~ msgstr " • Nombre de usuario o contraseña incorrectos.\n" + +#~ msgid "Unable to store server trust exception" +#~ msgstr "No se pudo almacenar la excepción de seguridad para el servidor" + +#~ msgid "Your settings are insecure" +#~ msgstr "Su configuración no es segura" + +#~ msgid "" +#~ "Your IMAP and/or SMTP settings do not specify SSL or TLS. This means " +#~ "your username and password could be read by another person on the " +#~ "network. Are you sure you want to do this?" +#~ msgstr "" +#~ "Los parámetros IMAP y/o SMTP no especifican SSL o TLS. Esto quiere decir " +#~ "que otra persona podría leer su nombre de usuario y contraseña en la " +#~ "misma red. ¿Está seguro de que quiere hacer esto?" + +#~ msgid "Co_ntinue" +#~ msgstr "Co_ntinuar" + +#~ msgid "" +#~ "Geary encountered an error sending an email. If the problem persists, " +#~ "please manually delete the email from your Outbox folder." +#~ msgstr "" +#~ "Geary encontró un error al enviar un mensaje. Si el problema persiste, " +#~ "elimine manualmente el mensaje de la bandeja de salida." + +#~ msgid "" +#~ "Geary encountered an error saving a sent message to Sent Mail. The " +#~ "message will stay in your Outbox folder until you delete it." +#~ msgstr "" +#~ "Geary encontró un error al guardar un mensaje enviado en Enviados. El " +#~ "mensaje permanecerá en la Bandeja de salida hasta que lo elimine." + +#~ msgid "Unable to open local mailbox for %s" +#~ msgstr "No se puede abrir el buzón local para %s" + +#~ msgid "" +#~ "There was an error opening the local mail database for this account. This " +#~ "is possibly due to a file permissions problem.\n" +#~ "\n" +#~ "Please check that you have read/write permissions for all files in this " +#~ "directory:\n" +#~ "\n" +#~ "%s" +#~ msgstr "" +#~ "Hubo un error al abrir la base de datos de correo local para esta cuenta. " +#~ "Esto se debe posiblemente a un problema de permisos de archivo.\n" +#~ "\n" +#~ "Revise que cuentas con permisos de lectura y escritura en la siguiente " +#~ "carpeta:\n" +#~ "\n" +#~ "%s" + +#~ msgid "" +#~ "The version number of the local mail database is formatted for a newer " +#~ "version of Geary. Unfortunately, the database cannot be “rolled back” to " +#~ "work with this version of Geary.\n" +#~ "\n" +#~ "Please install the latest version of Geary and try again." +#~ msgstr "" +#~ "La versión de la base de datos de correo local tiene el formato de una " +#~ "versión más reciente de Geary. Desafortunadamente, no se puede volver a " +#~ "una versión anterior para que funcione con esta versión de Geary.\n" +#~ "\n" +#~ "Instale la última versión de Geary e inténtelo de nuevo." + +#~ msgid "" +#~ "There was an error opening the local account. This is probably due to " +#~ "connectivity issues.\n" +#~ "\n" +#~ "Please check your network connection and restart Geary." +#~ msgstr "" +#~ "Hubo un error al abrir la cuenta local. Esto puede ser debido a problemas " +#~ "de conectividad.\n" +#~ "\n" +#~ "Revise su conexión de red y reinicie Geary." + +#~ msgid "Geary will exit if you have no other open email accounts." +#~ msgstr "Geary se cerrará si no hay otras cuentas de correo abiertas." + +#~ msgid "IMAP" +#~ msgstr "IMAP" + +#~ msgid "SMTP" +#~ msgstr "SMTP" + +#~ msgid "Other" +#~ msgstr "Otro" + +#~ msgid "Cannot remove account " +#~ msgstr "" +#~ "No se puede eliminar la cuenta " + +#~ msgid "" +#~ "A composer window associated with this account is currently open. Send or " +#~ "discard the message and try again." +#~ msgstr "" +#~ "Hay una ventana de redacción abierta asociada a esta cuenta. Envíe o " +#~ "descarte el mensaje e inténtelo de nuevo." + +#~ msgid "Please wait while Geary validates your account." +#~ msgstr "Espere mientras Geary valida su cuenta" + +#~ msgid "" +#~ "Some email services require additional addresses be configured on the " +#~ "server. Contact your email provider for more information." +#~ msgstr "" +#~ "Algunos servicios de correo necesitan que se configuren direcciones de " +#~ "correo adicionales en el servidor. Consulte con su proveedor de correo " +#~ "para obtener más información." + +#~ msgid "_Update" +#~ msgstr "_Actualizar" + +#~ msgid "E_mail address" +#~ msgstr "_Dirección de correo electrónico" + +#~ msgid "_Password" +#~ msgstr "_Contraseña" + +#~ msgid "S_ervice" +#~ msgstr "S_ervicio" + +#~ msgid "N_ame" +#~ msgstr "N_ombre" + +#~ msgid "N_ickname" +#~ msgstr "_Apodo" + +#~ msgid "Work, Home, etc." +#~ msgstr "Trabajo, casa, etc." + +#~ msgid "Addi_tional email addresses…" +#~ msgstr "Direcciones de correo-e adicionales…" + +#~ msgid "IMAP settings" +#~ msgstr "Configuración IMAP" + +#~ msgid "Se_rver" +#~ msgstr "Se_rvidor" + +#~ msgid "P_ort" +#~ msgstr "P_uerto" + +#~ msgid "Ser_ver" +#~ msgstr "Ser_vidor" + +#~ msgid "Por_t" +#~ msgstr "Pue_rto" + +#~ msgid "User_name" +#~ msgstr "_Nombre de usuario" + +#~ msgid "Pass_word" +#~ msgstr "_Contraseña" + +#~ msgid "SMTP password" +#~ msgstr "Contraseña de SMTP" + +#~ msgid "_Username" +#~ msgstr "Nombre de _usuario" + +#~ msgid "IMAP password" +#~ msgstr "Contraseña de IMAP" + +#~ msgid "Encr_yption" +#~ msgstr "Ci_frado" + +#~ msgid "Encrypt_ion" +#~ msgstr "Cifra_do" + +#~ msgid "STARTTLS" +#~ msgstr "STARTTLS" + +#~ msgid "No authentication re_quired" +#~ msgstr "No se _requiere autenticación" + +#~ msgid "Use IMAP cre_dentials" +#~ msgstr "Utilizar cre_denciales IMAP" + +#~ msgid "Composer" +#~ msgstr "Redactor" + +#~ msgid "Si_gn emails (HTML allowed):" +#~ msgstr "_Firmar los mensajes (HTML permitido):" + +#~ msgid "Storage" +#~ msgstr "Almacenamiento" + +#~ msgid "" +#~ "Are you sure you want to remove " +#~ "this account? " +#~ msgstr "" +#~ "¿Está seguro de que quiere eliminar " +#~ "esta cuenta? " + +#~ msgid "" +#~ "All email associated with this account will be removed from your " +#~ "computer. This will not affect email on the server." +#~ msgstr "" +#~ "Todos los correos electrónicos asociados con esta cuenta se eliminarán " +#~ "del equipo. Esto no afectará los correos en el servidor." + +#~ msgid "Nickname:" +#~ msgstr "Apodo:" + +#~ msgid "Default attachments directory" +#~ msgstr "Carpeta de adjuntos predeterminada" + +#~ msgid "Location used when opening and saving attachments." +#~ msgstr "Ubicación usada al abrir y guardar adjuntos." + +#~ msgid "Default print output directory" +#~ msgstr "Carpeta de salida de impresión predeterminada." + +#~ msgid "Location used when printing to a file." +#~ msgstr "Ubicación usada al imprimir en un archivo." + +#~ msgid "Geary will run in the background and notify of new mail" +#~ msgstr "" +#~ "Geary se ejecutará en segundo plano y le notificará de mensajes nuevos" + +#~ msgid "Geary Email" +#~ msgstr "Correo Geary" + #~ msgid "Mail Client" #~ msgstr "Cliente de correo" +#~ msgid "Geary Mail" +#~ msgstr "Correo Geary" + +#~ msgid "_Mark as…" +#~ msgstr "_Marcar como…" + +#~ msgid "Add label" +#~ msgstr "Añadir etiqueta" + +#~ msgid "_Label" +#~ msgstr "_Etiquetar" + +#~ msgid "_Move" +#~ msgstr "_Mover" + +#~ msgid "Compose new message (Ctrl+N, N)" +#~ msgstr "Redactar un mensaje nuevo (Ctrl+N, N)" + +#~ msgid "Reply (Ctrl+R, R)" +#~ msgstr "Responder (Ctrl+R, R)" + +#~ msgid "Reply all (Ctrl+Shift+R, Shift+R)" +#~ msgstr "Responder a todos (Ctrl+Mayús+R, Mayús+R)" + +#~ msgid "Forward (Ctrl+L, F)" +#~ msgstr "Reenviar (Ctrl+L, F)" + +#~ msgid "" +#~ "Geary encountered an error while connecting to the server. Please try " +#~ "again in a few moments." +#~ msgstr "" +#~ "Geary ha encontrado un error al conectar al servidor. Inténtelo de nuevo " +#~ "pasados unos momentos." + #~ msgid "Try Again" #~ msgstr "Intentar de nuevo" @@ -2724,12 +3751,6 @@ #~ msgid "No search results found." #~ msgstr "La búsqueda no devolvió ningún resultado." -#~ msgid "From:" -#~ msgstr "De:" - -#~ msgid "Date:" -#~ msgstr "Fecha:" - #~ msgid "Select _Message" #~ msgstr "Seleccionar _mensaje" @@ -2798,9 +3819,6 @@ #~ msgid "Fixed Width" #~ msgstr "Anchura fija" -#~ msgid "Detach" -#~ msgstr "Desacoplar" - #~ msgid "_Attach File" #~ msgstr "_Adjuntar archivo" diff -Nru geary-0.12.4/po/fi.po geary-3.32.0/po/fi.po --- geary-0.12.4/po/fi.po 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/po/fi.po 2019-03-17 13:39:29.000000000 +0000 @@ -9,10 +9,9 @@ msgid "" msgstr "" "Project-Id-Version: geary-0.4.1\n" -"Report-Msgid-Bugs-To: https://bugzilla.gnome.org/enter_bug.cgi?" -"product=geary&keywords=I18N+L10N&component=internationalization\n" -"POT-Creation-Date: 2017-09-26 12:56+0000\n" -"PO-Revision-Date: 2017-09-27 17:40+0300\n" +"Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/geary/issues\n" +"POT-Creation-Date: 2019-02-20 03:32+0000\n" +"PO-Revision-Date: 2019-02-21 10:23+0200\n" "Last-Translator: Jiri Grönroos \n" "Language-Team: suomi \n" "Language: fi\n" @@ -20,28 +19,57 @@ "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Poedit 1.8.7.1\n" +"X-Generator: Poedit 2.2.1\n" + +#: desktop/geary-attach.contract.desktop.in:3 +msgid "Send by email" +msgstr "Lähetä sähköpostitse" + +#. Translators: Do NOT translate or transliterate this text (this is an icon file name)! +#: desktop/geary-attach.contract.desktop.in:5 +msgid "mail-send" +msgstr "mail-send" + +#: desktop/geary-attach.contract.desktop.in:6 +msgid "Send files using Geary" +msgstr "Lähetä tiedostoja Gearylla" #. Translators: The application name -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:2 -#: ../desktop/org.gnome.Geary.desktop.in.h:1 -#: ../desktop/geary-autostart.desktop.in.h:1 +#: desktop/geary-autostart.desktop.in:3 +#: desktop/org.gnome.Geary.appdata.xml.in:11 +#: desktop/org.gnome.Geary.desktop.in:3 +#: src/client/accounts/accounts-editor-servers-pane.vala:555 msgid "Geary" msgstr "Geary" -#. Translators: The development team's name -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:4 -msgid "Geary Development Team" -msgstr "Geary-kehitystiimi" +#: desktop/geary-autostart.desktop.in:4 desktop/org.gnome.Geary.desktop.in:4 +msgid "Email" +msgstr "Sähköposti" #. Translators: The application's summary / tagline -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:6 -#: ../desktop/org.gnome.Geary.desktop.in.h:4 -#: ../desktop/geary-autostart.desktop.in.h:4 +#: desktop/geary-autostart.desktop.in:5 +#: desktop/org.gnome.Geary.appdata.xml.in:15 +#: desktop/org.gnome.Geary.desktop.in:5 +#: src/client/application/geary-application.vala:22 msgid "Send and receive email" msgstr "Lähetä ja vastaanota sähköpostia" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:7 +#. Translators: These are desktop search terms. Do not translate semicolons, end line with a semicolon. +#: desktop/geary-autostart.desktop.in:7 +msgid "Email;E-mail;Mail;" +msgstr "Email;E-mail;Mail;Sähköposti;Maili;posti;" + +#. Translators: Do NOT translate or transliterate this text (this is an icon file name)! +#: desktop/geary-autostart.desktop.in:9 desktop/org.gnome.Geary.desktop.in:9 +msgid "org.gnome.Geary" +msgstr "org.gnome.Geary" + +#. Translators: The development team's name +#: desktop/org.gnome.Geary.appdata.xml.in:13 +msgid "Geary Development Team" +msgstr "Geary-kehitystiimi" + +#: desktop/org.gnome.Geary.appdata.xml.in:17 msgid "" "Geary is an email application built around conversations, for the GNOME 3 " "desktop. It allows you to read, find and send email with a straightforward, " @@ -51,7 +79,7 @@ "keskusteluihin. Sen avulla voit lukea, etsiä ja lähettää sähköpostia " "modernin käyttöliittymän kautta." -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:8 +#: desktop/org.gnome.Geary.appdata.xml.in:22 msgid "" "Conversations allow you to read a complete discussion without having to find " "and click from message to message." @@ -59,220 +87,695 @@ "Keskusteluiden avulla voit lukea koko viestiketjun ilman, että joudut " "napsauttamaan viestistä toiseen itse." -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:9 +#: desktop/org.gnome.Geary.appdata.xml.in:26 msgid "Geary’s features include:" msgstr "Gearyn ominaisuuksiin kuuluu muun muassa:" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:10 +#: desktop/org.gnome.Geary.appdata.xml.in:28 msgid "Quick email account setup" msgstr "Nopea sähköpostitilin asetusten teko" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:11 +#: desktop/org.gnome.Geary.appdata.xml.in:29 msgid "Shows related messages together in conversations" msgstr "Näyttää toisiinsa liittyvät viestit yhdessä keskustelumuodossa" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:12 +#: desktop/org.gnome.Geary.appdata.xml.in:30 msgid "Fast, full text and keyword search" msgstr "Nopea haku koko tekstin ja avainsanojen tuella" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:13 +#: desktop/org.gnome.Geary.appdata.xml.in:31 msgid "Full-featured HTML and plain text message composer" msgstr "Monipuolinen HTML- ja raakatekstilähetys" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:14 +#: desktop/org.gnome.Geary.appdata.xml.in:32 msgid "Desktop notification of new mail" msgstr "Työpöytäilmoitukset uusista viesteistä" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:15 +#: desktop/org.gnome.Geary.appdata.xml.in:33 msgid "Compatible with GMail, Yahoo! Mail, Outlook.com and other IMAP servers" msgstr "" "Yhteensopiva GMailin, Yahoo! Mailin, Outlook.comin ja muiden IMAP-" "palvelinten kanssa" #. Translators: A screenshot description. -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:17 +#: desktop/org.gnome.Geary.appdata.xml.in:47 msgid "Geary displaying a conversation" msgstr "Geary ja viestiketju" #. Translators: A screenshot description. -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:19 +#: desktop/org.gnome.Geary.appdata.xml.in:52 msgid "Geary showing the rich text composer" msgstr "Geary ja viestin kirjoitusikkuna" -#: ../desktop/org.gnome.Geary.desktop.in.h:2 -msgid "Email" -msgstr "Sähköposti" - -#: ../desktop/org.gnome.Geary.desktop.in.h:3 -msgid "Geary Email" -msgstr "Geary-sähköposti" - #. Translators: These are desktop search terms. Do not translate semicolons, end line with a semicolon. -#: ../desktop/org.gnome.Geary.desktop.in.h:6 +#: desktop/org.gnome.Geary.desktop.in:7 msgid "Mail;E-mail;IMAP;GMail;Yahoo;Hotmail;Outlook;" msgstr "Mail;E-mail;IMAP;GMail;Yahoo;Hotmail;Outlook;sähköposti;" -#: ../desktop/org.gnome.Geary.desktop.in.h:7 ../ui/gtk/menus.ui.h:1 +#: desktop/org.gnome.Geary.desktop.in:21 ui/gtk/menus.ui:7 msgid "Compose Message" msgstr "Lähetä viesti" -#: ../desktop/geary-autostart.desktop.in.h:2 -#: ../src/client/application/geary-application.vala:21 -msgid "Mail Client" -msgstr "Sähköpostisovellus" - -#: ../desktop/geary-autostart.desktop.in.h:3 -msgid "Geary Mail" -msgstr "Geary-sähköposti" +#: desktop/org.gnome.Geary.gschema.xml:8 +msgid "Maximize window" +msgstr "Suurenna ikkuna" -#: ../desktop/geary-autostart.desktop.in.h:5 -msgid "Email;E-mail;Mail;" -msgstr "Email;E-mail;Mail;Sähköposti;Maili;posti;" +#: desktop/org.gnome.Geary.gschema.xml:9 +msgid "True if the application window is maximized, false otherwise." +msgstr "" -#: ../desktop/geary-attach.contract.in.h:1 -msgid "Send by email" -msgstr "Lähetä sähköpostitse" +#: desktop/org.gnome.Geary.gschema.xml:14 +msgid "Width of window" +msgstr "Ikkunan leveys" -#: ../desktop/geary-attach.contract.in.h:2 -msgid "Send files using Geary" -msgstr "Lähetä tiedostoja Gearylla" +#: desktop/org.gnome.Geary.gschema.xml:15 +msgid "The last recorded width of the application window." +msgstr "Viimeisin sovelluksen ikkunan tallennettu leveys." -#: ../src/client/accounts/account-dialog-add-edit-pane.vala:51 -#: ../src/client/components/stock.vala:31 -#: ../ui/conversation-email-menus.ui.h:10 -msgid "_Save" -msgstr "_Tallenna" +#: desktop/org.gnome.Geary.gschema.xml:20 +msgid "Height of window" +msgstr "Ikkunan korkeus" -#: ../src/client/accounts/account-dialog-add-edit-pane.vala:51 -#: ../src/client/components/stock.vala:22 -msgid "_Add" -msgstr "_Lisää" +#: desktop/org.gnome.Geary.gschema.xml:21 +msgid "The last recorded height of the application window." +msgstr "Viimeisin sovelluksen ikkunan tallennettu korkeus." -#. reset/clear widgets -#: ../src/client/accounts/account-dialog-edit-alternate-emails-pane.vala:120 -#, c-format -msgid "Additional addresses for %s" -msgstr "Lisäosoitteet tilille %s" +#: desktop/org.gnome.Geary.gschema.xml:26 +msgid "Position of folder list pane" +msgstr "" -#. Sets min size. -#: ../src/client/accounts/account-dialog.vala:21 -msgid "Accounts" -msgstr "Tilit" +#: desktop/org.gnome.Geary.gschema.xml:27 +msgid "Position of the folder list Paned grabber." +msgstr "" -#. Copyright 2016 Software Freedom Conservancy Inc. -#. * -#. * This software is licensed under the GNU Lesser General Public License -#. * (version 2.1 or later). See the COPYING file in this distribution. -#. -#. Page for adding or editing an account. -#. / Placeholder text indicating that the user should type their first name and last name -#: ../src/client/accounts/add-edit-page.vala:10 -msgid "First Last" -msgstr "Etunimi Sukunimi" - -#: ../src/client/accounts/add-edit-page.vala:235 -msgid "Welcome to Geary." -msgstr "Tervetuloa, käytössäsi on Geary." - -#: ../src/client/accounts/add-edit-page.vala:235 -msgid "Enter your account information to get started." -msgstr "Anna tilisi tiedot, jotta voit aloittaa käytön." +#: desktop/org.gnome.Geary.gschema.xml:32 +msgid "Position of folder list pane when horizontal" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:33 +msgid "" +"Position of the folder list Paned grabber in the horizontal orientation." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:38 +msgid "Position of folder list pane when vertical" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:39 +msgid "Position of the folder list Paned grabber in the vertical orientation." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:44 +msgid "Orientation of the folder list pane" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:45 +msgid "True if the folder list Paned is in the horizontal orientation." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:50 +msgid "Position of message list pane" +msgstr "Viestiluettelopaneelin sijainti" + +#: desktop/org.gnome.Geary.gschema.xml:51 +msgid "Position of the message list Paned grabber." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:56 +msgid "Autoselect next message" +msgstr "Valitse automaattisesti seuraava viesti" + +#: desktop/org.gnome.Geary.gschema.xml:57 +msgid "True if we should autoselect the next available conversation." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:62 +msgid "Display message previews" +msgstr "Näytä viestien esikatselut" + +#: desktop/org.gnome.Geary.gschema.xml:63 +msgid "True if we should display a short preview of each message." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:68 +msgid "Languages that shall be used in the spell checker" +msgstr "Oikoluvussa käytettävät kielet" + +#: desktop/org.gnome.Geary.gschema.xml:69 +msgid "List of the languages to use in the spell checker." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:74 +msgid "Languages that are displayed in the spell checker popover" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:75 +msgid "" +"List of languages that are always displayed in the popover of the spell " +"checker." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:80 +msgid "Enable notification sounds" +msgstr "Käytä ilmoitusääniä" + +#: desktop/org.gnome.Geary.gschema.xml:81 +msgid "True to play sounds for notifications and sending." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:86 +msgid "Show notifications for new mail" +msgstr "Näytä ilmoitus uusista viesteistä" + +#: desktop/org.gnome.Geary.gschema.xml:87 +msgid "True to show notification bubbles." +msgstr "" -#: ../src/client/accounts/add-edit-page.vala:255 +#: desktop/org.gnome.Geary.gschema.xml:92 +msgid "Notify of new mail at startup" +msgstr "Ilmoita uusista viesteistä käynnistyksen yhteydessä" + +#: desktop/org.gnome.Geary.gschema.xml:93 +#, fuzzy +#| msgid "Notify of new mail at start_up" +msgid "True to notify of new mail at startup." +msgstr "_Ilmoita uusista viesteistä käynnistyksen yhteydessä" + +#: desktop/org.gnome.Geary.gschema.xml:98 +msgid "Ask when opening an attachment" +msgstr "Kysy liitettä avattaessa" + +#: desktop/org.gnome.Geary.gschema.xml:99 +#, fuzzy +#| msgid "To add them as attachments" +msgid "True to ask when opening an attachment." +msgstr "lisätäksesi ne liitteiksi" + +#: desktop/org.gnome.Geary.gschema.xml:104 +msgid "Whether to compose emails in HTML" +msgstr "Muodostetaanko lähetettävät viestit HTML-muodossa" + +#: desktop/org.gnome.Geary.gschema.xml:105 +msgid "True to compose emails in HTML; false for plain text." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:110 +msgid "Advisory strategy for full-text searching" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:111 +msgid "" +"Acceptable values are “exact”, “conservative”, “aggressive”, and “horizon”." +msgstr "" +"Hyväksyttävät arvot ovat “exact”, “conservative”, “aggressive” ja “horizon”." + +#: desktop/org.gnome.Geary.gschema.xml:116 +msgid "Zoom of conversation viewer" +msgstr "Keskustelunäkymän suurennus" + +#: desktop/org.gnome.Geary.gschema.xml:117 +msgid "The zoom to apply on the conservation view." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:122 +msgid "Size of detached composer window" +msgstr "Irrotetun lähetysikkunan koko" + +#: desktop/org.gnome.Geary.gschema.xml:123 +msgid "The last recorded size of the detached composer window." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:128 +msgid "Base URL to look up contact avatars" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:129 +msgid "" +"A Gravatar or Libravatar compatible URL, set to the empty string to disable." +msgstr "" +"Gravatar- tai Libravatar-yhteensopiva osoite, aseta tyhjäksi poistaaksesi " +"käytöstä." + +#: desktop/org.gnome.Geary.gschema.xml:134 +msgid "Whether we migrated the old settings" +msgstr "Tehtiinkö vanhojen asetusten migraatio" + +#: desktop/org.gnome.Geary.gschema.xml:135 +msgid "" +"False to check for the old “org.yorba.geary”-schema and copy its values." +msgstr "" + +#. Translators: In-app notification label, when +#. the app had a problem pinning an otherwise +#. untrusted TLS certificate +#: src/client/accounts/accounts-editor.vala:203 +msgid "Failed to store certificate" +msgstr "Varmenteen tallentaminen epäonnistui" + +#. Translators: Label for adding an email account +#. account for a generic IMAP service provider. +#: src/client/accounts/accounts-editor-add-pane.vala:109 +msgid "All others" +msgstr "Kaikki muut" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:196 +#: src/client/accounts/accounts-editor-servers-pane.vala:316 +msgid "Check your receiving login and password" +msgstr "Tarkista vastaanottamiseen käytettävä käyttäjätunnus ja salasana" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:211 +#: src/client/accounts/accounts-editor-servers-pane.vala:329 +msgid "Check your receiving server details" +msgstr "Tarkista vastaanottamiseen käytettävän palvelimen tiedot" + +#. Translators: In-app notification label +#. There was an SMTP auth error, but IMAP already +#. succeeded, so the user probably needs to +#. specify custom creds here +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:233 +#: src/client/accounts/accounts-editor-servers-pane.vala:350 +msgid "Check your sending login and password" +msgstr "Tarkista lähettämiseen käytettävä käyttäjätunnus ja salasana" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:247 +#: src/client/accounts/accounts-editor-servers-pane.vala:363 +msgid "Check your sending server details" +msgstr "Tarkista lähettämiseen käytettävän palvelimen tiedot" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:262 +msgid "Check your email address and password" +msgstr "Tarkista sähköpostiosoitteesi ja salasanasi" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:273 +msgid "Could not connect, check your network" +msgstr "Yhdistäminen ei onnistu, tarkista verkkoyhteytesi" + +#. Translators: In-app notification label for a +#. generic error creating an account +#: src/client/accounts/accounts-editor-add-pane.vala:286 +msgid "An unexpected problem occurred" +msgstr "Tapahtui odottomaton ongelma" + +#. Translators: In-app notification label, the +#. string substitution is a more detailed reason. +#: src/client/accounts/accounts-editor-add-pane.vala:304 +#, c-format +msgid "Account not created: %s" +msgstr "Tiliä ei luotu: %s" + +#. Translators: Label for the person's actual name when adding +#. an account +#: src/client/accounts/accounts-editor-add-pane.vala:551 +msgid "Your name" +msgstr "Nimi" + +#. Translators: Label used for the address part of an +#. email address when editing a user's sender address +#. preferences for an account. +#: src/client/accounts/accounts-editor-add-pane.vala:568 +#: src/client/accounts/accounts-editor-edit-pane.vala:501 +msgid "Email address" +msgstr "Sähköpostiosoite" + +#. Translators: Placeholder for the default sender address +#. when adding an account +#. Translators: This is used as a placeholder for the +#. address part of an email address when editing a user's +#. sender address preferences for an account. +#: src/client/accounts/accounts-editor-add-pane.vala:571 +#: src/client/accounts/accounts-editor-edit-pane.vala:469 +msgid "person@example.com" +msgstr "nimi@verkkotunnus.fi" + +#. Translators: Label for an IMAP/SMTP service login/user name +#. when adding an account +#. Translators: Label for the user's login name for an +#. IMAP, SMTP, etc service +#: src/client/accounts/accounts-editor-add-pane.vala:585 +#: src/client/accounts/accounts-editor-servers-pane.vala:880 +msgid "Login name" +msgstr "Käyttäjätunnus" + +#. Translators: Label for the user's password for an IMAP, +#. SMTP, etc service +#: src/client/accounts/accounts-editor-add-pane.vala:599 +#: src/client/accounts/accounts-editor-servers-pane.vala:999 +#: ui/password-dialog.glade:108 +msgid "Password" +msgstr "Salasana" + +#. Translators: Label for the IMAP server hostname when +#. adding an account. +#. Translators: This label describes the host name or IP +#. address and port used by an account's IMAP service. +#: src/client/accounts/accounts-editor-add-pane.vala:621 +#: src/client/accounts/accounts-editor-servers-pane.vala:727 +msgid "IMAP server" +msgstr "IMAP-palvelin" + +#. Translators: Placeholder for the IMAP server hostname +#. when adding an account. +#: src/client/accounts/accounts-editor-add-pane.vala:624 +msgid "imap.example.com" +msgstr "imap.esimerkki.com" + +#. Translators: Label for the SMTP server hostname when +#. adding an account. +#. Translators: This label describes the host name or IP +#. address and port used by an account's SMTP service. +#: src/client/accounts/accounts-editor-add-pane.vala:630 +#: src/client/accounts/accounts-editor-servers-pane.vala:733 +msgid "SMTP server" +msgstr "SMTP-palvelin" + +#. Translators: Placeholder for the SMTP server hostname +#. when adding an account. +#: src/client/accounts/accounts-editor-add-pane.vala:633 +msgid "smtp.example.com" +msgstr "smtp.esimerkki.com" + +#. Translators: Label in the account editor for the user's +#. custom name for an account. +#: src/client/accounts/accounts-editor-edit-pane.vala:278 +#: ui/accounts_editor_remove_pane.ui:123 +msgid "Account name" +msgstr "Tilin nimi" + +#. Translators: Tooltip used to undo changing +#. the name of an account. The string +#. substitution is the old name of the +#. account. +#: src/client/accounts/accounts-editor-edit-pane.vala:312 +#, c-format +msgid "Change account name back to “%s”" +msgstr "Muuta takaisin tilin nimeksi “%s”" + +#. Translators: Tooltip for adding a new email sender/from +#. address's address to an account +#: src/client/accounts/accounts-editor-edit-pane.vala:336 +msgid "Add a new sender email address" +msgstr "Lisää uusi lähettäjän sähköpostiosoite" + +#. Translators: Label used to indicate the user has +#. provided no display name for one of their sender +#. email addresses in their account settings. +#: src/client/accounts/accounts-editor-edit-pane.vala:417 +msgid "Name not set" +msgstr "Nimeä ei ole asetettu" + +#. Translators: This is used as a placeholder for the +#. display name for an email address when editing a user's +#. sender address preferences for an account. +#: src/client/accounts/accounts-editor-edit-pane.vala:456 +msgid "Sender Name" +msgstr "Lähettäjän nimi" + +#: src/client/accounts/accounts-editor-edit-pane.vala:479 +msgid "Remove" +msgstr "Poista" + +#. Translators: Label used for the display name part of an +#. email address when editing a user's sender address +#. preferences for an account. +#: src/client/accounts/accounts-editor-edit-pane.vala:494 +msgid "Sender name" +msgstr "Lähettäjän nimi" + +#. Translators: Label used as the undo tooltip after adding an +#. new sender email address to an account. The string +#. substitution is the email address added. +#: src/client/accounts/accounts-editor-edit-pane.vala:561 +#, c-format +msgid "Remove “%s”" +msgstr "Poista “%s”" + +#. Translators: Label used as the undo tooltip after editing a +#. sender address for an account. The string substitution is +#. the email address edited. +#: src/client/accounts/accounts-editor-edit-pane.vala:601 +#, c-format +msgid "Undo changes to “%s”" +msgstr "Kumoa muutokset tiliin “%s”" + +#. Translators: Label used as the undo tooltip after removing +#. a sender address from an account. The string substitution +#. is the email address edited. +#: src/client/accounts/accounts-editor-edit-pane.vala:690 +#, c-format +msgid "Add “%s” back" +msgstr "Lisää “%s” takaisin" + +#. Translators: Label used as the undo tooltip after removing +#. a sender address from an account. The string substitution +#. is the email address edited. +#: src/client/accounts/accounts-editor-edit-pane.vala:732 +msgid "Undo signature changes" +msgstr "" + +#. Translators: This label describes the account +#. preference for the length of time (weeks, months or +#. years) that past email should be downloaded. +#: src/client/accounts/accounts-editor-edit-pane.vala:780 +msgid "Download mail" +msgstr "Lataa sähköposti" + +#. Translators: Tooltip for undoing a change +#. to the length of time that past email +#. should be downloaded for an account. The +#. string substitution is the duration, +#. e.g. "1 month back". +#: src/client/accounts/accounts-editor-edit-pane.vala:812 +#, c-format +msgid "Change download period back to: %s" +msgstr "Muuta latausaikajaksoksi: %s" + +#: src/client/accounts/accounts-editor-edit-pane.vala:833 +msgid "Everything" +msgstr "Kaikki" + +#: src/client/accounts/accounts-editor-edit-pane.vala:837 msgid "2 weeks back" msgstr "2 viikkoa sitten" -#. IDs are # of days -#: ../src/client/accounts/add-edit-page.vala:256 +#: src/client/accounts/accounts-editor-edit-pane.vala:841 msgid "1 month back" msgstr "1 kuukausi sitten" -#: ../src/client/accounts/add-edit-page.vala:257 +#: src/client/accounts/accounts-editor-edit-pane.vala:845 msgid "3 months back" msgstr "3 kuukautta sitten" -#: ../src/client/accounts/add-edit-page.vala:258 +#: src/client/accounts/accounts-editor-edit-pane.vala:849 msgid "6 months back" msgstr "6 kuukautta sitten" -#: ../src/client/accounts/add-edit-page.vala:259 +#: src/client/accounts/accounts-editor-edit-pane.vala:853 msgid "1 year back" msgstr "1 vuosi sitten" -#: ../src/client/accounts/add-edit-page.vala:260 +#: src/client/accounts/accounts-editor-edit-pane.vala:857 msgid "2 years back" msgstr "2 vuotta sitten" -#: ../src/client/accounts/add-edit-page.vala:261 +#: src/client/accounts/accounts-editor-edit-pane.vala:861 msgid "4 years back" msgstr "4 vuotta sitten" -#. Separator -#: ../src/client/accounts/add-edit-page.vala:263 -msgid "Everything" -msgstr "Kaikki" +#: src/client/accounts/accounts-editor-edit-pane.vala:867 +#, c-format +msgid "%d day back" +msgid_plural "%d days back" +msgstr[0] "%d päivän ajalta" +msgstr[1] "%d päivän ajalta" + +#: src/client/accounts/accounts-editor-list-pane.vala:243 +msgid "Undo" +msgstr "Kumoa" + +#: src/client/accounts/accounts-editor-list-pane.vala:251 +msgid "Redo" +msgstr "Tee uudelleen" + +#: src/client/accounts/accounts-editor-list-pane.vala:345 +#: src/client/accounts/accounts-editor-list-pane.vala:433 +#: src/client/accounts/accounts-editor-row.vala:278 +msgid "Gmail" +msgstr "Gmail" -#: ../src/client/accounts/add-edit-page.vala:283 -msgid "Edit" -msgstr "Muokkaa" - -#: ../src/client/accounts/add-edit-page.vala:285 -msgid "Preview" -msgstr "Esikatsele" - -#: ../src/client/accounts/add-edit-page.vala:751 -msgid "Remem_ber passwords" -msgstr "M_uista salasanat" +#: src/client/accounts/accounts-editor-list-pane.vala:349 +#: src/client/accounts/accounts-editor-list-pane.vala:437 +#: src/client/accounts/accounts-editor-row.vala:282 +msgid "Outlook.com" +msgstr "Outlook.com" -#: ../src/client/accounts/add-edit-page.vala:758 ../ui/login.glade.h:7 -msgid "Remem_ber password" -msgstr "_Muista salasana" +#: src/client/accounts/accounts-editor-list-pane.vala:353 +#: src/client/accounts/accounts-editor-list-pane.vala:441 +#: src/client/accounts/accounts-editor-row.vala:286 +msgid "Yahoo" +msgstr "Yahoo" + +#. Translators: Tooltip for accounts that have been +#. loaded but disabled by the user. +#: src/client/accounts/accounts-editor-list-pane.vala:371 +msgid "This account has been disabled" +msgstr "Tämä tili on poistettu käytöstä" + +#. Translators: Tooltip for accounts that have been +#. loaded but because of some error are not able to be +#. used. +#: src/client/accounts/accounts-editor-list-pane.vala:380 +msgid "This account has encountered a problem and is unavailable" +msgstr "Tämä tili kohtasi ongelman, eikä ole käytettävissä" + +#. Translators: Label for adding a generic email account +#: src/client/accounts/accounts-editor-list-pane.vala:430 +msgid "Other email providers" +msgstr "Muut sähköpostipalveluntarjoajat" + +#. Translators: Notification shown after removing an +#. account. The string substitution is the name of the +#. account. +#: src/client/accounts/accounts-editor-list-pane.vala:547 +#, c-format +msgid "Account “%s” removed" +msgstr "Tili “%s” poistettu" + +#. Translators: Notification shown after removing an account +#. is undone. The string substitution is the name of the +#. account. +#: src/client/accounts/accounts-editor-list-pane.vala:554 +#, c-format +msgid "Account “%s” restored" +msgstr "Tili “%s” palautettu" + +#. Translators: Tooltip for dragging list items +#: src/client/accounts/accounts-editor-row.vala:49 +msgid "Drag to move this item" +msgstr "Raahaa liikuttaaksesi tätä kohdetta" + +#. Translators: Label describes the service provider +#. hosting the email account, e.g. Gmail, Yahoo, or some +#. other generic IMAP service. +#: src/client/accounts/accounts-editor-row.vala:294 +msgid "Service provider" +msgstr "Palveluntarjoaja" + +#. Translators: This label describes what form of transport +#. security (TLS, StartTLS, etc) used by an account's IMAP or SMTP +#. service. +#: src/client/accounts/accounts-editor-row.vala:467 +msgid "Connection security" +msgstr "Yhteyden salaus" + +#. Translators: Label used when no auth scheme is used +#. by an account's IMAP or SMTP service. +#: src/client/accounts/accounts-editor-row.vala:478 +#: src/client/accounts/accounts-editor-servers-pane.vala:752 +#: src/client/accounts/accounts-editor-servers-pane.vala:964 +#: src/engine/api/geary-special-folder-type.vala:58 +msgid "None" +msgstr "Ei mitään" -#: ../src/client/accounts/add-edit-page.vala:792 -msgid "Unable to validate:\n" -msgstr "Tarkistaminen epäonnistui:\n" - -#: ../src/client/accounts/add-edit-page.vala:794 -msgid " • Invalid account nickname.\n" -msgstr " • Virheellinen tilin nimi.\n" - -#: ../src/client/accounts/add-edit-page.vala:797 -msgid " • Email address already added to Geary.\n" -msgstr " • Sähköpostiosoite on jo lisätty Gearyyn.\n" - -#: ../src/client/accounts/add-edit-page.vala:801 -msgid " • IMAP connection error.\n" -msgstr " • IMAP-yhteysvirhe.\n" - -#: ../src/client/accounts/add-edit-page.vala:804 -msgid " • IMAP username or password incorrect.\n" -msgstr " • IMAP-käyttäjätunnus tai -salasana on väärin.\n" - -#: ../src/client/accounts/add-edit-page.vala:807 -msgid " • SMTP connection error.\n" -msgstr " • SMTP-yhteysvirhe.\n" - -#: ../src/client/accounts/add-edit-page.vala:810 -msgid " • SMTP username or password incorrect.\n" -msgstr " • SMTP-käyttäjätunnus tai -salasana on väärin.\n" - -#: ../src/client/accounts/add-edit-page.vala:814 -msgid " • Connection error.\n" -msgstr " • Yhteysvirhe.\n" - -#: ../src/client/accounts/add-edit-page.vala:818 -msgid " • Username or password incorrect.\n" -msgstr " • Käyttäjätunnus tai salasana on väärin.\n" +#: src/client/accounts/accounts-editor-row.vala:485 +msgid "StartTLS" +msgstr "StartTLS" + +#: src/client/accounts/accounts-editor-row.vala:492 +msgid "TLS" +msgstr "TLS" + +#. Translators: Label for source of SMTP authentication +#. credentials (none, use IMAP, custom) when adding a new +#. account +#. Button label for retrying when a login error has occurred +#: src/client/accounts/accounts-editor-row.vala:533 ui/main-window.ui:455 +msgid "Login" +msgstr "Kirjaudu" + +#. Translators: ComboBox value for source of SMTP +#. authentication credentials (none) when adding a new +#. account +#: src/client/accounts/accounts-editor-row.vala:540 +msgid "No login needed" +msgstr "Kirjautumista ei vaadita" + +#. Translators: ComboBox value for source of SMTP +#. authentication credentials (use IMAP) when adding a new +#. account +#: src/client/accounts/accounts-editor-row.vala:548 +msgid "Use same login as receiving" +msgstr "Käytä samoja kirjautumistietoja kuin lähettäessä" + +#. Translators: ComboBox value for source of SMTP +#. authentication credentials (custom) when adding a new +#. account +#: src/client/accounts/accounts-editor-row.vala:556 +msgid "Use a different login" +msgstr "Käytä eri kirjautumistietoja" + +#. Translators: In-app notification label, the +#. string substitution is a more detailed reason. +#: src/client/accounts/accounts-editor-servers-pane.vala:377 +#, c-format +msgid "Account not updated: %s" +msgstr "Tiliä ei päivitetty: %s" + +#. Translators: This label describes the program that +#. created the account, e.g. an SSO service like GOA, or +#. locally by Geary. +#: src/client/accounts/accounts-editor-servers-pane.vala:540 +msgid "Account source" +msgstr "Tilin lähde" + +#: src/client/accounts/accounts-editor-servers-pane.vala:552 +msgid "GNOME Online Accounts" +msgstr "Gnomen verkkotilit" + +#. Translators: This label describes an account +#. preference. +#: src/client/accounts/accounts-editor-servers-pane.vala:611 +msgid "Save drafts on server" +msgstr "Tallenna luonnokset palvelimelle" + +#. Translators: This label describes an account +#. preference. +#: src/client/accounts/accounts-editor-servers-pane.vala:666 +msgid "Save sent email on server" +msgstr "Tallenna lähetetyt viestit palvelimelle" + +#. Add a suffix for OAuth2 auth so people know they +#. shouldn't expect to be prompted for a password +#. Translators: Label used when an account's IMAP or +#. SMTP service uses OAuth2. The string replacement is +#. the service's login name. +#: src/client/accounts/accounts-editor-servers-pane.vala:950 +#, c-format +msgid "%s using OAuth2" +msgstr "%s käyttää OAuth2:ta" + +#: src/client/accounts/accounts-editor-servers-pane.vala:960 +msgid "Use receiving server login" +msgstr "Käytä vastaanottavan palvelimen kirjautumistietoja" -#: ../src/client/application/geary-application.vala:22 +#: src/client/application/geary-application.vala:23 msgid "Copyright 2016 Software Freedom Conservancy Inc." msgstr "Tekijänoikeus 2016 Software Freedom Conservancy Inc." -#: ../src/client/application/geary-application.vala:24 +#: src/client/application/geary-application.vala:24 +msgid "Copyright 2016-2019 Geary Development Team." +msgstr "Tekijänoikeus 2016-2019 Geary-kehitystiimi." + +#: src/client/application/geary-application.vala:26 msgid "Visit the Geary web site" msgstr "Vieraile Gearyn verkkosivustolla" -#: ../src/client/application/geary-application.vala:464 +#: src/client/application/geary-application.vala:420 #, c-format msgid "About %s" msgstr "Tietoja %ssta" @@ -280,319 +783,113 @@ #. Translators: add your name and email address to receive #. credit in the About dialog For example: Yamada Taro #. -#: ../src/client/application/geary-application.vala:468 +#: src/client/application/geary-application.vala:424 msgid "translator-credits" msgstr "Jiri Grönroos" -#: ../src/client/application/geary-args.vala:10 +#: src/client/application/geary-args.vala:10 msgid "Start Geary with hidden main window" msgstr "Käynnistä Geary pääikkuna piilotettuna" -#: ../src/client/application/geary-args.vala:11 +#: src/client/application/geary-args.vala:11 msgid "Output debugging information" msgstr "" -#: ../src/client/application/geary-args.vala:12 +#: src/client/application/geary-args.vala:12 msgid "Log conversation monitoring" msgstr "Lokita keskustelujen tarkkailu" -#: ../src/client/application/geary-args.vala:13 +#: src/client/application/geary-args.vala:13 msgid "Log network deserialization" msgstr "" -#: ../src/client/application/geary-args.vala:14 +#: src/client/application/geary-args.vala:14 msgid "Log network activity" msgstr "Lokita verkkotoiminta" #. / The IMAP replay queue is how changes on the server are replicated on the client. #. / It could also be called the IMAP events queue. -#: ../src/client/application/geary-args.vala:17 +#: src/client/application/geary-args.vala:17 msgid "Log IMAP replay queue" msgstr "" #. / Serialization is how commands and responses are converted into a stream of bytes for #. / network transmission -#: ../src/client/application/geary-args.vala:20 +#: src/client/application/geary-args.vala:20 msgid "Log network serialization" msgstr "" -#: ../src/client/application/geary-args.vala:21 +#: src/client/application/geary-args.vala:21 msgid "Log periodic activity" msgstr "" -#: ../src/client/application/geary-args.vala:22 +#: src/client/application/geary-args.vala:22 msgid "Log database queries (generates lots of messages)" msgstr "Lokita tietokantaan kohdistuvat kyselyt (luo paljon viestejä)" #. / "Normalization" can also be called "synchronization" -#: ../src/client/application/geary-args.vala:24 +#: src/client/application/geary-args.vala:24 msgid "Log folder normalization" msgstr "Lokita kansioiden normalisointi" -#: ../src/client/application/geary-args.vala:25 +#: src/client/application/geary-args.vala:25 msgid "Allow inspection of WebView" msgstr "" -#: ../src/client/application/geary-args.vala:26 +#: src/client/application/geary-args.vala:26 msgid "Revoke all server certificates with TLS warnings" msgstr "Kumoa kaikki palvelinvarmenteet, joissa on TLS-varoituksia" -#: ../src/client/application/geary-args.vala:27 +#: src/client/application/geary-args.vala:27 msgid "Perform a graceful quit" msgstr "Suorita siisti lopetus" -#: ../src/client/application/geary-args.vala:28 +#: src/client/application/geary-args.vala:28 msgid "Display program version" msgstr "Näytä sovelluksen versio" #. This gives a command-line hint on how to open new composer windows with mailto: -#: ../src/client/application/geary-args.vala:53 +#: src/client/application/geary-args.vala:53 #, c-format msgid "Use %s to open a new composer window" msgstr "Käytä %s uuden lähetysikkunan avaamista varten" -#: ../src/client/application/geary-args.vala:54 +#: src/client/application/geary-args.vala:56 msgid "Please report comments, suggestions and bugs to:" msgstr "Kommentoi, ehdota uusia ideoita ja ilmoita vioista:" #. i18n: Command line arguments are invalid -#: ../src/client/application/geary-args.vala:61 +#: src/client/application/geary-args.vala:63 #, c-format msgid "Failed to parse command line options: %s\n" msgstr "Komentorivivalintojen jäsennys epäonnistui: %s\n" -#: ../src/client/application/geary-args.vala:72 +#: src/client/application/geary-args.vala:74 #, c-format msgid "Unrecognized command line option “%s”\n" msgstr "" "Tunnistamaton komentorivivalinta “%s”\n" "\n" -#: ../src/client/application/geary-controller.vala:57 -msgid "Delete conversation" -msgstr "Poista keskustelu" +#. Translators: File name used in save chooser when saving +#. attachments that do not otherwise have a name. +#: src/client/application/geary-controller.vala:61 +msgid "Untitled" +msgstr "Nimetön" -#: ../src/client/application/geary-controller.vala:58 -msgid "Delete conversation (Shift+Delete)" -msgstr "Poista keskustelu (Shift+Delete)" - -#: ../src/client/application/geary-controller.vala:59 -msgid "Delete conversations (Shift+Delete)" -msgstr "Poista keskustelut (Shift+Delete)" - -#. This refers to the action ("move email to the trash"), not the Trash folder itself -#: ../src/client/application/geary-controller.vala:63 -msgid "Move conversation to Trash (Delete, Backspace)" -msgstr "Siirrä keskustelu roskakoriin (Delete, Backspace)" - -#: ../src/client/application/geary-controller.vala:64 -msgid "Move conversations to Trash (Delete, Backspace)" -msgstr "Siirrä keskustelut roskakoriin (Delete, Backspace)" - -#. This refers to the action ("archive an email"), not the Archive folder itself -#: ../src/client/application/geary-controller.vala:68 -msgid "_Archive" -msgstr "_Arkistoi" - -#: ../src/client/application/geary-controller.vala:69 -msgid "Archive conversation (A)" -msgstr "Arkistoi keskustelu (A)" - -#: ../src/client/application/geary-controller.vala:70 -msgid "Archive conversations (A)" -msgstr "Arkistoi keskustelut (A)" - -#: ../src/client/application/geary-controller.vala:73 -msgid "Mark as S_pam" -msgstr "Me_rkitse roskapostiksi" - -#: ../src/client/application/geary-controller.vala:74 -msgid "Mark as not S_pam" -msgstr "Poista _roskapostimerkintä" - -#: ../src/client/application/geary-controller.vala:76 -#: ../src/client/application/geary-controller.vala:448 -msgid "Mark conversation" -msgstr "Merkitse keskustelu" - -#: ../src/client/application/geary-controller.vala:77 -msgid "Mark conversations" -msgstr "Merkitse keskustelut" - -#: ../src/client/application/geary-controller.vala:78 -msgid "Add label to conversation" -msgstr "Lisää tunniste keskusteluun" - -#: ../src/client/application/geary-controller.vala:79 -msgid "Add label to conversations" -msgstr "Lisää tunniste keskusteluihin" - -#: ../src/client/application/geary-controller.vala:80 -#: ../src/client/application/geary-controller.vala:487 -msgid "Move conversation" -msgstr "Siirrä keskustelu" - -#: ../src/client/application/geary-controller.vala:81 -msgid "Move conversations" -msgstr "Siirrä keskustelut" - -#: ../src/client/application/geary-controller.vala:450 -msgid "_Mark as…" -msgstr "_Merkitse…" - -#: ../src/client/application/geary-controller.vala:456 -msgid "Mark as _Read" -msgstr "Merkitse _luetuksi" - -#: ../src/client/application/geary-controller.vala:462 -msgid "Mark as _Unread" -msgstr "Merkitse l_ukemattomaksi" - -#: ../src/client/application/geary-controller.vala:468 -msgid "_Star" -msgstr "_Merkitse tähdellä" - -#: ../src/client/application/geary-controller.vala:473 -msgid "U_nstar" -msgstr "_Poista tähti" - -#: ../src/client/application/geary-controller.vala:483 -msgid "Add label" -msgstr "Lisää tunniste" - -#: ../src/client/application/geary-controller.vala:484 -msgid "_Label" -msgstr "_Tunniste" - -#: ../src/client/application/geary-controller.vala:488 -msgid "_Move" -msgstr "_Siirrä" - -#: ../src/client/application/geary-controller.vala:492 -msgid "Compose new message (Ctrl+N, N)" -msgstr "Kirjoita uusi viesti (Ctrl+N, N)" - -#: ../src/client/application/geary-controller.vala:496 -#: ../ui/conversation-email-menus.ui.h:1 -msgid "_Reply" -msgstr "_Vastaa" - -#: ../src/client/application/geary-controller.vala:497 -msgid "Reply (Ctrl+R, R)" -msgstr "Vastaa (Ctrl+R, R)" - -#: ../src/client/application/geary-controller.vala:501 -msgid "R_eply All" -msgstr "_Vastaa kaikille" - -#: ../src/client/application/geary-controller.vala:502 -msgid "Reply all (Ctrl+Shift+R, Shift+R)" -msgstr "Vastaa kaikille (Ctrl+Shift+R, Shift+R)" - -#: ../src/client/application/geary-controller.vala:507 -#: ../ui/conversation-email-menus.ui.h:3 -msgid "_Forward" -msgstr "_Lähetä edelleen" - -#: ../src/client/application/geary-controller.vala:508 -msgid "Forward (Ctrl+L, F)" -msgstr "Lähetä edelleen (Ctrl+L, F)" - -#: ../src/client/application/geary-controller.vala:538 -msgid "Empty _Spam…" -msgstr "Tyhjennä ro_skapostikansio…" - -#: ../src/client/application/geary-controller.vala:542 -msgid "Empty _Trash…" -msgstr "Tyhjennä _roskakorikansio…" - -#. No callback is connected, since we bind the toggle button to the search bar visibility -#: ../src/client/application/geary-controller.vala:574 -msgid "Toggle search bar" -msgstr "Hakupalkki päälle/pois" - -#. No callback is connected, since we bind the toggle button to the find bar visibility -#: ../src/client/application/geary-controller.vala:579 -msgid "Toggle find bar" -msgstr "Hakupalkki päälle/pois" - -#: ../src/client/application/geary-controller.vala:757 -msgid "Unable to store server trust exception" -msgstr "Palvelimen luottamuspoikkeuksen varastointi epäonnistui" - -#: ../src/client/application/geary-controller.vala:992 -msgid "Your settings are insecure" -msgstr "Asetuksesi eivät ole turvalliset" - -#: ../src/client/application/geary-controller.vala:993 -msgid "" -"Your IMAP and/or SMTP settings do not specify SSL or TLS. This means your " -"username and password could be read by another person on the network. Are " -"you sure you want to do this?" -msgstr "" -"Käytössäsi olevat IMAP- ja/tai SMTP-asetukset eivät määritä salausasetuksesi " -"SSL:ää tai TLS:ää. Tämä tarkoittaa, että käyttäjätunnuksesi ja salasanasi on " -"mahdollista lukea verkkoliikenteestä. Haluatko varmasti sallia tämän?" - -#: ../src/client/application/geary-controller.vala:994 -msgid "Co_ntinue" -msgstr "_Jatka" - -#: ../src/client/application/geary-controller.vala:1041 -msgid "Error connecting to the server" -msgstr "Virhe yhdistäessä palvelimeen" - -#: ../src/client/application/geary-controller.vala:1042 -msgid "" -"Geary encountered an error while connecting to the server. Please try again " -"in a few moments." -msgstr "" -"Geary kohtasi virheen palvelimeen yhdistäessä. Yritä uudelleen hetken " -"kuluttua." - -#. / Displayed in the space-limited status bar when a message fails to be sent due to error. -#: ../src/client/application/geary-controller.vala:1079 -#: ../src/client/components/status-bar.vala:29 -msgid "Error sending email" -msgstr "Virhe sähköpostia lähettäessä" - -#: ../src/client/application/geary-controller.vala:1080 -msgid "" -"Geary encountered an error sending an email. If the problem persists, " -"please manually delete the email from your Outbox folder." -msgstr "" -"Geary kohtasi virheen viestiä lähettäessä. Jos ongelma toistuu, poista käsin " -"kyseinen viesti Lähtevät-kansiosta." - -#. Displayed in the space-limited status bar when a message fails to be uploaded -#. to Sent Mail after being sent. -#: ../src/client/application/geary-controller.vala:1084 -#: ../src/client/components/status-bar.vala:33 -msgid "Error saving sent mail" -msgstr "Virhe lähetettyä postia tallennettaessa" - -#: ../src/client/application/geary-controller.vala:1085 -msgid "" -"Geary encountered an error saving a sent message to Sent Mail. The message " -"will stay in your Outbox folder until you delete it." -msgstr "" -"Geary kohtasi virheen lähetettyä viestiä tallennettaessa Lähetetyt-" -"kansioon. Tämä viesti säilyy Lähtevät-kansiossa niin kauan, kunnes poistat " -"sen." - -#: ../src/client/application/geary-controller.vala:1154 +#: src/client/application/geary-controller.vala:924 msgid "Labels" msgstr "Tunnisteet" #. give the user two options: reset the Account local store, or exit Geary. A third #. could be done to leave the Account in an unopened state, but we don't currently #. have provisions for that. -#: ../src/client/application/geary-controller.vala:1166 +#: src/client/application/geary-controller.vala:937 #, c-format msgid "Unable to open the database for %s" msgstr "Kohteen %s tietokannan avaaminen epäonnistui" -#: ../src/client/application/geary-controller.vala:1167 +#: src/client/application/geary-controller.vala:938 #, c-format msgid "" "There was an error opening the local mail database for this account. This is " @@ -618,20 +915,20 @@ "niiden liitteet. Toimenpiteellä ei ole vaikutusta palvelimella oleviin " "viesteihin ja liitteisiin." -#: ../src/client/application/geary-controller.vala:1169 +#: src/client/application/geary-controller.vala:940 msgid "_Rebuild" msgstr "Ra_kenna uudelleen" -#: ../src/client/application/geary-controller.vala:1169 +#: src/client/application/geary-controller.vala:940 msgid "E_xit" msgstr "_Poistu" -#: ../src/client/application/geary-controller.vala:1178 +#: src/client/application/geary-controller.vala:949 #, c-format msgid "Unable to rebuild database for “%s”" msgstr "Tietokannan jälleenrakennus tilille “%s” epäonnistui" -#: ../src/client/application/geary-controller.vala:1179 +#: src/client/application/geary-controller.vala:950 #, c-format msgid "" "Error during rebuild:\n" @@ -642,69 +939,15 @@ "\n" "%s" -#. some other problem opening the account ... as with other flow path, can't run -#. Geary today with an account in unopened state, so have to exit -#: ../src/client/application/geary-controller.vala:1201 -#: ../src/client/application/geary-controller.vala:1211 -#: ../src/client/application/geary-controller.vala:1222 -#, c-format -msgid "Unable to open local mailbox for %s" -msgstr "Paikallisen postilaatikon avaaminen tilillä %s epäonnistui" - -#: ../src/client/application/geary-controller.vala:1202 -#, c-format -msgid "" -"There was an error opening the local mail database for this account. This is " -"possibly due to a file permissions problem.\n" -"\n" -"Please check that you have read/write permissions for all files in this " -"directory:\n" -"\n" -"%s" -msgstr "" -"Tämän tilin paikallista viestitietokantaa avattaessa tapahtui virhe. Se " -"johtuu mitä luultavimmin puutteellisista käyttöoikeuksista.\n" -"\n" -"Varmista, että sinulla on luku- ja kirjoitusoikeus kaikkiin tiedostoihin " -"seuraavassa kansiossa:\n" -"\n" -"%s" - -#: ../src/client/application/geary-controller.vala:1212 -msgid "" -"The version number of the local mail database is formatted for a newer " -"version of Geary. Unfortunately, the database cannot be “rolled back” to " -"work with this version of Geary.\n" -"\n" -"Please install the latest version of Geary and try again." -msgstr "" -"Paikallinen viestitietokanta on muotoiltu uudemmalle Geary-versiolle. " -"Valitettavasti tietokantaa ei voi “palauttaa menneisyyteen”, jotta se " -"toimisi tämän Geary-version kanssa.\n" -"\n" -"Asenna uusin version Gearysta ja yritä uudelleen." - -#: ../src/client/application/geary-controller.vala:1223 -msgid "" -"There was an error opening the local account. This is probably due to " -"connectivity issues.\n" -"\n" -"Please check your network connection and restart Geary." -msgstr "" -"Paikallista tiliä avattaessa tapahtui virhe. Se johtuu luultavasti verkko-" -"ongelmista.\n" -"\n" -"Tarkista yhteyden tila ja käynnistä Geary uudelleen." - -#: ../src/client/application/geary-controller.vala:1999 +#: src/client/application/geary-controller.vala:1803 msgid "Undo move (Ctrl+Z)" msgstr "Kumoa siirtäminen (Ctrl+Z)" -#: ../src/client/application/geary-controller.vala:2009 +#: src/client/application/geary-controller.vala:1813 msgid "Are you sure you want to open these attachments?" msgstr "Haluatko varmasti avata nämä liitteet?" -#: ../src/client/application/geary-controller.vala:2010 +#: src/client/application/geary-controller.vala:1814 msgid "" "Attachments may cause damage to your system if opened. Only open files from " "trusted sources." @@ -712,16 +955,22 @@ "Liitetiedostot saattavat sisältää haitallista sisältöä. Avaa liitteet " "ainoastaan, jos luotat niiden lähettäjään." -#: ../src/client/application/geary-controller.vala:2011 +#: src/client/application/geary-controller.vala:1815 msgid "Don’t _ask me again" msgstr "_Älä kysy uudelleen" -#: ../src/client/application/geary-controller.vala:2121 +#. Translators: Dialog primary label when prompting to +#. overwrite a file. The string substitution is the file'sx +#. name. +#: src/client/application/geary-controller.vala:1944 #, c-format msgid "A file named “%s” already exists. Do you want to replace it?" msgstr "Tiedosto nimellä “%s” on jo olemassa. Haluatko korvata sen?" -#: ../src/client/application/geary-controller.vala:2123 +#. Translators: Dialog secondary label when prompting to +#. overwrite a file. The string substitution is the parent +#. folder's name. +#: src/client/application/geary-controller.vala:1951 #, c-format msgid "" "The file already exists in “%s”. Replacing it will overwrite its contents." @@ -729,183 +978,457 @@ "Tiedosto on jo olemassa kohteessa “%s”. Tiedoston korvaaminen korvaa " "nykyisen tiedoston sisällön." -#: ../src/client/application/geary-controller.vala:2126 +#: src/client/application/geary-controller.vala:1955 msgid "_Replace" msgstr "_Korvaa" -#. Find out what to do with the inline composers. -#. TODO: Remove this in favor of automatically saving drafts -#: ../src/client/application/geary-controller.vala:2374 -msgid "Close open draft messages?" -msgstr "Suljetaanko luonnosviesti?" +#: src/client/application/geary-controller.vala:2231 +msgid "Close the draft message?" +msgid_plural "Close all draft messages?" +msgstr[0] "Suljetaanko luonnosviesti?" +msgstr[1] "Suljetaanko kaikki luonnosviestit?" -#: ../src/client/application/geary-controller.vala:2496 +#: src/client/application/geary-controller.vala:2357 #, c-format msgid "Empty all email from your %s folder?" msgstr "Poistetaanko kaikki viesit \"%s\"-kansiosta?" -#: ../src/client/application/geary-controller.vala:2497 +#: src/client/application/geary-controller.vala:2358 msgid "This removes the email from Geary and your email server." msgstr "Tämä poistaa viestit Gearysta ja sähköpostipalvelimelta." -#: ../src/client/application/geary-controller.vala:2498 +#: src/client/application/geary-controller.vala:2359 msgid "This cannot be undone." msgstr "Toimintoa ei voi perua." -#: ../src/client/application/geary-controller.vala:2499 +#: src/client/application/geary-controller.vala:2360 #, c-format msgid "Empty %s" msgstr "Tyhjennä %s" -#: ../src/client/application/geary-controller.vala:2516 +#: src/client/application/geary-controller.vala:2377 #, c-format msgid "Error emptying %s" msgstr "Virhe tyhjennettäessä kohdetta %s" -#: ../src/client/application/geary-controller.vala:2546 +#: src/client/application/geary-controller.vala:2409 msgid "Do you want to permanently delete this message?" msgid_plural "Do you want to permanently delete these messages?" msgstr[0] "Haluatko poistaa tämän viestin pysyvästi?" msgstr[1] "Haluatko poistaa nämä viestit pysyvästi?" -#: ../src/client/application/geary-controller.vala:2548 +#: src/client/application/geary-controller.vala:2411 msgid "Delete" msgstr "Poista" -#: ../src/client/application/geary-controller.vala:2580 -msgid "Undo archive (Ctrl+Z)" -msgstr "Kumoa arkistointi (Ctrl+Z)" - -#: ../src/client/application/geary-controller.vala:2595 +#: src/client/application/geary-controller.vala:2425 msgid "Undo trash (Ctrl+Z)" msgstr "Kumoa roskakoriin siirto (Ctrl+Z)" -#: ../src/client/application/geary-controller.vala:2649 +#: src/client/application/geary-controller.vala:2475 +msgid "Undo archive (Ctrl+Z)" +msgstr "Kumoa arkistointi (Ctrl+Z)" + +#: src/client/application/geary-controller.vala:2520 msgid "Undo (Ctrl+Z)" msgstr "Kumoa (Ctrl+Z)" -#: ../src/client/application/geary-controller.vala:2780 +#. Translators: The label for an in-app notification. The +#. string substitution is a list of recipients of the email. +#: src/client/application/geary-controller.vala:2597 +#, c-format +msgid "Successfully sent mail to %s." +msgstr "Lähetetty onnistuneesti sähköpostia vastaanottajille %s." + +#: src/client/application/geary-controller.vala:2679 msgid "Failed to open default text editor." msgstr "Oletustekstimuokkaimen avaus epäonnistui." -#: ../src/client/components/main-window.vala:389 +#. Translators: Tooltip used when an entry requires a valid +#. email address to be entered, but one is not provided. +#: src/client/components/components-validator.vala:341 +msgid "An email address is required" +msgstr "Sähköpostiosoite vaaditaan" + +#. Translators: Tooltip used when an entry requires a valid +#. email address to be entered, but the address is invalid. +#: src/client/components/components-validator.vala:345 +msgid "Not a valid email address" +msgstr "Virheellinen sähköpostiosoite" + +#. Translators: Tooltip used when an entry requires a valid, +#. resolvable server name to be entered, but one is not +#. provided. +#: src/client/components/components-validator.vala:391 +msgid "A server name is required" +msgstr "Palvelimen nimi vaaditaan" + +#. Translators: Tooltip used when an entry requires a valid +#. server name to be entered, but it was unable to be +#. looked-up in the DNS. +#: src/client/components/components-validator.vala:396 +msgid "Could not look up server name" +msgstr "Palvelimen nimeä ei voitu kysellä" + +#: src/client/components/main-toolbar.vala:151 +msgid "Mark conversation" +msgid_plural "Mark conversations" +msgstr[0] "Merkitse keskustelu" +msgstr[1] "Merkitse keskustelut" + +#: src/client/components/main-toolbar.vala:156 +msgid "Add label to conversation" +msgid_plural "Add label to conversations" +msgstr[0] "Lisää tunniste keskusteluun" +msgstr[1] "Lisää tunniste keskusteluihin" + +#: src/client/components/main-toolbar.vala:161 +msgid "Move conversation" +msgid_plural "Move conversations" +msgstr[0] "Siirrä keskustelu" +msgstr[1] "Siirrä keskustelut" + +#: src/client/components/main-toolbar.vala:166 +msgid "Archive conversation (A)" +msgid_plural "Archive conversations (A)" +msgstr[0] "Arkistoi keskustelu (A)" +msgstr[1] "Arkistoi keskustelut (A)" + +#: src/client/components/main-toolbar.vala:175 +msgid "Move conversation to Trash (Delete, Backspace)" +msgid_plural "Move conversations to Trash (Delete, Backspace)" +msgstr[0] "Siirrä keskustelu roskakoriin (Delete, Backspace)" +msgstr[1] "Siirrä keskustelut roskakoriin (Delete, Backspace)" + +#: src/client/components/main-toolbar.vala:183 +msgid "Delete conversation (Shift+Delete)" +msgid_plural "Delete conversations (Shift+Delete)" +msgstr[0] "Poista keskustelu (Shift+Delete)" +msgstr[1] "Poista keskustelut (Shift+Delete)" + +#: src/client/components/main-window.vala:503 #, c-format msgid "%s (%d)" msgstr "%s (%d)" -#: ../src/client/components/search-bar.vala:8 -#: ../src/client/folder-list/folder-list-search-branch.vala:38 -#: ../src/engine/api/geary-special-folder-type.vala:51 +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:47 +#, c-format +msgid "Problem connecting to incoming server for %s" +msgstr "Virhe yhdistäessä tilin %s saapuvan postin palvelimeen" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:49 +#: src/client/components/main-window-info-bar.vala:58 +#, c-format +msgid "" +"Could not connect to %s, check your Internet access and the server name and " +"try again" +msgstr "" +"Ei voitu yhdistää palvelimeen %s. Tarkista internetyhteytesi tila ja " +"palvelimen nimi, yritä sen jälkeen uudelleen" + +#. Translators: Tooltip label for Retry button +#. Button tooltip for retrying an account problem +#: src/client/components/main-window-info-bar.vala:51 +#: src/client/components/main-window-info-bar.vala:60 +#: src/client/components/main-window-info-bar.vala:69 +#: src/client/components/main-window-info-bar.vala:78 +#: src/client/components/main-window-info-bar.vala:87 +#: src/client/components/main-window-info-bar.vala:96 +#: src/client/components/main-window-info-bar.vala:136 ui/main-window.ui:265 +msgid "Try reconnecting" +msgstr "Yritä yhdistää uudelleen" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:56 +#, c-format +msgid "Problem connecting to outgoing server for %s" +msgstr "Virhe yhdistäessä tilin %s lähtevän postin palvelimeen" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:65 +#: src/client/components/main-window-info-bar.vala:83 +#, c-format +msgid "Problem communicating with incoming server for %s" +msgstr "Ongelma oltaessa yhteydessä tilin %s saapuvan postin palvelimeen" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:67 +#: src/client/components/main-window-info-bar.vala:76 +#, c-format +msgid "Network error talking to %s, check your Internet access and try again" +msgstr "" +"Verkkovirhe palvelimeen %s. Tarkista internetyhteytesi tila ja yritä " +"uudelleen" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:74 +#: src/client/components/main-window-info-bar.vala:91 +msgid "Problem communicating with outgoing mail server" +msgstr "Ongelma oltaessa yhteydessä lähtevän postin palvelimeen" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:85 +#, c-format +msgid "" +"Geary did not understand a message from %s or vice versa, please file a bug " +"report" +msgstr "" +"Geary ei ymmärtänyt palvelimen %s viestiä tai palvelin ei ymmärtänyt Gearyn " +"viestiä.. Ilmoita tästä ongelmasta." + +#. Translators: First string substitution is the server +#. name, second is the account name +#: src/client/components/main-window-info-bar.vala:94 +#, c-format +msgid "" +"Could not communicate with %s for %s, check the server name and try again in " +"a moment" +msgstr "" +"Yhteys palvelimeen %s tilillä %s asiointia varten ei onnistunut, tarkista " +"palvelimen nimi ja yritä uudelleen hetken kuluttua" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:101 +#, c-format +msgid "Incoming mail server password required for %s" +msgstr "Tilin %s saapuvan postin palvelin vaatii salasanan" + +#: src/client/components/main-window-info-bar.vala:102 +msgid "Messages cannot be received without the correct password." +msgstr "Viestejä ei voi vastaanottaa ilman oikeaa salasanaa." + +#. Translators: Tooltip label for Retry button +#: src/client/components/main-window-info-bar.vala:104 +msgid "Retry receiving email, you will be prompted for a password" +msgstr "" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:109 +#, c-format +msgid "Outgoing mail server password required for %s" +msgstr "Tilin %s salasana vaaditaan lähtevän postin palvelimelle" + +#: src/client/components/main-window-info-bar.vala:110 +msgid "Messages cannot be sent without the correct password." +msgstr "Viestejä ei voi lähettää ilman oikeaa salasanaa." + +#. Translators: Tooltip label for Retry button +#: src/client/components/main-window-info-bar.vala:112 +msgid "Retry sending queued messages, you will be prompted for a password" +msgstr "" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:117 +#, c-format +msgid "Incoming mail server security is not trusted for %s" +msgstr "Tilin %s saapuvan postin palvelimen turvallisuuteen ei luoteta" + +#: src/client/components/main-window-info-bar.vala:118 +msgid "Messages will not be received until checked." +msgstr "Viestejä ei voi vastaanottaa ennen tarkistusta." + +#. Translators: Tooltip label for Retry button +#: src/client/components/main-window-info-bar.vala:120 +#: src/client/components/main-window-info-bar.vala:128 +msgid "Check security details" +msgstr "Tarkista salaustiedot" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:125 +#, c-format +msgid "Outgoing mail server security is not trusted for %s" +msgstr "Tilin %s lähtevän postin palvelimen turvallisuuteen ei luoteta" + +#: src/client/components/main-window-info-bar.vala:126 +msgid "Messages cannot be sent until checked." +msgstr "Viestejä ei voi lähettää ennen tarkistusta." + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:133 +#, c-format +msgid "A problem occurred checking mail for %s" +msgstr "Tilin %s viestejä tarkastaessa tapahtui virhe" + +#: src/client/components/main-window-info-bar.vala:134 +#: src/client/components/main-window-info-bar.vala:142 +msgid "Something went wrong, please file a bug report if the problem persists" +msgstr "Jokin meni vikaan. Tee vikailmoitus, jos ongelma ilmenee uudelleen" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:141 +#, c-format +msgid "A problem occurred sending mail for %s" +msgstr "Tilin %s viestejä lähettäessä tapahtui virhe" + +#. Translators: Tooltip label for Retry button +#: src/client/components/main-window-info-bar.vala:144 +msgid "Retry sending queued messages" +msgstr "Yritä lähettää uudelleen jonossa olevat viestit" + +#: src/client/components/main-window-info-bar.vala:155 +msgid "A database problem has occurred" +msgstr "Havaittiin tietokantaongelma" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:157 +#, c-format +msgid "Messages for %s must be downloaded again." +msgstr "Tilin %s viestit tulee ladata uudelleen." + +#: src/client/components/main-window-info-bar.vala:170 +msgid "Geary has encountered a problem" +msgstr "Geary kohtasi ongelman" + +#: src/client/components/main-window-info-bar.vala:171 +msgid "" +"Please check the technical details and report the problem if it persists." +msgstr "" +"Katso tekniset yksityiskohdat ja ilmoita ongelmasta, jos se ilmenee " +"uudelleen." + +#: src/client/components/main-window-info-bar.vala:179 +msgid "_Details" +msgstr "_Tiedot" + +#. Button tooltip for displaying technical details about an account problem +#: src/client/components/main-window-info-bar.vala:180 ui/main-window.ui:250 +msgid "View technical details about the error" +msgstr "Näytä virheen tekniset yksityiskohdat" + +#: src/client/components/main-window-info-bar.vala:184 +msgid "_Retry" +msgstr "_Yritä uudelleen" + +#: src/client/components/search-bar.vala:8 +#: src/client/folder-list/folder-list-search-branch.vala:38 +#: src/engine/api/geary-special-folder-type.vala:51 msgid "Search" msgstr "Etsi" #. Search entry. -#: ../src/client/components/search-bar.vala:23 +#: src/client/components/search-bar.vala:23 msgid "Search all mail in account for keywords (Ctrl+S)" msgstr "Etsi hakusanoilla kaikista tilin sähköpostiviesteistä (Ctrl+S)" -#: ../src/client/components/search-bar.vala:100 +#: src/client/components/search-bar.vala:101 #, c-format msgid "Indexing %s account" msgstr "Indeksoidaan tiliä %s" -#: ../src/client/components/search-bar.vala:111 -#: ../src/client/folder-list/folder-list-search-branch.vala:39 +#: src/client/components/search-bar.vala:112 +#: src/client/folder-list/folder-list-search-branch.vala:39 #, c-format msgid "Search %s account" msgstr "Etsi tililtä %s" #. / Displayed in the space-limited status bar while a message is in the process of being sent. -#: ../src/client/components/status-bar.vala:26 +#: src/client/components/status-bar.vala:26 msgid "Sending…" msgstr "Lähetetään…" -#: ../src/client/components/stock.vala:18 ../ui/account_cannot_remove.glade.h:3 +#. / Displayed in the space-limited status bar when a message fails to be sent due to error. +#: src/client/components/status-bar.vala:29 +msgid "Error sending email" +msgstr "Virhe sähköpostia lähettäessä" + +#. Displayed in the space-limited status bar when a message fails to be uploaded +#. to Sent Mail after being sent. +#: src/client/components/status-bar.vala:33 +msgid "Error saving sent mail" +msgstr "Virhe lähetettyä postia tallennettaessa" + +#: src/client/components/stock.vala:18 msgid "_OK" msgstr "_OK" -#: ../src/client/components/stock.vala:19 ../ui/edit_alternate_emails.glade.h:3 -#: ../ui/password-dialog.glade.h:5 ../ui/remove_confirm.glade.h:5 +#: src/client/components/stock.vala:19 ui/password-dialog.glade:196 msgid "_Cancel" msgstr "_Peru" -#: ../src/client/components/stock.vala:21 ../ui/gtk/menus.ui.h:6 +#: src/client/components/stock.vala:21 ui/gtk/menus.ui:34 msgid "_About" msgstr "_Tietoja" -#: ../src/client/components/stock.vala:23 +#: src/client/components/stock.vala:22 +msgid "_Add" +msgstr "_Lisää" + +#: src/client/components/stock.vala:23 msgid "_Close" msgstr "_Sulje" -#: ../src/client/components/stock.vala:24 +#: src/client/components/stock.vala:24 msgid "_Discard" msgstr "_Hylkää" -#: ../src/client/components/stock.vala:25 ../ui/gtk/menus.ui.h:5 +#: src/client/components/stock.vala:25 ui/gtk/menus.ui:29 msgid "_Help" msgstr "_Ohje" -#: ../src/client/components/stock.vala:26 ../ui/conversation-email-menus.ui.h:9 +#: src/client/components/stock.vala:26 ui/conversation-email-menus.ui:77 msgid "_Open" msgstr "_Avaa" -#: ../src/client/components/stock.vala:27 ../ui/gtk/menus.ui.h:3 +#: src/client/components/stock.vala:27 ui/gtk/menus.ui:17 msgid "_Preferences" msgstr "_Asetukset" -#: ../src/client/components/stock.vala:28 ../ui/conversation-email-menus.ui.h:7 +#. Translators: Menu item to print a single, specific message +#: src/client/components/stock.vala:28 ui/conversation-email-menus.ui:64 msgid "_Print…" msgstr "_Tulosta…" -#: ../src/client/components/stock.vala:29 ../ui/gtk/menus.ui.h:7 +#: src/client/components/stock.vala:29 ui/gtk/menus.ui:38 msgid "_Quit" msgstr "_Lopeta" -#: ../src/client/components/stock.vala:30 ../ui/remove_confirm.glade.h:6 +#: src/client/components/stock.vala:30 msgid "_Remove" msgstr "_Poista" -#: ../src/client/components/stock.vala:32 +#: src/client/components/stock.vala:31 ui/conversation-email-menus.ui:83 +msgid "_Save" +msgstr "_Tallenna" + +#: src/client/components/stock.vala:32 msgid "_Keep" msgstr "Säil_ytä" -#: ../src/client/composer/composer-link-popover.vala:150 +#: src/client/composer/composer-link-popover.vala:149 msgid "Link URL is not correctly formatted, e.g. http://example.com" msgstr "Linkin osoite ei ole muotoiltu kelvollisesti, esim. http://example.com" -#: ../src/client/composer/composer-link-popover.vala:157 +#: src/client/composer/composer-link-popover.vala:156 msgid "Invalid link URL" msgstr "Virheellinen linkin osoite" -#: ../src/client/composer/composer-link-popover.vala:157 +#: src/client/composer/composer-link-popover.vala:156 msgid "Invalid email address" msgstr "Virheellinen sähköpostiosoite" -#: ../src/client/composer/composer-widget.vala:154 +#: src/client/composer/composer-widget.vala:157 msgid "Saved" msgstr "Tallennettu" -#: ../src/client/composer/composer-widget.vala:155 +#: src/client/composer/composer-widget.vala:158 msgid "Saving" msgstr "Tallennetaan" -#: ../src/client/composer/composer-widget.vala:156 +#: src/client/composer/composer-widget.vala:159 msgid "Error saving" msgstr "Virhe tallentaessa" -#: ../src/client/composer/composer-widget.vala:157 +#: src/client/composer/composer-widget.vala:160 msgid "Press Backspace to delete quote" msgstr "Paina askelpalautinta poistaaksesi lainauksen" -#: ../src/client/composer/composer-widget.vala:158 -msgid "New Message" -msgstr "Uusi viesti" - #. Translators: This is list of keywords, separated by pipe ("|") #. characters, that suggest an attachment; since this is full-word #. checking, include all variants of each word. No spaces are #. allowed. -#: ../src/client/composer/composer-widget.vala:167 +#: src/client/composer/composer-widget.vala:169 msgid "" "attach|attaching|attaches|attachment|attachments|attached|enclose|enclosed|" "enclosing|encloses|enclosure|enclosures" @@ -914,28 +1437,37 @@ "enclosing|encloses|enclosure|enclosures|liite|liitetiedosto|liitteen|" "liitetiedostot|liitän" -#: ../src/client/composer/composer-widget.vala:1122 -#: ../src/client/composer/composer-widget.vala:1141 -msgid "Do you want to discard this message?" -msgstr "Haluatko hylätä tämän viestin?" +#. Translators: This dialog text is displayed to the +#. user when closing a composer where the options are +#. Keep, Discard or Cancel. +#: src/client/composer/composer-widget.vala:1128 +msgid "Do you want to keep or discard this draft message?" +msgstr "Haluatko säilyttää vai hylätä tämän luonnosviestin?" + +#. Translators: This dialog text is displayed to the +#. user when closing a composer where the options are +#. only Discard or Cancel. +#: src/client/composer/composer-widget.vala:1156 +msgid "Do you want to discard this draft message?" +msgstr "Haluatko hylätä tämän luonnosviestin?" -#: ../src/client/composer/composer-widget.vala:1245 +#: src/client/composer/composer-widget.vala:1273 msgid "Send message with an empty subject and body?" msgstr "Lähetetäänkö viesti ilman aihetta ja sisältöä?" -#: ../src/client/composer/composer-widget.vala:1247 +#: src/client/composer/composer-widget.vala:1275 msgid "Send message with an empty subject?" msgstr "Lähetetäänkö viesti ilman aihetta?" -#: ../src/client/composer/composer-widget.vala:1249 +#: src/client/composer/composer-widget.vala:1277 msgid "Send message with an empty body?" msgstr "Lähetetäänkö viesti ilman sisältöä?" -#: ../src/client/composer/composer-widget.vala:1253 +#: src/client/composer/composer-widget.vala:1281 msgid "Send message without an attachment?" msgstr "Lähetetäänkö viesti ilman liitettä?" -#: ../src/client/composer/composer-widget.vala:1515 +#: src/client/composer/composer-widget.vala:1586 #, c-format msgid "“%s” already attached for delivery." msgstr "“%s” on jo liitetty lähetystä varten." @@ -945,169 +1477,261 @@ #. description of the document type, the second will #. be a human-friendly size string. For example: #. Document (100.9MB) -#: ../src/client/composer/composer-widget.vala:1523 -#: ../src/client/conversation-viewer/conversation-email.vala:138 +#: src/client/composer/composer-widget.vala:1594 +#: src/client/conversation-viewer/conversation-email.vala:173 #, c-format msgid "%s (%s)" msgstr "%s (%s)" -#: ../src/client/composer/composer-widget.vala:1560 +#: src/client/composer/composer-widget.vala:1631 #, c-format msgid "“%s” could not be found." msgstr "Kohdetta “%s” ei löytynyt." -#: ../src/client/composer/composer-widget.vala:1566 +#: src/client/composer/composer-widget.vala:1637 #, c-format msgid "“%s” is a folder." msgstr "“%s” on kansio." -#: ../src/client/composer/composer-widget.vala:1572 +#: src/client/composer/composer-widget.vala:1643 #, c-format msgid "“%s” is an empty file." msgstr "“%s” on tyhjä tiedosto." -#: ../src/client/composer/composer-widget.vala:1585 +#: src/client/composer/composer-widget.vala:1656 #, c-format msgid "“%s” could not be opened for reading." msgstr "Kohteen “%s” avaus lukua varten epäonnistui." -#: ../src/client/composer/composer-widget.vala:1593 +#: src/client/composer/composer-widget.vala:1664 msgid "Cannot add attachment" msgstr "Liitteen lisäys epäonnistui" -#: ../src/client/composer/composer-widget.vala:1645 -msgid "To: " +#. Translators: Human-readable version of the RFC 822 To header +#: src/client/composer/composer-widget.vala:1714 +#: src/client/conversation-viewer/conversation-email.vala:975 +#: src/engine/rfc822/rfc822-utils.vala:298 ui/conversation-message.ui:313 +msgid "To:" msgstr "Vastaanottaja:" -#: ../src/client/composer/composer-widget.vala:1648 -msgid "Cc: " +#. Translators: Human-readable version of the RFC 822 CC header +#: src/client/composer/composer-widget.vala:1720 +#: src/client/conversation-viewer/conversation-email.vala:980 +#: src/engine/rfc822/rfc822-utils.vala:303 ui/conversation-message.ui:358 +msgid "Cc:" msgstr "Kopio:" -#: ../src/client/composer/composer-widget.vala:1651 -msgid "Bcc: " +#. Translators: Human-readable version of the RFC 822 BCC header +#: src/client/composer/composer-widget.vala:1726 +#: src/client/conversation-viewer/conversation-email.vala:985 +#: ui/conversation-message.ui:403 +msgid "Bcc:" msgstr "Piilokopio:" -#: ../src/client/composer/composer-widget.vala:1654 +#. Translators: Human-readable version of the RFC 822 Reply-To header +#: src/client/composer/composer-widget.vala:1732 msgid "Reply-To: " -msgstr "Vastausosoite:" +msgstr "Vastausosoite: " -#: ../src/client/composer/composer-widget.vala:1786 +#: src/client/composer/composer-widget.vala:1872 msgid "Select Color" msgstr "Valitse väri" -#. Displayed in the From dropdown to indicate an "alternate email address" -#. for an account. The first printf argument will be the alternate email -#. address, and the second will be the account's primary email address. -#: ../src/client/composer/composer-widget.vala:1986 +#. Displayed in the From dropdown to indicate an +#. "alternate email address" for an account. The first +#. printf argument will be the alternate email address, +#. and the second will be the account's primary email +#. address. +#: src/client/composer/composer-widget.vala:2062 #, c-format msgid "%1$s via %2$s" msgstr "%1$s välityksellä %2$s" #. Composer label (with mnemonic underscore) for the account selector #. when choosing what address to send a message from. -#: ../src/client/composer/composer-widget.vala:2028 +#: src/client/composer/composer-widget.vala:2121 msgid "_From:" msgstr "_Lähettäjä:" #. Translators: This is the name of the file chooser filter #. when inserting an image in the composer. -#: ../src/client/composer/composer-widget.vala:2252 +#: src/client/composer/composer-widget.vala:2349 msgid "Images" msgstr "Kuvat" -#: ../src/client/composer/spell-check-popover.vala:117 +#: src/client/composer/composer-window.vala:14 +msgid "New Message" +msgstr "Uusi viesti" + +#: src/client/composer/spell-check-popover.vala:117 msgid "Remove this language from the preferred list" msgstr "Poista tämä kieli suositeltujen listalta" -#: ../src/client/composer/spell-check-popover.vala:121 +#: src/client/composer/spell-check-popover.vala:121 msgid "Add this language to the preferred list" msgstr "Lisää tämä kieli suositeltujen listalle" -#: ../src/client/composer/spell-check-popover.vala:217 +#: src/client/composer/spell-check-popover.vala:217 msgid "Search for more languages" msgstr "Etsi lisää kieliä" -#: ../src/client/conversation-list/formatted-conversation-data.vala:11 +#: src/client/conversation-list/conversation-list-view.vala:283 +msgid "Delete conversation" +msgstr "Poista keskustelu" + +#: src/client/conversation-list/conversation-list-view.vala:286 +#: ui/main-toolbar-menus.ui:16 +msgid "Mark as _Read" +msgstr "Merkitse _luetuksi" + +#: src/client/conversation-list/conversation-list-view.vala:289 +#: ui/main-toolbar-menus.ui:20 +msgid "Mark as _Unread" +msgstr "Merkitse l_ukemattomaksi" + +#: src/client/conversation-list/conversation-list-view.vala:292 +#: ui/main-toolbar-menus.ui:28 +msgid "U_nstar" +msgstr "_Poista tähti" + +#: src/client/conversation-list/conversation-list-view.vala:294 +#: ui/main-toolbar-menus.ui:24 +msgid "_Star" +msgstr "_Merkitse tähdellä" + +#. Translators: Menu item to reply to a specific message. +#: src/client/conversation-list/conversation-list-view.vala:297 +#: ui/conversation-email-menus.ui:9 +msgid "_Reply" +msgstr "_Vastaa" + +#: src/client/conversation-list/conversation-list-view.vala:298 +msgid "R_eply All" +msgstr "_Vastaa kaikille" + +#. Translators: Menu item to forward a specific message. +#: src/client/conversation-list/conversation-list-view.vala:299 +#: ui/conversation-email-menus.ui:21 +msgid "_Forward" +msgstr "_Lähetä edelleen" + +#: src/client/conversation-list/formatted-conversation-data.vala:11 msgid "Me" msgstr "Minä" #. Translators: This is the file type displayed for #. attachments with unknown file types. -#: ../src/client/conversation-viewer/conversation-email.vala:124 +#: src/client/conversation-viewer/conversation-email.vala:159 msgid "Unknown" msgstr "Tuntematon" -#. Preview headers +#. Translators: Human-readable version of the RFC 822 From header +#: src/client/conversation-viewer/conversation-email.vala:970 +#: src/engine/rfc822/rfc822-utils.vala:289 +msgid "From:" +msgstr "Lähettäjä:" + +#. Translators: Human-readable version of the RFC 822 Date header +#: src/client/conversation-viewer/conversation-email.vala:990 +#: src/engine/rfc822/rfc822-utils.vala:294 +msgid "Date:" +msgstr "Päiväys:" + +#. Translators: Human-readable version of the RFC 822 Subject header +#: src/client/conversation-viewer/conversation-email.vala:995 +#: src/engine/rfc822/rfc822-utils.vala:292 +msgid "Subject:" +msgstr "Aihe:" + +#: src/client/conversation-viewer/conversation-message.vala:65 +msgid "This email address may have been forged" +msgstr "Tämä sähköpostiosoite saattaa olla väärennetty" + +#. Compact headers #. Translators: This is displayed in place of the from address #. when the message has no from address. -#: ../src/client/conversation-viewer/conversation-message.vala:331 +#: src/client/conversation-viewer/conversation-message.vala:394 msgid "No sender" msgstr "Ei lähettäjää" #. Translators: This separates multiple 'from' -#. addresses in the header preview for a message. -#: ../src/client/conversation-viewer/conversation-message.vala:586 +#. addresses in the compact header for a message. +#: src/client/conversation-viewer/conversation-message.vala:767 msgid ", " msgstr ", " #. Translators: This string is used as the HTML IMG ALT #. attribute value when displaying an inline image in an email #. that did not specify a file name. E.g. ImageCannot remove account " -msgstr "" -"Tilin poistaminen epäonnistui " +#: ui/accounts_editor_edit_pane.ui:201 +msgid "Settings" +msgstr "Asetukset" -#: ../ui/account_cannot_remove.glade.h:2 -msgid "" -"A composer window associated with this account is currently open. Send or " -"discard the message and try again." -msgstr "" -"Viestin lähetysikkuna tälle tilille on jo avoinna. Lähetä tai hylkää viesti, " -"ja yritä sitten uudelleen." +#. This is a button in the account settings to show server settings. +#: ui/accounts_editor_edit_pane.ui:243 ui/accounts_editor_servers_pane.ui:8 +msgid "Server Settings" +msgstr "Palvelinasetukset" + +#. This is the remove account button in the account settings. +#: ui/accounts_editor_edit_pane.ui:258 ui/accounts_editor_remove_pane.ui:23 +msgid "Remove Account" +msgstr "Poista tili" -#: ../ui/account_list.glade.h:1 -msgid "Add account" -msgstr "Lisää tili" +#: ui/accounts_editor_edit_pane.ui:262 ui/accounts_editor_remove_pane.ui:27 +msgid "Remove this account from Geary" +msgstr "Poista tämä tili Gearysta" -#: ../ui/account_list.glade.h:2 -msgid "Edit account" -msgstr "Muokkaa tiliä" +#: ui/accounts_editor_list_pane.ui:8 +msgid "Accounts" +msgstr "Tilit" -#: ../ui/account_list.glade.h:3 +#: ui/accounts_editor_list_pane.ui:63 +msgid "To get started, select an email provider below." +msgstr "Aloita valitsemalla sähköpostipalvelusi tarjoaja." + +#: ui/accounts_editor_list_pane.ui:76 +msgid "Welcome to Geary" +msgstr "Tervetuloa, käytössäsi on Geary" + +#. This title is shown to users when confirming if they want to remove an account. The string substitution is replaced with the account's name. +#: ui/accounts_editor_remove_pane.ui:73 +#, c-format +msgid "Confirm removing: %s" +msgstr "Vahvista poistaminen: %s" + +#: ui/accounts_editor_remove_pane.ui:91 +msgid "" +"Removing an account will remove it from Geary and delete locally cached " +"email data from your computer, but not from your service provider." +msgstr "" +"Tilin poistaminen poistaa tilin Gearysta ja poistaa kaikki paikallisesti " +"tallennetut sähköpostit, mutta jättää ne palvelimelle." + +#: ui/accounts_editor_remove_pane.ui:122 msgid "Remove account" msgstr "Poista tili" -#: ../ui/account_spinner.glade.h:1 -msgid "Please wait while Geary validates your account." -msgstr "Odota hetki, Geary tarkistaa tiliäsi." +#: ui/accounts_editor_servers_pane.ui:17 +msgid "Cancel" +msgstr "Peru" + +#: ui/accounts_editor_servers_pane.ui:47 +msgid "Apply" +msgstr "Toteuta" -#: ../ui/certificate_warning_dialog.glade.h:1 +#: ui/certificate_warning_dialog.glade:7 msgid "Untrusted Connection" msgstr "Ei-luotettu yhteys" -#: ../ui/certificate_warning_dialog.glade.h:2 +#: ui/certificate_warning_dialog.glade:29 msgid "_Always Trust This Server" msgstr "_Luota aina tähän palvelimeen" -#: ../ui/certificate_warning_dialog.glade.h:3 +#: ui/certificate_warning_dialog.glade:43 msgid "_Trust This Server" msgstr "Luota _tähän palvelimeen" -#: ../ui/certificate_warning_dialog.glade.h:4 +#: ui/certificate_warning_dialog.glade:57 msgid "_Don’t Trust This Server" msgstr "_Älä luota tähän palvelimeen" -#: ../ui/composer-headerbar.ui.h:1 +#: ui/composer-headerbar.ui:17 ui/composer-headerbar.ui:174 msgid "Detach (Ctrl+D)" msgstr "Irrota (Ctrl+D)" -#: ../ui/composer-headerbar.ui.h:2 +#: ui/composer-headerbar.ui:57 ui/composer-headerbar.ui:83 msgid "Attach File (Ctrl+T)" msgstr "Liitä tiedosto (Ctrl+T)" -#: ../ui/composer-headerbar.ui.h:3 +#: ui/composer-headerbar.ui:107 msgid "Include Original Attachments" msgstr "Sisällytä alkuperäiset liitteet" -#: ../ui/composer-headerbar.ui.h:4 -msgid "Send (Ctrl+Enter)" -msgstr "Lähetä (Ctrl+Enter)" - -#: ../ui/composer-headerbar.ui.h:5 +#: ui/composer-headerbar.ui:202 msgid "_Send" msgstr "_Lähetä" -#: ../ui/composer-headerbar.ui.h:6 +#: ui/composer-headerbar.ui:207 +msgid "Send (Ctrl+Enter)" +msgstr "Lähetä (Ctrl+Enter)" + +#: ui/composer-headerbar.ui:230 msgid "Discard and Close" msgstr "Hylkää ja sulje" -#: ../ui/composer-headerbar.ui.h:7 +#: ui/composer-headerbar.ui:254 msgid "Save and Close" msgstr "Tallenna ja sulje" #. Note that this button and the Update button will never be shown at the same time to the user. -#: ../ui/composer-link-popover.ui.h:2 +#: ui/composer-link-popover.ui:41 msgid "Insert the new link with this URL" msgstr "Lisää uusi linkki tällä osoitteella" -#: ../ui/composer-link-popover.ui.h:3 +#: ui/composer-link-popover.ui:52 msgid "Link URL" msgstr "Linkin osoite" #. Note that this button and the Insert button will never be shown at the same time to the user. -#: ../ui/composer-link-popover.ui.h:5 +#: ui/composer-link-popover.ui:66 msgid "Update this link’s URL" msgstr "Päivitä tämän linkin osoite" -#: ../ui/composer-link-popover.ui.h:6 +#: ui/composer-link-popover.ui:86 msgid "Delete this link" msgstr "Poista tämä linkki" -#: ../ui/composer-link-popover.ui.h:7 +#: ui/composer-link-popover.ui:106 msgid "Open this link" msgstr "Avaa tämä linkki" -#: ../ui/composer-menus.ui.h:1 +#: ui/composer-menus.ui:7 msgid "S_ans Serif" msgstr "_Päätteetön" -#: ../ui/composer-menus.ui.h:2 +#: ui/composer-menus.ui:12 msgid "S_erif" msgstr "P_äätteellinen" -#: ../ui/composer-menus.ui.h:3 +#: ui/composer-menus.ui:17 msgid "_Fixed Width" msgstr "_Tasalevyinen" -#: ../ui/composer-menus.ui.h:4 +#: ui/composer-menus.ui:24 msgid "_Small" msgstr "_Pieni" -#: ../ui/composer-menus.ui.h:5 +#: ui/composer-menus.ui:29 msgid "_Medium" msgstr "Ta_vallinen" -#: ../ui/composer-menus.ui.h:6 +#: ui/composer-menus.ui:34 msgid "Lar_ge" msgstr "_Suuri" -#: ../ui/composer-menus.ui.h:7 +#: ui/composer-menus.ui:41 msgid "C_olor" msgstr "Vä_ri" -#: ../ui/composer-menus.ui.h:8 +#: ui/composer-menus.ui:47 ui/composer-menus.ui:62 msgid "_Rich Text" msgstr "Muotoilu - _RTF" -#: ../ui/composer-menus.ui.h:9 +#: ui/composer-menus.ui:53 ui/composer-menus.ui:68 msgid "Show Extended Fields" msgstr "Näytä laajennetut kentät" -#: ../ui/composer-menus.ui.h:10 +#: ui/composer-menus.ui:78 msgid "_Undo" msgstr "_Kumoa" -#: ../ui/composer-menus.ui.h:11 +#: ui/composer-menus.ui:82 msgid "_Redo" msgstr "_Tee uudelleen" -#: ../ui/composer-menus.ui.h:12 +#: ui/composer-menus.ui:88 ui/composer-menus.ui:106 msgid "Cu_t" msgstr "_Leikkaa" -#: ../ui/composer-menus.ui.h:13 ../ui/conversation-message-menus.ui.h:7 +#: ui/composer-menus.ui:92 ui/composer-menus.ui:110 +#: ui/conversation-message-menus.ui:37 msgid "_Copy" msgstr "_Kopioi" -#: ../ui/composer-menus.ui.h:14 +#: ui/composer-menus.ui:96 ui/composer-menus.ui:114 msgid "_Paste" msgstr "L_iitä" -#: ../ui/composer-menus.ui.h:15 -msgctxt "Clipboard paste with rich text" -msgid "Paste _With Formatting" -msgstr "Lii_tä muotoilujen kera" +#: ui/composer-menus.ui:100 +msgctxt "Clipboard paste as plain text" +msgid "Paste _Without Formatting" +msgstr "Liit_ä ilman muotoilua" -#: ../ui/composer-menus.ui.h:16 +#: ui/composer-menus.ui:120 msgid "Select _All" msgstr "Valitse _kaikki" -#: ../ui/composer-menus.ui.h:17 ../ui/conversation-message-menus.ui.h:9 +#: ui/composer-menus.ui:127 ui/conversation-message-menus.ui:49 msgid "_Inspect…" msgstr "T_utki…" #. Address(es) e-mail is to be sent to -#: ../ui/composer-widget.ui.h:2 +#: ui/composer-widget.ui:56 msgid "_To" msgstr "_Vastaanottaja" -#: ../ui/composer-widget.ui.h:3 +#: ui/composer-widget.ui:75 msgid "_Cc" msgstr "_Kopio" -#: ../ui/composer-widget.ui.h:4 +#: ui/composer-widget.ui:130 msgid "_Subject" msgstr "_Aihe" -#: ../ui/composer-widget.ui.h:5 +#: ui/composer-widget.ui:149 msgid "_Bcc" msgstr "_Piilokopio" -#: ../ui/composer-widget.ui.h:6 +#: ui/composer-widget.ui:179 msgid "_Reply-To" msgstr "Vasta_usosoite" #. Geary account mail will be sent from -#: ../ui/composer-widget.ui.h:8 +#: ui/composer-widget.ui:208 msgid "From" msgstr "Lähettäjä" -#: ../ui/composer-widget.ui.h:9 +#: ui/composer-widget.ui:293 msgid "Drop files here" msgstr "Pudota tiedostot tähän" -#: ../ui/composer-widget.ui.h:10 +#: ui/composer-widget.ui:309 msgid "To add them as attachments" msgstr "lisätäksesi ne liitteiksi" -#: ../ui/composer-widget.ui.h:11 +#: ui/composer-widget.ui:353 msgid "Undo last edit (Ctrl+Z)" msgstr "Kumoa viimeisin muokkaus (Ctrl+Z)" -#: ../ui/composer-widget.ui.h:12 +#: ui/composer-widget.ui:377 msgid "Redo last edit (Ctrl+Shift+Z)" msgstr "Tee uudelleen viimeisin muokkaus (Ctrl+Shift+Z)" -#: ../ui/composer-widget.ui.h:13 +#: ui/composer-widget.ui:415 msgid "Bold (Ctrl+B)" msgstr "Lihavoitu (Ctrl+B)" -#: ../ui/composer-widget.ui.h:14 +#: ui/composer-widget.ui:439 msgid "Italic (Ctrl+I)" msgstr "Kursivoitu (Ctrl+I)" -#: ../ui/composer-widget.ui.h:15 +#: ui/composer-widget.ui:463 msgid "Underline (Ctrl+U)" msgstr "Alleviivattu (Ctrl+U)" -#: ../ui/composer-widget.ui.h:16 +#: ui/composer-widget.ui:487 msgid "Strikethrough (Ctrl+K)" msgstr "Yliviivattu (Ctrl+K)" -#: ../ui/composer-widget.ui.h:17 +#: ui/composer-widget.ui:525 +msgid "Insert unordered list" +msgstr "Lisää numeroimaton luettelo" + +#: ui/composer-widget.ui:549 +msgid "Insert ordered list" +msgstr "Lisää numeroitu luettelo" + +#: ui/composer-widget.ui:587 msgid "Quote text (Ctrl+])" msgstr "Lainaa tekstiä (Ctrl+])" -#: ../ui/composer-widget.ui.h:18 +#: ui/composer-widget.ui:611 msgid "Unquote text (Ctrl+[)" msgstr "Poista tekstin lainaus (Ctrl+[)" -#: ../ui/composer-widget.ui.h:19 +#: ui/composer-widget.ui:649 msgid "Insert or update selection link (Ctrl+L)" msgstr "Lisää tai päivitä valintalinkki (Ctrl+L)" -#: ../ui/composer-widget.ui.h:20 +#: ui/composer-widget.ui:673 msgid "Insert an image (Ctrl+G)" msgstr "Lisää kuva (Ctrl+G)" -#: ../ui/composer-widget.ui.h:21 +#: ui/composer-widget.ui:707 msgid "Remove selection formatting (Ctrl+Space)" msgstr "Poista muotoilu valinnasta (Ctrl+Space)" -#: ../ui/composer-widget.ui.h:22 +#: ui/composer-widget.ui:731 msgid "Select spell checking languages" msgstr "Valitse oikoluvun kielet" -#: ../ui/conversation-email.ui.h:1 +#: ui/conversation-email.ui:27 msgid "Save all attachments" msgstr "Tallenna kaikki liitteet" #. Note: The application will never show this button at the same time as unstar_button, one will always be hidden. -#: ../ui/conversation-email.ui.h:3 +#: ui/conversation-email.ui:50 msgid "Mark this message as starred" msgstr "Merkitse tämä viesti tähdellä" #. Note: The application will never show this button at the same time as star_button, one will always be hidden. -#: ../ui/conversation-email.ui.h:5 +#: ui/conversation-email.ui:72 msgid "Mark this message as not starred" msgstr "Poista tähti tältä viestiltä" -#: ../ui/conversation-email.ui.h:6 +#: ui/conversation-email.ui:95 msgid "Display the message menu" msgstr "Näytä viestivalikko" -#: ../ui/conversation-email.ui.h:7 +#: ui/conversation-email.ui:161 msgid "Open selected attachments" msgstr "Avaa valitut litteet" -#: ../ui/conversation-email.ui.h:8 +#: ui/conversation-email.ui:178 msgid "Save selected attachments" msgstr "Tallenna valitut liitteet" -#: ../ui/conversation-email.ui.h:9 +#: ui/conversation-email.ui:195 msgid "Select all attachments" msgstr "Valitse kaikki liitteet" -#: ../ui/conversation-email.ui.h:10 +#: ui/conversation-email.ui:240 msgid "Edit Draft" msgstr "Muokkaa luonnosta" -#: ../ui/conversation-email.ui.h:11 +#: ui/conversation-email.ui:267 msgid "Draft message" msgstr "Luonnosviesti" -#: ../ui/conversation-email.ui.h:12 +#: ui/conversation-email.ui:283 msgid "This message has not yet been sent." msgstr "Tätä viestiä ei ole vielä lähetetty." -#: ../ui/conversation-email.ui.h:13 -msgid "Try Again" -msgstr "Yritä uudelleen" - -#: ../ui/conversation-email.ui.h:14 +#: ui/conversation-email.ui:329 msgid "Message not saved" msgstr "Viestiä ei tallennettu" -#: ../ui/conversation-email.ui.h:15 +#: ui/conversation-email.ui:345 msgid "This message was sent, but has not been saved to your account." msgstr "" "Tämä viesti lähetettiin onnistuneesti, mutta sen tallennus tilillesi " "epäonnistui." -#: ../ui/conversation-email-menus.ui.h:2 +#. Translators: Menu item to reply to a specific message. +#: ui/conversation-email-menus.ui:15 msgid "Reply to _All" msgstr "_Vastaa kaikille" -#: ../ui/conversation-email-menus.ui.h:4 +#. Translators: Menu item to mark a specific message as +#. read. +#: ui/conversation-email-menus.ui:30 msgid "_Mark Read" msgstr "Merkitse _luetuksi" -#: ../ui/conversation-email-menus.ui.h:5 +#: ui/conversation-email-menus.ui:36 msgid "_Mark Unread" msgstr "_Merkitse lukemattomaksi" -#: ../ui/conversation-email-menus.ui.h:6 +#. Translators: Menu item to mark all messages in a +#. conversation from this one as unread. +#: ui/conversation-email-menus.ui:42 msgid "Mark Unread From _Here" msgstr "Merkitse lukemattomaksi tästä e_delleen" -#: ../ui/conversation-email-menus.ui.h:8 +#. Translators: Menu item to move a single, specific message +#. to the trash +#: ui/conversation-email-menus.ui:50 +msgid "_Trash" +msgstr "Siirr_ä roskakoriin" + +#. Translators: Menu item to delete a single, specific message +#: ui/conversation-email-menus.ui:57 +msgid "_Delete…" +msgstr "_Poista…" + +#. Translators: Menu item to view the source for a message +#: ui/conversation-email-menus.ui:69 msgid "_View Source" msgstr "Näytä lä_hde" -#: ../ui/conversation-email-menus.ui.h:11 +#: ui/conversation-email-menus.ui:87 msgid "_Save All" msgstr "_Tallenna kaikki" -#: ../ui/conversation-message-menus.ui.h:1 +#: ui/conversation-message-menus.ui:7 msgid "_Open Link" msgstr "_Avaa linkki" -#: ../ui/conversation-message-menus.ui.h:2 +#: ui/conversation-message-menus.ui:11 msgid "Copy Link _Address" msgstr "Kopioi _linkin osoite" -#: ../ui/conversation-message-menus.ui.h:3 +#: ui/conversation-message-menus.ui:17 msgid "Send New _Message…" msgstr "L_ähetä uusi viesti…" -#: ../ui/conversation-message-menus.ui.h:4 +#: ui/conversation-message-menus.ui:21 msgid "Copy Email _Address" msgstr "Kopioi _sähköpostiosoite" -#: ../ui/conversation-message-menus.ui.h:5 +#: ui/conversation-message-menus.ui:27 msgid "Save _Image As…" msgstr "T_allenna kuva nimellä…" -#: ../ui/conversation-message-menus.ui.h:6 +#: ui/conversation-message-menus.ui:33 msgid "_Select All" msgstr "Valitse _kaikki" -#: ../ui/conversation-message-menus.ui.h:8 +#: ui/conversation-message-menus.ui:43 msgid "Search for messages from" msgstr "Etsi viestejä lähettäjältä" -#: ../ui/conversation-message.ui.h:1 +#: ui/conversation-message.ui:64 msgid "From " -msgstr "" +msgstr "Lähettäjä " -#: ../ui/conversation-message.ui.h:2 +#: ui/conversation-message.ui:80 ui/conversation-message.ui:179 msgid "1/1/1970\t" msgstr "" -#: ../ui/conversation-message.ui.h:3 +#: ui/conversation-message.ui:103 msgid "Preview body text." -msgstr "" +msgstr "Esikatsele rungon teksti." -#: ../ui/conversation-message.ui.h:4 +#: ui/conversation-message.ui:203 msgid "Sent by:" msgstr "Lähettänyt:" -#: ../ui/conversation-message.ui.h:5 +#: ui/conversation-message.ui:248 msgid "Reply to:" msgstr "Vastausosoite:" -#: ../ui/conversation-message.ui.h:6 +#: ui/conversation-message.ui:292 msgid "Subject" msgstr "Aihe" -#: ../ui/conversation-message.ui.h:7 -msgid "To:" -msgstr "Vastaanottaja:" - -#: ../ui/conversation-message.ui.h:8 -msgid "Cc:" -msgstr "Kopio:" - -#: ../ui/conversation-message.ui.h:9 -msgid "Bcc:" -msgstr "Piilokopio:" - -#: ../ui/conversation-message.ui.h:10 +#: ui/conversation-message.ui:502 msgid "Show Images" msgstr "Näytä kuvat" -#: ../ui/conversation-message.ui.h:11 +#: ui/conversation-message.ui:515 msgid "Always Show From Sender" msgstr "Näytä aina lähettäjältä" -#: ../ui/conversation-message.ui.h:12 +#: ui/conversation-message.ui:543 msgid "Remote images not shown" msgstr "Etäkuvia ei näytetä" -#: ../ui/conversation-message.ui.h:13 +#: ui/conversation-message.ui:560 msgid "Only show remote images from senders you trust." -msgstr "Näytä etäkuvat vain lähettäjiltä, joihin luotat" +msgstr "Näytä etäkuvat vain lähettäjiltä, joihin luotat." -#: ../ui/conversation-message.ui.h:14 +#: ui/conversation-message.ui:693 msgid "But actually goes to:" msgstr "Mutta viekin kohteeseen:" -#: ../ui/conversation-message.ui.h:15 +#: ui/conversation-message.ui:724 msgid "The link appears to go to:" msgstr "Tämä linkki vaikuttaa johtavan kohteeseen:" -#: ../ui/conversation-message.ui.h:16 +#: ui/conversation-message.ui:736 msgid "Deceptive link found" msgstr "Harhaanjohtava linkki löydetty" -#: ../ui/conversation-message.ui.h:17 +#: ui/conversation-message.ui:751 msgid "The email sender may be leading you to the wrong web site." msgstr "" "Sähköpostin lähettäjä saattaa yrittää johdattaa sinut väärälle sivulle." -#: ../ui/conversation-message.ui.h:18 +#: ui/conversation-message.ui:764 msgid "If unsure, contact the sender and ask before continuing." msgstr "" "Jos olet epävarma, ole yhteydessä viestin lähettäjään ennen kuin jatkat." -#: ../ui/conversation-viewer.ui.h:1 +#: ui/conversation-viewer.ui:60 msgid "Find in conversation" msgstr "Etsi keskustelusta" -#: ../ui/conversation-viewer.ui.h:2 +#: ui/conversation-viewer.ui:74 msgid "Find the previous occurrence of the search string." msgstr "Etsi hakuehdon edellinen ilmentymä." -#: ../ui/conversation-viewer.ui.h:3 +#: ui/conversation-viewer.ui:95 msgid "Find the next occurrence of the search string." msgstr "Etsi hakuehdon seuraava ilmentymä." -#: ../ui/edit_alternate_emails.glade.h:1 -msgid "Remove email address" -msgstr "Poista sähköpostiosoite" - -#: ../ui/edit_alternate_emails.glade.h:2 -msgid "" -"Some email services require additional addresses be configured on the " -"server. Contact your email provider for more information." -msgstr "" -"Jotkin sähköpostipalvelut vaativat lisäosoitteiden oltava määritelty " -"palvelimelle. Lisätietoja saat sähköpostipalvelun sinulle tarjoavalta " -"taholta." - -#: ../ui/edit_alternate_emails.glade.h:4 -msgid "_Update" -msgstr "P_äivitä" - -#: ../ui/find_bar.glade.h:1 +#: ui/find_bar.glade:66 msgid "Find:" msgstr "Etsi:" -#: ../ui/find_bar.glade.h:2 +#: ui/find_bar.glade:89 msgid "_Previous" msgstr "_Edellinen" -#: ../ui/find_bar.glade.h:3 +#: ui/find_bar.glade:107 msgid "_Next" msgstr "_Seuraava" -#: ../ui/find_bar.glade.h:4 +#: ui/find_bar.glade:125 msgid "_Case sensitive" msgstr "_Huomioi kirjainkoko" -#: ../ui/find_bar.glade.h:5 +#: ui/find_bar.glade:145 msgid "label" msgstr "nimike" -#: ../ui/gtk/help-overlay.ui.h:1 +#: ui/gtk/help-overlay.ui:9 msgid "Conversation Shortcuts" msgstr "Keskustelun pikanäppäimet" -#: ../ui/gtk/help-overlay.ui.h:2 +#: ui/gtk/help-overlay.ui:13 ui/gtk/help-overlay.ui:254 msgctxt "shortcut window" msgid "General" msgstr "Yleiset" -#: ../ui/gtk/help-overlay.ui.h:3 +#: ui/gtk/help-overlay.ui:17 msgctxt "shortcut window" msgid "Move focus to the next/previous pane" msgstr "Siirrä kohdistus seuraavaan/edelliseen osioon" -#: ../ui/gtk/help-overlay.ui.h:4 +#: ui/gtk/help-overlay.ui:24 msgctxt "shortcut window" msgid "Move focus to conversation list" msgstr "Siirrä kohdistus keskusteluluetteloon" -#: ../ui/gtk/help-overlay.ui.h:5 +#: ui/gtk/help-overlay.ui:31 msgctxt "shortcut window" msgid "Detach composer window" msgstr "Irrota lähetysikkuna" -#: ../ui/gtk/help-overlay.ui.h:6 +#: ui/gtk/help-overlay.ui:38 msgctxt "shortcut window" msgid "Close composer window" msgstr "Sulje lähetysikkuna" -#: ../ui/gtk/help-overlay.ui.h:7 +#: ui/gtk/help-overlay.ui:45 msgctxt "shortcut window" msgid "Show keyboard shortcuts" msgstr "Näytä pikanäppäimet" -#: ../ui/gtk/help-overlay.ui.h:8 +#: ui/gtk/help-overlay.ui:52 msgctxt "shortcut window" msgid "Show help" msgstr "Näytä ohje" -#: ../ui/gtk/help-overlay.ui.h:9 +#: ui/gtk/help-overlay.ui:59 msgctxt "shortcut window" msgid "Quit the application" msgstr "Lopeta sovellus" -#: ../ui/gtk/help-overlay.ui.h:10 +#: ui/gtk/help-overlay.ui:68 msgctxt "shortcut window" msgid "Search" msgstr "Haku" -#: ../ui/gtk/help-overlay.ui.h:11 +#: ui/gtk/help-overlay.ui:72 msgctxt "shortcut window" msgid "Jump to search box" msgstr "Siirry hakukenttään" -#: ../ui/gtk/help-overlay.ui.h:12 +#: ui/gtk/help-overlay.ui:79 msgctxt "shortcut window" msgid "Find in current conversation" msgstr "Etsi nykyisestä keskustelusta" -#: ../ui/gtk/help-overlay.ui.h:13 +#: ui/gtk/help-overlay.ui:86 msgctxt "shortcut window" msgid "Find next/previous in current conversation" msgstr "Etsi seuraava/edellinen nykyisestä keskustelusta" -#: ../ui/gtk/help-overlay.ui.h:14 +#: ui/gtk/help-overlay.ui:95 ui/gtk/help-overlay.ui:274 msgctxt "shortcut window" msgid "Actions" msgstr "Toiminnot" -#: ../ui/gtk/help-overlay.ui.h:15 +#: ui/gtk/help-overlay.ui:99 msgctxt "shortcut window" msgid "Compose a new message" msgstr "Luo uusi viesti" -#: ../ui/gtk/help-overlay.ui.h:16 +#: ui/gtk/help-overlay.ui:106 msgctxt "shortcut window" msgid "Reply to sender " -msgstr "Vastaa lähettäjälle" +msgstr "Vastaa lähettäjälle " -#: ../ui/gtk/help-overlay.ui.h:17 +#: ui/gtk/help-overlay.ui:113 msgctxt "shortcut window" msgid "Reply to all" msgstr "Vastaa kaikille" -#: ../ui/gtk/help-overlay.ui.h:18 +#: ui/gtk/help-overlay.ui:120 msgctxt "shortcut window" msgid "Forward" msgstr "Lähetä edelleen" -#: ../ui/gtk/help-overlay.ui.h:19 +#: ui/gtk/help-overlay.ui:127 msgctxt "shortcut window" msgid "Archive" msgstr "Arkistoi" -#: ../ui/gtk/help-overlay.ui.h:20 +#: ui/gtk/help-overlay.ui:134 msgctxt "shortcut window" msgid "Move to trash" msgstr "Siirrä roskakoriin" -#: ../ui/gtk/help-overlay.ui.h:21 +#: ui/gtk/help-overlay.ui:141 msgctxt "shortcut window" msgid "Toggle spam" -msgstr "Merkitse roskapostiksi/poista roskapostimerkintä " +msgstr "Merkitse roskapostiksi/poista roskapostimerkintä" -#: ../ui/gtk/help-overlay.ui.h:22 +#: ui/gtk/help-overlay.ui:148 msgctxt "shortcut window" msgid "Move the conversation" msgstr "Siirrä keskustelu" -#: ../ui/gtk/help-overlay.ui.h:23 +#: ui/gtk/help-overlay.ui:155 msgctxt "shortcut window" msgid "Label the conversation" msgstr "Merkitse tunniste keskusteluun" -#: ../ui/gtk/help-overlay.ui.h:24 +#: ui/gtk/help-overlay.ui:163 msgctxt "shortcut window" msgid "Mark read" msgstr "Merkitse luetuksi" -#: ../ui/gtk/help-overlay.ui.h:25 +#: ui/gtk/help-overlay.ui:170 msgctxt "shortcut window" msgid "Mark unread" msgstr "Merkitse lukemattomaksi" -#: ../ui/gtk/help-overlay.ui.h:26 +#: ui/gtk/help-overlay.ui:179 msgctxt "shortcut window" msgid "View" msgstr "Näkymä" -#: ../ui/gtk/help-overlay.ui.h:27 +#: ui/gtk/help-overlay.ui:183 msgctxt "shortcut window" msgid "Zoom in" msgstr "Lähennä" -#: ../ui/gtk/help-overlay.ui.h:28 +#: ui/gtk/help-overlay.ui:190 msgctxt "shortcut window" msgid "Zoom out" msgstr "Loitonna" -#: ../ui/gtk/help-overlay.ui.h:29 +#: ui/gtk/help-overlay.ui:197 msgctxt "shortcut window" msgid "Reset zoom" msgstr "Palauta mittakaava" -#: ../ui/gtk/help-overlay.ui.h:30 +#: ui/gtk/help-overlay.ui:206 msgctxt "shortcut window" msgid "Additional Shortcuts" msgstr "Muut pikanäppäimet" -#: ../ui/gtk/help-overlay.ui.h:31 +#: ui/gtk/help-overlay.ui:210 msgctxt "shortcut window" msgid "Star" msgstr "Merkitse tähdellä" -#: ../ui/gtk/help-overlay.ui.h:32 +#: ui/gtk/help-overlay.ui:217 msgctxt "shortcut window" msgid "Unstar" msgstr "Poista tähti" -#: ../ui/gtk/help-overlay.ui.h:33 +#: ui/gtk/help-overlay.ui:224 msgctxt "shortcut window" msgid "Delete" msgstr "Poista" -#: ../ui/gtk/help-overlay.ui.h:34 +#: ui/gtk/help-overlay.ui:231 msgctxt "shortcut window" msgid "Jump to next (older) conversation" msgstr "Siirry seuravaan (vanhempaan) keskusteluun" -#: ../ui/gtk/help-overlay.ui.h:35 +#: ui/gtk/help-overlay.ui:238 msgctxt "shortcut window" msgid "Jump to previous (newer) conversation" msgstr "Siirry edelliseen (uudempaan) keskusteluun" -#: ../ui/gtk/help-overlay.ui.h:36 +#: ui/gtk/help-overlay.ui:250 msgid "Composer Shortcuts" msgstr "Lähettämisen pikanäppäimet" -#: ../ui/gtk/help-overlay.ui.h:37 +#: ui/gtk/help-overlay.ui:258 msgctxt "shortcut window" msgid "Quote text" msgstr "Lainaa tekstiä" -#: ../ui/gtk/help-overlay.ui.h:38 +#: ui/gtk/help-overlay.ui:265 msgctxt "shortcut window" msgid "Unquote text" msgstr "Poista tekstin lainaus" -#: ../ui/gtk/help-overlay.ui.h:39 +#: ui/gtk/help-overlay.ui:278 msgctxt "shortcut window" msgid "Send" msgstr "Lähetä" -#: ../ui/gtk/help-overlay.ui.h:40 +#: ui/gtk/help-overlay.ui:285 msgctxt "shortcut window" msgid "Add attachment" msgstr "Lisää liite" -#: ../ui/gtk/help-overlay.ui.h:41 +#: ui/gtk/help-overlay.ui:294 msgctxt "shortcut window" msgid "Rich text mode" msgstr "Muotoilutila" -#: ../ui/gtk/help-overlay.ui.h:42 +#: ui/gtk/help-overlay.ui:298 msgctxt "shortcut window" msgid "Bold text" msgstr "Lihavoitu teksti" -#: ../ui/gtk/help-overlay.ui.h:43 +#: ui/gtk/help-overlay.ui:305 msgctxt "shortcut window" msgid "Italicize text" msgstr "Kursivoitu teksti" -#: ../ui/gtk/help-overlay.ui.h:44 +#: ui/gtk/help-overlay.ui:312 msgctxt "shortcut window" msgid "Underline text" msgstr "Alleviivattu teksti" -#: ../ui/gtk/help-overlay.ui.h:45 +#: ui/gtk/help-overlay.ui:319 msgctxt "shortcut window" msgid "Strike text" msgstr "Yliviivattu teksti" -#: ../ui/gtk/help-overlay.ui.h:46 +#: ui/gtk/help-overlay.ui:326 msgctxt "shortcut window" msgid "Insert a link" msgstr "Lisää linkki" -#: ../ui/gtk/help-overlay.ui.h:47 +#: ui/gtk/help-overlay.ui:333 msgctxt "shortcut window" msgid "Remove formatting" msgstr "Poista muotoilu" -#: ../ui/gtk/menus.ui.h:2 +#: ui/gtk/menus.ui:13 msgid "A_ccounts" msgstr "_Tilit" -#: ../ui/gtk/menus.ui.h:4 +#: ui/gtk/menus.ui:23 msgid "_Keyboard Shortcuts" msgstr "_Pikanäppäimet" -#: ../ui/login.glade.h:1 -msgid "email@example.com" -msgstr "osoite@esimerkki.com" +#: ui/main-toolbar.ui:51 +msgid "Toggle search bar" +msgstr "Hakupalkki päälle/pois" -#: ../ui/login.glade.h:2 ../ui/password-dialog.glade.h:3 -msgid "Password" -msgstr "Salasana" +#: ui/main-toolbar.ui:72 +msgid "Empty Spam or Trash folders" +msgstr "Tyhjennä roskaposti- tai roskakorikansiot" -#: ../ui/login.glade.h:3 -msgid "E_mail address" -msgstr "_Sähköpostiosoite" - -#: ../ui/login.glade.h:4 -msgid "_Password" -msgstr "_Salasana" - -#: ../ui/login.glade.h:5 -msgid "S_ervice" -msgstr "_Palvelu" - -#: ../ui/login.glade.h:6 -msgid "N_ame" -msgstr "_Nimi" - -#: ../ui/login.glade.h:8 -msgid "N_ickname" -msgstr "Ni_mimerkki" - -#: ../ui/login.glade.h:9 -msgid "Work, Home, etc." -msgstr "Koti, työ tms." - -#: ../ui/login.glade.h:10 -msgid "_Save sent mail" -msgstr "_Tallenna lähetetyt viestit" - -#: ../ui/login.glade.h:11 -msgid "Addi_tional email addresses…" -msgstr "Lisäosoitteet…" - -#: ../ui/login.glade.h:12 -msgid "IMAP settings" -msgstr "IMAP-asetukset" - -#: ../ui/login.glade.h:13 -msgid "Se_rver" -msgstr "P_alvelin" +#: ui/main-toolbar.ui:112 +msgid "Reply" +msgstr "Vastaa" -#: ../ui/login.glade.h:14 -msgid "imap.example.com" -msgstr "imap.esimerkki.com" +#: ui/main-toolbar.ui:135 +msgid "Reply All" +msgstr "Vastaa kaikille" -#: ../ui/login.glade.h:15 -msgid "P_ort" -msgstr "P_ortti" +#: ui/main-toolbar.ui:158 +msgid "Forward" +msgstr "Lähetä edelleen" -#: ../ui/login.glade.h:16 -msgid "smtp.example.com" -msgstr "smtp.esimerkki.com" +#: ui/main-toolbar.ui:264 +msgid "Toggle find bar" +msgstr "Hakupalkki päälle/pois" -#: ../ui/login.glade.h:17 -msgid "Ser_ver" -msgstr "Pal_velin" - -#: ../ui/login.glade.h:18 -msgid "Por_t" -msgstr "Portt_i" - -#: ../ui/login.glade.h:19 -msgid "SMTP settings" -msgstr "SMTP-asetukset" - -#: ../ui/login.glade.h:20 -msgid "User_name" -msgstr "_Käyttäjätunnus" - -#: ../ui/login.glade.h:21 -msgid "Pass_word" -msgstr "_Salasana" - -#: ../ui/login.glade.h:22 -msgid "SMTP username" -msgstr "SMTP-käyttäjätunnus" - -#: ../ui/login.glade.h:23 -msgid "SMTP password" -msgstr "SMTP-salasana" - -#: ../ui/login.glade.h:24 -msgid "_Username" -msgstr "K_äyttäjätunnus" - -#: ../ui/login.glade.h:25 -msgid "IMAP username" -msgstr "IMAP-käyttäjätunnus" - -#: ../ui/login.glade.h:26 -msgid "IMAP password" -msgstr "IMAP-salasana" - -#: ../ui/login.glade.h:27 -msgid "Encr_yption" -msgstr "Sala_us" - -#: ../ui/login.glade.h:28 -msgid "Encrypt_ion" -msgstr "Salau_s" - -#: ../ui/login.glade.h:30 -msgid "SSL/TLS" -msgstr "SSL/TLS" - -#: ../ui/login.glade.h:31 -msgid "STARTTLS" -msgstr "STARTTLS" - -#: ../ui/login.glade.h:32 -msgid "No authentication re_quired" -msgstr "Tunnistautumista ei _vaadita" - -#: ../ui/login.glade.h:33 -msgid "Use IMAP cre_dentials" -msgstr "Kä_ytä IMAP-tunnuksia" +#: ui/main-toolbar.ui:306 +msgid "_Archive" +msgstr "_Arkistoi" -#: ../ui/login.glade.h:34 -msgid "Composer" -msgstr "Lähettäminen" +#: ui/main-toolbar-menus.ui:5 +msgid "Empty _Spam…" +msgstr "Tyhjennä ro_skapostikansio…" -#: ../ui/login.glade.h:35 -msgid "Save dra_fts on server" -msgstr "Tall_enna luonnokset palvelimelle" - -#: ../ui/login.glade.h:36 -msgid "Si_gn emails (HTML allowed):" -msgstr "_Allekirjoita viestit (HTML sallittu):" - -#: ../ui/login.glade.h:37 -msgid "Storage" -msgstr "Varastointi" - -#: ../ui/login.glade.h:38 -msgid "_Download mail" -msgstr "_Lataa sähköposti" +#: ui/main-toolbar-menus.ui:9 +msgid "Empty _Trash…" +msgstr "Tyhjennä _roskakorikansio…" -#: ../ui/main-toolbar.ui.h:1 -msgid "Empty Spam or Trash folders" -msgstr "Tyhjennä roskaposti- tai roskakorikansiot" +#: ui/main-toolbar-menus.ui:32 +msgid "Mark as S_pam" +msgstr "Me_rkitse roskapostiksi" + +#: ui/main-toolbar-menus.ui:36 +msgid "Mark as not S_pam" +msgstr "Poista _roskapostimerkintä" + +#. Infobar title when one or more accounts are offline +#: ui/main-window.ui:183 +msgid "Working offline" +msgstr "Työskennellään offline-tilassa" + +#. Label and tooltip for offline infobar +#: ui/main-window.ui:197 +msgid "" +"Your computer does not appear to be connected to the Internet.\n" +"You will not be able to send or receive email until it is re-connected." +msgstr "" +"Tietokoneesi ei vaikuta olevan yhteydessä internetiin.\n" +"Viestien vastaanottaminen tai lähettäminen ei onnistu, ennen kuin yhteys on " +"muodostettu uudelleen." + +#. Label and tooltip for offline infobar +#: ui/main-window.ui:200 +msgid "You will not be able to send or receive email until re-connected." +msgstr "" +"Viestien vastaanottaminen tai lähettäminen ei onnistu, ennen kuin yhteys on " +"muodostettu uudelleen." + +#. Button label for displaying technical details about an account problem +#. Dialog title for displaying technical details of a problem. Same as the button that invokes it. +#: ui/main-window.ui:247 ui/problem-details-dialog.ui:13 +msgid "Details" +msgstr "Yksityiskohdat" + +#. Button label for retrying an account problem +#: ui/main-window.ui:261 +msgid "Retry" +msgstr "Yritä uudelleen" -#: ../ui/password-dialog.glade.h:1 +#. Infobar title when one or more accounts have encounted an error +#: ui/main-window.ui:294 +msgid "Account problem" +msgstr "Tiliongelma" + +#. Label and tooltip for account service problem infobar +#: ui/main-window.ui:308 +msgid "" +"Geary encountered a problem connecting to an account.\n" +"Please check your Internet connection, the server configuration and try " +"again." +msgstr "" +"Geary kohtasi ongelman yhdistäessä tiliin.\n" +"Tarkista internetyhteytesi tila ja palvelimen asetukset, yritä sen jälkeen " +"uudelleen." + +#. Label and tooltip for account service problem infobar +#: ui/main-window.ui:311 +msgid "Geary encountered a problem connecting to an account." +msgstr "Geary kohtasi ongelman yhdistäessä tiliin." + +#. Button label for retrying TLS cert validation +#: ui/main-window.ui:358 +msgid "Check" +msgstr "Tarkista" + +#. Button tooltip for retrying TLS cert validation +#: ui/main-window.ui:362 +msgid "Check the security details for the connection" +msgstr "Tarkista yhteyden salaustiedot" + +#. Infobar title when one or more accounts have a TLS cert validation error +#: ui/main-window.ui:391 +msgid "Security problem" +msgstr "Tietoturvaongelma" + +#. Label and tooltip for TLS cert validation error infobar +#: ui/main-window.ui:405 +msgid "" +"An account has reported an untrusted server.\n" +"Please check the server configuration and try again." +msgstr "" +"Tili ilmoitti ei-luotetusta palvelimesta.\n" +"Tarkista palvelimen asetukset ja yritä uudelleen." + +#. Label and tooltip for TLS cert validation error infobar +#: ui/main-window.ui:408 +msgid "An account has reported an untrusted server." +msgstr "Tili on ilmoittanut ei-luotetusta palvelimesta." + +#. Button tooltip for retrying when a login error has occurred +#: ui/main-window.ui:459 +msgid "Retry login, you will be prompted for your password" +msgstr "Yritä uudelleen kirjautumista, sinulta kysytään salasanaa" + +#. Infobar title when one or more accounts have a login error +#: ui/main-window.ui:488 +msgid "Login problem" +msgstr "Kirjautumisongelma" + +#. Label and tooltip for authentication problem infobar +#: ui/main-window.ui:502 +msgid "" +"An account has reported an incorrect login or password.\n" +"Please check your login name and try again." +msgstr "" +"Tili ilmoitti virheellisestä käyttäjätunnuksesta tai salasanasta.\n" +"Tarkista käyttäjätunnus ja yritä uudelleen." + +#. Label and tooltip for authentication problem infobar +#: ui/main-window.ui:505 +msgid "An account has reported an incorrect login or password." +msgstr "Tili ilmoitti väärästä käyttäjätunnuksesta tai salasanasta." + +#: ui/password-dialog.glade:74 msgid "SMTP Credentials" msgstr "SMTP-tunnukset" -#: ../ui/password-dialog.glade.h:2 +#: ui/password-dialog.glade:91 msgid "Username" msgstr "Käyttäjätunnus" -#: ../ui/password-dialog.glade.h:4 +#: ui/password-dialog.glade:152 msgid "_Remember password" msgstr "_Muista salasana" -#: ../ui/password-dialog.glade.h:6 +#: ui/password-dialog.glade:210 msgid "_Authenticate" msgstr "_Tunnistaudu" -#: ../ui/preferences-dialog.ui.h:1 +#: ui/preferences-dialog.ui:38 msgid "Reading" msgstr "Lukeminen" -#: ../ui/preferences-dialog.ui.h:2 +#: ui/preferences-dialog.ui:51 msgid "_Automatically select next message" msgstr "_Valitse automaattisesti seuraava viesti" -#: ../ui/preferences-dialog.ui.h:3 +#: ui/preferences-dialog.ui:70 msgid "_Display conversation preview" msgstr "N_äytä keskustelun esikatselu" -#: ../ui/preferences-dialog.ui.h:4 +#: ui/preferences-dialog.ui:89 msgid "Use _three pane view" msgstr "Kä_ytä kolmen paneelin näkymää" -#: ../ui/preferences-dialog.ui.h:5 +#: ui/preferences-dialog.ui:113 msgid "Notifications" msgstr "Ilmoitukset" -#: ../ui/preferences-dialog.ui.h:6 +#: ui/preferences-dialog.ui:126 msgid "_Play notification sounds" msgstr "_Toista ilmoitusäänet" -#: ../ui/preferences-dialog.ui.h:7 +#: ui/preferences-dialog.ui:145 msgid "Show _notifications for new mail" msgstr "_Näytä ilmoitus uuden viestin saapuessa" -#: ../ui/preferences-dialog.ui.h:8 -msgid "Always _watch for new mail" -msgstr "_Tarkkaile aina uusia viestejä" - -#: ../ui/preferences-dialog.ui.h:9 -msgid "Geary will run in the background and notify of new mail" -msgstr "Geary toimii taustalla ja ilmoittaa uusista viesteistä" +#: ui/preferences-dialog.ui:164 +msgid "_Watch for new mail when closed" +msgstr "_Tarkkaile uusia viestejä suljettuna" + +#: ui/preferences-dialog.ui:168 +msgid "Geary will keep running after all windows are closed" +msgstr "Geary jatkaa käynnissä olemista kun kaikki ikkunat on suljettu" -#: ../ui/preferences-dialog.ui.h:10 +#: ui/preferences-dialog.ui:195 msgid "Preferences" msgstr "Asetukset" -#: ../ui/remove_confirm.glade.h:1 +#. Button label for copying technical information to the clipboard +#: ui/problem-details-dialog.ui:17 +msgid "Copy to Clipboard" +msgstr "Kopioi leikepöydälle" + +#. Button tooltip for copying technical information to the clipboard +#: ui/problem-details-dialog.ui:21 msgid "" -"Are you sure you want to remove this " -"account? " +"Copy technical details to clipboard for pasting into an email or bug report" msgstr "" -"Haluatko varmasti poistaa tämän tilin?" -" " +"Kopioi tekniset yksityiskohdat leikepöydälle, jotta voit liittää ne " +"sähköpostiviestiin tai vikailmoitukseen" -#: ../ui/remove_confirm.glade.h:2 +#: ui/problem-details-dialog.ui:73 msgid "" -"All email associated with this account will be removed from your computer. " -"This will not affect email on the server." +"If the problem is serious or persists, please copy and send these details to " +"the mailing list " +"or file a new " +"bug report." msgstr "" -"Kaikki tähän tiliin liittyvä posti poistetaan koneeltasi. Mitään postia ei " -"poisteta palvelimelta." +"Jos ongelma on vakava tai se toistuu, kopioi ja lähetä virheen " +"yksityiskohtaiset tiedot postituslistalle tai tee uusi vikailmoitus." -#: ../ui/remove_confirm.glade.h:3 -msgid "Nickname:" -msgstr "Kutsumanimi:" +#: ui/problem-details-dialog.ui:89 +msgid "Details:" +msgstr "Tiedot:" -#: ../ui/remove_confirm.glade.h:4 -msgid "Email address:" -msgstr "Sähköpostiosoite:" - -#: ../ui/upgrade_dialog.glade.h:1 +#: ui/upgrade_dialog.glade:60 msgid "Geary update in progress…" msgstr "Gearyn päivitys on meneillään…" +#~ msgid "Email address:" +#~ msgstr "Sähköpostiosoite:" + +#~ msgid "Delete conversations (Shift+Delete)" +#~ msgstr "Poista keskustelut (Shift+Delete)" + +#~ msgid "Move conversations to Trash (Delete, Backspace)" +#~ msgstr "Siirrä keskustelut roskakoriin (Delete, Backspace)" + +#~ msgid "Archive conversations (A)" +#~ msgstr "Arkistoi keskustelut (A)" + +#~ msgid "Mark conversations" +#~ msgstr "Merkitse keskustelut" + +#~ msgid "Add label to conversations" +#~ msgstr "Lisää tunniste keskusteluihin" + +#~ msgid "Move conversations" +#~ msgstr "Siirrä keskustelut" + +#~ msgid "Retry connecting now" +#~ msgstr "Yritä yhdistää uudelleen nyt" + +#~ msgid "Try reconnecting now" +#~ msgstr "Yritä yhdistää uudelleen nyt" + +#~ msgid "Problem with connection to outgoing server for %s" +#~ msgstr "Virhe yhteydessä tilin %s lähtevän postin palvelimeen" + +#~ msgid "To: " +#~ msgstr "Vastaanottaja: " + +#~ msgid "Cc: " +#~ msgstr "Kopio: " + +#~ msgid "Bcc: " +#~ msgstr "Piilokopio: " + +#~ msgid "From: %s\n" +#~ msgstr "Lähettäjä: %s\n" + +#~ msgid "Subject: %s\n" +#~ msgstr "Aihe: %s\n" + +#~ msgid "Date: %s\n" +#~ msgstr "Päiväys: %s\n" + +#~ msgid "To: %s\n" +#~ msgstr "Vastaanottaja: %s\n" + +#~ msgid "Cc: %s\n" +#~ msgstr "Kopio: %s\n" + +#~ msgid "Default attachments directory" +#~ msgstr "Liitteiden oletushakemisto" + +#~ msgid "Additional addresses for %s" +#~ msgstr "Lisäosoitteet tilille %s" + +#~ msgid "First Last" +#~ msgstr "Etunimi Sukunimi" + +#~ msgid "Enter your account information to get started." +#~ msgstr "Anna tilisi tiedot, jotta voit aloittaa käytön." + +#~ msgid "Edit" +#~ msgstr "Muokkaa" + +#~ msgid "Preview" +#~ msgstr "Esikatsele" + +#~ msgid "Remem_ber passwords" +#~ msgstr "M_uista salasanat" + +#~ msgid "Remem_ber password" +#~ msgstr "_Muista salasana" + +#~ msgid "Unable to validate:\n" +#~ msgstr "Tarkistaminen epäonnistui:\n" + +#~ msgid " • Invalid account nickname.\n" +#~ msgstr " • Virheellinen tilin nimi.\n" + +#~ msgid " • Email address already added to Geary.\n" +#~ msgstr " • Sähköpostiosoite on jo lisätty Gearyyn.\n" + +#~ msgid " • IMAP connection error.\n" +#~ msgstr " • IMAP-yhteysvirhe.\n" + +#~ msgid " • IMAP username or password incorrect.\n" +#~ msgstr " • IMAP-käyttäjätunnus tai -salasana on väärin.\n" + +#~ msgid " • SMTP connection error.\n" +#~ msgstr " • SMTP-yhteysvirhe.\n" + +#~ msgid " • SMTP username or password incorrect.\n" +#~ msgstr " • SMTP-käyttäjätunnus tai -salasana on väärin.\n" + +#~ msgid " • Connection error.\n" +#~ msgstr " • Yhteysvirhe.\n" + +#~ msgid " • Username or password incorrect.\n" +#~ msgstr " • Käyttäjätunnus tai salasana on väärin.\n" + +#~ msgid "Unable to store server trust exception" +#~ msgstr "Palvelimen luottamuspoikkeuksen varastointi epäonnistui" + +#~ msgid "Your settings are insecure" +#~ msgstr "Asetuksesi eivät ole turvalliset" + +#~ msgid "" +#~ "Your IMAP and/or SMTP settings do not specify SSL or TLS. This means " +#~ "your username and password could be read by another person on the " +#~ "network. Are you sure you want to do this?" +#~ msgstr "" +#~ "Käytössäsi olevat IMAP- ja/tai SMTP-asetukset eivät määritä " +#~ "salausasetuksesi SSL:ää tai TLS:ää. Tämä tarkoittaa, että " +#~ "käyttäjätunnuksesi ja salasanasi on mahdollista lukea verkkoliikenteestä. " +#~ "Haluatko varmasti sallia tämän?" + +#~ msgid "Co_ntinue" +#~ msgstr "_Jatka" + +#~ msgid "" +#~ "Geary encountered an error sending an email. If the problem persists, " +#~ "please manually delete the email from your Outbox folder." +#~ msgstr "" +#~ "Geary kohtasi virheen viestiä lähettäessä. Jos ongelma toistuu, poista " +#~ "käsin kyseinen viesti Lähtevät-kansiosta." + +#~ msgid "" +#~ "Geary encountered an error saving a sent message to Sent Mail. The " +#~ "message will stay in your Outbox folder until you delete it." +#~ msgstr "" +#~ "Geary kohtasi virheen lähetettyä viestiä tallennettaessa Lähetetyt-" +#~ "kansioon. Tämä viesti säilyy Lähtevät-kansiossa niin kauan, kunnes " +#~ "poistat sen." + +#~ msgid "Unable to open local mailbox for %s" +#~ msgstr "Paikallisen postilaatikon avaaminen tilillä %s epäonnistui" + +#~ msgid "" +#~ "There was an error opening the local mail database for this account. This " +#~ "is possibly due to a file permissions problem.\n" +#~ "\n" +#~ "Please check that you have read/write permissions for all files in this " +#~ "directory:\n" +#~ "\n" +#~ "%s" +#~ msgstr "" +#~ "Tämän tilin paikallista viestitietokantaa avattaessa tapahtui virhe. Se " +#~ "johtuu mitä luultavimmin puutteellisista käyttöoikeuksista.\n" +#~ "\n" +#~ "Varmista, että sinulla on luku- ja kirjoitusoikeus kaikkiin tiedostoihin " +#~ "seuraavassa kansiossa:\n" +#~ "\n" +#~ "%s" + +#~ msgid "" +#~ "The version number of the local mail database is formatted for a newer " +#~ "version of Geary. Unfortunately, the database cannot be “rolled back” to " +#~ "work with this version of Geary.\n" +#~ "\n" +#~ "Please install the latest version of Geary and try again." +#~ msgstr "" +#~ "Paikallinen viestitietokanta on muotoiltu uudemmalle Geary-versiolle. " +#~ "Valitettavasti tietokantaa ei voi “palauttaa menneisyyteen”, jotta se " +#~ "toimisi tämän Geary-version kanssa.\n" +#~ "\n" +#~ "Asenna uusin version Gearysta ja yritä uudelleen." + +#~ msgid "" +#~ "There was an error opening the local account. This is probably due to " +#~ "connectivity issues.\n" +#~ "\n" +#~ "Please check your network connection and restart Geary." +#~ msgstr "" +#~ "Paikallista tiliä avattaessa tapahtui virhe. Se johtuu luultavasti verkko-" +#~ "ongelmista.\n" +#~ "\n" +#~ "Tarkista yhteyden tila ja käynnistä Geary uudelleen." + +#~ msgid "Geary will exit if you have no other open email accounts." +#~ msgstr "Geary sulkeutuu, jos avoinna ei ole muita sähköpostitilejä." + +#~ msgid "IMAP" +#~ msgstr "IMAP" + +#~ msgid "SMTP" +#~ msgstr "SMTP" + +#~ msgid "Other" +#~ msgstr "Muu" + +#~ msgid "Cannot remove account " +#~ msgstr "" +#~ "Tilin poistaminen epäonnistui " + +#~ msgid "" +#~ "A composer window associated with this account is currently open. Send or " +#~ "discard the message and try again." +#~ msgstr "" +#~ "Viestin lähetysikkuna tälle tilille on jo avoinna. Lähetä tai hylkää " +#~ "viesti, ja yritä sitten uudelleen." + +#~ msgid "Please wait while Geary validates your account." +#~ msgstr "Odota hetki, Geary tarkistaa tiliäsi." + +#~ msgid "" +#~ "Some email services require additional addresses be configured on the " +#~ "server. Contact your email provider for more information." +#~ msgstr "" +#~ "Jotkin sähköpostipalvelut vaativat lisäosoitteiden oltava määritelty " +#~ "palvelimelle. Lisätietoja saat sähköpostipalvelun sinulle tarjoavalta " +#~ "taholta." + +#~ msgid "_Update" +#~ msgstr "P_äivitä" + +#~ msgid "E_mail address" +#~ msgstr "_Sähköpostiosoite" + +#~ msgid "_Password" +#~ msgstr "_Salasana" + +#~ msgid "S_ervice" +#~ msgstr "_Palvelu" + +#~ msgid "N_ame" +#~ msgstr "_Nimi" + +#~ msgid "N_ickname" +#~ msgstr "Ni_mimerkki" + +#~ msgid "Work, Home, etc." +#~ msgstr "Koti, työ tms." + +#~ msgid "Addi_tional email addresses…" +#~ msgstr "Lisäosoitteet…" + +#~ msgid "IMAP settings" +#~ msgstr "IMAP-asetukset" + +#~ msgid "Se_rver" +#~ msgstr "P_alvelin" + +#~ msgid "P_ort" +#~ msgstr "P_ortti" + +#~ msgid "Ser_ver" +#~ msgstr "Pal_velin" + +#~ msgid "Por_t" +#~ msgstr "Portt_i" + +#~ msgid "User_name" +#~ msgstr "_Käyttäjätunnus" + +#~ msgid "Pass_word" +#~ msgstr "_Salasana" + +#~ msgid "SMTP password" +#~ msgstr "SMTP-salasana" + +#~ msgid "_Username" +#~ msgstr "K_äyttäjätunnus" + +#~ msgid "IMAP password" +#~ msgstr "IMAP-salasana" + +#~ msgid "Encr_yption" +#~ msgstr "Sala_us" + +#~ msgid "Encrypt_ion" +#~ msgstr "Salau_s" + +#~ msgid "STARTTLS" +#~ msgstr "STARTTLS" + +#~ msgid "No authentication re_quired" +#~ msgstr "Tunnistautumista ei _vaadita" + +#~ msgid "Use IMAP cre_dentials" +#~ msgstr "Kä_ytä IMAP-tunnuksia" + +#~ msgid "Composer" +#~ msgstr "Lähettäminen" + +#~ msgid "Si_gn emails (HTML allowed):" +#~ msgstr "_Allekirjoita viestit (HTML sallittu):" + +#~ msgid "Storage" +#~ msgstr "Varastointi" + +#~ msgid "" +#~ "Are you sure you want to remove " +#~ "this account? " +#~ msgstr "" +#~ "Haluatko varmasti poistaa tämän " +#~ "tilin? " + +#~ msgid "" +#~ "All email associated with this account will be removed from your " +#~ "computer. This will not affect email on the server." +#~ msgstr "" +#~ "Kaikki tähän tiliin liittyvä posti poistetaan koneeltasi. Mitään postia " +#~ "ei poisteta palvelimelta." + +#~ msgid "Nickname:" +#~ msgstr "Kutsumanimi:" + +#~ msgid "Geary Email" +#~ msgstr "Geary-sähköposti" + +#~ msgid "Mail Client" +#~ msgstr "Sähköpostisovellus" + +#~ msgid "Geary Mail" +#~ msgstr "Geary-sähköposti" + +#~ msgid "_Mark as…" +#~ msgstr "_Merkitse…" + +#~ msgid "Add label" +#~ msgstr "Lisää tunniste" + +#~ msgid "_Label" +#~ msgstr "_Tunniste" + +#~ msgid "_Move" +#~ msgstr "_Siirrä" + +#~ msgid "Compose new message (Ctrl+N, N)" +#~ msgstr "Kirjoita uusi viesti (Ctrl+N, N)" + +#~ msgid "Reply (Ctrl+R, R)" +#~ msgstr "Vastaa (Ctrl+R, R)" + +#~ msgid "Reply all (Ctrl+Shift+R, Shift+R)" +#~ msgstr "Vastaa kaikille (Ctrl+Shift+R, Shift+R)" + +#~ msgid "Forward (Ctrl+L, F)" +#~ msgstr "Lähetä edelleen (Ctrl+L, F)" + +#~ msgid "" +#~ "Geary encountered an error while connecting to the server. Please try " +#~ "again in a few moments." +#~ msgstr "" +#~ "Geary kohtasi virheen palvelimeen yhdistäessä. Yritä uudelleen hetken " +#~ "kuluttua." + +#~ msgid "Try Again" +#~ msgstr "Yritä uudelleen" + +#~ msgid "Geary will run in the background and notify of new mail" +#~ msgstr "Geary toimii taustalla ja ilmoittaa uusista viesteistä" + #~ msgid "Empty" #~ msgstr "Tyhjennä" @@ -2715,12 +3719,6 @@ #~ msgid "No search results found." #~ msgstr "Hakutuloksia ei löytynyt." -#~ msgid "From:" -#~ msgstr "Lähettäjä:" - -#~ msgid "Date:" -#~ msgstr "Päiväys:" - #~ msgid "Copy _Link" #~ msgstr "Kopioi _linkki" @@ -2830,12 +3828,6 @@ #~ msgid "_Donate" #~ msgstr "La_hjoita" -#~ msgid "_Delete" -#~ msgstr "_Poista" - -#~ msgid "_Trash" -#~ msgstr "Siirr_ä roskakoriin" - #~ msgid "attach|enclosed|enclosing|cover letter" #~ msgstr "" #~ "attach|enclosed|enclosing|cover letter|liite|tiedosto|liitteenä|" @@ -2847,15 +3839,9 @@ #~ msgid "Please enter your password" #~ msgstr "Anna salasana" -#~ msgid "Username:" -#~ msgstr "Käyttäjätunnus:" - #~ msgid "Password:" #~ msgstr "Salasana:" -#~ msgid "Notify of new mail at start_up" -#~ msgstr "_Ilmoita uusista viesteistä käynnistyksen yhteydessä" - #~| msgid "_Close" #~ msgid "C_lose" #~ msgstr "_Sulje" @@ -2883,6 +3869,3 @@ #~ msgid "Unable to login to email server" #~ msgstr "Sähköpostipalvelimelle kirjautuminen epäonnistui" - -#~ msgid "_Details" -#~ msgstr "_Tiedot" diff -Nru geary-0.12.4/po/fr.po geary-3.32.0/po/fr.po --- geary-0.12.4/po/fr.po 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/po/fr.po 2019-03-17 13:39:29.000000000 +0000 @@ -1,4 +1,3 @@ -# po/geary.pot # PO message string template file for Geary email client # Copyright 2016 Software Freedom Conservancy Inc. # This file is distributed under the GNU LGPL, version 2.1. @@ -17,513 +16,917 @@ # Alain Lojewski , 2015. # Cédric Bellegarde , 2015. # Julien Hardelin , 2016. +# Gautier Pelloux-Prayer , 2016. +# Claude Paroz , 2017 +# Kévin Commaille , 2018. +# Alexandre Franke , 2019. # msgid "" msgstr "" -"Project-Id-Version: geary-0.4.1\n" -"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?" -"product=geary&keywords=I18N+L10N&component=general\n" -"POT-Creation-Date: 2016-06-19 12:32+0000\n" -"PO-Revision-Date: 2016-06-22 11:22+0200\n" -"Last-Translator: Julien Hardelin \n" +"Project-Id-Version: geary-master\n" +"Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/geary/issues\n" +"POT-Creation-Date: 2019-01-21 03:38+0000\n" +"PO-Revision-Date: 2018-07-17 15:55+0200\n" +"Last-Translator: Alexandre Franke , 2019.\n" "Language-Team: français \n" "Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" +"X-Generator: Poedit 2.0.9\n" -#: ../desktop/geary.desktop.in.h:1 ../desktop/geary-autostart.desktop.in.h:1 +#: desktop/geary-attach.contract.desktop.in:3 +msgid "Send by email" +msgstr "Envoyer par courriel" + +#. Translators: Do NOT translate or transliterate this text (this is an icon file name)! +#: desktop/geary-attach.contract.desktop.in:5 +msgid "mail-send" +msgstr "mail-send" + +#: desktop/geary-attach.contract.desktop.in:6 +msgid "Send files using Geary" +msgstr "Envoyer des fichiers avec Geary" + +#. Translators: The application name +#: desktop/geary-autostart.desktop.in:3 +#: desktop/org.gnome.Geary.appdata.xml.in:11 +#: desktop/org.gnome.Geary.desktop.in:3 +#: src/client/accounts/accounts-editor-servers-pane.vala:539 msgid "Geary" msgstr "Geary" -#: ../desktop/geary.desktop.in.h:2 ../desktop/geary-autostart.desktop.in.h:2 -#: ../src/client/application/geary-application.vala:18 -msgid "Mail Client" -msgstr "Client de messagerie" - -#: ../desktop/geary.desktop.in.h:3 ../desktop/geary-autostart.desktop.in.h:3 -msgid "Geary Mail" -msgstr "Messagerie Geary" - -#: ../desktop/geary.desktop.in.h:4 ../desktop/geary-autostart.desktop.in.h:4 +#: desktop/geary-autostart.desktop.in:4 desktop/org.gnome.Geary.desktop.in:4 +msgid "Email" +msgstr "Courriel" + +#. Translators: The application's summary / tagline +#: desktop/geary-autostart.desktop.in:5 +#: desktop/org.gnome.Geary.appdata.xml.in:15 +#: desktop/org.gnome.Geary.desktop.in:5 +#: src/client/application/geary-application.vala:22 msgid "Send and receive email" msgstr "Envoyer et recevoir des courriels" -#: ../desktop/geary.desktop.in.h:5 ../desktop/geary-autostart.desktop.in.h:5 +#. Translators: These are desktop search terms. Do not translate semicolons, end line with a semicolon. +#: desktop/geary-autostart.desktop.in:7 msgid "Email;E-mail;Mail;" msgstr "Email;E-mail;Messagerie;Courriels;" -#: ../desktop/geary.desktop.in.h:6 +#. Translators: Do NOT translate or transliterate this text (this is an icon file name)! +#: desktop/geary-autostart.desktop.in:9 desktop/org.gnome.Geary.desktop.in:9 +msgid "org.gnome.Geary" +msgstr "org.gnome.Geary" + +#. Translators: The development team's name +#: desktop/org.gnome.Geary.appdata.xml.in:13 +msgid "Geary Development Team" +msgstr "Équipe de développement de Geary" + +#: desktop/org.gnome.Geary.appdata.xml.in:17 +msgid "" +"Geary is an email application built around conversations, for the GNOME 3 " +"desktop. It allows you to read, find and send email with a straightforward, " +"modern interface." +msgstr "" +"Geary est une application de messagerie construite autour de conversations, " +"pour le bureau GNOME 3. Elle permet de lire, rechercher et envoyer des " +"courriels de façon simple, avec une interface élégante." + +#: desktop/org.gnome.Geary.appdata.xml.in:22 +msgid "" +"Conversations allow you to read a complete discussion without having to find " +"and click from message to message." +msgstr "" +"Les conversations permettent de lire les échanges sans avoir à naviguer d’un " +"message à un autre." + +#: desktop/org.gnome.Geary.appdata.xml.in:26 +msgid "Geary’s features include:" +msgstr "Geary intègre les fonctionnalités suivantes :" + +#: desktop/org.gnome.Geary.appdata.xml.in:28 +msgid "Quick email account setup" +msgstr "Configuration rapide du compte de messagerie" + +#: desktop/org.gnome.Geary.appdata.xml.in:29 +msgid "Shows related messages together in conversations" +msgstr "Affichage des messages liés sous forme de conversation" + +#: desktop/org.gnome.Geary.appdata.xml.in:30 +msgid "Fast, full text and keyword search" +msgstr "Recherche rapide, par mot clé ou par texte" + +#: desktop/org.gnome.Geary.appdata.xml.in:31 +msgid "Full-featured HTML and plain text message composer" +msgstr "Édition de courriel en texte simple ou enrichi en HTML" + +#: desktop/org.gnome.Geary.appdata.xml.in:32 +msgid "Desktop notification of new mail" +msgstr "Notification de nouveaux messages" + +#: desktop/org.gnome.Geary.appdata.xml.in:33 +msgid "Compatible with GMail, Yahoo! Mail, Outlook.com and other IMAP servers" +msgstr "" +"Compatible avec GMail, Yahoo! Mail, Outlook.com et autres serveurs IMAP" + +#. Translators: A screenshot description. +#: desktop/org.gnome.Geary.appdata.xml.in:47 +msgid "Geary displaying a conversation" +msgstr "Geary affichant une conversation" + +#. Translators: A screenshot description. +#: desktop/org.gnome.Geary.appdata.xml.in:52 +msgid "Geary showing the rich text composer" +msgstr "Geary montrant l’éditeur de texte enrichi" + +#. Translators: These are desktop search terms. Do not translate semicolons, end line with a semicolon. +#: desktop/org.gnome.Geary.desktop.in:7 +msgid "Mail;E-mail;IMAP;GMail;Yahoo;Hotmail;Outlook;" +msgstr "" +"Messagerie;Mail;Courriel;email;E-mail;IMAP;GMail;Yahoo;Hotmail;Outlook;" + +#: desktop/org.gnome.Geary.desktop.in:21 ui/gtk/menus.ui:7 msgid "Compose Message" msgstr "Écrire un message" -#: ../desktop/geary-attach.contract.in.h:1 -msgid "Send by email" -msgstr "Envoyer par courriel" +#: desktop/org.gnome.Geary.gschema.xml:8 +msgid "Maximize window" +msgstr "Maximiser la fenêtre" -#: ../desktop/geary-attach.contract.in.h:2 -msgid "Send files using Geary" -msgstr "Envoyer des fichiers avec Geary" +#: desktop/org.gnome.Geary.gschema.xml:9 +msgid "True if the application window is maximized, false otherwise." +msgstr "Vrai si la fenêtre de l’application est maximisée, faux sinon." -#: ../src/client/accounts/account-dialog-add-edit-pane.vala:51 -#: ../src/client/components/stock.vala:31 -msgid "_Save" -msgstr "_Enregistrer" +#: desktop/org.gnome.Geary.gschema.xml:14 +msgid "Width of window" +msgstr "Largeur de la fenêtre" -#: ../src/client/accounts/account-dialog-add-edit-pane.vala:51 -#: ../src/client/components/stock.vala:22 -msgid "_Add" -msgstr "_Ajouter" +#: desktop/org.gnome.Geary.gschema.xml:15 +msgid "The last recorded width of the application window." +msgstr "La dernière largeur enregistrée pour la fenêtre de l’application." -#. reset/clear widgets -#: ../src/client/accounts/account-dialog-edit-alternate-emails-pane.vala:124 -#, c-format -msgid "Additional addresses for %s" -msgstr "Adresses supplémentaires pour %s" +#: desktop/org.gnome.Geary.gschema.xml:20 +msgid "Height of window" +msgstr "Hauteur de la fenêtre" -#. Sets min size. -#: ../src/client/accounts/account-dialog.vala:21 -msgid "Accounts" -msgstr "Comptes" +#: desktop/org.gnome.Geary.gschema.xml:21 +msgid "The last recorded height of the application window." +msgstr "La dernière hauteur enregistrée pour la fenêtre de l’application." -#. Copyright 2016 Software Freedom Conservancy Inc. -#. * -#. * This software is licensed under the GNU Lesser General Public License -#. * (version 2.1 or later). See the COPYING file in this distribution. -#. -#. Page for adding or editing an account. -#. / Placeholder text indicating that the user should type their first name and last name -#: ../src/client/accounts/add-edit-page.vala:10 -msgid "First Last" -msgstr "Prénom Nom" +#: desktop/org.gnome.Geary.gschema.xml:26 +msgid "Position of folder list pane" +msgstr "Position du panneau de la liste des dossiers" -#: ../src/client/accounts/add-edit-page.vala:233 -msgid "Welcome to Geary." -msgstr "Bienvenue dans Geary." +# Paned Grabber ? +#: desktop/org.gnome.Geary.gschema.xml:27 +#, fuzzy +msgid "Position of the folder list Paned grabber." +msgstr "Position du panneau de la liste des dossiers." -#: ../src/client/accounts/add-edit-page.vala:233 -msgid "Enter your account information to get started." -msgstr "Saisissez vos informations de connexion pour commencer." +#: desktop/org.gnome.Geary.gschema.xml:32 +msgid "Position of folder list pane when horizontal" +msgstr "Position du panneau de la liste des dossiers en horizontal" -#: ../src/client/accounts/add-edit-page.vala:253 -msgid "2 weeks back" -msgstr "il y a 2 semaines" +# Paned Grabber ? +#: desktop/org.gnome.Geary.gschema.xml:33 +#, fuzzy +msgid "" +"Position of the folder list Paned grabber in the horizontal orientation." +msgstr "" +"Position du panneau de la liste des dossiers en orientation horizontale." -#. IDs are # of days -#: ../src/client/accounts/add-edit-page.vala:254 -msgid "1 month back" -msgstr "il y a 1 mois" +#: desktop/org.gnome.Geary.gschema.xml:38 +msgid "Position of folder list pane when vertical" +msgstr "Position du panneau de la liste des dossiers en vertical" -#: ../src/client/accounts/add-edit-page.vala:255 -msgid "3 months back" -msgstr "il y a 3 mois" +# Paned Grabber ? +#: desktop/org.gnome.Geary.gschema.xml:39 +#, fuzzy +msgid "Position of the folder list Paned grabber in the vertical orientation." +msgstr "Position du panneau de la liste des dossiers en orientation verticale." -#: ../src/client/accounts/add-edit-page.vala:256 -msgid "6 months back" -msgstr "il y a 6 mois" +#: desktop/org.gnome.Geary.gschema.xml:44 +msgid "Orientation of the folder list pane" +msgstr "Orientation du panneau de la liste des dossiers" -#: ../src/client/accounts/add-edit-page.vala:257 -msgid "1 year back" -msgstr "il y a 1 an" +#: desktop/org.gnome.Geary.gschema.xml:45 +#, fuzzy +msgid "True if the folder list Paned is in the horizontal orientation." +msgstr "" +"Vrai si le panneau de la liste des dossiers est en orientation horizontale." -#: ../src/client/accounts/add-edit-page.vala:258 -msgid "2 years back" -msgstr "il y a 2 ans" +#: desktop/org.gnome.Geary.gschema.xml:50 +msgid "Position of message list pane" +msgstr "Position du panneau de la liste des messages" -#: ../src/client/accounts/add-edit-page.vala:259 -msgid "4 years back" -msgstr "il y a 4 ans" +# Paned Grabber ? +#: desktop/org.gnome.Geary.gschema.xml:51 +#, fuzzy +msgid "Position of the message list Paned grabber." +msgstr "Position du panneau de la liste des messages." -#. Separator -#: ../src/client/accounts/add-edit-page.vala:261 -msgid "Everything" -msgstr "Tout" +#: desktop/org.gnome.Geary.gschema.xml:56 +msgid "Autoselect next message" +msgstr "Sélectionner automatiquement le message suivant" -#: ../src/client/accounts/add-edit-page.vala:280 -msgid "Edit" -msgstr "Éditer" - -#: ../src/client/accounts/add-edit-page.vala:282 -msgid "Preview" -msgstr "Prévisualiser" - -#: ../src/client/accounts/add-edit-page.vala:723 -msgid "Remem_ber passwords" -msgstr "_Retenir les mots de passe" - -#: ../src/client/accounts/add-edit-page.vala:730 ../ui/login.glade.h:6 -msgid "Remem_ber password" -msgstr "_Retenir le mot de passe" - -#: ../src/client/accounts/add-edit-page.vala:764 -msgid "Unable to validate:\n" -msgstr "Impossible de valider :\n" - -#: ../src/client/accounts/add-edit-page.vala:766 -msgid " • Invalid account nickname.\n" -msgstr " • Nom d'utilisateur invalide.\n" - -#: ../src/client/accounts/add-edit-page.vala:769 -msgid " • Email address already added to Geary.\n" -msgstr " • Adresse électronique déjà ajoutée à Geary.\n" - -#: ../src/client/accounts/add-edit-page.vala:773 -msgid " • IMAP connection error.\n" -msgstr " • Erreur de connexion IMAP.\n" - -#: ../src/client/accounts/add-edit-page.vala:776 -msgid " • IMAP username or password incorrect.\n" -msgstr " • Mot de passe ou nom d'utilisateur IMAP incorrect.\n" - -#: ../src/client/accounts/add-edit-page.vala:779 -msgid " • SMTP connection error.\n" -msgstr " • Erreur de connexion SMTP.\n" - -#: ../src/client/accounts/add-edit-page.vala:782 -msgid " • SMTP username or password incorrect.\n" -msgstr " • Mot de passe ou nom d'utilisateur SMTP incorrect.\n" - -#: ../src/client/accounts/add-edit-page.vala:786 -msgid " • Connection error.\n" -msgstr " • Erreur de connexion.\n" - -#: ../src/client/accounts/add-edit-page.vala:790 -msgid " • Username or password incorrect.\n" -msgstr " • Mot de passe ou nom d'utilisateur incorrect.\n" +#: desktop/org.gnome.Geary.gschema.xml:57 +msgid "True if we should autoselect the next available conversation." +msgstr "" +"Vrai si la conversation suivante doit être sélectionnée automatiquement." -#: ../src/client/application/geary-application.vala:19 -msgid "Copyright 2016 Software Freedom Conservancy Inc." -msgstr "Copyright 2016 Software Freedom Conservancy Inc." +#: desktop/org.gnome.Geary.gschema.xml:62 +msgid "Display message previews" +msgstr "Afficher les aperçus des messages" -#: ../src/client/application/geary-application.vala:21 -msgid "Visit the Geary web site" -msgstr "Visiter le site Web de Geary" +#: desktop/org.gnome.Geary.gschema.xml:63 +msgid "True if we should display a short preview of each message." +msgstr "Vrai si un court aperçu de chaque message doit être affiché." -#: ../src/client/application/geary-args.vala:10 -msgid "Start Geary with hidden main window" -msgstr "Démarrer Geary en mode caché" +#: desktop/org.gnome.Geary.gschema.xml:68 +msgid "Languages that shall be used in the spell checker" +msgstr "Langues à utiliser pour le vérificateur orthographique" -#: ../src/client/application/geary-args.vala:11 -msgid "Output debugging information" -msgstr "Afficher les informations de débogage" +#: desktop/org.gnome.Geary.gschema.xml:69 +msgid "List of the languages to use in the spell checker." +msgstr "Liste des langues à utiliser pour le vérificateur orthographique." -#: ../src/client/application/geary-args.vala:12 -msgid "Log conversation monitoring" -msgstr "Journaliser la surveillance des conversations" +#: desktop/org.gnome.Geary.gschema.xml:74 +msgid "Languages that are displayed in the spell checker popover" +msgstr "" +"Langues à afficher dans la fenêtre contextuelle du vérificateur " +"orthographique" -#: ../src/client/application/geary-args.vala:13 -msgid "Log network deserialization" -msgstr "Journaliser la désérialisation du réseau" +#: desktop/org.gnome.Geary.gschema.xml:75 +msgid "" +"List of languages that are always displayed in the popover of the spell " +"checker." +msgstr "" +"Liste des langues qui sont toujours affichées dans la fenêtre contextuelle " +"du vérificateur orthographique." -#: ../src/client/application/geary-args.vala:14 -msgid "Log network activity" -msgstr "Journaliser l'activité du réseau" +#: desktop/org.gnome.Geary.gschema.xml:80 +msgid "Enable notification sounds" +msgstr "Activer les sons de notification" -#. / The IMAP replay queue is how changes on the server are replicated on the client. -#. / It could also be called the IMAP events queue. -#: ../src/client/application/geary-args.vala:17 -msgid "Log IMAP replay queue" -msgstr "Journaliser la liste des événements IMAP" +#: desktop/org.gnome.Geary.gschema.xml:81 +msgid "True to play sounds for notifications and sending." +msgstr "Vrai pour jouer les sons des notifications et lors de l’envoi." -#. / Serialization is how commands and responses are converted into a stream of bytes for -#. / network transmission -#: ../src/client/application/geary-args.vala:20 -msgid "Log network serialization" -msgstr "Journaliser la sérialisation du réseau" +#: desktop/org.gnome.Geary.gschema.xml:86 +msgid "Show notifications for new mail" +msgstr "Afficher les notifications de nouveaux messages" -#: ../src/client/application/geary-args.vala:21 -msgid "Log periodic activity" -msgstr "Journaliser l'activité périodique" +#: desktop/org.gnome.Geary.gschema.xml:87 +msgid "True to show notification bubbles." +msgstr "Vrai pour afficher les bulles de notification." -#: ../src/client/application/geary-args.vala:22 -msgid "Log database queries (generates lots of messages)" +#: desktop/org.gnome.Geary.gschema.xml:92 +msgid "Notify of new mail at startup" +msgstr "Notification des nouveaux messages au démarrage" + +#: desktop/org.gnome.Geary.gschema.xml:93 +msgid "True to notify of new mail at startup." msgstr "" -"Journaliser les requêtes de la base de données (génère beaucoup de messages)" +"Vrai pour recevoir une notification des nouveaux messages au démarrage." -#. / "Normalization" can also be called "synchronization" -#: ../src/client/application/geary-args.vala:24 -msgid "Log folder normalization" -msgstr "Journaliser la normalisation des dossiers" +#: desktop/org.gnome.Geary.gschema.xml:98 +msgid "Ask when opening an attachment" +msgstr "Demander lors de l’ouverture d’une pièce jointe" -#: ../src/client/application/geary-args.vala:25 -msgid "Allow inspection of WebView" -msgstr "Autoriser l'inspection WebView" +#: desktop/org.gnome.Geary.gschema.xml:99 +msgid "True to ask when opening an attachment." +msgstr "Vrai pour demander lors de l’ouverture d’une pièce jointe." -#: ../src/client/application/geary-args.vala:26 -msgid "Revoke all server certificates with TLS warnings" -msgstr "Supprimer tous les certificats avec avertissements TLS." +#: desktop/org.gnome.Geary.gschema.xml:104 +msgid "Whether to compose emails in HTML" +msgstr "Composer les courriels en HTML" -#: ../src/client/application/geary-args.vala:27 -msgid "Display program version" -msgstr "Afficher la version du programme" +#: desktop/org.gnome.Geary.gschema.xml:105 +msgid "True to compose emails in HTML; false for plain text." +msgstr "Vrai pour composer les messages en HTML, faux pour du texte simple." -#. This gives a command-line hint on how to open new composer windows with mailto: -#: ../src/client/application/geary-args.vala:51 +#: desktop/org.gnome.Geary.gschema.xml:110 +msgid "Advisory strategy for full-text searching" +msgstr "Stratégie consultative pour la recherche de texte intégral" + +#: desktop/org.gnome.Geary.gschema.xml:111 +msgid "" +"Acceptable values are “exact”, “conservative”, “aggressive”, and “horizon”." +msgstr "" +"Les valeurs acceptables sont \"exact\" (« exacte »), \"conservative\" (« conservatrice »), \"aggressive\" et « horizon »." + +#: desktop/org.gnome.Geary.gschema.xml:116 +msgid "Zoom of conversation viewer" +msgstr "Zoom de l’afficheur de conversation" + +#: desktop/org.gnome.Geary.gschema.xml:117 +msgid "The zoom to apply on the conservation view." +msgstr "Le zoom à appliquer sur la vue de conversation." + +#: desktop/org.gnome.Geary.gschema.xml:122 +msgid "Size of detached composer window" +msgstr "Taille de la fenêtre d’édition détachée" + +#: desktop/org.gnome.Geary.gschema.xml:123 +msgid "The last recorded size of the detached composer window." +msgstr "La dernière taille enregistrée de la fenêtre d’édition détachée." + +#: desktop/org.gnome.Geary.gschema.xml:128 +msgid "Base URL to look up contact avatars" +msgstr "URL de base pour la recherche d’avatars" + +#: desktop/org.gnome.Geary.gschema.xml:129 +msgid "" +"A Gravatar or Libravatar compatible URL, set to the empty string to disable." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:134 +msgid "Whether we migrated the old settings" +msgstr "Indique si les anciens paramètres ont été migrés" + +#: desktop/org.gnome.Geary.gschema.xml:135 +msgid "" +"False to check for the old “org.yorba.geary”-schema and copy its values." +msgstr "" +"Faux pour chercher le vieux schéma « org.yorba.geary » et copier ses valeurs." + +#. Translators: In-app notification label, when +#. the app had a problem pinning an otherwise +#. untrusted TLS certificate +#: src/client/accounts/accounts-editor.vala:203 +msgid "Failed to store certificate" +msgstr "" + +#. Translators: Label for adding an email account +#. account for a generic IMAP service provider. +#: src/client/accounts/accounts-editor-add-pane.vala:109 +msgid "All others" +msgstr "Tous les autres" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:196 +#: src/client/accounts/accounts-editor-servers-pane.vala:300 +msgid "Check your receiving login and password" +msgstr "" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:211 +#: src/client/accounts/accounts-editor-servers-pane.vala:313 +msgid "Check your receiving server details" +msgstr "" + +#. Translators: In-app notification label +#. There was an SMTP auth error, but IMAP already +#. succeeded, so the user probably needs to +#. specify custom creds here +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:233 +#: src/client/accounts/accounts-editor-servers-pane.vala:334 +msgid "Check your sending login and password" +msgstr "" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:247 +#: src/client/accounts/accounts-editor-servers-pane.vala:347 +msgid "Check your sending server details" +msgstr "" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:262 +msgid "Check your email address and password" +msgstr "" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:273 +msgid "Could not connect, check your network" +msgstr "" + +#. Translators: In-app notification label for a +#. generic error creating an account +#: src/client/accounts/accounts-editor-add-pane.vala:286 +msgid "An unexpected problem occurred" +msgstr "Un problème inattendu est apparu" + +#. Translators: In-app notification label, the +#. string substitution is a more detailed reason. +#: src/client/accounts/accounts-editor-add-pane.vala:304 #, c-format -msgid "Use %s to open a new composer window" -msgstr "Utilisez %s pour ouvrir une nouvelle fenêtre d'édition" +msgid "Account not created: %s" +msgstr "" -#: ../src/client/application/geary-args.vala:52 -msgid "Please report comments, suggestions and bugs to:" +#. Translators: Label for the person's actual name when adding +#. an account +#: src/client/accounts/accounts-editor-add-pane.vala:552 +msgid "Your name" +msgstr "Votre nom" + +#: src/client/accounts/accounts-editor-add-pane.vala:569 +msgid "Email address" +msgstr "Adresse de courriel" + +#. Translators: Placeholder for the default sender address +#. when adding an account +#. Translators: This is used as a placeholder for the +#. address part of an email address when editing a user's +#. sender address preferences for an account. +#: src/client/accounts/accounts-editor-add-pane.vala:572 +#: src/client/accounts/accounts-editor-edit-pane.vala:469 +msgid "person@example.com" +msgstr "courriel@exemple.fr" + +#. Translators: Label for an IMAP/SMTP service login/user name +#. when adding an account +#. Translators: Label for the user's login name for an +#. IMAP, SMTP, etc service +#: src/client/accounts/accounts-editor-add-pane.vala:586 +#: src/client/accounts/accounts-editor-servers-pane.vala:809 +msgid "Login name" +msgstr "Identifiant" + +#. Translators: Label for the user's password for an IMAP, +#. SMTP, etc service +#: src/client/accounts/accounts-editor-add-pane.vala:600 +#: src/client/accounts/accounts-editor-servers-pane.vala:928 +#: ui/password-dialog.glade:108 +msgid "Password" +msgstr "Mot de passe" + +#. Translators: Label for the IMAP server hostname when +#. adding an account. +#. Translators: This label describes the host name or IP +#. address and port used by an account's IMAP service. +#: src/client/accounts/accounts-editor-add-pane.vala:622 +#: src/client/accounts/accounts-editor-servers-pane.vala:656 +msgid "IMAP server" +msgstr "Serveur IMAP" + +#. Translators: Placeholder for the IMAP server hostname +#. when adding an account. +#: src/client/accounts/accounts-editor-add-pane.vala:625 +msgid "imap.example.com" +msgstr "imap.exemple.fr" + +#. Translators: Label for the SMTP server hostname when +#. adding an account. +#. Translators: This label describes the host name or IP +#. address and port used by an account's SMTP service. +#: src/client/accounts/accounts-editor-add-pane.vala:631 +#: src/client/accounts/accounts-editor-servers-pane.vala:662 +msgid "SMTP server" +msgstr "Serveur SMTP" + +#. Translators: Placeholder for the SMTP server hostname +#. when adding an account. +#: src/client/accounts/accounts-editor-add-pane.vala:634 +msgid "smtp.example.com" +msgstr "smtp.exemple.fr" + +#. Translators: Label in the account editor for the user's +#. custom name for an account. +#: src/client/accounts/accounts-editor-edit-pane.vala:278 +#: ui/accounts_editor_remove_pane.ui:123 +msgid "Account name" +msgstr "Nom du compte" + +#. Translators: Tooltip used to undo changing +#. the name of an account. The string +#. substitution is the old name of the +#. account. +#: src/client/accounts/accounts-editor-edit-pane.vala:312 +#, c-format +msgid "Change account name back to “%s”" +msgstr "" + +#. Translators: Tooltip for adding a new email sender/from +#. address's address to an account +#: src/client/accounts/accounts-editor-edit-pane.vala:336 +msgid "Add a new sender email address" +msgstr "" + +#. Translators: Label used to indicate the user has +#. provided no display name for one of their sender +#. email addresses in their account settings. +#: src/client/accounts/accounts-editor-edit-pane.vala:417 +msgid "Name not set" +msgstr "" + +#. Translators: This is used as a placeholder for the +#. display name for an email address when editing a user's +#. sender address preferences for an account. +#: src/client/accounts/accounts-editor-edit-pane.vala:456 +msgid "Sender Name" msgstr "" -"N'hésitez pas à envoyer vos commentaires, suggestions et rapports " -"d'anomalies à :" -#. i18n: Command line arguments are invalid -#: ../src/client/application/geary-args.vala:59 +#: src/client/accounts/accounts-editor-edit-pane.vala:479 +msgid "Remove" +msgstr "Supprimer" + +#. Translators: Label used for the display name part of an +#. email address when editing a user's sender address +#. preferences for an account. +#: src/client/accounts/accounts-editor-edit-pane.vala:494 +msgid "Sender name:" +msgstr "" + +#. Translators: Label used for the address part of an +#. email address when editing a user's sender address +#. preferences for an account. +#: src/client/accounts/accounts-editor-edit-pane.vala:501 +msgid "Email address:" +msgstr "Adresse de courriel :" + +#. Translators: Label used as the undo tooltip after adding an +#. new sender email address to an account. The string +#. substitution is the email address added. +#: src/client/accounts/accounts-editor-edit-pane.vala:561 #, c-format -msgid "Failed to parse command line options: %s\n" -msgstr "Impossible d'analyser les options de ligne de commande : %s\n" +msgid "Remove “%s”" +msgstr "" -#: ../src/client/application/geary-args.vala:70 +#. Translators: Label used as the undo tooltip after editing a +#. sender address for an account. The string substitution is +#. the email address edited. +#: src/client/accounts/accounts-editor-edit-pane.vala:601 #, c-format -msgid "Unrecognized command line option \"%s\"\n" -msgstr "Option de ligne de commande « %s » inconnue\n" +msgid "Undo changes to “%s”" +msgstr "" -#: ../src/client/application/geary-controller.vala:61 -msgid "Delete conversation" -msgstr "Supprimer la conversation" +#. Translators: Label used as the undo tooltip after removing +#. a sender address from an account. The string substitution +#. is the email address edited. +#: src/client/accounts/accounts-editor-edit-pane.vala:690 +#, c-format +msgid "Add “%s” back" +msgstr "" -#: ../src/client/application/geary-controller.vala:62 -msgid "Delete conversation (Shift+Delete)" -msgstr "Supprimer la conversation (Maj+Suppr)" +#. Translators: Label used as the undo tooltip after removing +#. a sender address from an account. The string substitution +#. is the email address edited. +#: src/client/accounts/accounts-editor-edit-pane.vala:728 +msgid "Undo signature changes" +msgstr "" -#: ../src/client/application/geary-controller.vala:63 -msgid "Delete conversations (Shift+Delete)" -msgstr "Supprimer les conversations (Maj+Suppr)" +#. Translators: This label describes the account +#. preference for the length of time (weeks, months or +#. years) that past email should be downloaded. +#: src/client/accounts/accounts-editor-edit-pane.vala:772 +msgid "Download mail" +msgstr "Télécharger les messages" -#. This refers to the action ("move email to the trash"), not the Trash folder itself -#: ../src/client/application/geary-controller.vala:67 -msgid "Move conversation to Trash (Delete, Backspace)" -msgstr "Mettre la conversation à la corbeille (Suppr, Retour arrière)" +#. Translators: Tooltip for undoing a change +#. to the length of time that past email +#. should be downloaded for an account. The +#. string substitution is the duration, +#. e.g. "1 month back". +#: src/client/accounts/accounts-editor-edit-pane.vala:804 +#, c-format +msgid "Change download period back to: %s" +msgstr "" -#: ../src/client/application/geary-controller.vala:68 -msgid "Move conversations to Trash (Delete, Backspace)" -msgstr "Mettre les conversations à la corbeille (Suppr, Retour arrière)" +#: src/client/accounts/accounts-editor-edit-pane.vala:825 +msgid "Everything" +msgstr "Tout" -#. This refers to the action ("archive an email"), not the Archive folder itself -#: ../src/client/application/geary-controller.vala:72 -msgid "_Archive" -msgstr "_Archiver" +#: src/client/accounts/accounts-editor-edit-pane.vala:829 +msgid "2 weeks back" +msgstr "Les 2 dernières semaines" -#: ../src/client/application/geary-controller.vala:73 -msgid "Archive conversation (A)" -msgstr "Archiver la conversation (A)" +#: src/client/accounts/accounts-editor-edit-pane.vala:833 +msgid "1 month back" +msgstr "Le mois dernier" -#: ../src/client/application/geary-controller.vala:74 -msgid "Archive conversations (A)" -msgstr "Archiver les conversations (A)" +#: src/client/accounts/accounts-editor-edit-pane.vala:837 +msgid "3 months back" +msgstr "Les 3 derniers mois" -#: ../src/client/application/geary-controller.vala:77 -msgid "Mark as S_pam" -msgstr "Marquer comme _pourriel" +#: src/client/accounts/accounts-editor-edit-pane.vala:841 +msgid "6 months back" +msgstr "Les 6 derniers mois" -#: ../src/client/application/geary-controller.vala:78 -msgid "Mark as not S_pam" -msgstr "Marquer comme non _pourriel" +#: src/client/accounts/accounts-editor-edit-pane.vala:845 +msgid "1 year back" +msgstr "1 an" -#: ../src/client/application/geary-controller.vala:80 -#: ../src/client/application/geary-controller.vala:396 -msgid "Mark conversation" -msgstr "Marquer la conversation" +#: src/client/accounts/accounts-editor-edit-pane.vala:849 +msgid "2 years back" +msgstr "Les 2 dernières années" -#: ../src/client/application/geary-controller.vala:81 -msgid "Mark conversations" -msgstr "Marquer les conversations" +#: src/client/accounts/accounts-editor-edit-pane.vala:853 +msgid "4 years back" +msgstr "Les 4 dernières années" -#: ../src/client/application/geary-controller.vala:82 -msgid "Add label to conversation" -msgstr "Ajouter une étiquette à la conversation" +#: src/client/accounts/accounts-editor-edit-pane.vala:859 +#, c-format +msgid "%d day back" +msgid_plural "%d days back" +msgstr[0] "%d jour" +msgstr[1] "Les %d derniers jours" + +#: src/client/accounts/accounts-editor-list-pane.vala:243 +msgid "Undo" +msgstr "Annuler" + +#: src/client/accounts/accounts-editor-list-pane.vala:251 +msgid "Redo" +msgstr "Rétablir" + +#: src/client/accounts/accounts-editor-list-pane.vala:345 +#: src/client/accounts/accounts-editor-list-pane.vala:433 +#: src/client/accounts/accounts-editor-row.vala:278 +msgid "Gmail" +msgstr "Gmail" -#: ../src/client/application/geary-controller.vala:83 -msgid "Add label to conversations" -msgstr "Ajouter une étiquette aux conversations" +#: src/client/accounts/accounts-editor-list-pane.vala:349 +#: src/client/accounts/accounts-editor-list-pane.vala:437 +#: src/client/accounts/accounts-editor-row.vala:282 +msgid "Outlook.com" +msgstr "Outlook.com" -#: ../src/client/application/geary-controller.vala:84 -#: ../src/client/application/geary-controller.vala:435 -msgid "Move conversation" -msgstr "Déplacer la conversation" +#: src/client/accounts/accounts-editor-list-pane.vala:353 +#: src/client/accounts/accounts-editor-list-pane.vala:441 +#: src/client/accounts/accounts-editor-row.vala:286 +msgid "Yahoo" +msgstr "Yahoo" + +#. Translators: Tooltip for accounts that have been +#. loaded but disabled by the user. +#: src/client/accounts/accounts-editor-list-pane.vala:371 +msgid "This account has been disabled" +msgstr "Ce compte a été désactivé" + +#. Translators: Tooltip for accounts that have been +#. loaded but because of some error are not able to be +#. used. +#: src/client/accounts/accounts-editor-list-pane.vala:380 +msgid "This account has encountered a problem and is unavailable" +msgstr "" + +#. Translators: Label for adding a generic email account +#: src/client/accounts/accounts-editor-list-pane.vala:430 +msgid "Other email providers" +msgstr "Autres fournisseurs de courriel" + +#. Translators: Notification shown after removing an +#. account. The string substitution is the name of the +#. account. +#: src/client/accounts/accounts-editor-list-pane.vala:547 +#, c-format +msgid "Account “%s” removed" +msgstr "" + +#. Translators: Notification shown after removing an account +#. is undone. The string substitution is the name of the +#. account. +#: src/client/accounts/accounts-editor-list-pane.vala:554 +#, c-format +msgid "Account “%s” restored" +msgstr "" + +#. Translators: Tooltip for dragging list items +#: src/client/accounts/accounts-editor-row.vala:49 +msgid "Drag to move this item" +msgstr "" + +#. Translators: Label describes the service provider +#. hosting the email account, e.g. Gmail, Yahoo, or some +#. other generic IMAP service. +#: src/client/accounts/accounts-editor-row.vala:294 +msgid "Service provider" +msgstr "Fournisseur de service" + +#. Translators: This label describes what form of transport +#. security (TLS, StartTLS, etc) used by an account's IMAP or SMTP +#. service. +#: src/client/accounts/accounts-editor-row.vala:467 +msgid "Connection security" +msgstr "Sécurité de la connexion" + +#. Translators: Label used when no auth scheme is used +#. by an account's IMAP or SMTP service. +#: src/client/accounts/accounts-editor-row.vala:478 +#: src/client/accounts/accounts-editor-servers-pane.vala:681 +#: src/client/accounts/accounts-editor-servers-pane.vala:893 +#: src/engine/api/geary-special-folder-type.vala:58 +msgid "None" +msgstr "Aucune" -#: ../src/client/application/geary-controller.vala:85 -msgid "Move conversations" -msgstr "Déplacer les conversations" +#: src/client/accounts/accounts-editor-row.vala:485 +msgid "StartTLS" +msgstr "StartTLS" -#: ../src/client/application/geary-controller.vala:376 -#: ../ui/app_menu.interface.h:1 -msgid "A_ccounts" -msgstr "C_omptes" +#: src/client/accounts/accounts-editor-row.vala:492 +msgid "TLS" +msgstr "TLS" -#: ../src/client/application/geary-controller.vala:381 -#: ../src/client/components/stock.vala:27 ../ui/app_menu.interface.h:2 -msgid "_Preferences" -msgstr "_Préférences" +#. Translators: Label for source of SMTP authentication +#. credentials (none, use IMAP, custom) when adding a new +#. account +#. Button label for retrying when a login error has occurred +#: src/client/accounts/accounts-editor-row.vala:533 ui/main-window.ui:455 +msgid "Login" +msgstr "" -#: ../src/client/application/geary-controller.vala:385 -#: ../src/client/components/stock.vala:25 ../ui/app_menu.interface.h:3 -msgid "_Help" -msgstr "Aid_e" +#. Translators: ComboBox value for source of SMTP +#. authentication credentials (none) when adding a new +#. account +#: src/client/accounts/accounts-editor-row.vala:540 +msgid "No login needed" +msgstr "" -#: ../src/client/application/geary-controller.vala:389 -#: ../src/client/components/stock.vala:21 ../ui/app_menu.interface.h:4 -msgid "_About" -msgstr "À _propos" +#. Translators: ComboBox value for source of SMTP +#. authentication credentials (use IMAP) when adding a new +#. account +#: src/client/accounts/accounts-editor-row.vala:548 +msgid "Use same login as receiving" +msgstr "" -#: ../src/client/application/geary-controller.vala:393 -#: ../src/client/components/stock.vala:29 ../ui/app_menu.interface.h:5 -msgid "_Quit" -msgstr "_Quitter" +#. Translators: ComboBox value for source of SMTP +#. authentication credentials (custom) when adding a new +#. account +#: src/client/accounts/accounts-editor-row.vala:556 +msgid "Use a different login" +msgstr "" -#: ../src/client/application/geary-controller.vala:398 -msgid "_Mark as..." -msgstr "_Marquer comme…" +#. Translators: In-app notification label, the +#. string substitution is a more detailed reason. +#: src/client/accounts/accounts-editor-servers-pane.vala:361 +#, c-format +msgid "Account not updated: %s" +msgstr "" -#: ../src/client/application/geary-controller.vala:404 -msgid "Mark as _Read" -msgstr "Marquer comme _lu" +#. Translators: This label describes the program that +#. created the account, e.g. an SSO service like GOA, or +#. locally by Geary. +#: src/client/accounts/accounts-editor-servers-pane.vala:524 +msgid "Account source" +msgstr "Source de comptes" -#: ../src/client/application/geary-controller.vala:410 -msgid "Mark as _Unread" -msgstr "Marquer comme _non Lu" +#: src/client/accounts/accounts-editor-servers-pane.vala:536 +msgid "GNOME Online Accounts" +msgstr "Comptes en ligne de GNOME" -#: ../src/client/application/geary-controller.vala:416 -msgid "_Star" -msgstr "Ma_rquer d'une étoile" +#. Translators: This label describes an account +#. preference. +#: src/client/accounts/accounts-editor-servers-pane.vala:595 +msgid "Save drafts on server" +msgstr "Enregistrer les brouillons sur le serveur" -#: ../src/client/application/geary-controller.vala:421 -msgid "U_nstar" -msgstr "_Supprimer l'étoile" +#. Add a suffix for OAuth2 auth so people know they +#. shouldn't expect to be prompted for a password +#. Translators: Label used when an account's IMAP or +#. SMTP service uses OAuth2. The string replacement is +#. the service's login name. +#: src/client/accounts/accounts-editor-servers-pane.vala:879 +#, c-format +msgid "%s using OAuth2" +msgstr "%s par OAuth2" -#: ../src/client/application/geary-controller.vala:431 -msgid "Add label" -msgstr "Ajouter une étiquette" - -#: ../src/client/application/geary-controller.vala:432 -msgid "_Label" -msgstr "É_tiqueter" - -#: ../src/client/application/geary-controller.vala:436 -msgid "_Move" -msgstr "_Déplacer" - -#: ../src/client/application/geary-controller.vala:440 -msgid "Compose new message (Ctrl+N, N)" -msgstr "Composer un nouveau message (Ctrl+N, N)" - -#. Reply to a message. -#: ../src/client/application/geary-controller.vala:444 -#: ../src/client/conversation-viewer/conversation-viewer.vala:1858 -msgid "_Reply" -msgstr "_Répondre" +#: src/client/accounts/accounts-editor-servers-pane.vala:889 +msgid "Use receiving server login" +msgstr "" -#: ../src/client/application/geary-controller.vala:445 -msgid "Reply (Ctrl+R, R)" -msgstr "Répondre (Ctrl+R, R)" +#: src/client/application/geary-application.vala:23 +msgid "Copyright 2016 Software Freedom Conservancy Inc." +msgstr "Copyright 2016 Software Freedom Conservancy Inc." -#: ../src/client/application/geary-controller.vala:449 -msgid "R_eply All" -msgstr "R_épondre à tous" +#: src/client/application/geary-application.vala:24 +msgid "Copyright 2016-2018 Geary Development Team." +msgstr "Copyright 2016-2018 Équipe de développement de Geary." -#: ../src/client/application/geary-controller.vala:450 -msgid "Reply all (Ctrl+Shift+R, Shift+R)" -msgstr "Répondre à tous (Ctrl+Maj+R, Maj+R)" - -#. Forward a message. -#: ../src/client/application/geary-controller.vala:455 -#: ../src/client/conversation-viewer/conversation-viewer.vala:1868 -msgid "_Forward" -msgstr "_Transférer" +#: src/client/application/geary-application.vala:26 +msgid "Visit the Geary web site" +msgstr "Visiter le site Web de Geary" -#: ../src/client/application/geary-controller.vala:456 -msgid "Forward (Ctrl+L, F)" -msgstr "Transférer (Ctr+L, F)" - -#: ../src/client/application/geary-controller.vala:495 -msgid "Empty" -msgstr "Vider" +#: src/client/application/geary-application.vala:413 +#, c-format +msgid "About %s" +msgstr "À propos de %s" -#: ../src/client/application/geary-controller.vala:496 -msgid "Empty Spam or Trash folders" -msgstr "Vider les répertoires pourriels ou la corbeille" +#. Translators: add your name and email address to receive +#. credit in the About dialog For example: Yamada Taro +#. +#: src/client/application/geary-application.vala:417 +msgid "translator-credits" +msgstr "" +"ttoine \n" +"bmoez \n" +"boubakr92 \n" +"carlito \n" +"alucryd \n" +"merle \n" +"Philippe Bernery \n" +"kalia35 \n" +"Claude Paroz \n" +"Julien Hardelin \n" +"Kévin Commaille \n" +"Alexandre Franke " -#: ../src/client/application/geary-controller.vala:500 -msgid "Empty _Spam…" -msgstr "Vider les pourriel_s…" +#: src/client/application/geary-args.vala:10 +msgid "Start Geary with hidden main window" +msgstr "Démarrer Geary en mode caché" -#: ../src/client/application/geary-controller.vala:504 -msgid "Empty _Trash…" -msgstr "Vider la _corbeille…" +#: src/client/application/geary-args.vala:11 +msgid "Output debugging information" +msgstr "Afficher les informations de débogage" -#. No callback is connected, since we bind the toggle button to the search bar visibility -#: ../src/client/application/geary-controller.vala:537 -msgid "Toggle search bar" -msgstr "Afficher la barre de recherche" +#: src/client/application/geary-args.vala:12 +msgid "Log conversation monitoring" +msgstr "Journaliser la surveillance des conversations" -#: ../src/client/application/geary-controller.vala:744 -msgid "Unable to store server trust exception" -msgstr "Impossible d'enregistrer l'exception de confiance pour le serveur" - -#: ../src/client/application/geary-controller.vala:981 -msgid "Your settings are insecure" -msgstr "Vos paramètres ne sont pas sécurisés" - -#: ../src/client/application/geary-controller.vala:982 -msgid "" -"Your IMAP and/or SMTP settings do not specify SSL or TLS. This means your " -"username and password could be read by another person on the network. Are " -"you sure you want to do this?" -msgstr "" -"Votre configuration IMAP ou SMTP ne définit aucun chiffrement SSL ou TLS. " -"Cela signifie qu'une autre personne sur votre réseau pourra avoir accès à " -"vos identifiants. Voulez-vous vraiment continuer ?" - -#: ../src/client/application/geary-controller.vala:983 -msgid "Co_ntinue" -msgstr "Co_ntinuer" +#: src/client/application/geary-args.vala:13 +msgid "Log network deserialization" +msgstr "Journaliser la désérialisation du réseau" -#. / Displayed in the space-limited status bar when a message fails to be sent due to error. -#: ../src/client/application/geary-controller.vala:1061 -#: ../src/client/components/status-bar.vala:29 -msgid "Error sending email" -msgstr "Erreur durant l'envoi" +#: src/client/application/geary-args.vala:14 +msgid "Log network activity" +msgstr "Journaliser l’activité du réseau" + +#. / The IMAP replay queue is how changes on the server are replicated on the client. +#. / It could also be called the IMAP events queue. +#: src/client/application/geary-args.vala:17 +msgid "Log IMAP replay queue" +msgstr "Journaliser la liste des événements IMAP" + +#. / Serialization is how commands and responses are converted into a stream of bytes for +#. / network transmission +#: src/client/application/geary-args.vala:20 +msgid "Log network serialization" +msgstr "Journaliser la sérialisation du réseau" + +#: src/client/application/geary-args.vala:21 +msgid "Log periodic activity" +msgstr "Journaliser l’activité périodique" + +#: src/client/application/geary-args.vala:22 +msgid "Log database queries (generates lots of messages)" +msgstr "" +"Journaliser les requêtes de la base de données (génère beaucoup de messages)" + +#. / "Normalization" can also be called "synchronization" +#: src/client/application/geary-args.vala:24 +msgid "Log folder normalization" +msgstr "Journaliser la normalisation des dossiers" + +#: src/client/application/geary-args.vala:25 +msgid "Allow inspection of WebView" +msgstr "Autoriser l’inspection WebView" + +#: src/client/application/geary-args.vala:26 +msgid "Revoke all server certificates with TLS warnings" +msgstr "Supprimer tous les certificats avec avertissements TLS" + +#: src/client/application/geary-args.vala:27 +msgid "Perform a graceful quit" +msgstr "Quitter l’application proprement" + +#: src/client/application/geary-args.vala:28 +msgid "Display program version" +msgstr "Afficher la version du programme" + +#. This gives a command-line hint on how to open new composer windows with mailto: +#: src/client/application/geary-args.vala:53 +#, c-format +msgid "Use %s to open a new composer window" +msgstr "Utilisez %s pour ouvrir une nouvelle fenêtre d’édition" -#: ../src/client/application/geary-controller.vala:1062 -msgid "" -"Geary encountered an error sending an email. If the problem persists, " -"please manually delete the email from your Outbox folder." +#: src/client/application/geary-args.vala:56 +msgid "Please report comments, suggestions and bugs to:" msgstr "" -"Une erreur est apparue durant l'envoi d'un courriel. Si le problème " -"persiste, supprimez manuellement ce message de la boîte d'envoi." +"N’hésitez pas à envoyer vos commentaires, suggestions et rapports " +"d’anomalies à :" -#. Displayed in the space-limited status bar when a message fails to be uploaded -#. to Sent Mail after being sent. -#: ../src/client/application/geary-controller.vala:1066 -#: ../src/client/components/status-bar.vala:33 -msgid "Error saving sent mail" -msgstr "Erreur d'enregistrement du message envoyé" +#. i18n: Command line arguments are invalid +#: src/client/application/geary-args.vala:63 +#, c-format +msgid "Failed to parse command line options: %s\n" +msgstr "Impossible d’analyser les options de ligne de commande : %s\n" -#: ../src/client/application/geary-controller.vala:1067 -msgid "" -"Geary encountered an error saving a sent message to Sent Mail. The message " -"will stay in your Outbox folder until you delete it." -msgstr "" -"Une erreur est apparue durant l'enregistrement d'un message envoyé dans le " -"dossier des messages envoyés. Le message restera dans la boîte d'envoi " -"jusqu'à ce que vous le supprimiez." +#: src/client/application/geary-args.vala:74 +#, c-format +msgid "Unrecognized command line option “%s”\n" +msgstr "Option de ligne de commande « %s » inconnue\n" + +#. Translators: File name used in save chooser when saving +#. attachments that do not otherwise have a name. +#: src/client/application/geary-controller.vala:61 +msgid "Untitled" +msgstr "Sans titre" -#: ../src/client/application/geary-controller.vala:1136 +#: src/client/application/geary-controller.vala:899 msgid "Labels" msgstr "Étiquettes" #. give the user two options: reset the Account local store, or exit Geary. A third #. could be done to leave the Account in an unopened state, but we don't currently #. have provisions for that. -#: ../src/client/application/geary-controller.vala:1148 +#: src/client/application/geary-controller.vala:911 #, c-format msgid "Unable to open the database for %s" msgstr "Ouverture de la base de données de %s impossible" -#: ../src/client/application/geary-controller.vala:1149 +#: src/client/application/geary-controller.vala:912 #, c-format msgid "" "There was an error opening the local mail database for this account. This is " @@ -536,7 +939,7 @@ "Rebuilding the database will destroy all local email and its attachments. " "The mail on the your server will not be affected." msgstr "" -"Une erreur est survenue lors de l'ouverture de la base de données locale " +"Une erreur est survenue lors de l’ouverture de la base de données locale " "pour ce compte. Il est possible que le fichier associé, situé dans le " "répertoire suivant, soit corrompu :\n" "\n" @@ -549,20 +952,20 @@ "jointes associées. Les courriels présents sur le serveur ne seront pas " "affectés." -#: ../src/client/application/geary-controller.vala:1151 +#: src/client/application/geary-controller.vala:914 msgid "_Rebuild" msgstr "_Reconstruire" -#: ../src/client/application/geary-controller.vala:1151 +#: src/client/application/geary-controller.vala:914 msgid "E_xit" msgstr "_Quitter" -#: ../src/client/application/geary-controller.vala:1160 +#: src/client/application/geary-controller.vala:923 #, c-format -msgid "Unable to rebuild database for \"%s\"" +msgid "Unable to rebuild database for “%s”" msgstr "Reconstruction de la base de données de « %s » impossible" -#: ../src/client/application/geary-controller.vala:1161 +#: src/client/application/geary-controller.vala:924 #, c-format msgid "" "Error during rebuild:\n" @@ -573,283 +976,532 @@ "\n" "%s" -#. some other problem opening the account ... as with other flow path, can't run -#. Geary today with an account in unopened state, so have to exit -#: ../src/client/application/geary-controller.vala:1183 -#: ../src/client/application/geary-controller.vala:1193 -#: ../src/client/application/geary-controller.vala:1204 -#, c-format -msgid "Unable to open local mailbox for %s" -msgstr "Ouverture de la boîte de messagerie locale de %s impossible" - -#: ../src/client/application/geary-controller.vala:1184 -#, c-format -msgid "" -"There was an error opening the local mail database for this account. This is " -"possibly due to a file permissions problem.\n" -"\n" -"Please check that you have read/write permissions for all files in this " -"directory:\n" -"\n" -"%s" -msgstr "" -"Une erreur est survenue lors de l'ouverture de la base de données de " -"messagerie locale pour ce compte, probablement due à un problème de " -"permissions.\n" -"\n" -"Vérifiez les permissions en lecture/écriture pour tous les fichiers de ce " -"répertoire :\n" -"\n" -"%s" - -#: ../src/client/application/geary-controller.vala:1194 -msgid "" -"The version number of the local mail database is formatted for a newer " -"version of Geary. Unfortunately, the database cannot be \"rolled back\" to " -"work with this version of Geary.\n" -"\n" -"Please install the latest version of Geary and try again." -msgstr "" -"La version de la base de données de messagerie locale correspond à une " -"version plus récente de Geary et n'est pas rétrocompatible avec la version " -"installée.\n" -"\n" -"Veuillez installer la dernière version de Geary et réessayer." - -#: ../src/client/application/geary-controller.vala:1205 -msgid "" -"There was an error opening the local account. This is probably due to " -"connectivity issues.\n" -"\n" -"Please check your network connection and restart Geary." -msgstr "" -"Une erreur est survenue lors de l'ouverture du compte local, probablement " -"due à un problème de connexion.\n" -"\n" -"Vérifiez la connexion et redémarrez Geary." - -#: ../src/client/application/geary-controller.vala:1716 -#, c-format -msgid "About %s" -msgstr "À propos de %s" - -#. / Translators: add your name and email address to receive credit in the About dialog -#. / For example: Yamada Taro -#: ../src/client/application/geary-controller.vala:1719 -msgid "translator-credits" -msgstr "" -"ttoine \n" -"bmoez \n" -"boubakr92 \n" -"carlito \n" -"alucryd \n" -"merle \n" -"Philippe Bernery \n" -"kalia35 \n" -"Claude Paroz \n" -"Julien Hardelin " - -#: ../src/client/application/geary-controller.vala:1977 +#: src/client/application/geary-controller.vala:1765 msgid "Undo move (Ctrl+Z)" msgstr "Annuler le déplacement (Ctrl+Z)" -#: ../src/client/application/geary-controller.vala:1987 -#, c-format -msgid "Are you sure you want to open \"%s\"?" -msgstr "Voulez-vous vraiment ouvrir « %s » ?" +#: src/client/application/geary-controller.vala:1775 +msgid "Are you sure you want to open these attachments?" +msgstr "Voulez-vous vraiment ouvrir ces pièces jointes ?" -#: ../src/client/application/geary-controller.vala:1988 +#: src/client/application/geary-controller.vala:1776 msgid "" "Attachments may cause damage to your system if opened. Only open files from " "trusted sources." msgstr "" -"L'ouverture de certaines pièces jointes peut endommager votre système. " -"Veillez à n'ouvrir que les fichiers provenant de sources fiables." +"L’ouverture de certaines pièces jointes peut endommager votre système. " +"Veillez à n’ouvrir que les fichiers provenant de sources fiables." -#: ../src/client/application/geary-controller.vala:1989 -msgid "Don't _ask me again" +#: src/client/application/geary-controller.vala:1777 +msgid "Don’t _ask me again" msgstr "Ne plus me _demander" -#: ../src/client/application/geary-controller.vala:2007 +#. Translators: Dialog primary label when prompting to +#. overwrite a file. The string substitution is the file'sx +#. name. +#: src/client/application/geary-controller.vala:1906 #, c-format -msgid "A file named \"%s\" already exists. Do you want to replace it?" +msgid "A file named “%s” already exists. Do you want to replace it?" msgstr "Un fichier nommé « %s » existe déjà. Voulez-vous le remplacer ?" -#: ../src/client/application/geary-controller.vala:2009 +#. Translators: Dialog secondary label when prompting to +#. overwrite a file. The string substitution is the parent +#. folder's name. +#: src/client/application/geary-controller.vala:1913 #, c-format msgid "" -"The file already exists in \"%s\". Replacing it will overwrite its contents." +"The file already exists in “%s”. Replacing it will overwrite its contents." msgstr "" "Le fichier existe déjà dans « %s ». En le remplaçant, vous perdrez son " "contenu." -#: ../src/client/application/geary-controller.vala:2012 +#: src/client/application/geary-controller.vala:1917 msgid "_Replace" msgstr "_Remplacer" -#. Find out what to do with the inline composers. -#. TODO: Remove this in favor of automatically saving drafts -#: ../src/client/application/geary-controller.vala:2310 -msgid "Close open draft messages?" -msgstr "Fermer tous les brouillons ouverts ?" +#: src/client/application/geary-controller.vala:2193 +msgid "Close the draft message?" +msgid_plural "Close all draft messages?" +msgstr[0] "Fermer le brouillon ouvert ?" +msgstr[1] "Fermer tous les brouillons ouverts ?" -#: ../src/client/application/geary-controller.vala:2440 +#: src/client/application/geary-controller.vala:2319 #, c-format msgid "Empty all email from your %s folder?" msgstr "Supprimer tous les courriels du dossier %s ?" -#: ../src/client/application/geary-controller.vala:2441 +#: src/client/application/geary-controller.vala:2320 msgid "This removes the email from Geary and your email server." -msgstr "Supprime le mail de Geary et du serveur de mail." +msgstr "Supprime le courriel de Geary et du serveur de messagerie." -#: ../src/client/application/geary-controller.vala:2442 +#: src/client/application/geary-controller.vala:2321 msgid "This cannot be undone." -msgstr "L'action ne peut être annulée." +msgstr "L’action ne peut être annulée." -#: ../src/client/application/geary-controller.vala:2443 +#: src/client/application/geary-controller.vala:2322 #, c-format msgid "Empty %s" msgstr "Vider %s" -#: ../src/client/application/geary-controller.vala:2460 +#: src/client/application/geary-controller.vala:2339 #, c-format msgid "Error emptying %s" -msgstr "Erreur au nettoyage de %s" +msgstr "Erreur lors du nettoyage de %s" -#: ../src/client/application/geary-controller.vala:2490 +#: src/client/application/geary-controller.vala:2371 msgid "Do you want to permanently delete this message?" msgid_plural "Do you want to permanently delete these messages?" msgstr[0] "Voulez-vous supprimer définitivement ce message ?" msgstr[1] "Voulez-vous supprimer définitivement ces messages ?" -#: ../src/client/application/geary-controller.vala:2492 +#: src/client/application/geary-controller.vala:2373 msgid "Delete" msgstr "Supprimer" -#: ../src/client/application/geary-controller.vala:2523 -msgid "Undo archive (Ctrl+Z)" -msgstr "Annuler archiver (Ctrl+Z)" - -#: ../src/client/application/geary-controller.vala:2538 +#: src/client/application/geary-controller.vala:2387 msgid "Undo trash (Ctrl+Z)" msgstr "Annuler la mise à la corbeille (Ctrl+Z)" -#: ../src/client/application/geary-controller.vala:2591 +#: src/client/application/geary-controller.vala:2437 +msgid "Undo archive (Ctrl+Z)" +msgstr "Annuler l’archivage (Ctrl+Z)" + +#: src/client/application/geary-controller.vala:2482 msgid "Undo (Ctrl+Z)" msgstr "Annuler (Ctrl+Z)" -#: ../src/client/components/conversation-find-bar.vala:214 +#. Translators: The label for an in-app notification. The +#. string substitution is a list of recipients of the email. +#: src/client/application/geary-controller.vala:2559 #, c-format -msgid "%i match" -msgid_plural "%i matches" -msgstr[0] "%i correspondance" -msgstr[1] "%i correspondances" +msgid "Successfully sent mail to %s." +msgstr "Courriel envoyé à %s." -#: ../src/client/components/conversation-find-bar.vala:216 -#, c-format -msgid "%i match (wrapped)" -msgid_plural "%i matches (wrapped)" -msgstr[0] "%i correspondance" -msgstr[1] "%i correspondances" +#: src/client/application/geary-controller.vala:2640 +msgid "Failed to open default text editor." +msgstr "Erreur lors de l’ouverture de l’éditeur de texte par défaut." + +#. Translators: Tooltip used when an entry requires a valid +#. email address to be entered, but one is not provided. +#: src/client/components/components-validator.vala:341 +msgid "An email address is required" +msgstr "Une adresse électronique est nécessaire" + +#. Translators: Tooltip used when an entry requires a valid +#. email address to be entered, but the address is invalid. +#: src/client/components/components-validator.vala:345 +msgid "Not a valid email address" +msgstr "Adresse électronique non valable" + +#. Translators: Tooltip used when an entry requires a valid, +#. resolvable server name to be entered, but one is not +#. provided. +#: src/client/components/components-validator.vala:391 +msgid "A server name is required" +msgstr "Un nom de serveur est nécessaire" + +#. Translators: Tooltip used when an entry requires a valid +#. server name to be entered, but it was unable to be +#. looked-up in the DNS. +#: src/client/components/components-validator.vala:396 +msgid "Could not look up server name" +msgstr "" + +#. Tooltips +#: src/client/components/main-toolbar.vala:69 +msgid "Delete conversation (Shift+Delete)" +msgstr "Supprimer la conversation (Maj+Suppr)" + +#: src/client/components/main-toolbar.vala:70 +msgid "Delete conversations (Shift+Delete)" +msgstr "Supprimer les conversations (Maj+Suppr)" + +#: src/client/components/main-toolbar.vala:71 +msgid "Move conversation to Trash (Delete, Backspace)" +msgstr "Mettre la conversation à la corbeille (Suppr, Retour arrière)" + +#: src/client/components/main-toolbar.vala:72 +msgid "Move conversations to Trash (Delete, Backspace)" +msgstr "Mettre les conversations à la corbeille (Suppr, Retour arrière)" + +#: src/client/components/main-toolbar.vala:73 +msgid "Archive conversation (A)" +msgstr "Archiver la conversation (A)" + +#: src/client/components/main-toolbar.vala:74 +msgid "Archive conversations (A)" +msgstr "Archiver les conversations (A)" -#: ../src/client/components/conversation-find-bar.vala:218 -msgid "not found" -msgstr "non trouvé" +#: src/client/components/main-toolbar.vala:75 +msgid "Mark conversation" +msgstr "Marquer la conversation" + +#: src/client/components/main-toolbar.vala:76 +msgid "Mark conversations" +msgstr "Marquer les conversations" + +#: src/client/components/main-toolbar.vala:77 +msgid "Add label to conversation" +msgstr "Ajouter une étiquette à la conversation" + +#: src/client/components/main-toolbar.vala:78 +msgid "Add label to conversations" +msgstr "Ajouter une étiquette aux conversations" + +#: src/client/components/main-toolbar.vala:79 +msgid "Move conversation" +msgstr "Déplacer la conversation" + +#: src/client/components/main-toolbar.vala:80 +msgid "Move conversations" +msgstr "Déplacer les conversations" -#: ../src/client/components/main-window.vala:423 +#: src/client/components/main-window.vala:500 #, c-format msgid "%s (%d)" msgstr "%s (%d)" -#: ../src/client/components/search-bar.vala:10 -#: ../src/client/folder-list/folder-list-search-branch.vala:38 -#: ../src/engine/api/geary-special-folder-type.vala:51 +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:47 +#, c-format +msgid "Problem connecting to incoming server for %s" +msgstr "Problème de connexion au serveur entrant pour %s" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:49 +#: src/client/components/main-window-info-bar.vala:57 +#, c-format +msgid "" +"Could not connect to %s, check your Internet access and the server name and " +"try again" +msgstr "" +"Impossible de se connecter à %s, vérifiez votre connexion internet et le nom " +"du serveur et réessayez" + +#. Button tooltip for retrying an account problem +#: src/client/components/main-window-info-bar.vala:50 +#: src/client/components/main-window-info-bar.vala:59 ui/main-window.ui:265 +msgid "Retry connecting now" +msgstr "Réessayer de se connecter" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:55 +#, c-format +msgid "Problem connecting to outgoing server for %s" +msgstr "Problème de connexion au serveur sortant pour %s" + +#: src/client/components/main-window-info-bar.vala:58 +msgid "Try reconnecting now" +msgstr "Essayer de se reconnecter" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:64 +#, c-format +msgid "Problem with connection to incoming server for %s" +msgstr "Problème avec la connexion au serveur entrant pour %s" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:66 +#: src/client/components/main-window-info-bar.vala:74 +#, c-format +msgid "Network error talking to %s, check your Internet access and try again" +msgstr "" +"Erreur de réseau lors de la communication avec %s, vérifiez votre connexion " +"internet et réessayez" + +#: src/client/components/main-window-info-bar.vala:67 +#: src/client/components/main-window-info-bar.vala:75 +#: src/client/components/main-window-info-bar.vala:83 +#: src/client/components/main-window-info-bar.vala:91 +#: src/client/components/main-window-info-bar.vala:126 +msgid "Try reconnecting" +msgstr "Essayer de se reconnecter" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:72 +#, c-format +msgid "Problem with connection to outgoing server for %s" +msgstr "Problème avec la connexion au serveur sortant pour %s" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:80 +#, c-format +msgid "Problem communicating with incoming server for %s" +msgstr "Problème de communication avec le serveur entrant pour %s" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:82 +#, c-format +msgid "" +"Geary did not understand a message from %s or vice versa, please file a bug " +"report" +msgstr "" +"Geary n’a pas compris un message de %s ou vice versa, veuillez nous faire " +"parvenir un rapport d’anomalie" + +#: src/client/components/main-window-info-bar.vala:87 +msgid "Problem communicating with outgoing mail server" +msgstr "Problème de communication avec le serveur sortant" + +#. Translators: First string substitution is the server +#. name, second is the account name +#: src/client/components/main-window-info-bar.vala:90 +#, c-format +msgid "" +"Could not communicate with %s for %s, check the server name and try again in " +"a moment" +msgstr "" +"Impossible de communiquer avec %s pour %s, vérifiez le nom du serveur et " +"réessayez dans quelques instants" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:96 +#, c-format +msgid "Incoming mail server password required for %s" +msgstr "Mot de passe requis pour le serveur entrant de %s" + +#: src/client/components/main-window-info-bar.vala:97 +msgid "Messages cannot be received without the correct password." +msgstr "Les messages ne peuvent être reçus sans le mot de passe approprié." + +#: src/client/components/main-window-info-bar.vala:98 +msgid "Retry receiving email, you will be prompted for a password" +msgstr "" +"Effectuez une nouvelle tentative de récupération des courriels, le mot de " +"passe vous sera demandé" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:103 +#, c-format +msgid "Outgoing mail server password required for %s" +msgstr "Mot de passe requis pour le serveur sortant de %s" + +#: src/client/components/main-window-info-bar.vala:104 +msgid "Messages cannot be sent without the correct password." +msgstr "Les messages ne peuvent être envoyés sans le mot de passe approprié." + +#: src/client/components/main-window-info-bar.vala:105 +msgid "Retry sending queued messages, you will be prompted for a password" +msgstr "" +"Effectuez une nouvelle tentative d’envoi des courriels en attente, le mot de " +"passe vous sera demandé" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:110 +#, fuzzy, c-format +#| msgid "Incoming mail server password required for %s" +msgid "Incoming mail server security is not trusted for %s" +msgstr "Mot de passe requis pour le serveur entrant de %s" + +#: src/client/components/main-window-info-bar.vala:111 +#, fuzzy +#| msgid "Messages cannot be received without the correct password." +msgid "Messages will not be received until checked." +msgstr "Les messages ne peuvent être reçus sans le mot de passe approprié." + +#: src/client/components/main-window-info-bar.vala:112 +#: src/client/components/main-window-info-bar.vala:119 +msgid "Check security details" +msgstr "" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:117 +#, fuzzy, c-format +#| msgid "Outgoing mail server password required for %s" +msgid "Outgoing mail server security is not trusted for %s" +msgstr "Mot de passe requis pour le serveur sortant de %s" + +#: src/client/components/main-window-info-bar.vala:118 +#, fuzzy +#| msgid "Messages cannot be sent without the correct password." +msgid "Messages cannot be sent until checked." +msgstr "Les messages ne peuvent être envoyés sans le mot de passe approprié." + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:124 +#, c-format +msgid "A problem occurred checking mail for %s" +msgstr "Un problème est apparu lors de la vérification des courriels pour %s" + +#: src/client/components/main-window-info-bar.vala:125 +#: src/client/components/main-window-info-bar.vala:132 +msgid "Something went wrong, please file a bug report if the problem persists" +msgstr "" +"Une erreur est survenue, veuillez nous faire parvenir un rapport d’anomalie " +"si le problème apparait à nouveau" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:131 +#, c-format +msgid "A problem occurred sending mail for %s" +msgstr "Un problème est apparu lors de l’envoi de courriels pour %s" + +#: src/client/components/main-window-info-bar.vala:133 +msgid "Retry sending queued messages" +msgstr "Réessayer d’envoyer les messages en attente" + +#: src/client/components/main-window-info-bar.vala:144 +msgid "A database problem has occurred" +msgstr "Un problème de base de données est s’est produit" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:146 +#, c-format +msgid "Messages for %s must be downloaded again." +msgstr "Les messages de %s doivent être téléchargés à nouveau." + +#: src/client/components/main-window-info-bar.vala:159 +msgid "Geary has encountered a problem" +msgstr "Geary a rencontré un problème" + +#: src/client/components/main-window-info-bar.vala:160 +msgid "" +"Please check the technical details and report the problem if it persists." +msgstr "" +"Vérifiez les détails techniques et rapporter le problème s’il persiste." + +#: src/client/components/main-window-info-bar.vala:168 +msgid "_Details" +msgstr "_Détails" + +#. Button tooltip for displaying technical details about an account problem +#: src/client/components/main-window-info-bar.vala:169 ui/main-window.ui:250 +msgid "View technical details about the error" +msgstr "Voir les détails techniques à propos de l’erreur" + +#: src/client/components/main-window-info-bar.vala:173 +msgid "_Retry" +msgstr "_Réessayer" + +#: src/client/components/search-bar.vala:8 +#: src/client/folder-list/folder-list-search-branch.vala:38 +#: src/engine/api/geary-special-folder-type.vala:51 msgid "Search" msgstr "Recherche" #. Search entry. -#: ../src/client/components/search-bar.vala:25 +#: src/client/components/search-bar.vala:23 msgid "Search all mail in account for keywords (Ctrl+S)" msgstr "Rechercher par mots-clés dans tous les messages du compte (Ctrl+S)" -#: ../src/client/components/search-bar.vala:118 +#: src/client/components/search-bar.vala:101 #, c-format msgid "Indexing %s account" msgstr "Indexation du compte %s" -#: ../src/client/components/search-bar.vala:129 -#: ../src/client/folder-list/folder-list-search-branch.vala:39 +#: src/client/components/search-bar.vala:112 +#: src/client/folder-list/folder-list-search-branch.vala:39 #, c-format msgid "Search %s account" msgstr "Recherche dans le compte %s" #. / Displayed in the space-limited status bar while a message is in the process of being sent. -#: ../src/client/components/status-bar.vala:26 -msgid "Sending..." +#: src/client/components/status-bar.vala:26 +msgid "Sending…" msgstr "Envoi en cours…" -#: ../src/client/components/stock.vala:18 ../ui/account_cannot_remove.glade.h:3 +#. / Displayed in the space-limited status bar when a message fails to be sent due to error. +#: src/client/components/status-bar.vala:29 +msgid "Error sending email" +msgstr "Erreur durant l’envoi" + +#. Displayed in the space-limited status bar when a message fails to be uploaded +#. to Sent Mail after being sent. +#: src/client/components/status-bar.vala:33 +msgid "Error saving sent mail" +msgstr "Erreur d’enregistrement du message envoyé" + +#: src/client/components/stock.vala:18 msgid "_OK" msgstr "_Valider" -#: ../src/client/components/stock.vala:19 ../ui/edit_alternate_emails.glade.h:3 -#: ../ui/password-dialog.glade.h:5 ../ui/remove_confirm.glade.h:5 +#: src/client/components/stock.vala:19 ui/password-dialog.glade:196 msgid "_Cancel" msgstr "_Annuler" -#: ../src/client/components/stock.vala:23 +#: src/client/components/stock.vala:21 ui/gtk/menus.ui:34 +msgid "_About" +msgstr "À _propos" + +#: src/client/components/stock.vala:22 +msgid "_Add" +msgstr "_Ajouter" + +#: src/client/components/stock.vala:23 msgid "_Close" msgstr "_Fermer" -#: ../src/client/components/stock.vala:24 +#: src/client/components/stock.vala:24 msgid "_Discard" msgstr "A_bandonner" -#: ../src/client/components/stock.vala:26 +#: src/client/components/stock.vala:25 ui/gtk/menus.ui:29 +msgid "_Help" +msgstr "Aid_e" + +#: src/client/components/stock.vala:26 ui/conversation-email-menus.ui:77 msgid "_Open" msgstr "_Ouvrir" -#: ../src/client/components/stock.vala:28 -msgid "_Print..." +#: src/client/components/stock.vala:27 ui/gtk/menus.ui:17 +msgid "_Preferences" +msgstr "_Préférences" + +#. Translators: Menu item to print a single, specific message +#: src/client/components/stock.vala:28 ui/conversation-email-menus.ui:64 +msgid "_Print…" msgstr "Im_primer…" -#: ../src/client/components/stock.vala:30 ../ui/remove_confirm.glade.h:6 +#: src/client/components/stock.vala:29 ui/gtk/menus.ui:38 +msgid "_Quit" +msgstr "_Quitter" + +#: src/client/components/stock.vala:30 msgid "_Remove" msgstr "_Supprimer" -#. Select all. -#: ../src/client/components/stock.vala:32 -#: ../src/client/conversation-viewer/conversation-viewer.vala:1324 -msgid "Select _All" -msgstr "T_out sélectionner" +#: src/client/components/stock.vala:31 ui/conversation-email-menus.ui:83 +msgid "_Save" +msgstr "_Enregistrer" -#: ../src/client/components/stock.vala:33 +#: src/client/components/stock.vala:32 msgid "_Keep" msgstr "_Garder" -#: ../src/client/composer/composer-widget.vala:73 +#: src/client/composer/composer-link-popover.vala:149 +msgid "Link URL is not correctly formatted, e.g. http://example.com" +msgstr "" +"L’URL du lien n’est pas mis en forme correctement, par ex. http://example.com" + +#: src/client/composer/composer-link-popover.vala:156 +msgid "Invalid link URL" +msgstr "URL de lien non valide" + +#: src/client/composer/composer-link-popover.vala:156 +msgid "Invalid email address" +msgstr "Adresse électronique non valable" + +#: src/client/composer/composer-widget.vala:158 msgid "Saved" msgstr "Enregistré" -#: ../src/client/composer/composer-widget.vala:74 +#: src/client/composer/composer-widget.vala:159 msgid "Saving" msgstr "Enregistrement" -#: ../src/client/composer/composer-widget.vala:75 +#: src/client/composer/composer-widget.vala:160 msgid "Error saving" -msgstr "Erreur d'enregistrement" +msgstr "Erreur d’enregistrement" -#: ../src/client/composer/composer-widget.vala:76 +#: src/client/composer/composer-widget.vala:161 msgid "Press Backspace to delete quote" msgstr "Appuyez sur Retour arrière pour supprimer la citation" -#: ../src/client/composer/composer-widget.vala:77 -msgid "New Message" -msgstr "Nouveau message" - -#. A list of keywords, separated by pipe ("|") characters, that suggest an attachment; since -#. this is full-word checking, include all variants of each word. No spaces are allowed. -#: ../src/client/composer/composer-widget.vala:138 +#. Translators: This is list of keywords, separated by pipe ("|") +#. characters, that suggest an attachment; since this is full-word +#. checking, include all variants of each word. No spaces are +#. allowed. +#: src/client/composer/composer-widget.vala:170 msgid "" "attach|attaching|attaches|attachment|attachments|attached|enclose|enclosed|" "enclosing|encloses|enclosure|enclosures" @@ -857,324 +1509,333 @@ "pièce|pièces|joint|jointe|joindre|joints|jointes|attaché|attachés|attachées|" "attachement|attachements|fichier|fichiers" -#: ../src/client/composer/composer-widget.vala:1102 -#: ../src/client/composer/composer-widget.vala:1106 -msgid "Do you want to discard this message?" -msgstr "Voulez-vous abandonner ce message ?" +#. Translators: This dialog text is displayed to the +#. user when closing a composer where the options are +#. Keep, Discard or Cancel. +#: src/client/composer/composer-widget.vala:1103 +msgid "Do you want to keep or discard this draft message?" +msgstr "Voulez-vous conserver ou abandonner ce message ?" + +#. Translators: This dialog text is displayed to the +#. user when closing a composer where the options are +#. only Discard or Cancel. +#: src/client/composer/composer-widget.vala:1131 +msgid "Do you want to discard this draft message?" +msgstr "Voulez-vous abandonner ce brouillon ?" -#: ../src/client/composer/composer-widget.vala:1262 +#: src/client/composer/composer-widget.vala:1248 msgid "Send message with an empty subject and body?" msgstr "Envoyer le message avec un objet et un contenu vide ?" -#: ../src/client/composer/composer-widget.vala:1264 +#: src/client/composer/composer-widget.vala:1250 msgid "Send message with an empty subject?" msgstr "Envoyer le message avec un objet vide ?" -#: ../src/client/composer/composer-widget.vala:1266 +#: src/client/composer/composer-widget.vala:1252 msgid "Send message with an empty body?" msgstr "Envoyer le message avec un contenu vide ?" -#: ../src/client/composer/composer-widget.vala:1268 +#: src/client/composer/composer-widget.vala:1256 msgid "Send message without an attachment?" msgstr "Envoyer le message sans pièce jointe ?" -#: ../src/client/composer/composer-widget.vala:1530 -msgid "Cannot add attachment" -msgstr "Impossible d'ajouter la pièce jointe" +#: src/client/composer/composer-widget.vala:1561 +#, c-format +msgid "“%s” already attached for delivery." +msgstr "« %s » a déjà été joint au message." + +#. / In the composer, the filename followed by its filesize, i.e. "notes.txt (1.12KB)" +#. Translators: The first argument will be a +#. description of the document type, the second will +#. be a human-friendly size string. For example: +#. Document (100.9MB) +#: src/client/composer/composer-widget.vala:1569 +#: src/client/conversation-viewer/conversation-email.vala:149 +#, c-format +msgid "%s (%s)" +msgstr "%s (%s)" -#: ../src/client/composer/composer-widget.vala:1541 +#: src/client/composer/composer-widget.vala:1606 #, c-format -msgid "\"%s\" could not be found." +msgid "“%s” could not be found." msgstr "« %s » est introuvable." -#: ../src/client/composer/composer-widget.vala:1548 +#: src/client/composer/composer-widget.vala:1612 #, c-format -msgid "\"%s\" is a folder." +msgid "“%s” is a folder." msgstr "« %s » est un dossier." -#: ../src/client/composer/composer-widget.vala:1555 +#: src/client/composer/composer-widget.vala:1618 #, c-format -msgid "\"%s\" is an empty file." +msgid "“%s” is an empty file." msgstr "« %s » est un fichier vide." -#: ../src/client/composer/composer-widget.vala:1569 +#: src/client/composer/composer-widget.vala:1631 #, c-format -msgid "\"%s\" could not be opened for reading." -msgstr "« %s » n'a pas pu être lu." +msgid "“%s” could not be opened for reading." +msgstr "« %s » n’a pas pu être ouvert pour lecture." -#: ../src/client/composer/composer-widget.vala:1576 -#, c-format -msgid "\"%s\" already attached for delivery." -msgstr "« %s » a déjà été joint au message." - -#. / In the composer, the filename followed by its filesize, i.e. "notes.txt (1.12KB)" -#: ../src/client/composer/composer-widget.vala:1585 -#, c-format -msgid "%s (%s)" -msgstr "%s (%s)" +#: src/client/composer/composer-widget.vala:1639 +msgid "Cannot add attachment" +msgstr "Impossible d’ajouter la pièce jointe" -#: ../src/client/composer/composer-widget.vala:1645 +#: src/client/composer/composer-widget.vala:1688 msgid "To: " -msgstr "À :" +msgstr "À : " -#: ../src/client/composer/composer-widget.vala:1648 +#: src/client/composer/composer-widget.vala:1691 msgid "Cc: " -msgstr "Cc :" +msgstr "Cc : " -#: ../src/client/composer/composer-widget.vala:1651 +#: src/client/composer/composer-widget.vala:1694 msgid "Bcc: " -msgstr "Cci :" +msgstr "Cci : " -#: ../src/client/composer/composer-widget.vala:1654 +#: src/client/composer/composer-widget.vala:1697 msgid "Reply-To: " -msgstr "Répondre à :" +msgstr "Répondre à : " -#: ../src/client/composer/composer-widget.vala:1887 +#: src/client/composer/composer-widget.vala:1835 msgid "Select Color" msgstr "Sélectionner une couleur" -#. Displayed in the From dropdown to indicate an "alternate email address" -#. for an account. The first printf argument will be the alternate email -#. address, and the second will be the account's primary email address. -#: ../src/client/composer/composer-widget.vala:2322 +#. Displayed in the From dropdown to indicate an +#. "alternate email address" for an account. The first +#. printf argument will be the alternate email address, +#. and the second will be the account's primary email +#. address. +#: src/client/composer/composer-widget.vala:2030 #, c-format msgid "%1$s via %2$s" msgstr "%1$s via %2$s" #. Composer label (with mnemonic underscore) for the account selector #. when choosing what address to send a message from. -#: ../src/client/composer/composer-widget.vala:2364 +#: src/client/composer/composer-widget.vala:2090 msgid "_From:" msgstr "_De :" -#: ../src/client/conversation-list/formatted-conversation-data.vala:11 -msgid "Me" -msgstr "Moi" - -#: ../src/client/conversation-viewer/conversation-viewer.vala:413 -msgid "No conversations selected." -msgstr "Aucune conversation sélectionnée." - -#: ../src/client/conversation-viewer/conversation-viewer.vala:415 -#, c-format -msgid "%u conversation selected." -msgid_plural "%u conversations selected." -msgstr[0] "%u conversation sélectionnée." -msgstr[1] "%u conversations sélectionnées." - -#: ../src/client/conversation-viewer/conversation-viewer.vala:444 -msgid "No search results found." -msgstr "Aucun résultat." - -#: ../src/client/conversation-viewer/conversation-viewer.vala:446 -msgid "No conversations in folder." -msgstr "Aucune conversation dans le dossier." - -#: ../src/client/conversation-viewer/conversation-viewer.vala:708 -msgid "This message contains remote images." -msgstr "Ce message contient des images distantes." - -#: ../src/client/conversation-viewer/conversation-viewer.vala:708 -msgid "Show Images" -msgstr "Afficher les images" - -#: ../src/client/conversation-viewer/conversation-viewer.vala:709 -msgid "Always Show From Sender" -msgstr "Toujours afficher pour cet expéditeur" +#. Translators: This is the name of the file chooser filter +#. when inserting an image in the composer. +#: src/client/composer/composer-widget.vala:2315 +msgid "Images" +msgstr "Images" -#: ../src/client/conversation-viewer/conversation-viewer.vala:733 -msgid "Edit Draft" -msgstr "Éditer le brouillon" +#: src/client/composer/composer-window.vala:14 +msgid "New Message" +msgstr "Nouveau message" -#: ../src/client/conversation-viewer/conversation-viewer.vala:809 -msgid "From:" -msgstr "De :" +#: src/client/composer/spell-check-popover.vala:117 +msgid "Remove this language from the preferred list" +msgstr "Enlever cette langue de la liste des favorites" + +#: src/client/composer/spell-check-popover.vala:121 +msgid "Add this language to the preferred list" +msgstr "Ajouter cette langue à la liste des favorites" + +#: src/client/composer/spell-check-popover.vala:217 +msgid "Search for more languages" +msgstr "Rechercher des langues supplémentaires" -#: ../src/client/conversation-viewer/conversation-viewer.vala:812 -msgid "To:" -msgstr "À :" +#: src/client/conversation-list/conversation-list-view.vala:283 +msgid "Delete conversation" +msgstr "Supprimer la conversation" -#: ../src/client/conversation-viewer/conversation-viewer.vala:815 -msgid "Cc:" -msgstr "Cc :" +#: src/client/conversation-list/conversation-list-view.vala:286 +#: ui/main-toolbar-menus.ui:16 +msgid "Mark as _Read" +msgstr "Marquer comme _lu" -#: ../src/client/conversation-viewer/conversation-viewer.vala:818 -msgid "Bcc:" -msgstr "Cci :" +#: src/client/conversation-list/conversation-list-view.vala:289 +#: ui/main-toolbar-menus.ui:20 +msgid "Mark as _Unread" +msgstr "Marquer comme _non Lu" -#: ../src/client/conversation-viewer/conversation-viewer.vala:821 -msgid "Subject:" -msgstr "Objet :" +#: src/client/conversation-list/conversation-list-view.vala:292 +#: ui/main-toolbar-menus.ui:28 +msgid "U_nstar" +msgstr "_Supprimer l’étoile" -#: ../src/client/conversation-viewer/conversation-viewer.vala:824 -msgid "Date:" -msgstr "Date :" +#: src/client/conversation-list/conversation-list-view.vala:294 +#: ui/main-toolbar-menus.ui:24 +msgid "_Star" +msgstr "Ma_rquer d’une étoile" -#: ../src/client/conversation-viewer/conversation-viewer.vala:1139 -#, c-format -msgid "%u read message" -msgid_plural "%u read messages" -msgstr[0] "%u message lu" -msgstr[1] "%u messages lus" +#. Translators: Menu item to reply to a specific message. +#: src/client/conversation-list/conversation-list-view.vala:297 +#: ui/conversation-email-menus.ui:9 +msgid "_Reply" +msgstr "_Répondre" -#: ../src/client/conversation-viewer/conversation-viewer.vala:1271 -#, c-format -msgid "This message was sent successfully, but could not be saved to %s." -msgstr "" -"Ce message a été envoyé avec succès, mais n'a pas pu être enregistré dans %s." +#: src/client/conversation-list/conversation-list-view.vala:298 +msgid "R_eply All" +msgstr "R_épondre à tous" -#. Add a menu item for copying the current selection. -#: ../src/client/conversation-viewer/conversation-viewer.vala:1297 -#: ../ui/composer.glade.h:4 -msgid "_Copy" -msgstr "_Copier" +#. Translators: Menu item to forward a specific message. +#: src/client/conversation-list/conversation-list-view.vala:299 +#: ui/conversation-email-menus.ui:21 +msgid "_Forward" +msgstr "_Transférer" -#. Add a menu item for copying the address. -#: ../src/client/conversation-viewer/conversation-viewer.vala:1305 -msgid "Copy _Email Address" -msgstr "Copier l'_adresse électronique" - -#. Add a menu item for copying the link. -#: ../src/client/conversation-viewer/conversation-viewer.vala:1310 -#: ../ui/composer.glade.h:17 -msgid "Copy _Link" -msgstr "Copier le _lien" - -#. Select message. -#: ../src/client/conversation-viewer/conversation-viewer.vala:1318 -msgid "Select _Message" -msgstr "Sélectionner le _message" - -#. Inspect. -#: ../src/client/conversation-viewer/conversation-viewer.vala:1330 -msgid "_Inspect" -msgstr "_Inspecter" - -#: ../src/client/conversation-viewer/conversation-viewer.vala:1557 -msgid "This link appears to go to" -msgstr "Ce lien semble pointer vers" - -#: ../src/client/conversation-viewer/conversation-viewer.vala:1558 -msgid "but actually goes to" -msgstr "mais pointe plutôt vers" - -#. If href doesn't look like a URL, something is fishy, so warn the user -#: ../src/client/conversation-viewer/conversation-viewer.vala:1613 -msgid " (Invalid?)" -msgstr " (Non valide ?)" - -#: ../src/client/conversation-viewer/conversation-viewer.vala:1719 -msgid "_Save Image As..." -msgstr "_Enregistrer l'image sous…" - -#: ../src/client/conversation-viewer/conversation-viewer.vala:1823 -msgid "_Save As..." -msgstr "Enregi_strer sous…" - -#: ../src/client/conversation-viewer/conversation-viewer.vala:1828 -msgid "Save All A_ttachments..." -msgstr "Enregistrer toutes les pièces _jointes…" - -#: ../src/client/conversation-viewer/conversation-viewer.vala:1848 -msgid "Save A_ttachment..." -msgid_plural "Save All A_ttachments..." -msgstr[0] "Enregistrer la pièce _jointe…" -msgstr[1] "Enregistrer toutes les pièces _jointes…" +#: src/client/conversation-list/formatted-conversation-data.vala:11 +msgid "Me" +msgstr "Moi" -#. Reply to all on a message. -#: ../src/client/conversation-viewer/conversation-viewer.vala:1863 -msgid "Reply to _All" -msgstr "Répondre à _tous" +#. Translators: This is the file type displayed for +#. attachments with unknown file types. +#: src/client/conversation-viewer/conversation-email.vala:135 +msgid "Unknown" +msgstr "Inconnu" -#. Mark as read/unread. -#: ../src/client/conversation-viewer/conversation-viewer.vala:1880 -msgid "_Mark as Read" -msgstr "Marquer comme l_u" +#: src/client/conversation-viewer/conversation-email.vala:890 +msgid "From:" +msgstr "De :" -#: ../src/client/conversation-viewer/conversation-viewer.vala:1884 -msgid "_Mark as Unread" -msgstr "Marquer comme non l_u" +#: src/client/conversation-viewer/conversation-email.vala:894 +#: ui/conversation-message.ui:313 +msgid "To:" +msgstr "À :" -#: ../src/client/conversation-viewer/conversation-viewer.vala:1890 -msgid "Mark Unread From _Here" -msgstr "Marquer comme non lu à partir d'_ici" +#: src/client/conversation-viewer/conversation-email.vala:898 +#: ui/conversation-message.ui:358 +msgid "Cc:" +msgstr "Cc :" -#. Separator. -#. View original message source. -#: ../src/client/conversation-viewer/conversation-viewer.vala:1905 -msgid "_View Source" -msgstr "Voir la _source" +#: src/client/conversation-viewer/conversation-email.vala:902 +#: ui/conversation-message.ui:403 +msgid "Bcc:" +msgstr "Cci :" -#. Generate the attachment table. -#. / Placeholder filename for attachments with no filename. -#: ../src/client/conversation-viewer/conversation-viewer.vala:2229 -#: ../src/engine/rfc822/rfc822-utils.vala:378 -msgid "none" -msgstr "aucun nom" +#: src/client/conversation-viewer/conversation-email.vala:906 +msgid "Date:" +msgstr "Date :" -#: ../src/client/conversation-viewer/conversation-viewer.vala:2364 -msgid "Failed to open default text editor." -msgstr "Erreur lors de l'ouverture de l'éditeur de texte par défaut." +#: src/client/conversation-viewer/conversation-email.vala:910 +msgid "Subject:" +msgstr "Objet:" -#: ../src/client/conversation-viewer/conversation-web-view.vala:322 -#, c-format -msgid "%s - Conversation Inspector" -msgstr "%s - Inspecteur de conversation" +#: src/client/conversation-viewer/conversation-message.vala:62 +msgid "This email address may have been forged" +msgstr "Cette adresse électronique a peut-être été falsifiée" + +#. Compact headers +#. Translators: This is displayed in place of the from address +#. when the message has no from address. +#: src/client/conversation-viewer/conversation-message.vala:388 +msgid "No sender" +msgstr "Aucun expéditeur" + +#. Translators: This separates multiple 'from' +#. addresses in the compact header for a message. +#: src/client/conversation-viewer/conversation-message.vala:676 +msgid ", " +msgstr ", " + +#. Translators: This string is used as the HTML IMG ALT +#. attribute value when displaying an inline image in an email +#. that did not specify a file name. E.g. ImageCannot remove account " -msgstr "" -"Impossible de supprimer le compte" +#: ui/accounts_editor_add_pane.ui:8 ui/accounts_editor_list_pane.ui:127 +msgid "Add an account" +msgstr "Ajouter un compte" -#: ../ui/account_cannot_remove.glade.h:2 -msgid "" -"A composer window associated with this account is currently open. Send or " -"discard the message and try again." +#: ui/accounts_editor_add_pane.ui:53 +msgid "Create" +msgstr "Créer" + +#: ui/accounts_editor_add_pane.ui:130 ui/accounts_editor_servers_pane.ui:125 +msgid "Receiving" +msgstr "Réception" + +#: ui/accounts_editor_add_pane.ui:178 ui/accounts_editor_servers_pane.ui:165 +msgid "Sending" +msgstr "Envoi" + +#: ui/accounts_editor_edit_pane.ui:8 +msgid "Edit Account" +msgstr "Modifier le compte" + +#: ui/accounts_editor_edit_pane.ui:9 ui/accounts_editor_servers_pane.ui:9 +msgid "Account Name" +msgstr "Nom du compte" + +#: ui/accounts_editor_edit_pane.ui:124 +msgid "Email addresses" +msgstr "Adresse électronique" + +#: ui/accounts_editor_edit_pane.ui:164 +msgid "Signature" +msgstr "Signature" + +#: ui/accounts_editor_edit_pane.ui:201 +msgid "Settings" +msgstr "Paramètres" + +#. This is a button in the account settings to show server settings. +#: ui/accounts_editor_edit_pane.ui:243 ui/accounts_editor_servers_pane.ui:8 +#, fuzzy +#| msgid "SMTP settings" +msgid "Server Settings" +msgstr "Configuration SMTP" + +#. This is the remove account button in the account settings. +#: ui/accounts_editor_edit_pane.ui:258 ui/accounts_editor_remove_pane.ui:23 +#, fuzzy +#| msgid "Remove account" +msgid "Remove Account" +msgstr "Supprimer le compte" + +#: ui/accounts_editor_edit_pane.ui:262 ui/accounts_editor_remove_pane.ui:27 +#, fuzzy +#| msgid "Remove account" +msgid "Remove this account from Geary" +msgstr "Supprimer le compte" + +#: ui/accounts_editor_list_pane.ui:8 +msgid "Accounts" +msgstr "Comptes" + +#: ui/accounts_editor_list_pane.ui:63 +msgid "To get started, select an email provider below." msgstr "" -"Une fenêtre d'édition associée à ce compte est ouverte. Envoyez ou " -"abandonnez le message en cours et réessayez." -#: ../ui/account_list.glade.h:1 -msgid "Add account" -msgstr "Ajouter un compte" +#: ui/accounts_editor_list_pane.ui:76 +msgid "Welcome to Geary" +msgstr "Bienvenue dans Geary." -#: ../ui/account_list.glade.h:2 -msgid "Edit account" -msgstr "Modifier le compte" +#. This title is shown to users when confirming if they want to remove an account. The string substitution is replaced with the account's name. +#: ui/accounts_editor_remove_pane.ui:73 +#, c-format +msgid "Confirm removing: %s" +msgstr "" + +#: ui/accounts_editor_remove_pane.ui:91 +msgid "" +"Removing an account will remove it from Geary and delete locally cached " +"email data from your computer, but not from your service provider." +msgstr "" -#: ../ui/account_list.glade.h:3 +#: ui/accounts_editor_remove_pane.ui:122 msgid "Remove account" msgstr "Supprimer le compte" -#: ../ui/account_spinner.glade.h:1 -msgid "Please wait while Geary validates your account." -msgstr "Veuillez patienter pendant que Geary valide votre compte." +#: ui/accounts_editor_servers_pane.ui:17 +msgid "Cancel" +msgstr "Annuler" + +#: ui/accounts_editor_servers_pane.ui:47 +msgid "Apply" +msgstr "Appliquer" -#: ../ui/certificate_warning_dialog.glade.h:1 +#: ui/certificate_warning_dialog.glade:7 msgid "Untrusted Connection" msgstr "Connexion non certifiée" -#: ../ui/certificate_warning_dialog.glade.h:2 +#: ui/certificate_warning_dialog.glade:29 msgid "_Always Trust This Server" msgstr "_Toujours faire confiance à ce serveur" -#: ../ui/certificate_warning_dialog.glade.h:3 +#: ui/certificate_warning_dialog.glade:43 msgid "_Trust This Server" msgstr "Faire _confiance à ce serveur" -#: ../ui/certificate_warning_dialog.glade.h:4 -msgid "_Don't Trust This Server" +#: ui/certificate_warning_dialog.glade:57 +msgid "_Don’t Trust This Server" msgstr "Ne _pas faire confiance à ce serveur" -#: ../ui/composer.glade.h:1 +#: ui/composer-headerbar.ui:17 ui/composer-headerbar.ui:174 +msgid "Detach (Ctrl+D)" +msgstr "Détacher (Ctrl+D)" + +#: ui/composer-headerbar.ui:57 ui/composer-headerbar.ui:83 +msgid "Attach File (Ctrl+T)" +msgstr "Ajouter une pièce jointe (Ctrl+T)" + +#: ui/composer-headerbar.ui:107 +msgid "Include Original Attachments" +msgstr "Inclure les pièces jointes d’origine" + +#: ui/composer-headerbar.ui:202 +msgid "_Send" +msgstr "_Envoyer" + +#: ui/composer-headerbar.ui:207 +msgid "Send (Ctrl+Enter)" +msgstr "Envoyer (Ctrl+Entrée)" + +#: ui/composer-headerbar.ui:230 +msgid "Discard and Close" +msgstr "Abandonner et fermer" + +#: ui/composer-headerbar.ui:254 +msgid "Save and Close" +msgstr "Enregistrer et fermer" + +#. Note that this button and the Update button will never be shown at the same time to the user. +#: ui/composer-link-popover.ui:41 +msgid "Insert the new link with this URL" +msgstr "Insérer le nouveau lien avec cette URL" + +#: ui/composer-link-popover.ui:52 +msgid "Link URL" +msgstr "URL du lien" + +#. Note that this button and the Insert button will never be shown at the same time to the user. +#: ui/composer-link-popover.ui:66 +msgid "Update this link’s URL" +msgstr "Mettre à jour l’URL de ce lien" + +#: ui/composer-link-popover.ui:86 +msgid "Delete this link" +msgstr "Supprimer ce lien" + +#: ui/composer-link-popover.ui:106 +msgid "Open this link" +msgstr "Ouvrir ce lien" + +#: ui/composer-menus.ui:7 +msgid "S_ans Serif" +msgstr "S_ans Serif" + +#: ui/composer-menus.ui:12 +msgid "S_erif" +msgstr "S_erif" + +#: ui/composer-menus.ui:17 +msgid "_Fixed Width" +msgstr "_Largeur fixe" + +#: ui/composer-menus.ui:24 +msgid "_Small" +msgstr "_Petit" + +#: ui/composer-menus.ui:29 +msgid "_Medium" +msgstr "_Moyen" + +#: ui/composer-menus.ui:34 +msgid "Lar_ge" +msgstr "_Grand" + +#: ui/composer-menus.ui:41 +msgid "C_olor" +msgstr "C_ouleur" + +#: ui/composer-menus.ui:47 ui/composer-menus.ui:62 +msgid "_Rich Text" +msgstr "Texte _riche" + +#: ui/composer-menus.ui:53 ui/composer-menus.ui:68 +msgid "Show Extended Fields" +msgstr "Afficher plus de champs" + +#: ui/composer-menus.ui:78 msgid "_Undo" msgstr "A_nnuler" -#: ../ui/composer.glade.h:2 +#: ui/composer-menus.ui:82 msgid "_Redo" msgstr "_Rétablir" -#: ../ui/composer.glade.h:3 +#: ui/composer-menus.ui:88 ui/composer-menus.ui:106 msgid "Cu_t" msgstr "Co_uper" -#: ../ui/composer.glade.h:5 +#: ui/composer-menus.ui:92 ui/composer-menus.ui:110 +#: ui/conversation-message-menus.ui:37 +msgid "_Copy" +msgstr "_Copier" + +#: ui/composer-menus.ui:96 ui/composer-menus.ui:114 msgid "_Paste" msgstr "Co_ller" -#: ../ui/composer.glade.h:6 -msgid "_Left" -msgstr "Aligner à _gauche" - -#: ../ui/composer.glade.h:7 -msgid "_Right" -msgstr "Aligner à _droite" - -#: ../ui/composer.glade.h:8 -msgid "_Center" -msgstr "_Centrer" - -#: ../ui/composer.glade.h:9 -msgid "_Justify" -msgstr "_Justifier" - -#: ../ui/composer.glade.h:10 -msgid "Link (Ctrl+L)" -msgstr "Lien (Ctrl+L)" +#: ui/composer-menus.ui:100 +msgctxt "Clipboard paste as plain text" +msgid "Paste _Without Formatting" +msgstr "Coller sans la _mise en forme" -#: ../ui/composer.glade.h:11 -msgid "C_olor" -msgstr "C_ouleur" +#: ui/composer-menus.ui:120 +msgid "Select _All" +msgstr "T_out sélectionner" -#: ../ui/composer.glade.h:12 -msgid "More options" -msgstr "Plus d'options" +#: ui/composer-menus.ui:127 ui/conversation-message-menus.ui:49 +msgid "_Inspect…" +msgstr "_Inspecter…" -#: ../ui/composer.glade.h:13 -msgid "Quote text (Ctrl+])" -msgstr "Indenter le texte (Ctrl+])" +#. Address(es) e-mail is to be sent to +#: ui/composer-widget.ui:56 +msgid "_To" +msgstr "_À" + +#: ui/composer-widget.ui:75 +msgid "_Cc" +msgstr "_Cc" + +#: ui/composer-widget.ui:130 +msgid "_Subject" +msgstr "_Objet" + +#: ui/composer-widget.ui:149 +msgid "_Bcc" +msgstr "_Cci" + +#: ui/composer-widget.ui:179 +msgid "_Reply-To" +msgstr "_Répondre à" + +#. Geary account mail will be sent from +#: ui/composer-widget.ui:208 +msgid "From" +msgstr "De" + +#: ui/composer-widget.ui:293 +msgid "Drop files here" +msgstr "Déposer des fichiers ici" + +#: ui/composer-widget.ui:309 +msgid "To add them as attachments" +msgstr "Pour les ajouter en tant que pièces jointes" -#: ../ui/composer.glade.h:14 -msgid "Unquote text (Ctrl+[)" -msgstr "Désindenter le texte (Ctrl+[)" - -#: ../ui/composer.glade.h:15 -msgid "Remove formatting (Ctrl+Space)" -msgstr "Effacer la mise en forme (Ctrl+Espace)" - -#: ../ui/composer.glade.h:16 -msgctxt "Clipboard paste with rich text" -msgid "Paste _With Formatting" -msgstr "Coller avec la _mise en forme" +#: ui/composer-widget.ui:348 +msgid "Undo last edit (Ctrl+Z)" +msgstr "Annuler la dernière modification (Ctrl+Z)" + +#: ui/composer-widget.ui:372 +msgid "Redo last edit (Ctrl+Shift+Z)" +msgstr "Rétablir la dernière modification (Ctrl+Maj+Z)" -#: ../ui/composer.glade.h:18 +#: ui/composer-widget.ui:410 msgid "Bold (Ctrl+B)" msgstr "Gras (Ctrl+B)" -#: ../ui/composer.glade.h:19 +#: ui/composer-widget.ui:434 msgid "Italic (Ctrl+I)" msgstr "Italique (Ctrl+I)" -#: ../ui/composer.glade.h:20 +#: ui/composer-widget.ui:458 msgid "Underline (Ctrl+U)" msgstr "Souligné (Ctrl+U)" -#: ../ui/composer.glade.h:21 +#: ui/composer-widget.ui:482 msgid "Strikethrough (Ctrl+K)" msgstr "Barré (Ctrl+K)" -#: ../ui/composer.glade.h:22 -msgid "_Rich Text" -msgstr "Texte _riche" - -#: ../ui/composer.glade.h:23 -msgid "Show Extended Fields" -msgstr "Afficher plus de champs" +#: ui/composer-widget.ui:520 +msgid "Insert unordered list" +msgstr "Insérer une liste à puces" + +#: ui/composer-widget.ui:544 +msgid "Insert ordered list" +msgstr "Insérer une liste numérotée" -#: ../ui/composer.glade.h:24 -msgctxt "Label" -msgid "Close and Save" -msgstr "Fermer et enregistrer" - -#: ../ui/composer.glade.h:25 -msgctxt "Short Label" -msgid "Close and Save" -msgstr "Fermer et enregistrer" - -#: ../ui/composer.glade.h:26 -msgctxt "Tooltip" -msgid "Close and Save" -msgstr "Fermer et enregistrer" - -#: ../ui/composer.glade.h:27 -msgctxt "Label" -msgid "Close and Discard" -msgstr "Fermer et abandonner" - -#: ../ui/composer.glade.h:28 -msgctxt "Short Label" -msgid "Close and Discard" -msgstr "Fermer et abandonner" - -#: ../ui/composer.glade.h:29 -msgctxt "Tooltip" -msgid "Close and Discard" -msgstr "Fermer et abandonner" - -#: ../ui/composer.glade.h:30 -msgid "Lar_ge" -msgstr "_Grand" +#: ui/composer-widget.ui:582 +msgid "Quote text (Ctrl+])" +msgstr "Augmenter l’indentation (Ctrl+])" -#: ../ui/composer.glade.h:31 -msgid "Large" -msgstr "Grand" +#: ui/composer-widget.ui:606 +msgid "Unquote text (Ctrl+[)" +msgstr "Diminuer l’indentation (Ctrl+[)" + +#: ui/composer-widget.ui:644 +msgid "Insert or update selection link (Ctrl+L)" +msgstr "Insérer ou mettre à jour le lien de la sélection (Ctrl+L)" + +#: ui/composer-widget.ui:668 +msgid "Insert an image (Ctrl+G)" +msgstr "Insérer une image (Ctrl+G)" + +#: ui/composer-widget.ui:702 +msgid "Remove selection formatting (Ctrl+Space)" +msgstr "Effacer la mise en forme de la sélection (Ctrl+Espace)" + +#: ui/composer-widget.ui:726 +msgid "Select spell checking languages" +msgstr "Choisir les langues de vérification orthographique" + +#: ui/conversation-email.ui:27 +msgid "Save all attachments" +msgstr "Enregistrer toutes les pièces jointes" + +#. Note: The application will never show this button at the same time as unstar_button, one will always be hidden. +#: ui/conversation-email.ui:50 +msgid "Mark this message as starred" +msgstr "Marquer ce message d’une étoile" + +#. Note: The application will never show this button at the same time as star_button, one will always be hidden. +#: ui/conversation-email.ui:72 +msgid "Mark this message as not starred" +msgstr "Ne pas marquer ce message d’une étoile" + +#: ui/conversation-email.ui:95 +msgid "Display the message menu" +msgstr "Afficher le menu du message" + +#: ui/conversation-email.ui:161 +msgid "Open selected attachments" +msgstr "Ouvrir les pièces jointes sélectionnées" + +#: ui/conversation-email.ui:178 +msgid "Save selected attachments" +msgstr "Enregistrer les pièces jointes sélectionnées" + +#: ui/conversation-email.ui:195 +msgid "Select all attachments" +msgstr "Sélectionner toutes les pièces jointes" -#: ../ui/composer.glade.h:32 -msgid "_Medium" -msgstr "_Moyen" +#: ui/conversation-email.ui:240 +msgid "Edit Draft" +msgstr "Éditer le brouillon" -#: ../ui/composer.glade.h:33 -msgid "Medium" -msgstr "Moyen" +#: ui/conversation-email.ui:267 +msgid "Draft message" +msgstr "Brouillon" -#: ../ui/composer.glade.h:34 -msgid "_Small" -msgstr "_Petit" +#: ui/conversation-email.ui:283 +msgid "This message has not yet been sent." +msgstr "Ce message n’a pas été envoyé pour le moment." -#: ../ui/composer.glade.h:35 -msgid "Small" -msgstr "Petit" +#: ui/conversation-email.ui:329 +msgid "Message not saved" +msgstr "Message non enregistré" -#: ../ui/composer.glade.h:36 -msgid "S_ans Serif" -msgstr "S_ans Serif" +#: ui/conversation-email.ui:345 +msgid "This message was sent, but has not been saved to your account." +msgstr "" +"Ce message a été envoyé, mais n’a pas été enregistré dans votre compte." -#: ../ui/composer.glade.h:37 -msgid "Sans Serif" -msgstr "Sans Serif" +#. Translators: Menu item to reply to a specific message. +#: ui/conversation-email-menus.ui:15 +msgid "Reply to _All" +msgstr "Répondre à _tous" -#: ../ui/composer.glade.h:38 -msgid "S_erif" -msgstr "S_erif" +#. Translators: Menu item to mark a specific message as +#. read. +#: ui/conversation-email-menus.ui:30 +msgid "_Mark Read" +msgstr "Marquer comme l_u" -#: ../ui/composer.glade.h:39 -msgid "Serif" -msgstr "Serif" +#: ui/conversation-email-menus.ui:36 +msgid "_Mark Unread" +msgstr "Marquer comme non l_u" -#: ../ui/composer.glade.h:40 -msgid "_Fixed Width" -msgstr "_Largeur fixe" +#. Translators: Menu item to mark all messages in a +#. conversation from this one as unread. +#: ui/conversation-email-menus.ui:42 +msgid "Mark Unread From _Here" +msgstr "Marquer comme non lu à partir d’_ici" -#: ../ui/composer.glade.h:41 -msgid "Fixed Width" -msgstr "Largeur fixe" - -#: ../ui/composer.glade.h:42 -msgid "Detach" -msgstr "Détacher" +#. Translators: Menu item to move a single, specific message +#. to the trash +#: ui/conversation-email-menus.ui:50 +msgid "_Trash" +msgstr "_Corbeille" + +#. Translators: Menu item to delete a single, specific message +#: ui/conversation-email-menus.ui:57 +msgid "_Delete…" +msgstr "_Supprimer…" -#: ../ui/composer.glade.h:43 -msgid "Detach (Ctrl+D)" -msgstr "Détacher (Ctrl+D)" +#. Translators: Menu item to view the source for a message +#: ui/conversation-email-menus.ui:69 +msgid "_View Source" +msgstr "_Voir la source" -#: ../ui/composer.glade.h:44 -msgid "_Send" -msgstr "_Envoyer" +#: ui/conversation-email-menus.ui:87 +msgid "_Save All" +msgstr "_Tout enregistrer" + +#: ui/conversation-message-menus.ui:7 +msgid "_Open Link" +msgstr "_Ouvrir le lien" + +#: ui/conversation-message-menus.ui:11 +msgid "Copy Link _Address" +msgstr "Copier l’_adresse du lien" + +#: ui/conversation-message-menus.ui:17 +msgid "Send New _Message…" +msgstr "Envoyer un nouveau _message…" + +#: ui/conversation-message-menus.ui:21 +msgid "Copy Email _Address" +msgstr "Copier l’_adresse électronique" + +#: ui/conversation-message-menus.ui:27 +msgid "Save _Image As…" +msgstr "_Enregistrer l’image sous…" -#: ../ui/composer.glade.h:45 -msgid "Send" -msgstr "_Envoyer" +#: ui/conversation-message-menus.ui:33 +msgid "_Select All" +msgstr "T_out sélectionner" -#: ../ui/composer.glade.h:46 -msgid "Send (Ctrl+Enter)" -msgstr "Envoyer (Ctrl+Entrée)" +#: ui/conversation-message-menus.ui:43 +msgid "Search for messages from" +msgstr "Rechercher des messages de" + +#: ui/conversation-message.ui:64 +msgid "From " +msgstr "De " + +#: ui/conversation-message.ui:80 ui/conversation-message.ui:179 +msgid "1/1/1970\t" +msgstr "1/1/1970\t" + +#: ui/conversation-message.ui:103 +msgid "Preview body text." +msgstr "Prévisualiser le corps du message." + +#: ui/conversation-message.ui:203 +msgid "Sent by:" +msgstr "Envoyé par :" -#: ../ui/composer.glade.h:47 -msgid "_Attach File" -msgstr "_Ajouter une pièce jointe" - -#: ../ui/composer.glade.h:48 -msgid "Attach File" -msgstr "_Ajouter une pièce jointe" - -#: ../ui/composer.glade.h:49 -msgid "_Include Original Attachments" -msgstr "_Inclure les pièces jointes d'origine" +#: ui/conversation-message.ui:248 +msgid "Reply to:" +msgstr "Répondre à :" -#: ../ui/composer.glade.h:50 -msgid "Include Original Attachments" -msgstr "_Inclure les pièces jointes d'origine" +#: ui/conversation-message.ui:292 +msgid "Subject" +msgstr "Objet" -#: ../ui/composer.glade.h:51 -msgid "Application Menu" -msgstr "Menu de l'application" +#: ui/conversation-message.ui:502 +msgid "Show Images" +msgstr "Afficher les images" -#. Address(es) e-mail is to be sent to -#: ../ui/composer.glade.h:53 -msgid "_To" -msgstr "_À :" +#: ui/conversation-message.ui:515 +msgid "Always Show From Sender" +msgstr "Toujours afficher pour cet expéditeur" -#: ../ui/composer.glade.h:54 -msgid "_Cc" -msgstr "_Cc :" +#: ui/conversation-message.ui:543 +msgid "Remote images not shown" +msgstr "Images distantes non affichées" -#: ../ui/composer.glade.h:55 -msgid "_Subject" -msgstr "_Objet :" +#: ui/conversation-message.ui:560 +msgid "Only show remote images from senders you trust." +msgstr "N’afficher les images distantes que pour les expéditeurs de confiance." -#: ../ui/composer.glade.h:56 -msgid "_Bcc" -msgstr "_Cci :" +#: ui/conversation-message.ui:693 +msgid "But actually goes to:" +msgstr "Mais pointe en réalité vers :" -#: ../ui/composer.glade.h:57 -msgid "_Reply-To" -msgstr "_Répondre à" +#: ui/conversation-message.ui:724 +msgid "The link appears to go to:" +msgstr "Le lien semble pointer vers :" -#. Geary account mail will be sent from -#: ../ui/composer.glade.h:59 -msgid "From" -msgstr "De :" +#: ui/conversation-message.ui:736 +msgid "Deceptive link found" +msgstr "Lien trompeur découvert" -#: ../ui/composer.glade.h:60 -msgid "Drop files here" -msgstr "Déposer des fichiers ici" +#: ui/conversation-message.ui:751 +msgid "The email sender may be leading you to the wrong web site." +msgstr "" +"Il est possible que l’expéditeur du message essaye de vous diriger vers le " +"mauvais site Web." -#: ../ui/composer.glade.h:61 -msgid "To add them as attachments" -msgstr "Pour les ajouter en tant que pièces jointes" +#: ui/conversation-message.ui:764 +msgid "If unsure, contact the sender and ask before continuing." +msgstr "" +"Si vous n’êtes pas sûr, contactez l’expéditeur et demandez avant de " +"continuer." -#: ../ui/edit_alternate_emails.glade.h:1 -msgid "Remove email address" -msgstr "Supprimer l'adresse électronique" +#: ui/conversation-viewer.ui:60 +msgid "Find in conversation" +msgstr "Rechercher dans la conversation" -#: ../ui/edit_alternate_emails.glade.h:2 -msgid "" -"Some email services require additional addresses be configured on the " -"server. Contact your email provider for more information." -msgstr "" -"Certains services de courrier électronique nécessitent qu'une adresse " -"supplémentaire soit configurée sur le serveur. Contactez votre fournisseur " -"de courrier électronique pour plus d'informations." +#: ui/conversation-viewer.ui:74 +msgid "Find the previous occurrence of the search string." +msgstr "Rechercher l’occurrence précédente de la chaîne recherchée." -#: ../ui/edit_alternate_emails.glade.h:4 -msgid "_Update" -msgstr "Mettre à jo_ur" +#: ui/conversation-viewer.ui:95 +msgid "Find the next occurrence of the search string." +msgstr "Rechercher l’occurrence suivante de la chaîne recherchée." -#: ../ui/find_bar.glade.h:1 +#: ui/find_bar.glade:66 msgid "Find:" msgstr "Chercher :" -#: ../ui/find_bar.glade.h:2 +#: ui/find_bar.glade:89 msgid "_Previous" msgstr "_Précédent" -#: ../ui/find_bar.glade.h:3 +#: ui/find_bar.glade:107 msgid "_Next" msgstr "_Suivant" -#: ../ui/find_bar.glade.h:4 +#: ui/find_bar.glade:125 msgid "_Case sensitive" msgstr "_Sensible à la casse" -#: ../ui/find_bar.glade.h:5 +#: ui/find_bar.glade:145 msgid "label" msgstr "étiquette" -#: ../ui/login.glade.h:1 ../ui/password-dialog.glade.h:3 -msgid "Password" -msgstr "Mot de passe" +#: ui/gtk/help-overlay.ui:9 +msgid "Conversation Shortcuts" +msgstr "Raccourcis de conversation" + +#: ui/gtk/help-overlay.ui:13 ui/gtk/help-overlay.ui:254 +msgctxt "shortcut window" +msgid "General" +msgstr "Général" + +#: ui/gtk/help-overlay.ui:17 +msgctxt "shortcut window" +msgid "Move focus to the next/previous pane" +msgstr "Déplacer le focus au volet suivant / précedent" + +#: ui/gtk/help-overlay.ui:24 +msgctxt "shortcut window" +msgid "Move focus to conversation list" +msgstr "Déplacer le focus sur la liste des conversations" + +#: ui/gtk/help-overlay.ui:31 +msgctxt "shortcut window" +msgid "Detach composer window" +msgstr "Détacher la fenêtre d’édition" + +#: ui/gtk/help-overlay.ui:38 +msgctxt "shortcut window" +msgid "Close composer window" +msgstr "Fermer la fenêtre d’édition" + +#: ui/gtk/help-overlay.ui:45 +msgctxt "shortcut window" +msgid "Show keyboard shortcuts" +msgstr "Afficher les raccourcis clavier" + +#: ui/gtk/help-overlay.ui:52 +msgctxt "shortcut window" +msgid "Show help" +msgstr "Afficher l’aide" + +#: ui/gtk/help-overlay.ui:59 +msgctxt "shortcut window" +msgid "Quit the application" +msgstr "Quitter l’application" -#: ../ui/login.glade.h:2 -msgid "E_mail address" -msgstr "_Adresse électronique" - -#: ../ui/login.glade.h:3 -msgid "_Password" -msgstr "Mot de _passe" - -#: ../ui/login.glade.h:4 -msgid "S_ervice" -msgstr "S_ervice" - -#: ../ui/login.glade.h:5 -msgid "N_ame" -msgstr "_Nom" - -#: ../ui/login.glade.h:7 -msgid "N_ickname" -msgstr "Nom d'ut_ilisateur" - -#: ../ui/login.glade.h:8 -msgid "Work, Home, etc." -msgstr "Travail, Maison, etc." - -#: ../ui/login.glade.h:9 -msgid "_Save sent mail" -msgstr "_Enregistrer les messages envoyés" - -#: ../ui/login.glade.h:10 -msgid "Addi_tional email addresses…" -msgstr "Adresses de messagerie supplémenta_ires" - -#: ../ui/login.glade.h:11 -msgid "IMAP settings" -msgstr "Configuration IMAP" - -#: ../ui/login.glade.h:12 -msgid "Se_rver" -msgstr "Se_rveur" - -#: ../ui/login.glade.h:13 -msgid "P_ort" -msgstr "P_ort" - -#: ../ui/login.glade.h:14 -msgid "Ser_ver" -msgstr "Ser_veur" - -#: ../ui/login.glade.h:15 -msgid "Por_t" -msgstr "Por_t" +#: ui/gtk/help-overlay.ui:68 +msgctxt "shortcut window" +msgid "Search" +msgstr "Rechercher" -#: ../ui/login.glade.h:16 -msgid "SMTP settings" -msgstr "Configuration SMTP" +#: ui/gtk/help-overlay.ui:72 +msgctxt "shortcut window" +msgid "Jump to search box" +msgstr "Aller à la recherche" + +#: ui/gtk/help-overlay.ui:79 +msgctxt "shortcut window" +msgid "Find in current conversation" +msgstr "Trouver dans la conversation actuelle" + +#: ui/gtk/help-overlay.ui:86 +msgctxt "shortcut window" +msgid "Find next/previous in current conversation" +msgstr "Recherche suivante/précédente dans la conversation actuelle" + +#: ui/gtk/help-overlay.ui:95 ui/gtk/help-overlay.ui:274 +msgctxt "shortcut window" +msgid "Actions" +msgstr "Actions" + +#: ui/gtk/help-overlay.ui:99 +msgctxt "shortcut window" +msgid "Compose a new message" +msgstr "Écrire un nouveau message" + +#: ui/gtk/help-overlay.ui:106 +msgctxt "shortcut window" +msgid "Reply to sender " +msgstr "Répondre à l’expéditeur " + +#: ui/gtk/help-overlay.ui:113 +msgctxt "shortcut window" +msgid "Reply to all" +msgstr "Répondre à tous" + +#: ui/gtk/help-overlay.ui:120 +msgctxt "shortcut window" +msgid "Forward" +msgstr "Transférer" + +#: ui/gtk/help-overlay.ui:127 +msgctxt "shortcut window" +msgid "Archive" +msgstr "Archiver" -#: ../ui/login.glade.h:17 -msgid "User_name" -msgstr "_Identifiant" - -#: ../ui/login.glade.h:18 -msgid "Pass_word" -msgstr "Mot de p_asse" - -#: ../ui/login.glade.h:19 -msgid "SMTP username" -msgstr "Identifiant SMTP" - -#: ../ui/login.glade.h:20 -msgid "SMTP password" -msgstr "Mot de passe SMTP" - -#: ../ui/login.glade.h:21 -msgid "_Username" -msgstr "_Identifiant" - -#: ../ui/login.glade.h:22 -msgid "IMAP username" -msgstr "Identifiant IMAP" - -#: ../ui/login.glade.h:23 -msgid "IMAP password" -msgstr "Mot de passe IMAP" - -#: ../ui/login.glade.h:24 -msgid "Encr_yption" -msgstr "_Chiffrement" - -#: ../ui/login.glade.h:25 -msgid "Encrypt_ion" -msgstr "C_hiffrement" - -#: ../ui/login.glade.h:27 -msgid "SSL/TLS" -msgstr "SSL/TLS" - -#: ../ui/login.glade.h:28 -msgid "STARTTLS" -msgstr "STARTTLS" - -#: ../ui/login.glade.h:29 -msgid "No authentication re_quired" -msgstr "L'authentification n'est pas re_quise" - -#: ../ui/login.glade.h:30 -msgid "Use IMAP cre_dentials" -msgstr "Utiliser les i_dentifiants IMAP" - -#: ../ui/login.glade.h:31 ../ui/preferences.glade.h:5 -msgid "Composer" -msgstr "Éditeur" - -#: ../ui/login.glade.h:32 -msgid "Save dra_fts on server" -msgstr "Sauvegarder les _brouillons sur le serveur" - -#: ../ui/login.glade.h:33 -msgid "Si_gn emails (HTML allowed):" -msgstr "_Signer les messages (HTML autorisé) :" - -#: ../ui/login.glade.h:34 -msgid "Storage" -msgstr "Stockage" - -#: ../ui/login.glade.h:35 -msgid "_Download mail" -msgstr "_Télécharger les messages" +#: ui/gtk/help-overlay.ui:134 +msgctxt "shortcut window" +msgid "Move to trash" +msgstr "Mettre à la corbeille" + +#: ui/gtk/help-overlay.ui:141 +msgctxt "shortcut window" +msgid "Toggle spam" +msgstr "Marquer comme pourriel" + +#: ui/gtk/help-overlay.ui:148 +msgctxt "shortcut window" +msgid "Move the conversation" +msgstr "Déplacer la conversation" + +#: ui/gtk/help-overlay.ui:155 +msgctxt "shortcut window" +msgid "Label the conversation" +msgstr "Étiqueter la conversation" + +#: ui/gtk/help-overlay.ui:163 +msgctxt "shortcut window" +msgid "Mark read" +msgstr "Marquer comme lu" + +#: ui/gtk/help-overlay.ui:170 +msgctxt "shortcut window" +msgid "Mark unread" +msgstr "Marquer comme non lu" + +#: ui/gtk/help-overlay.ui:179 +msgctxt "shortcut window" +msgid "View" +msgstr "Affichage" + +#: ui/gtk/help-overlay.ui:183 +msgctxt "shortcut window" +msgid "Zoom in" +msgstr "Zoom avant" + +#: ui/gtk/help-overlay.ui:190 +msgctxt "shortcut window" +msgid "Zoom out" +msgstr "Zoom arrière" + +#: ui/gtk/help-overlay.ui:197 +msgctxt "shortcut window" +msgid "Reset zoom" +msgstr "Réinitialiser le zoom" + +#: ui/gtk/help-overlay.ui:206 +msgctxt "shortcut window" +msgid "Additional Shortcuts" +msgstr "Raccourcis supplémentaires" + +#: ui/gtk/help-overlay.ui:210 +msgctxt "shortcut window" +msgid "Star" +msgstr "Marquer d’une étoile" + +#: ui/gtk/help-overlay.ui:217 +msgctxt "shortcut window" +msgid "Unstar" +msgstr "Supprimer l’étoile" + +#: ui/gtk/help-overlay.ui:224 +msgctxt "shortcut window" +msgid "Delete" +msgstr "Supprimer" + +#: ui/gtk/help-overlay.ui:231 +msgctxt "shortcut window" +msgid "Jump to next (older) conversation" +msgstr "Aller à la conversation suivante (antérieure)" + +#: ui/gtk/help-overlay.ui:238 +msgctxt "shortcut window" +msgid "Jump to previous (newer) conversation" +msgstr "Aller à la conversation précédente (postérieure)" + +#: ui/gtk/help-overlay.ui:250 +msgid "Composer Shortcuts" +msgstr "Raccourcis de l’éditeur" + +#: ui/gtk/help-overlay.ui:258 +msgctxt "shortcut window" +msgid "Quote text" +msgstr "Mettre en citation" + +#: ui/gtk/help-overlay.ui:265 +msgctxt "shortcut window" +msgid "Unquote text" +msgstr "Supprimer la citation" + +#: ui/gtk/help-overlay.ui:278 +msgctxt "shortcut window" +msgid "Send" +msgstr "Envoyer" + +#: ui/gtk/help-overlay.ui:285 +msgctxt "shortcut window" +msgid "Add attachment" +msgstr "Ajouter une pièce jointe" + +#: ui/gtk/help-overlay.ui:294 +msgctxt "shortcut window" +msgid "Rich text mode" +msgstr "Mode d’édition enrichi" + +#: ui/gtk/help-overlay.ui:298 +msgctxt "shortcut window" +msgid "Bold text" +msgstr "Texte en gras" + +#: ui/gtk/help-overlay.ui:305 +msgctxt "shortcut window" +msgid "Italicize text" +msgstr "Texte en italique" + +#: ui/gtk/help-overlay.ui:312 +msgctxt "shortcut window" +msgid "Underline text" +msgstr "Texte souligné" + +#: ui/gtk/help-overlay.ui:319 +msgctxt "shortcut window" +msgid "Strike text" +msgstr "Texte barré" + +#: ui/gtk/help-overlay.ui:326 +msgctxt "shortcut window" +msgid "Insert a link" +msgstr "Insérer un lien" + +#: ui/gtk/help-overlay.ui:333 +msgctxt "shortcut window" +msgid "Remove formatting" +msgstr "Effacer la mise en forme" + +#: ui/gtk/menus.ui:13 +msgid "A_ccounts" +msgstr "_Comptes" + +#: ui/gtk/menus.ui:23 +msgid "_Keyboard Shortcuts" +msgstr "_Raccourcis clavier" + +#: ui/main-toolbar.ui:51 +msgid "Toggle search bar" +msgstr "Afficher la barre de recherche" + +#: ui/main-toolbar.ui:72 +msgid "Empty Spam or Trash folders" +msgstr "Vider les dossiers pourriels ou la corbeille" + +#: ui/main-toolbar.ui:112 +msgid "Reply" +msgstr "Répondre" + +#: ui/main-toolbar.ui:135 +msgid "Reply All" +msgstr "Répondre à tous" + +#: ui/main-toolbar.ui:158 +msgid "Forward" +msgstr "Transférer" + +#: ui/main-toolbar.ui:264 +msgid "Toggle find bar" +msgstr "Afficher la barre de recherche" + +#: ui/main-toolbar.ui:306 +msgid "_Archive" +msgstr "_Archiver" + +#: ui/main-toolbar-menus.ui:5 +msgid "Empty _Spam…" +msgstr "Vider les pourriel_s…" + +#: ui/main-toolbar-menus.ui:9 +msgid "Empty _Trash…" +msgstr "Vider la _corbeille…" + +#: ui/main-toolbar-menus.ui:32 +msgid "Mark as S_pam" +msgstr "Marquer comme _pourriel" -#: ../ui/password-dialog.glade.h:1 +#: ui/main-toolbar-menus.ui:36 +msgid "Mark as not S_pam" +msgstr "Marquer comme non _pourriel" + +#. Infobar title when one or more accounts are offline +#: ui/main-window.ui:183 +msgid "Working offline" +msgstr "Travail hors ligne" + +#. Label and tooltip for offline infobar +#: ui/main-window.ui:197 +msgid "" +"Your computer does not appear to be connected to the Internet.\n" +"You will not be able to send or receive email until it is re-connected." +msgstr "" + +#. Label and tooltip for offline infobar +#: ui/main-window.ui:200 +msgid "You will not be able to send or receive email until re-connected." +msgstr "" + +#. Button label for displaying technical details about an account problem +#. Dialog title for displaying technical details of a problem. Same as the button that invokes it. +#: ui/main-window.ui:247 ui/problem-details-dialog.ui:13 +msgid "Details" +msgstr "Détails" + +#. Button label for retrying an account problem +#: ui/main-window.ui:261 +msgid "Retry" +msgstr "Réessayer" + +#. Infobar title when one or more accounts have encounted an error +#: ui/main-window.ui:294 +#, fuzzy +#| msgid "Accounts" +msgid "Account problem" +msgstr "Comptes" + +#. Label and tooltip for account service problem infobar +#: ui/main-window.ui:308 +#, fuzzy +#| msgid "" +#| "Could not connect to %s, check your Internet access and the server name " +#| "and try again" +msgid "" +"Geary encountered a problem connecting to an account.\n" +"Please check your Internet connection, the server configuration and try " +"again." +msgstr "" +"Impossible de se connecter à %s, vérifiez votre connexion internet et le nom " +"du serveur et réessayez" + +#. Label and tooltip for account service problem infobar +#: ui/main-window.ui:311 +#, fuzzy +#| msgid "Geary has encountered a problem" +msgid "Geary encountered a problem connecting to an account." +msgstr "Geary a rencontré un problème" + +#. Button label for retrying TLS cert validation +#: ui/main-window.ui:358 +msgid "Check" +msgstr "Vérifier" + +#. Button tooltip for retrying TLS cert validation +#: ui/main-window.ui:362 +msgid "Check the security details for the connection" +msgstr "" + +#. Infobar title when one or more accounts have a TLS cert validation error +#: ui/main-window.ui:391 +msgid "Security problem" +msgstr "Problème de sécurité" + +#. Label and tooltip for TLS cert validation error infobar +#: ui/main-window.ui:405 +msgid "" +"An account has reported an untrusted server.\n" +"Please check the server configuration and try again." +msgstr "" + +#. Label and tooltip for TLS cert validation error infobar +#: ui/main-window.ui:408 +msgid "An account has reported an untrusted server." +msgstr "" + +#. Button tooltip for retrying when a login error has occurred +#: ui/main-window.ui:459 +#, fuzzy +#| msgid "Retry receiving email, you will be prompted for a password" +msgid "Retry login, you will be prompted for your password" +msgstr "" +"Effectuez une nouvelle tentative de récupération des courriels, le mot de " +"passe vous sera demandé" + +#. Infobar title when one or more accounts have a login error +#: ui/main-window.ui:488 +msgid "Login problem" +msgstr "Problème d’identification" + +#. Label and tooltip for authentication problem infobar +#: ui/main-window.ui:502 +msgid "" +"An account has reported an incorrect login or password.\n" +"Please check your login name and try again." +msgstr "" + +#. Label and tooltip for authentication problem infobar +#: ui/main-window.ui:505 +msgid "An account has reported an incorrect login or password." +msgstr "" + +#: ui/password-dialog.glade:74 msgid "SMTP Credentials" msgstr "Identifiants SMTP" -#: ../ui/password-dialog.glade.h:2 +#: ui/password-dialog.glade:91 msgid "Username" msgstr "Identifiant" -#: ../ui/password-dialog.glade.h:4 +#: ui/password-dialog.glade:152 msgid "_Remember password" msgstr "_Se souvenir du mot de passe" -#: ../ui/password-dialog.glade.h:6 +#: ui/password-dialog.glade:210 msgid "_Authenticate" msgstr "_Authentifier" -#: ../ui/preferences.glade.h:1 +#: ui/preferences-dialog.ui:38 msgid "Reading" msgstr "Lecture" -#: ../ui/preferences.glade.h:2 +#: ui/preferences-dialog.ui:51 msgid "_Automatically select next message" msgstr "Sélectionner _automatiquement le message suivant" -#: ../ui/preferences.glade.h:3 +#: ui/preferences-dialog.ui:70 msgid "_Display conversation preview" -msgstr "A_fficher l'aperçu de la conversation" +msgstr "A_fficher l’aperçu de la conversation" -#: ../ui/preferences.glade.h:4 +#: ui/preferences-dialog.ui:89 msgid "Use _three pane view" msgstr "Utiliser la vue à _trois panneaux" -#: ../ui/preferences.glade.h:6 -msgid "Enable _spell checking" -msgstr "Activer la _vérification orthographique" - -#: ../ui/preferences.glade.h:7 +#: ui/preferences-dialog.ui:113 msgid "Notifications" msgstr "Notifications" -#: ../ui/preferences.glade.h:8 +#: ui/preferences-dialog.ui:126 msgid "_Play notification sounds" msgstr "_Jouer les sons de notification" -#: ../ui/preferences.glade.h:9 +#: ui/preferences-dialog.ui:145 msgid "Show _notifications for new mail" msgstr "Afficher les _notifications de nouveaux messages" -#: ../ui/preferences.glade.h:10 -msgid "Always _watch for new mail" -msgstr "Toujours _vérifier s'il y a de nouveaux messages" +#: ui/preferences-dialog.ui:164 +msgid "_Watch for new mail when closed" +msgstr "_Vérifier s’il y a de nouveaux messages après la fermeture" -#: ../ui/preferences.glade.h:11 -msgid "Geary will run in the background and notify of new mail" +#: ui/preferences-dialog.ui:168 +msgid "Geary will keep running after all windows are closed" msgstr "" -"Geary va tourner en arrière-plan et notifier l'arrivée de nouveaux messages" +"Geary continuera à fonctionner après la fermeture de toutes les fenêtres" -#: ../ui/preferences.glade.h:12 +#: ui/preferences-dialog.ui:195 msgid "Preferences" msgstr "Préférences" -#: ../ui/remove_confirm.glade.h:1 +#. Button label for copying technical information to the clipboard +#: ui/problem-details-dialog.ui:17 +msgid "Copy to Clipboard" +msgstr "Copier vers le presse-papiers" + +#. Button tooltip for copying technical information to the clipboard +#: ui/problem-details-dialog.ui:21 msgid "" -"Are you sure you want to remove this " -"account? " +"Copy technical details to clipboard for pasting into an email or bug report" msgstr "" -"Voulez-vous vraiment supprimer ce " -"compte ?" +"Copier les détails technique vers le presse-papiers pour les coller dans un " +"courriel ou un rapport d’anomalie" -#: ../ui/remove_confirm.glade.h:2 +#: ui/problem-details-dialog.ui:73 msgid "" -"All email associated with this account will be removed from your computer. " -"This will not affect email on the server." +"If the problem is serious or persists, please copy and send these details to " +"the mailing list " +"or file a new " +"bug report." msgstr "" -"Tous les courriels associés à ce compte seront supprimés de votre " -"ordinateur. Ceux présents sur le serveur ne seront pas affectés." +"Si le problème est grave ou persiste, copiez et envoyez ces détails à la liste de diffusion ou " +"créez un nouveau " +"rapport d’anomalie." -#: ../ui/remove_confirm.glade.h:3 -msgid "Nickname:" -msgstr "Nom d'utilisateur :" +#: ui/problem-details-dialog.ui:89 +msgid "Details:" +msgstr "Détails :" -#: ../ui/remove_confirm.glade.h:4 -msgid "Email address:" -msgstr "Adresse électronique :" - -#: ../ui/upgrade_dialog.glade.h:1 +#: ui/upgrade_dialog.glade:60 msgid "Geary update in progress…" msgstr "Mise à jour de Geary en cours…" - -#~ msgid "Copyright 2011-2015 Yorba Foundation" -#~ msgstr "Copyright 2011-2015 Fondation Yorba" - -#~ msgid "_Donate" -#~ msgstr "Faire un _don" diff -Nru geary-0.12.4/po/fur.po geary-3.32.0/po/fur.po --- geary-0.12.4/po/fur.po 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/po/fur.po 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,3240 @@ +# Friulian translation for geary. +# Copyright (C) 2019 geary's COPYRIGHT HOLDER +# This file is distributed under the same license as the geary package. +# Fabio Tomat , 2019. +# +msgid "" +msgstr "" +"Project-Id-Version: geary master\n" +"Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/geary/issues\n" +"POT-Creation-Date: 2019-03-07 22:09+0000\n" +"PO-Revision-Date: 2019-03-09 09:47+0100\n" +"Language-Team: Friulian \n" +"Language: fur\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Last-Translator: Fabio Tomat \n" +"X-Generator: Poedit 2.2.1\n" + +#: desktop/geary-attach.contract.desktop.in:3 +msgid "Send by email" +msgstr "Invie par e-mail" + +#. Translators: Do NOT translate or transliterate this text (this is an icon file name)! +#: desktop/geary-attach.contract.desktop.in:5 +msgid "mail-send" +msgstr "mail-send" + +#: desktop/geary-attach.contract.desktop.in:6 +msgid "Send files using Geary" +msgstr "Invie file doprant Geary" + +#. Translators: The application name +#: desktop/geary-autostart.desktop.in:3 +#: desktop/org.gnome.Geary.appdata.xml.in:11 +#: desktop/org.gnome.Geary.desktop.in:3 +#: src/client/accounts/accounts-editor-servers-pane.vala:555 +msgid "Geary" +msgstr "Geary" + +#: desktop/geary-autostart.desktop.in:4 desktop/org.gnome.Geary.desktop.in:4 +msgid "Email" +msgstr "E-mail" + +#. Translators: The application's summary / tagline +#: desktop/geary-autostart.desktop.in:5 +#: desktop/org.gnome.Geary.appdata.xml.in:15 +#: desktop/org.gnome.Geary.desktop.in:5 +#: src/client/application/geary-application.vala:23 +msgid "Send and receive email" +msgstr "Invie e ricêf e-mail" + +#. Translators: These are desktop search terms. Do not translate semicolons, end line with a semicolon. +#: desktop/geary-autostart.desktop.in:7 +msgid "Email;E-mail;Mail;" +msgstr "Email;E-mail;Mail;Pueste;" + +#. Translators: Do NOT translate or transliterate this text (this is an icon file name)! +#: desktop/geary-autostart.desktop.in:9 desktop/org.gnome.Geary.desktop.in:9 +msgid "org.gnome.Geary" +msgstr "org.gnome.Geary" + +#. Translators: The development team's name +#: desktop/org.gnome.Geary.appdata.xml.in:13 +msgid "Geary Development Team" +msgstr "Grup di svilup di Geary" + +#: desktop/org.gnome.Geary.appdata.xml.in:17 +msgid "" +"Geary is an email application built around conversations, for the GNOME 3 " +"desktop. It allows you to read, find and send email with a straightforward, " +"modern interface." +msgstr "" +"Geary e je une aplicazion par e-mail costruide tor ator des conversazions, " +"pal scritori GNOME 3. E permet di lei, cjatâ e inviâ e-mail cuntune " +"interface moderne e direte." + +#: desktop/org.gnome.Geary.appdata.xml.in:22 +msgid "" +"Conversations allow you to read a complete discussion without having to find " +"and click from message to message." +msgstr "" +"Lis conversazions ti permetin di lei une discussion complete cence vê di " +"cjatâ e fâ clic di messaç in messaç." + +#: desktop/org.gnome.Geary.appdata.xml.in:26 +msgid "Geary’s features include:" +msgstr "Lis funzionalitâts di Geary a includin:" + +#: desktop/org.gnome.Geary.appdata.xml.in:28 +msgid "Quick email account setup" +msgstr "Configurazion rapide dal account e-mail" + +#: desktop/org.gnome.Geary.appdata.xml.in:29 +msgid "Shows related messages together in conversations" +msgstr "Al mostre adun i messaçs colegâts in conversazions" + +#: desktop/org.gnome.Geary.appdata.xml.in:30 +msgid "Fast, full text and keyword search" +msgstr "Ricercje veloce di peraulis clâf e test complet" + +#: desktop/org.gnome.Geary.appdata.xml.in:31 +msgid "Full-featured HTML and plain text message composer" +msgstr "Compositôr complet di messaçs in HTML e test sempliç" + +#: desktop/org.gnome.Geary.appdata.xml.in:32 +msgid "Desktop notification of new mail" +msgstr "Notifiche sul scritori de gnove pueste" + +#: desktop/org.gnome.Geary.appdata.xml.in:33 +msgid "Compatible with GMail, Yahoo! Mail, Outlook.com and other IMAP servers" +msgstr "Compatibil cun GMail, Yahoo! Mail, Outlook.com e altris servidôrs IMAP" + +#. Translators: A screenshot description. +#: desktop/org.gnome.Geary.appdata.xml.in:47 +msgid "Geary displaying a conversation" +msgstr "Geary che al mostre une conversazion" + +#. Translators: A screenshot description. +#: desktop/org.gnome.Geary.appdata.xml.in:52 +msgid "Geary showing the rich text composer" +msgstr "Geary che al mostre il compositôr di test formatât" + +#. Translators: These are desktop search terms. Do not translate semicolons, end line with a semicolon. +#: desktop/org.gnome.Geary.desktop.in:7 +msgid "Mail;E-mail;IMAP;GMail;Yahoo;Hotmail;Outlook;" +msgstr "Pueste;Mail;E-mail;IMAP;GMail;Yahoo;Hotmail;Outlook;" + +#: desktop/org.gnome.Geary.desktop.in:21 +msgid "Compose Message" +msgstr "Scrîf messaç" + +#: desktop/org.gnome.Geary.gschema.xml:8 +msgid "Maximize window" +msgstr "Slargje il barcon" + +#: desktop/org.gnome.Geary.gschema.xml:9 +msgid "True if the application window is maximized, false otherwise." +msgstr "Vêr se il barcon de aplicazion al è slargjât, fals in câs contrari." + +#: desktop/org.gnome.Geary.gschema.xml:14 +msgid "Width of window" +msgstr "Largjece dal barcon" + +#: desktop/org.gnome.Geary.gschema.xml:15 +msgid "The last recorded width of the application window." +msgstr "L'ultime largjece regjistrade dal barcon de aplicazion." + +#: desktop/org.gnome.Geary.gschema.xml:20 +msgid "Height of window" +msgstr "Altece dal barcon" + +#: desktop/org.gnome.Geary.gschema.xml:21 +msgid "The last recorded height of the application window." +msgstr "L'ultime altece regjistrade dal barcon de aplicazion." + +#: desktop/org.gnome.Geary.gschema.xml:26 +msgid "Position of folder list pane" +msgstr "Posizion dal ricuadri de liste des cartelis" + +#: desktop/org.gnome.Geary.gschema.xml:27 +msgid "Position of the folder list Paned grabber." +msgstr "Posizion de mantie dal ricuadri de liste des cartelis." + +#: desktop/org.gnome.Geary.gschema.xml:32 +msgid "Position of folder list pane when horizontal" +msgstr "Posizion dal ricuadri de liste des cartelis se al è orizontâl" + +#: desktop/org.gnome.Geary.gschema.xml:33 +msgid "" +"Position of the folder list Paned grabber in the horizontal orientation." +msgstr "" +"Posizion de mantie dal ricuadri de liste des cartelis.cuant che al è " +"orientât par orizontâl." + +#: desktop/org.gnome.Geary.gschema.xml:38 +msgid "Position of folder list pane when vertical" +msgstr "Posizion dal ricuadri de liste des cartelis se al è verticâl" + +#: desktop/org.gnome.Geary.gschema.xml:39 +msgid "Position of the folder list Paned grabber in the vertical orientation." +msgstr "" +"Posizion de mantie dal ricuadri de liste des cartelis.cuant che al è " +"orientât par verticâl." + +#: desktop/org.gnome.Geary.gschema.xml:44 +msgid "Orientation of the folder list pane" +msgstr "Orientament dal ricuadri de liste des cartelis" + +#: desktop/org.gnome.Geary.gschema.xml:45 +msgid "True if the folder list Paned is in the horizontal orientation." +msgstr "" +"Vêr se la liste des cartelis ricuadrade e je tal orientament orizontâl." + +#: desktop/org.gnome.Geary.gschema.xml:50 +msgid "Position of message list pane" +msgstr "Posizion dal ricuadri de liste dai messaçs" + +#: desktop/org.gnome.Geary.gschema.xml:51 +msgid "Position of the message list Paned grabber." +msgstr "Posizion de mantie dal ricuadri de liste dai messaçs." + +#: desktop/org.gnome.Geary.gschema.xml:56 +msgid "Autoselect next message" +msgstr "Selezione in automatic il prossim messaç" + +#: desktop/org.gnome.Geary.gschema.xml:57 +msgid "True if we should autoselect the next available conversation." +msgstr "Vêr se o ven di auto-selezionâ la conversazion sucessive disponibile." + +#: desktop/org.gnome.Geary.gschema.xml:62 +msgid "Display message previews" +msgstr "Mostre lis anteprimis dal messaç" + +#: desktop/org.gnome.Geary.gschema.xml:63 +msgid "True if we should display a short preview of each message." +msgstr "Vêr se o ven di mostrâ une curte anteprime di ogni messaç." + +#: desktop/org.gnome.Geary.gschema.xml:68 +msgid "Languages that shall be used in the spell checker" +msgstr "Lenghis che si an di doprâ tal control ortografic" + +#: desktop/org.gnome.Geary.gschema.xml:69 +msgid "List of the languages to use in the spell checker." +msgstr "Liste des lenghis di doprâ intal control ortografic." + +#: desktop/org.gnome.Geary.gschema.xml:74 +msgid "Languages that are displayed in the spell checker popover" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:75 +msgid "" +"List of languages that are always displayed in the popover of the spell " +"checker." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:80 +msgid "Enable notification sounds" +msgstr "Abilite suns di notifiche" + +#: desktop/org.gnome.Geary.gschema.xml:81 +msgid "True to play sounds for notifications and sending." +msgstr "Vêr par riprodusi i suns pes notifichis e pe spedizion." + +#: desktop/org.gnome.Geary.gschema.xml:86 +msgid "Show notifications for new mail" +msgstr "Mostre lis notifichis pe gnove pueste" + +#: desktop/org.gnome.Geary.gschema.xml:87 +msgid "True to show notification bubbles." +msgstr "Vêr par mostrâ i fumets." + +#: desktop/org.gnome.Geary.gschema.xml:92 +msgid "Notify of new mail at startup" +msgstr "Notifiche la gnove pueste al inviament" + +#: desktop/org.gnome.Geary.gschema.xml:93 +msgid "True to notify of new mail at startup." +msgstr "Vêr par vê la notifiche de gnove puest al inviament." + +#: desktop/org.gnome.Geary.gschema.xml:98 +msgid "Ask when opening an attachment" +msgstr "Domande cuant che si vierç un alegât" + +#: desktop/org.gnome.Geary.gschema.xml:99 +msgid "True to ask when opening an attachment." +msgstr "Vêr par domandâ cuant che si vierç un alegât." + +#: desktop/org.gnome.Geary.gschema.xml:104 +msgid "Whether to compose emails in HTML" +msgstr "Indiche se scrivi lis e-mail in HTML" + +#: desktop/org.gnome.Geary.gschema.xml:105 +msgid "True to compose emails in HTML; false for plain text." +msgstr "Vêr par scrivi lis e-mail in HTML; fals pal test sempliç." + +#: desktop/org.gnome.Geary.gschema.xml:110 +msgid "Advisory strategy for full-text searching" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:111 +msgid "" +"Acceptable values are “exact”, “conservative”, “aggressive”, and “horizon”." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:116 +msgid "Zoom of conversation viewer" +msgstr "Ingrandiment dal visualizadôr de conversazion" + +#: desktop/org.gnome.Geary.gschema.xml:117 +msgid "The zoom to apply on the conservation view." +msgstr "L'ingrandiment di aplicâ te viodude de conversazion." + +#: desktop/org.gnome.Geary.gschema.xml:122 +msgid "Size of detached composer window" +msgstr "Dimension dal barcon dal compositôr distacât" + +#: desktop/org.gnome.Geary.gschema.xml:123 +msgid "The last recorded size of the detached composer window." +msgstr "L'ultime dimension regjistrade dal barcon dal compositôr distacât." + +#: desktop/org.gnome.Geary.gschema.xml:128 +msgid "Base URL to look up contact avatars" +msgstr "URL di base par cirî i avatar dai contats" + +#: desktop/org.gnome.Geary.gschema.xml:129 +msgid "" +"A Gravatar or Libravatar compatible URL, set to the empty string to disable." +msgstr "" +"Un URL compatibil cun Gravatar o Libravatar, met une stringhe vueide par " +"disabilitâ." + +#: desktop/org.gnome.Geary.gschema.xml:134 +msgid "Whether we migrated the old settings" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:135 +msgid "" +"False to check for the old “org.yorba.geary”-schema and copy its values." +msgstr "" + +#. Translators: In-app notification label, when +#. the app had a problem pinning an otherwise +#. untrusted TLS certificate +#: src/client/accounts/accounts-editor.vala:203 +msgid "Failed to store certificate" +msgstr "No si è rivâts a archiviâ il certificât" + +#. Translators: Label for adding an email account +#. account for a generic IMAP service provider. +#: src/client/accounts/accounts-editor-add-pane.vala:109 +msgid "All others" +msgstr "Ducj chei altris" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:196 +#: src/client/accounts/accounts-editor-servers-pane.vala:316 +msgid "Check your receiving login and password" +msgstr "Controle il non utent e la password pe ricezion" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:211 +#: src/client/accounts/accounts-editor-servers-pane.vala:329 +msgid "Check your receiving server details" +msgstr "Controle i detais dal servidôr pe ricezion" + +#. Translators: In-app notification label +#. There was an SMTP auth error, but IMAP already +#. succeeded, so the user probably needs to +#. specify custom creds here +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:233 +#: src/client/accounts/accounts-editor-servers-pane.vala:350 +msgid "Check your sending login and password" +msgstr "Controle il non utent e la password pe spedizion" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:247 +#: src/client/accounts/accounts-editor-servers-pane.vala:363 +msgid "Check your sending server details" +msgstr "Controle i detais dal servidôr pe spedizion" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:262 +msgid "Check your email address and password" +msgstr "Controle la direzion e-mail e la password" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:273 +msgid "Could not connect, check your network" +msgstr "Impussibil conetisi, controle la tô rêt" + +#. Translators: In-app notification label for a +#. generic error creating an account +#: src/client/accounts/accounts-editor-add-pane.vala:286 +msgid "An unexpected problem occurred" +msgstr "Al è vignût fûr un erôr inspietât" + +#. Translators: In-app notification label, the +#. string substitution is a more detailed reason. +#: src/client/accounts/accounts-editor-add-pane.vala:304 +#, c-format +msgid "Account not created: %s" +msgstr "Account no creât: %s" + +#. Translators: Label for the person's actual name when adding +#. an account +#: src/client/accounts/accounts-editor-add-pane.vala:551 +msgid "Your name" +msgstr "Il to non" + +#. Translators: Label used for the address part of an +#. email address when editing a user's sender address +#. preferences for an account. +#: src/client/accounts/accounts-editor-add-pane.vala:568 +#: src/client/accounts/accounts-editor-edit-pane.vala:501 +msgid "Email address" +msgstr "Direzion e-mail" + +#. Translators: Placeholder for the default sender address +#. when adding an account +#. Translators: This is used as a placeholder for the +#. address part of an email address when editing a user's +#. sender address preferences for an account. +#: src/client/accounts/accounts-editor-add-pane.vala:571 +#: src/client/accounts/accounts-editor-edit-pane.vala:469 +msgid "person@example.com" +msgstr "persone@esempli.com" + +#. Translators: Label for an IMAP/SMTP service login/user name +#. when adding an account +#. Translators: Label for the user's login name for an +#. IMAP, SMTP, etc service +#: src/client/accounts/accounts-editor-add-pane.vala:585 +#: src/client/accounts/accounts-editor-servers-pane.vala:880 +msgid "Login name" +msgstr "Non utent pal acès" + +#. Translators: Label for the user's password for an IMAP, +#. SMTP, etc service +#: src/client/accounts/accounts-editor-add-pane.vala:599 +#: src/client/accounts/accounts-editor-servers-pane.vala:999 +#: ui/password-dialog.glade:108 +msgid "Password" +msgstr "Password" + +#. Translators: Label for the IMAP server hostname when +#. adding an account. +#. Translators: This label describes the host name or IP +#. address and port used by an account's IMAP service. +#: src/client/accounts/accounts-editor-add-pane.vala:621 +#: src/client/accounts/accounts-editor-servers-pane.vala:727 +msgid "IMAP server" +msgstr "Servidôr IMAP" + +#. Translators: Placeholder for the IMAP server hostname +#. when adding an account. +#: src/client/accounts/accounts-editor-add-pane.vala:624 +msgid "imap.example.com" +msgstr "imap.esempli.com" + +#. Translators: Label for the SMTP server hostname when +#. adding an account. +#. Translators: This label describes the host name or IP +#. address and port used by an account's SMTP service. +#: src/client/accounts/accounts-editor-add-pane.vala:630 +#: src/client/accounts/accounts-editor-servers-pane.vala:733 +msgid "SMTP server" +msgstr "Servidôr SMTP" + +#. Translators: Placeholder for the SMTP server hostname +#. when adding an account. +#: src/client/accounts/accounts-editor-add-pane.vala:633 +msgid "smtp.example.com" +msgstr "smtp.esempli.com" + +#. Translators: Label in the account editor for the user's +#. custom name for an account. +#: src/client/accounts/accounts-editor-edit-pane.vala:278 +#: ui/accounts_editor_remove_pane.ui:123 +msgid "Account name" +msgstr "Non account" + +#. Translators: Tooltip used to undo changing +#. the name of an account. The string +#. substitution is the old name of the +#. account. +#: src/client/accounts/accounts-editor-edit-pane.vala:312 +#, c-format +msgid "Change account name back to “%s”" +msgstr "Torne met il non dal account a “%s”" + +#. Translators: Tooltip for adding a new email sender/from +#. address's address to an account +#: src/client/accounts/accounts-editor-edit-pane.vala:336 +msgid "Add a new sender email address" +msgstr "Zonte une gnove direzion e-mail di mitent" + +#. Translators: Label used to indicate the user has +#. provided no display name for one of their sender +#. email addresses in their account settings. +#: src/client/accounts/accounts-editor-edit-pane.vala:417 +msgid "Name not set" +msgstr "Non no configurât" + +#. Translators: This is used as a placeholder for the +#. display name for an email address when editing a user's +#. sender address preferences for an account. +#: src/client/accounts/accounts-editor-edit-pane.vala:456 +msgid "Sender Name" +msgstr "Non mitent" + +#: src/client/accounts/accounts-editor-edit-pane.vala:479 +msgid "Remove" +msgstr "Gjave" + +#. Translators: Label used for the display name part of an +#. email address when editing a user's sender address +#. preferences for an account. +#: src/client/accounts/accounts-editor-edit-pane.vala:494 +msgid "Sender name" +msgstr "Non mitent" + +#. Translators: Label used as the undo tooltip after adding an +#. new sender email address to an account. The string +#. substitution is the email address added. +#: src/client/accounts/accounts-editor-edit-pane.vala:561 +#, c-format +msgid "Remove “%s”" +msgstr "Gjave “%s”" + +#. Translators: Label used as the undo tooltip after editing a +#. sender address for an account. The string substitution is +#. the email address edited. +#: src/client/accounts/accounts-editor-edit-pane.vala:601 +#, c-format +msgid "Undo changes to “%s”" +msgstr "Anule modifichis a “%s”" + +#. Translators: Label used as the undo tooltip after removing +#. a sender address from an account. The string substitution +#. is the email address edited. +#: src/client/accounts/accounts-editor-edit-pane.vala:688 +#, c-format +msgid "Add “%s” back" +msgstr "Torne zonte “%s”" + +#. Translators: Label used as the undo tooltip after removing +#. a sender address from an account. The string substitution +#. is the email address edited. +#: src/client/accounts/accounts-editor-edit-pane.vala:730 +msgid "Undo signature changes" +msgstr "Anule modifichis ae firme" + +#. Translators: This label describes the account +#. preference for the length of time (weeks, months or +#. years) that past email should be downloaded. +#: src/client/accounts/accounts-editor-edit-pane.vala:778 +msgid "Download mail" +msgstr "Discjarie pueste" + +#. Translators: Tooltip for undoing a change +#. to the length of time that past email +#. should be downloaded for an account. The +#. string substitution is the duration, +#. e.g. "1 month back". +#: src/client/accounts/accounts-editor-edit-pane.vala:810 +#, c-format +msgid "Change download period back to: %s" +msgstr "" + +#: src/client/accounts/accounts-editor-edit-pane.vala:831 +msgid "Everything" +msgstr "" + +#: src/client/accounts/accounts-editor-edit-pane.vala:835 +msgid "2 weeks back" +msgstr "" + +#: src/client/accounts/accounts-editor-edit-pane.vala:839 +msgid "1 month back" +msgstr "" + +#: src/client/accounts/accounts-editor-edit-pane.vala:843 +msgid "3 months back" +msgstr "" + +#: src/client/accounts/accounts-editor-edit-pane.vala:847 +msgid "6 months back" +msgstr "" + +#: src/client/accounts/accounts-editor-edit-pane.vala:851 +msgid "1 year back" +msgstr "" + +#: src/client/accounts/accounts-editor-edit-pane.vala:855 +msgid "2 years back" +msgstr "" + +#: src/client/accounts/accounts-editor-edit-pane.vala:859 +msgid "4 years back" +msgstr "" + +#: src/client/accounts/accounts-editor-edit-pane.vala:865 +#, c-format +msgid "%d day back" +msgid_plural "%d days back" +msgstr[0] "" +msgstr[1] "" + +#: src/client/accounts/accounts-editor-list-pane.vala:243 +msgid "Undo" +msgstr "Disfe" + +#: src/client/accounts/accounts-editor-list-pane.vala:251 +msgid "Redo" +msgstr "Torne fâ" + +#: src/client/accounts/accounts-editor-list-pane.vala:345 +#: src/client/accounts/accounts-editor-list-pane.vala:433 +#: src/client/accounts/accounts-editor-row.vala:278 +msgid "Gmail" +msgstr "Gmail" + +#: src/client/accounts/accounts-editor-list-pane.vala:349 +#: src/client/accounts/accounts-editor-list-pane.vala:437 +#: src/client/accounts/accounts-editor-row.vala:282 +msgid "Outlook.com" +msgstr "Outlook.com" + +#: src/client/accounts/accounts-editor-list-pane.vala:353 +#: src/client/accounts/accounts-editor-list-pane.vala:441 +#: src/client/accounts/accounts-editor-row.vala:286 +msgid "Yahoo" +msgstr "Yahoo" + +#. Translators: Tooltip for accounts that have been +#. loaded but disabled by the user. +#: src/client/accounts/accounts-editor-list-pane.vala:371 +msgid "This account has been disabled" +msgstr "Chest account al è stât disabilitât" + +#. Translators: Tooltip for accounts that have been +#. loaded but because of some error are not able to be +#. used. +#: src/client/accounts/accounts-editor-list-pane.vala:380 +msgid "This account has encountered a problem and is unavailable" +msgstr "Chest account al à vût un probleme e nol è disponibil" + +#. Translators: Label for adding a generic email account +#: src/client/accounts/accounts-editor-list-pane.vala:430 +msgid "Other email providers" +msgstr "Altris furnidôrs di e-mail" + +#. Translators: Notification shown after removing an +#. account. The string substitution is the name of the +#. account. +#: src/client/accounts/accounts-editor-list-pane.vala:547 +#, c-format +msgid "Account “%s” removed" +msgstr "Account “%s” gjavât" + +#. Translators: Notification shown after removing an account +#. is undone. The string substitution is the name of the +#. account. +#: src/client/accounts/accounts-editor-list-pane.vala:554 +#, c-format +msgid "Account “%s” restored" +msgstr "Account “%s” ripristinât" + +#. Translators: Tooltip for dragging list items +#: src/client/accounts/accounts-editor-row.vala:49 +msgid "Drag to move this item" +msgstr "Strissine par spostâ chest element" + +#. Translators: Label describes the service provider +#. hosting the email account, e.g. Gmail, Yahoo, or some +#. other generic IMAP service. +#: src/client/accounts/accounts-editor-row.vala:294 +msgid "Service provider" +msgstr "Furnidôr dal servizi" + +#. Translators: This label describes what form of transport +#. security (TLS, StartTLS, etc) used by an account's IMAP or SMTP +#. service. +#: src/client/accounts/accounts-editor-row.vala:467 +msgid "Connection security" +msgstr "Sigurece conession" + +#. Translators: Label used when no auth scheme is used +#. by an account's IMAP or SMTP service. +#: src/client/accounts/accounts-editor-row.vala:478 +#: src/client/accounts/accounts-editor-servers-pane.vala:752 +#: src/client/accounts/accounts-editor-servers-pane.vala:964 +#: src/engine/api/geary-special-folder-type.vala:58 +msgid "None" +msgstr "Nissune" + +#: src/client/accounts/accounts-editor-row.vala:485 +msgid "StartTLS" +msgstr "StartTLS" + +#: src/client/accounts/accounts-editor-row.vala:492 +msgid "TLS" +msgstr "TLS" + +#. Translators: Label for source of SMTP authentication +#. credentials (none, use IMAP, custom) when adding a new +#. account +#. Button label for retrying when a login error has occurred +#: src/client/accounts/accounts-editor-row.vala:533 ui/main-window.ui:455 +msgid "Login" +msgstr "Jentre" + +#. Translators: ComboBox value for source of SMTP +#. authentication credentials (none) when adding a new +#. account +#: src/client/accounts/accounts-editor-row.vala:540 +msgid "No login needed" +msgstr "Nol covente nissun acès" + +#. Translators: ComboBox value for source of SMTP +#. authentication credentials (use IMAP) when adding a new +#. account +#: src/client/accounts/accounts-editor-row.vala:548 +msgid "Use same login as receiving" +msgstr "Dopre l'istès acès di ricezion" + +#. Translators: ComboBox value for source of SMTP +#. authentication credentials (custom) when adding a new +#. account +#: src/client/accounts/accounts-editor-row.vala:556 +msgid "Use a different login" +msgstr "Dopre un acès diferent" + +#. Translators: In-app notification label, the +#. string substitution is a more detailed reason. +#: src/client/accounts/accounts-editor-servers-pane.vala:377 +#, c-format +msgid "Account not updated: %s" +msgstr "Account no inzornât: %s" + +#. Translators: This label describes the program that +#. created the account, e.g. an SSO service like GOA, or +#. locally by Geary. +#: src/client/accounts/accounts-editor-servers-pane.vala:540 +msgid "Account source" +msgstr "Sorzint account" + +#: src/client/accounts/accounts-editor-servers-pane.vala:552 +msgid "GNOME Online Accounts" +msgstr "Account Online di GNOME" + +#. Translators: This label describes an account +#. preference. +#: src/client/accounts/accounts-editor-servers-pane.vala:611 +msgid "Save drafts on server" +msgstr "Salve stampons sul servidôr" + +#. Translators: This label describes an account +#. preference. +#: src/client/accounts/accounts-editor-servers-pane.vala:666 +msgid "Save sent email on server" +msgstr "Salve pueste inviade sul servidôr" + +#. Add a suffix for OAuth2 auth so people know they +#. shouldn't expect to be prompted for a password +#. Translators: Label used when an account's IMAP or +#. SMTP service uses OAuth2. The string replacement is +#. the service's login name. +#: src/client/accounts/accounts-editor-servers-pane.vala:950 +#, c-format +msgid "%s using OAuth2" +msgstr "" + +#: src/client/accounts/accounts-editor-servers-pane.vala:960 +msgid "Use receiving server login" +msgstr "" + +#: src/client/application/geary-application.vala:24 +msgid "Copyright 2016 Software Freedom Conservancy Inc." +msgstr "Copyright 2016 Software Freedom Conservancy Inc." + +#: src/client/application/geary-application.vala:25 +msgid "Copyright 2016-2019 Geary Development Team." +msgstr "Copyright 2016-2019 Geary Development Team." + +#: src/client/application/geary-application.vala:27 +msgid "Visit the Geary web site" +msgstr "Visite il sît web di Geary" + +#: src/client/application/geary-application.vala:454 +#, c-format +msgid "About %s" +msgstr "Informazions su %s" + +#. Translators: add your name and email address to receive +#. credit in the About dialog For example: Yamada Taro +#. +#: src/client/application/geary-application.vala:458 +msgid "translator-credits" +msgstr "Fabio Tomat " + +#: src/client/application/geary-args.vala:10 +msgid "Start Geary with hidden main window" +msgstr "" + +#: src/client/application/geary-args.vala:11 +msgid "Output debugging information" +msgstr "" + +#: src/client/application/geary-args.vala:12 +msgid "Log conversation monitoring" +msgstr "" + +#: src/client/application/geary-args.vala:13 +msgid "Log network deserialization" +msgstr "" + +#: src/client/application/geary-args.vala:14 +msgid "Log network activity" +msgstr "" + +#. / The IMAP replay queue is how changes on the server are replicated on the client. +#. / It could also be called the IMAP events queue. +#: src/client/application/geary-args.vala:17 +msgid "Log IMAP replay queue" +msgstr "" + +#. / Serialization is how commands and responses are converted into a stream of bytes for +#. / network transmission +#: src/client/application/geary-args.vala:20 +msgid "Log network serialization" +msgstr "" + +#: src/client/application/geary-args.vala:21 +msgid "Log periodic activity" +msgstr "" + +#: src/client/application/geary-args.vala:22 +msgid "Log database queries (generates lots of messages)" +msgstr "" + +#. / "Normalization" can also be called "synchronization" +#: src/client/application/geary-args.vala:24 +msgid "Log folder normalization" +msgstr "" + +#: src/client/application/geary-args.vala:25 +msgid "Allow inspection of WebView" +msgstr "" + +#: src/client/application/geary-args.vala:26 +msgid "Revoke all server certificates with TLS warnings" +msgstr "" + +#: src/client/application/geary-args.vala:27 +msgid "Perform a graceful quit" +msgstr "" + +#: src/client/application/geary-args.vala:28 +msgid "Display program version" +msgstr "" + +#. This gives a command-line hint on how to open new composer windows with mailto: +#: src/client/application/geary-args.vala:53 +#, c-format +msgid "Use %s to open a new composer window" +msgstr "" + +#: src/client/application/geary-args.vala:56 +msgid "Please report comments, suggestions and bugs to:" +msgstr "" + +#. i18n: Command line arguments are invalid +#: src/client/application/geary-args.vala:63 +#, c-format +msgid "Failed to parse command line options: %s\n" +msgstr "" + +#: src/client/application/geary-args.vala:74 +#, c-format +msgid "Unrecognized command line option “%s”\n" +msgstr "" + +#. Translators: File name used in save chooser when saving +#. attachments that do not otherwise have a name. +#: src/client/application/geary-controller.vala:58 +msgid "Untitled" +msgstr "Cence titul" + +#: src/client/application/geary-controller.vala:937 +msgid "Labels" +msgstr "Etichetis" + +#. give the user two options: reset the Account local store, or exit Geary. A third +#. could be done to leave the Account in an unopened state, but we don't currently +#. have provisions for that. +#: src/client/application/geary-controller.vala:950 +#, c-format +msgid "Unable to open the database for %s" +msgstr "" + +#: src/client/application/geary-controller.vala:951 +#, c-format +msgid "" +"There was an error opening the local mail database for this account. This is " +"possibly due to corruption of the database file in this directory:\n" +"\n" +"%s\n" +"\n" +"Geary can rebuild the database and re-synchronize with the server or exit.\n" +"\n" +"Rebuilding the database will destroy all local email and its attachments. " +"The mail on the your server will not be affected." +msgstr "" + +#: src/client/application/geary-controller.vala:953 +msgid "_Rebuild" +msgstr "" + +#: src/client/application/geary-controller.vala:953 +msgid "E_xit" +msgstr "" + +#: src/client/application/geary-controller.vala:962 +#, c-format +msgid "Unable to rebuild database for “%s”" +msgstr "" + +#: src/client/application/geary-controller.vala:963 +#, c-format +msgid "" +"Error during rebuild:\n" +"\n" +"%s" +msgstr "" + +#: src/client/application/geary-controller.vala:1818 +msgid "Undo move (Ctrl+Z)" +msgstr "" + +#: src/client/application/geary-controller.vala:1828 +msgid "Are you sure you want to open these attachments?" +msgstr "" + +#: src/client/application/geary-controller.vala:1829 +msgid "" +"Attachments may cause damage to your system if opened. Only open files from " +"trusted sources." +msgstr "" + +#: src/client/application/geary-controller.vala:1830 +msgid "Don’t _ask me again" +msgstr "_No sta domandâmi plui" + +#. Translators: Dialog primary label when prompting to +#. overwrite a file. The string substitution is the file'sx +#. name. +#: src/client/application/geary-controller.vala:1959 +#, c-format +msgid "A file named “%s” already exists. Do you want to replace it?" +msgstr "Un file clamât “%s” al esist za. Sostituîlu?" + +#. Translators: Dialog secondary label when prompting to +#. overwrite a file. The string substitution is the parent +#. folder's name. +#: src/client/application/geary-controller.vala:1966 +#, c-format +msgid "" +"The file already exists in “%s”. Replacing it will overwrite its contents." +msgstr "" +"Il file al esist za in “%s”. Se al ven sostituît, il sô contignût al " +"vignarà sorescrit." + +#: src/client/application/geary-controller.vala:1970 +msgid "_Replace" +msgstr "_Sostituìs" + +#: src/client/application/geary-controller.vala:2246 +msgid "Close the draft message?" +msgid_plural "Close all draft messages?" +msgstr[0] "" +msgstr[1] "" + +#: src/client/application/geary-controller.vala:2372 +#, c-format +msgid "Empty all email from your %s folder?" +msgstr "" + +#: src/client/application/geary-controller.vala:2373 +msgid "This removes the email from Geary and your email server." +msgstr "" + +#: src/client/application/geary-controller.vala:2374 +msgid "This cannot be undone." +msgstr "" + +#: src/client/application/geary-controller.vala:2375 +#, c-format +msgid "Empty %s" +msgstr "" + +#: src/client/application/geary-controller.vala:2392 +#, c-format +msgid "Error emptying %s" +msgstr "" + +#: src/client/application/geary-controller.vala:2424 +msgid "Do you want to permanently delete this message?" +msgid_plural "Do you want to permanently delete these messages?" +msgstr[0] "" +msgstr[1] "" + +#: src/client/application/geary-controller.vala:2426 +msgid "Delete" +msgstr "" + +#: src/client/application/geary-controller.vala:2440 +msgid "Undo trash (Ctrl+Z)" +msgstr "" + +#: src/client/application/geary-controller.vala:2490 +msgid "Undo archive (Ctrl+Z)" +msgstr "" + +#: src/client/application/geary-controller.vala:2535 +msgid "Undo (Ctrl+Z)" +msgstr "" + +#. Translators: The label for an in-app notification. The +#. string substitution is a list of recipients of the email. +#: src/client/application/geary-controller.vala:2616 +#, c-format +msgid "Successfully sent mail to %s." +msgstr "" + +#: src/client/application/geary-controller.vala:2698 +msgid "Failed to open default text editor." +msgstr "" + +#. Translators: Tooltip used when an entry requires a valid +#. email address to be entered, but one is not provided. +#: src/client/components/components-validator.vala:378 +msgid "An email address is required" +msgstr "" + +#. Translators: Tooltip used when an entry requires a valid +#. email address to be entered, but the address is invalid. +#: src/client/components/components-validator.vala:382 +msgid "Not a valid email address" +msgstr "" + +#. Translators: Tooltip used when an entry requires a valid, +#. resolvable server name to be entered, but one is not +#. provided. +#: src/client/components/components-validator.vala:428 +msgid "A server name is required" +msgstr "" + +#. Translators: Tooltip used when an entry requires a valid +#. server name to be entered, but it was unable to be +#. looked-up in the DNS. +#: src/client/components/components-validator.vala:433 +msgid "Could not look up server name" +msgstr "" + +#: src/client/components/main-toolbar.vala:151 +msgid "Mark conversation" +msgid_plural "Mark conversations" +msgstr[0] "" +msgstr[1] "" + +#: src/client/components/main-toolbar.vala:156 +msgid "Add label to conversation" +msgid_plural "Add label to conversations" +msgstr[0] "" +msgstr[1] "" + +#: src/client/components/main-toolbar.vala:161 +msgid "Move conversation" +msgid_plural "Move conversations" +msgstr[0] "" +msgstr[1] "" + +#: src/client/components/main-toolbar.vala:166 +msgid "Archive conversation (A)" +msgid_plural "Archive conversations (A)" +msgstr[0] "" +msgstr[1] "" + +#: src/client/components/main-toolbar.vala:175 +msgid "Move conversation to Trash (Delete, Backspace)" +msgid_plural "Move conversations to Trash (Delete, Backspace)" +msgstr[0] "" +msgstr[1] "" + +#: src/client/components/main-toolbar.vala:183 +msgid "Delete conversation (Shift+Delete)" +msgid_plural "Delete conversations (Shift+Delete)" +msgstr[0] "" +msgstr[1] "" + +#: src/client/components/main-window.vala:503 +#, c-format +msgid "%s (%d)" +msgstr "" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:47 +#, c-format +msgid "Problem connecting to incoming server for %s" +msgstr "" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:49 +#: src/client/components/main-window-info-bar.vala:58 +#, c-format +msgid "" +"Could not connect to %s, check your Internet access and the server name and " +"try again" +msgstr "" + +#. Translators: Tooltip label for Retry button +#. Button tooltip for retrying an account problem +#: src/client/components/main-window-info-bar.vala:51 +#: src/client/components/main-window-info-bar.vala:60 +#: src/client/components/main-window-info-bar.vala:69 +#: src/client/components/main-window-info-bar.vala:78 +#: src/client/components/main-window-info-bar.vala:87 +#: src/client/components/main-window-info-bar.vala:96 +#: src/client/components/main-window-info-bar.vala:136 ui/main-window.ui:265 +msgid "Try reconnecting" +msgstr "" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:56 +#, c-format +msgid "Problem connecting to outgoing server for %s" +msgstr "" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:65 +#: src/client/components/main-window-info-bar.vala:83 +#, c-format +msgid "Problem communicating with incoming server for %s" +msgstr "" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:67 +#: src/client/components/main-window-info-bar.vala:76 +#, c-format +msgid "Network error talking to %s, check your Internet access and try again" +msgstr "" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:74 +#: src/client/components/main-window-info-bar.vala:91 +msgid "Problem communicating with outgoing mail server" +msgstr "" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:85 +#, c-format +msgid "" +"Geary did not understand a message from %s or vice versa, please file a bug " +"report" +msgstr "" + +#. Translators: First string substitution is the server +#. name, second is the account name +#: src/client/components/main-window-info-bar.vala:94 +#, c-format +msgid "" +"Could not communicate with %s for %s, check the server name and try again in " +"a moment" +msgstr "" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:101 +#, c-format +msgid "Incoming mail server password required for %s" +msgstr "" + +#: src/client/components/main-window-info-bar.vala:102 +msgid "Messages cannot be received without the correct password." +msgstr "" + +#. Translators: Tooltip label for Retry button +#: src/client/components/main-window-info-bar.vala:104 +msgid "Retry receiving email, you will be prompted for a password" +msgstr "" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:109 +#, c-format +msgid "Outgoing mail server password required for %s" +msgstr "" + +#: src/client/components/main-window-info-bar.vala:110 +msgid "Messages cannot be sent without the correct password." +msgstr "" + +#. Translators: Tooltip label for Retry button +#: src/client/components/main-window-info-bar.vala:112 +msgid "Retry sending queued messages, you will be prompted for a password" +msgstr "" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:117 +#, c-format +msgid "Incoming mail server security is not trusted for %s" +msgstr "" + +#: src/client/components/main-window-info-bar.vala:118 +msgid "Messages will not be received until checked." +msgstr "" + +#. Translators: Tooltip label for Retry button +#: src/client/components/main-window-info-bar.vala:120 +#: src/client/components/main-window-info-bar.vala:128 +msgid "Check security details" +msgstr "" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:125 +#, c-format +msgid "Outgoing mail server security is not trusted for %s" +msgstr "" + +#: src/client/components/main-window-info-bar.vala:126 +msgid "Messages cannot be sent until checked." +msgstr "" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:133 +#, c-format +msgid "A problem occurred checking mail for %s" +msgstr "" + +#: src/client/components/main-window-info-bar.vala:134 +#: src/client/components/main-window-info-bar.vala:142 +msgid "Something went wrong, please file a bug report if the problem persists" +msgstr "" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:141 +#, c-format +msgid "A problem occurred sending mail for %s" +msgstr "" + +#. Translators: Tooltip label for Retry button +#: src/client/components/main-window-info-bar.vala:144 +msgid "Retry sending queued messages" +msgstr "" + +#: src/client/components/main-window-info-bar.vala:155 +msgid "A database problem has occurred" +msgstr "" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:157 +#, c-format +msgid "Messages for %s must be downloaded again." +msgstr "" + +#: src/client/components/main-window-info-bar.vala:170 +msgid "Geary has encountered a problem" +msgstr "" + +#: src/client/components/main-window-info-bar.vala:171 +msgid "" +"Please check the technical details and report the problem if it persists." +msgstr "" + +#: src/client/components/main-window-info-bar.vala:179 +msgid "_Details" +msgstr "" + +#. Button tooltip for displaying technical details about an account problem +#: src/client/components/main-window-info-bar.vala:180 ui/main-window.ui:250 +msgid "View technical details about the error" +msgstr "" + +#: src/client/components/main-window-info-bar.vala:184 +msgid "_Retry" +msgstr "" + +#: src/client/components/search-bar.vala:8 +#: src/client/folder-list/folder-list-search-branch.vala:38 +#: src/engine/api/geary-special-folder-type.vala:51 +msgid "Search" +msgstr "" + +#. Search entry. +#: src/client/components/search-bar.vala:23 +msgid "Search all mail in account for keywords (Ctrl+S)" +msgstr "" + +#: src/client/components/search-bar.vala:101 +#, c-format +msgid "Indexing %s account" +msgstr "" + +#: src/client/components/search-bar.vala:112 +#: src/client/folder-list/folder-list-search-branch.vala:39 +#, c-format +msgid "Search %s account" +msgstr "" + +#. / Displayed in the space-limited status bar while a message is in the process of being sent. +#: src/client/components/status-bar.vala:26 +msgid "Sending…" +msgstr "" + +#. / Displayed in the space-limited status bar when a message fails to be sent due to error. +#: src/client/components/status-bar.vala:29 +msgid "Error sending email" +msgstr "" + +#. Displayed in the space-limited status bar when a message fails to be uploaded +#. to Sent Mail after being sent. +#: src/client/components/status-bar.vala:33 +msgid "Error saving sent mail" +msgstr "" + +#: src/client/components/stock.vala:18 +msgid "_OK" +msgstr "" + +#: src/client/components/stock.vala:19 ui/password-dialog.glade:196 +msgid "_Cancel" +msgstr "" + +#: src/client/components/stock.vala:21 +msgid "_About" +msgstr "" + +#: src/client/components/stock.vala:22 +msgid "_Add" +msgstr "" + +#: src/client/components/stock.vala:23 +msgid "_Close" +msgstr "" + +#: src/client/components/stock.vala:24 +msgid "_Discard" +msgstr "" + +#: src/client/components/stock.vala:25 ui/main-toolbar-menus.ui:56 +msgid "_Help" +msgstr "" + +#: src/client/components/stock.vala:26 ui/conversation-email-menus.ui:77 +msgid "_Open" +msgstr "" + +#: src/client/components/stock.vala:27 ui/main-toolbar-menus.ui:46 +msgid "_Preferences" +msgstr "" + +#. Translators: Menu item to print a single, specific message +#: src/client/components/stock.vala:28 ui/conversation-email-menus.ui:64 +msgid "_Print…" +msgstr "" + +#: src/client/components/stock.vala:29 +msgid "_Quit" +msgstr "" + +#: src/client/components/stock.vala:30 +msgid "_Remove" +msgstr "" + +#: src/client/components/stock.vala:31 ui/conversation-email-menus.ui:83 +msgid "_Save" +msgstr "" + +#: src/client/components/stock.vala:32 +msgid "_Keep" +msgstr "" + +#: src/client/composer/composer-link-popover.vala:149 +msgid "Link URL is not correctly formatted, e.g. http://example.com" +msgstr "" + +#: src/client/composer/composer-link-popover.vala:156 +msgid "Invalid link URL" +msgstr "" + +#: src/client/composer/composer-link-popover.vala:156 +msgid "Invalid email address" +msgstr "" + +#: src/client/composer/composer-widget.vala:158 +msgid "Saved" +msgstr "" + +#: src/client/composer/composer-widget.vala:159 +msgid "Saving" +msgstr "" + +#: src/client/composer/composer-widget.vala:160 +msgid "Error saving" +msgstr "" + +#: src/client/composer/composer-widget.vala:161 +msgid "Press Backspace to delete quote" +msgstr "" + +#. Translators: This is list of keywords, separated by pipe ("|") +#. characters, that suggest an attachment; since this is full-word +#. checking, include all variants of each word. No spaces are +#. allowed. +#: src/client/composer/composer-widget.vala:170 +msgid "" +"attach|attaching|attaches|attachment|attachments|attached|enclose|enclosed|" +"enclosing|encloses|enclosure|enclosures" +msgstr "" + +#. Translators: This dialog text is displayed to the +#. user when closing a composer where the options are +#. Keep, Discard or Cancel. +#: src/client/composer/composer-widget.vala:1131 +msgid "Do you want to keep or discard this draft message?" +msgstr "" + +#. Translators: This dialog text is displayed to the +#. user when closing a composer where the options are +#. only Discard or Cancel. +#: src/client/composer/composer-widget.vala:1159 +msgid "Do you want to discard this draft message?" +msgstr "" + +#: src/client/composer/composer-widget.vala:1276 +msgid "Send message with an empty subject and body?" +msgstr "" + +#: src/client/composer/composer-widget.vala:1278 +msgid "Send message with an empty subject?" +msgstr "" + +#: src/client/composer/composer-widget.vala:1280 +msgid "Send message with an empty body?" +msgstr "" + +#: src/client/composer/composer-widget.vala:1284 +msgid "Send message without an attachment?" +msgstr "" + +#: src/client/composer/composer-widget.vala:1589 +#, c-format +msgid "“%s” already attached for delivery." +msgstr "" + +#. / In the composer, the filename followed by its filesize, i.e. "notes.txt (1.12KB)" +#. Translators: The first argument will be a +#. description of the document type, the second will +#. be a human-friendly size string. For example: +#. Document (100.9MB) +#: src/client/composer/composer-widget.vala:1597 +#: src/client/conversation-viewer/conversation-email.vala:173 +#, c-format +msgid "%s (%s)" +msgstr "" + +#: src/client/composer/composer-widget.vala:1634 +#, c-format +msgid "“%s” could not be found." +msgstr "" + +#: src/client/composer/composer-widget.vala:1640 +#, c-format +msgid "“%s” is a folder." +msgstr "" + +#: src/client/composer/composer-widget.vala:1646 +#, c-format +msgid "“%s” is an empty file." +msgstr "" + +#: src/client/composer/composer-widget.vala:1659 +#, c-format +msgid "“%s” could not be opened for reading." +msgstr "" + +#: src/client/composer/composer-widget.vala:1667 +msgid "Cannot add attachment" +msgstr "" + +#. Translators: Human-readable version of the RFC 822 To header +#: src/client/composer/composer-widget.vala:1717 +#: src/client/conversation-viewer/conversation-email.vala:975 +#: src/engine/rfc822/rfc822-utils.vala:298 ui/conversation-message.ui:313 +msgid "To:" +msgstr "" + +#. Translators: Human-readable version of the RFC 822 CC header +#: src/client/composer/composer-widget.vala:1723 +#: src/client/conversation-viewer/conversation-email.vala:980 +#: src/engine/rfc822/rfc822-utils.vala:303 ui/conversation-message.ui:358 +msgid "Cc:" +msgstr "" + +#. Translators: Human-readable version of the RFC 822 BCC header +#: src/client/composer/composer-widget.vala:1729 +#: src/client/conversation-viewer/conversation-email.vala:985 +#: ui/conversation-message.ui:403 +msgid "Bcc:" +msgstr "" + +#. Translators: Human-readable version of the RFC 822 Reply-To header +#: src/client/composer/composer-widget.vala:1735 +msgid "Reply-To: " +msgstr "" + +#: src/client/composer/composer-widget.vala:1875 +msgid "Select Color" +msgstr "" + +#. Displayed in the From dropdown to indicate an +#. "alternate email address" for an account. The first +#. printf argument will be the alternate email address, +#. and the second will be the account's primary email +#. address. +#: src/client/composer/composer-widget.vala:2065 +#, c-format +msgid "%1$s via %2$s" +msgstr "" + +#. Composer label (with mnemonic underscore) for the account selector +#. when choosing what address to send a message from. +#: src/client/composer/composer-widget.vala:2126 +msgid "_From:" +msgstr "" + +#. Translators: This is the name of the file chooser filter +#. when inserting an image in the composer. +#: src/client/composer/composer-widget.vala:2352 +msgid "Images" +msgstr "" + +#: src/client/composer/composer-window.vala:14 +msgid "New Message" +msgstr "" + +#: src/client/composer/spell-check-popover.vala:117 +msgid "Remove this language from the preferred list" +msgstr "" + +#: src/client/composer/spell-check-popover.vala:121 +msgid "Add this language to the preferred list" +msgstr "" + +#: src/client/composer/spell-check-popover.vala:217 +msgid "Search for more languages" +msgstr "" + +#: src/client/conversation-list/conversation-list-view.vala:283 +msgid "Delete conversation" +msgstr "" + +#: src/client/conversation-list/conversation-list-view.vala:286 +#: ui/main-toolbar-menus.ui:5 +msgid "Mark as _Read" +msgstr "" + +#: src/client/conversation-list/conversation-list-view.vala:289 +#: ui/main-toolbar-menus.ui:9 +msgid "Mark as _Unread" +msgstr "" + +#: src/client/conversation-list/conversation-list-view.vala:292 +#: ui/main-toolbar-menus.ui:17 +msgid "U_nstar" +msgstr "" + +#: src/client/conversation-list/conversation-list-view.vala:294 +#: ui/main-toolbar-menus.ui:13 +msgid "_Star" +msgstr "" + +#. Translators: Menu item to reply to a specific message. +#: src/client/conversation-list/conversation-list-view.vala:297 +#: ui/conversation-email-menus.ui:9 +msgid "_Reply" +msgstr "" + +#: src/client/conversation-list/conversation-list-view.vala:298 +msgid "R_eply All" +msgstr "" + +#. Translators: Menu item to forward a specific message. +#: src/client/conversation-list/conversation-list-view.vala:299 +#: ui/conversation-email-menus.ui:21 +msgid "_Forward" +msgstr "" + +#: src/client/conversation-list/formatted-conversation-data.vala:11 +msgid "Me" +msgstr "" + +#. Translators: This is the file type displayed for +#. attachments with unknown file types. +#: src/client/conversation-viewer/conversation-email.vala:159 +msgid "Unknown" +msgstr "" + +#. Translators: Human-readable version of the RFC 822 From header +#: src/client/conversation-viewer/conversation-email.vala:970 +#: src/engine/rfc822/rfc822-utils.vala:289 +msgid "From:" +msgstr "" + +#. Translators: Human-readable version of the RFC 822 Date header +#: src/client/conversation-viewer/conversation-email.vala:990 +#: src/engine/rfc822/rfc822-utils.vala:294 +msgid "Date:" +msgstr "" + +#. Translators: Human-readable version of the RFC 822 Subject header +#: src/client/conversation-viewer/conversation-email.vala:995 +#: src/engine/rfc822/rfc822-utils.vala:292 +msgid "Subject:" +msgstr "" + +#: src/client/conversation-viewer/conversation-message.vala:65 +msgid "This email address may have been forged" +msgstr "" + +#. Compact headers +#. Translators: This is displayed in place of the from address +#. when the message has no from address. +#: src/client/conversation-viewer/conversation-message.vala:394 +msgid "No sender" +msgstr "" + +#. Translators: This separates multiple 'from' +#. addresses in the compact header for a message. +#: src/client/conversation-viewer/conversation-message.vala:767 +msgid ", " +msgstr "" + +#. Translators: This string is used as the HTML IMG ALT +#. attribute value when displaying an inline image in an email +#. that did not specify a file name. E.g. Image" +msgstr "" + +#: ui/conversation-message.ui:80 ui/conversation-message.ui:179 +msgid "1/1/1970\t" +msgstr "" + +#: ui/conversation-message.ui:103 +msgid "Preview body text." +msgstr "" + +#: ui/conversation-message.ui:203 +msgid "Sent by:" +msgstr "" + +#: ui/conversation-message.ui:248 +msgid "Reply to:" +msgstr "" + +#: ui/conversation-message.ui:292 +msgid "Subject" +msgstr "" + +#: ui/conversation-message.ui:502 +msgid "Show Images" +msgstr "" + +#: ui/conversation-message.ui:515 +msgid "Always Show From Sender" +msgstr "" + +#: ui/conversation-message.ui:543 +msgid "Remote images not shown" +msgstr "" + +#: ui/conversation-message.ui:560 +msgid "Only show remote images from senders you trust." +msgstr "" + +#: ui/conversation-message.ui:693 +msgid "But actually goes to:" +msgstr "" + +#: ui/conversation-message.ui:724 +msgid "The link appears to go to:" +msgstr "" + +#: ui/conversation-message.ui:736 +msgid "Deceptive link found" +msgstr "" + +#: ui/conversation-message.ui:751 +msgid "The email sender may be leading you to the wrong web site." +msgstr "" + +#: ui/conversation-message.ui:764 +msgid "If unsure, contact the sender and ask before continuing." +msgstr "" + +#: ui/conversation-viewer.ui:60 +msgid "Find in conversation" +msgstr "" + +#: ui/conversation-viewer.ui:74 +msgid "Find the previous occurrence of the search string." +msgstr "" + +#: ui/conversation-viewer.ui:95 +msgid "Find the next occurrence of the search string." +msgstr "" + +#: ui/find_bar.glade:66 +msgid "Find:" +msgstr "" + +#: ui/find_bar.glade:89 +msgid "_Previous" +msgstr "" + +#: ui/find_bar.glade:107 +msgid "_Next" +msgstr "" + +#: ui/find_bar.glade:125 +msgid "_Case sensitive" +msgstr "" + +#: ui/find_bar.glade:145 +msgid "label" +msgstr "" + +#: ui/gtk/help-overlay.ui:9 +msgid "Conversation Shortcuts" +msgstr "" + +#: ui/gtk/help-overlay.ui:13 ui/gtk/help-overlay.ui:254 +msgctxt "shortcut window" +msgid "General" +msgstr "" + +#: ui/gtk/help-overlay.ui:17 +msgctxt "shortcut window" +msgid "Move focus to the next/previous pane" +msgstr "" + +#: ui/gtk/help-overlay.ui:24 +msgctxt "shortcut window" +msgid "Move focus to conversation list" +msgstr "" + +#: ui/gtk/help-overlay.ui:31 +msgctxt "shortcut window" +msgid "Detach composer window" +msgstr "" + +#: ui/gtk/help-overlay.ui:38 +msgctxt "shortcut window" +msgid "Close composer window" +msgstr "" + +#: ui/gtk/help-overlay.ui:45 +msgctxt "shortcut window" +msgid "Show keyboard shortcuts" +msgstr "" + +#: ui/gtk/help-overlay.ui:52 +msgctxt "shortcut window" +msgid "Show help" +msgstr "" + +#: ui/gtk/help-overlay.ui:59 +msgctxt "shortcut window" +msgid "Quit the application" +msgstr "" + +#: ui/gtk/help-overlay.ui:68 +msgctxt "shortcut window" +msgid "Search" +msgstr "" + +#: ui/gtk/help-overlay.ui:72 +msgctxt "shortcut window" +msgid "Jump to search box" +msgstr "" + +#: ui/gtk/help-overlay.ui:79 +msgctxt "shortcut window" +msgid "Find in current conversation" +msgstr "" + +#: ui/gtk/help-overlay.ui:86 +msgctxt "shortcut window" +msgid "Find next/previous in current conversation" +msgstr "" + +#: ui/gtk/help-overlay.ui:95 ui/gtk/help-overlay.ui:274 +msgctxt "shortcut window" +msgid "Actions" +msgstr "" + +#: ui/gtk/help-overlay.ui:99 +msgctxt "shortcut window" +msgid "Compose a new message" +msgstr "" + +#: ui/gtk/help-overlay.ui:106 +msgctxt "shortcut window" +msgid "Reply to sender " +msgstr "" + +#: ui/gtk/help-overlay.ui:113 +msgctxt "shortcut window" +msgid "Reply to all" +msgstr "" + +#: ui/gtk/help-overlay.ui:120 +msgctxt "shortcut window" +msgid "Forward" +msgstr "" + +#: ui/gtk/help-overlay.ui:127 +msgctxt "shortcut window" +msgid "Archive" +msgstr "" + +#: ui/gtk/help-overlay.ui:134 +msgctxt "shortcut window" +msgid "Move to trash" +msgstr "" + +#: ui/gtk/help-overlay.ui:141 +msgctxt "shortcut window" +msgid "Toggle spam" +msgstr "" + +#: ui/gtk/help-overlay.ui:148 +msgctxt "shortcut window" +msgid "Move the conversation" +msgstr "" + +#: ui/gtk/help-overlay.ui:155 +msgctxt "shortcut window" +msgid "Label the conversation" +msgstr "" + +#: ui/gtk/help-overlay.ui:163 +msgctxt "shortcut window" +msgid "Mark read" +msgstr "" + +#: ui/gtk/help-overlay.ui:170 +msgctxt "shortcut window" +msgid "Mark unread" +msgstr "" + +#: ui/gtk/help-overlay.ui:179 +msgctxt "shortcut window" +msgid "View" +msgstr "" + +#: ui/gtk/help-overlay.ui:183 +msgctxt "shortcut window" +msgid "Zoom in" +msgstr "" + +#: ui/gtk/help-overlay.ui:190 +msgctxt "shortcut window" +msgid "Zoom out" +msgstr "" + +#: ui/gtk/help-overlay.ui:197 +msgctxt "shortcut window" +msgid "Reset zoom" +msgstr "" + +#: ui/gtk/help-overlay.ui:206 +msgctxt "shortcut window" +msgid "Additional Shortcuts" +msgstr "" + +#: ui/gtk/help-overlay.ui:210 +msgctxt "shortcut window" +msgid "Star" +msgstr "" + +#: ui/gtk/help-overlay.ui:217 +msgctxt "shortcut window" +msgid "Unstar" +msgstr "" + +#: ui/gtk/help-overlay.ui:224 +msgctxt "shortcut window" +msgid "Delete" +msgstr "" + +#: ui/gtk/help-overlay.ui:231 +msgctxt "shortcut window" +msgid "Jump to next (older) conversation" +msgstr "" + +#: ui/gtk/help-overlay.ui:238 +msgctxt "shortcut window" +msgid "Jump to previous (newer) conversation" +msgstr "" + +#: ui/gtk/help-overlay.ui:250 +msgid "Composer Shortcuts" +msgstr "" + +#: ui/gtk/help-overlay.ui:258 +msgctxt "shortcut window" +msgid "Quote text" +msgstr "" + +#: ui/gtk/help-overlay.ui:265 +msgctxt "shortcut window" +msgid "Unquote text" +msgstr "" + +#: ui/gtk/help-overlay.ui:278 +msgctxt "shortcut window" +msgid "Send" +msgstr "" + +#: ui/gtk/help-overlay.ui:285 +msgctxt "shortcut window" +msgid "Add attachment" +msgstr "" + +#: ui/gtk/help-overlay.ui:294 +msgctxt "shortcut window" +msgid "Rich text mode" +msgstr "" + +#: ui/gtk/help-overlay.ui:298 +msgctxt "shortcut window" +msgid "Bold text" +msgstr "" + +#: ui/gtk/help-overlay.ui:305 +msgctxt "shortcut window" +msgid "Italicize text" +msgstr "" + +#: ui/gtk/help-overlay.ui:312 +msgctxt "shortcut window" +msgid "Underline text" +msgstr "" + +#: ui/gtk/help-overlay.ui:319 +msgctxt "shortcut window" +msgid "Strike text" +msgstr "" + +#: ui/gtk/help-overlay.ui:326 +msgctxt "shortcut window" +msgid "Insert a link" +msgstr "" + +#: ui/gtk/help-overlay.ui:333 +msgctxt "shortcut window" +msgid "Remove formatting" +msgstr "" + +#: ui/main-toolbar.ui:23 +msgctxt "tooltip" +msgid "Compose Message" +msgstr "" + +#: ui/main-toolbar.ui:52 +msgid "Toggle search bar" +msgstr "" + +#: ui/main-toolbar.ui:112 +msgid "Reply" +msgstr "" + +#: ui/main-toolbar.ui:135 +msgid "Reply All" +msgstr "" + +#: ui/main-toolbar.ui:158 +msgid "Forward" +msgstr "" + +#: ui/main-toolbar.ui:264 +msgid "Toggle find bar" +msgstr "" + +#: ui/main-toolbar.ui:306 +msgid "_Archive" +msgstr "" + +#: ui/main-toolbar-menus.ui:21 +msgid "Mark as S_pam" +msgstr "" + +#: ui/main-toolbar-menus.ui:25 +msgid "Mark as not S_pam" +msgstr "" + +#: ui/main-toolbar-menus.ui:32 +msgid "Empty _Spam…" +msgstr "" + +#: ui/main-toolbar-menus.ui:36 +msgid "Empty _Trash…" +msgstr "" + +#: ui/main-toolbar-menus.ui:42 +msgid "_Accounts" +msgstr "" + +#: ui/main-toolbar-menus.ui:50 +msgid "_Keyboard Shortcuts" +msgstr "" + +#: ui/main-toolbar-menus.ui:61 +msgid "_About Geary" +msgstr "" + +#. Infobar title when one or more accounts are offline +#: ui/main-window.ui:183 +msgid "Working offline" +msgstr "" + +#. Label and tooltip for offline infobar +#: ui/main-window.ui:197 +msgid "" +"Your computer does not appear to be connected to the Internet.\n" +"You will not be able to send or receive email until it is re-connected." +msgstr "" + +#. Label and tooltip for offline infobar +#: ui/main-window.ui:200 +msgid "You will not be able to send or receive email until re-connected." +msgstr "" + +#. Button label for displaying technical details about an account problem +#. Dialog title for displaying technical details of a problem. Same as the button that invokes it. +#: ui/main-window.ui:247 ui/problem-details-dialog.ui:13 +msgid "Details" +msgstr "" + +#. Button label for retrying an account problem +#: ui/main-window.ui:261 +msgid "Retry" +msgstr "" + +#. Infobar title when one or more accounts have encounted an error +#: ui/main-window.ui:294 +msgid "Account problem" +msgstr "" + +#. Label and tooltip for account service problem infobar +#: ui/main-window.ui:308 +msgid "" +"Geary encountered a problem connecting to an account.\n" +"Please check your Internet connection, the server configuration and try " +"again." +msgstr "" + +#. Label and tooltip for account service problem infobar +#: ui/main-window.ui:311 +msgid "Geary encountered a problem connecting to an account." +msgstr "" + +#. Button label for retrying TLS cert validation +#: ui/main-window.ui:358 +msgid "Check" +msgstr "" + +#. Button tooltip for retrying TLS cert validation +#: ui/main-window.ui:362 +msgid "Check the security details for the connection" +msgstr "" + +#. Infobar title when one or more accounts have a TLS cert validation error +#: ui/main-window.ui:391 +msgid "Security problem" +msgstr "" + +#. Label and tooltip for TLS cert validation error infobar +#: ui/main-window.ui:405 +msgid "" +"An account has reported an untrusted server.\n" +"Please check the server configuration and try again." +msgstr "" + +#. Label and tooltip for TLS cert validation error infobar +#: ui/main-window.ui:408 +msgid "An account has reported an untrusted server." +msgstr "" + +#. Button tooltip for retrying when a login error has occurred +#: ui/main-window.ui:459 +msgid "Retry login, you will be prompted for your password" +msgstr "" + +#. Infobar title when one or more accounts have a login error +#: ui/main-window.ui:488 +msgid "Login problem" +msgstr "" + +#. Label and tooltip for authentication problem infobar +#: ui/main-window.ui:502 +msgid "" +"An account has reported an incorrect login or password.\n" +"Please check your login name and try again." +msgstr "" + +#. Label and tooltip for authentication problem infobar +#: ui/main-window.ui:505 +msgid "An account has reported an incorrect login or password." +msgstr "" + +#: ui/password-dialog.glade:74 +msgid "SMTP Credentials" +msgstr "" + +#: ui/password-dialog.glade:91 +msgid "Username" +msgstr "" + +#: ui/password-dialog.glade:152 +msgid "_Remember password" +msgstr "" + +#: ui/password-dialog.glade:210 +msgid "_Authenticate" +msgstr "" + +#: ui/preferences-dialog.ui:38 +msgid "Reading" +msgstr "" + +#: ui/preferences-dialog.ui:51 +msgid "_Automatically select next message" +msgstr "" + +#: ui/preferences-dialog.ui:70 +msgid "_Display conversation preview" +msgstr "" + +#: ui/preferences-dialog.ui:89 +msgid "Use _three pane view" +msgstr "" + +#: ui/preferences-dialog.ui:113 +msgid "Notifications" +msgstr "" + +#: ui/preferences-dialog.ui:126 +msgid "_Play notification sounds" +msgstr "" + +#: ui/preferences-dialog.ui:145 +msgid "Show _notifications for new mail" +msgstr "" + +#: ui/preferences-dialog.ui:164 +msgid "_Watch for new mail when closed" +msgstr "" + +#: ui/preferences-dialog.ui:168 +msgid "Geary will keep running after all windows are closed" +msgstr "" + +#: ui/preferences-dialog.ui:195 +msgid "Preferences" +msgstr "" + +#. Button label for copying technical information to the clipboard +#: ui/problem-details-dialog.ui:17 +msgid "Copy to Clipboard" +msgstr "" + +#. Button tooltip for copying technical information to the clipboard +#: ui/problem-details-dialog.ui:21 +msgid "" +"Copy technical details to clipboard for pasting into an email or bug report" +msgstr "" + +#: ui/problem-details-dialog.ui:73 +msgid "" +"If the problem is serious or persists, please copy and send these details to " +"the mailing list " +"or file a new " +"bug report." +msgstr "" + +#: ui/problem-details-dialog.ui:89 +msgid "Details:" +msgstr "" + +#: ui/upgrade_dialog.glade:60 +msgid "Geary update in progress…" +msgstr "" diff -Nru geary-0.12.4/po/hu.po geary-3.32.0/po/hu.po --- geary-0.12.4/po/hu.po 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/po/hu.po 2019-03-17 13:39:29.000000000 +0000 @@ -1,48 +1,76 @@ -# Hungarian translation for Geary email client. -# Copyright 2012, 2013, 2014, 2015, 2016, 2017 Free Software Foundation, Inc. +# Hungarian translation for geary. +# Copyright (C) 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 Free Software Foundation, Inc. # This file is distributed under the same license as the geary package. # -# lukibeni , 2012, 2013. -# metalsasi , 2012. -# Balázs Úr , 2014, 2015, 2017. +# lukibeni , 2012, 2013. +# metalsasi , 2012. +# Balázs Úr , 2014, 2015, 2017, 2018, 2019. # Gabor Kelemen , 2014. +# Balázs Meskó , 2018. msgid "" msgstr "" "Project-Id-Version: geary-master\n" -"Report-Msgid-Bugs-To: https://bugzilla.gnome.org/enter_bug.cgi?" -"product=geary&keywords=I18N+L10N&component=internationalization\n" -"POT-Creation-Date: 2017-10-02 14:41+0000\n" -"PO-Revision-Date: 2017-10-25 12:27+0200\n" -"Last-Translator: Meskó Balázs \n" -"Language-Team: Hungarian \n" +"Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/geary/issues\n" +"POT-Creation-Date: 2019-03-07 03:56+0000\n" +"PO-Revision-Date: 2019-03-07 23:07+0100\n" +"Last-Translator: Balázs Úr \n" +"Language-Team: Hungarian \n" "Language: hu\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Poedit 2.0.4\n" +"X-Generator: Lokalize 2.0\n" + +#: desktop/geary-attach.contract.desktop.in:3 +msgid "Send by email" +msgstr "Elküldés levélben" + +#. Translators: Do NOT translate or transliterate this text (this is an icon file name)! +#: desktop/geary-attach.contract.desktop.in:5 +msgid "mail-send" +msgstr "mail-send" + +#: desktop/geary-attach.contract.desktop.in:6 +msgid "Send files using Geary" +msgstr "Fájlok küldése a Geary használatával" #. Translators: The application name -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:2 -#: ../desktop/org.gnome.Geary.desktop.in.h:1 -#: ../desktop/geary-autostart.desktop.in.h:1 +#: desktop/geary-autostart.desktop.in:3 +#: desktop/org.gnome.Geary.appdata.xml.in:11 +#: desktop/org.gnome.Geary.desktop.in:3 +#: src/client/accounts/accounts-editor-servers-pane.vala:555 msgid "Geary" msgstr "Geary" -#. Translators: The development team's name -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:4 -msgid "Geary Development Team" -msgstr "Geary fejlesztőcsapat" +#: desktop/geary-autostart.desktop.in:4 desktop/org.gnome.Geary.desktop.in:4 +msgid "Email" +msgstr "E-mail" #. Translators: The application's summary / tagline -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:6 -#: ../desktop/org.gnome.Geary.desktop.in.h:4 -#: ../desktop/geary-autostart.desktop.in.h:4 -#: ../src/client/application/geary-application.vala:21 +#: desktop/geary-autostart.desktop.in:5 +#: desktop/org.gnome.Geary.appdata.xml.in:15 +#: desktop/org.gnome.Geary.desktop.in:5 +#: src/client/application/geary-application.vala:23 msgid "Send and receive email" msgstr "E-mailek küldése és fogadása" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:7 +#. Translators: These are desktop search terms. Do not translate semicolons, end line with a semicolon. +#: desktop/geary-autostart.desktop.in:7 +msgid "Email;E-mail;Mail;" +msgstr "Email;E-mail;Levél;" + +#. Translators: Do NOT translate or transliterate this text (this is an icon file name)! +#: desktop/geary-autostart.desktop.in:9 desktop/org.gnome.Geary.desktop.in:9 +msgid "org.gnome.Geary" +msgstr "org.gnome.Geary" + +#. Translators: The development team's name +#: desktop/org.gnome.Geary.appdata.xml.in:13 +msgid "Geary Development Team" +msgstr "Geary fejlesztőcsapat" + +#: desktop/org.gnome.Geary.appdata.xml.in:17 msgid "" "Geary is an email application built around conversations, for the GNOME 3 " "desktop. It allows you to read, find and send email with a straightforward, " @@ -52,7 +80,7 @@ "GNOME 3 asztalhoz készült. Segítségével e-maileket olvashat, kereshet és " "küldhet egy lényegre törő és modern felületen." -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:8 +#: desktop/org.gnome.Geary.appdata.xml.in:22 msgid "" "Conversations allow you to read a complete discussion without having to find " "and click from message to message." @@ -60,220 +88,698 @@ "A társalgások segítségével teljes beszélgetéseket olvashat el anélkül, hogy " "egyesével végig kellene kattintania a leveleken." -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:9 +#: desktop/org.gnome.Geary.appdata.xml.in:26 msgid "Geary’s features include:" msgstr "A Geary néhány funkciója:" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:10 +#: desktop/org.gnome.Geary.appdata.xml.in:28 msgid "Quick email account setup" msgstr "Gyors e-mailfiók beállítás" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:11 +#: desktop/org.gnome.Geary.appdata.xml.in:29 msgid "Shows related messages together in conversations" msgstr "A kapcsolódó leveleket együtt jeleníti meg a beszélgetésekben" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:12 +#: desktop/org.gnome.Geary.appdata.xml.in:30 msgid "Fast, full text and keyword search" msgstr "Gyors, teljes szöveges és kulcsszavas keresés" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:13 +#: desktop/org.gnome.Geary.appdata.xml.in:31 msgid "Full-featured HTML and plain text message composer" msgstr "Teljes funkcionalitású HTML és egyszerű szöveges levélszerkesztő" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:14 +#: desktop/org.gnome.Geary.appdata.xml.in:32 msgid "Desktop notification of new mail" msgstr "Asztali értesítések új levél érkezésekor" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:15 +#: desktop/org.gnome.Geary.appdata.xml.in:33 msgid "Compatible with GMail, Yahoo! Mail, Outlook.com and other IMAP servers" msgstr "" "Kompatibilis a GMaillel, a Yahoo! Maillel, az Outlook.commal és más IMAP " "kiszolgálókkal" #. Translators: A screenshot description. -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:17 +#: desktop/org.gnome.Geary.appdata.xml.in:47 msgid "Geary displaying a conversation" -msgstr "Egy beszélgetés megjelenítése a Gearyben" +msgstr "A Geary egy beszélgetést jelenít meg" #. Translators: A screenshot description. -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:19 +#: desktop/org.gnome.Geary.appdata.xml.in:52 msgid "Geary showing the rich text composer" msgstr "A Geary gazdag szövegszerkesztője" -#: ../desktop/org.gnome.Geary.desktop.in.h:2 -#: ../desktop/geary-autostart.desktop.in.h:2 -msgid "Email" -msgstr "E-mail" - -#: ../desktop/org.gnome.Geary.desktop.in.h:3 -msgid "Geary Email" -msgstr "Geary levelező" - #. Translators: These are desktop search terms. Do not translate semicolons, end line with a semicolon. -#: ../desktop/org.gnome.Geary.desktop.in.h:6 +#: desktop/org.gnome.Geary.desktop.in:7 msgid "Mail;E-mail;IMAP;GMail;Yahoo;Hotmail;Outlook;" msgstr "Levél;Levelezés;E-mail;IMAP;GMail;Yahoo;Hotmail;Outlook;" -#: ../desktop/org.gnome.Geary.desktop.in.h:7 ../ui/gtk/menus.ui.h:1 +#: desktop/org.gnome.Geary.desktop.in:21 msgid "Compose Message" msgstr "Levél írása" -#: ../desktop/geary-autostart.desktop.in.h:3 -msgid "Geary Mail" -msgstr "Geary levelező" - -#: ../desktop/geary-autostart.desktop.in.h:5 -msgid "Email;E-mail;Mail;" -msgstr "Email;E-mail;Levél;" - -#: ../desktop/geary-attach.contract.in.h:1 -msgid "Send by email" -msgstr "Elküldés levélben" +#: desktop/org.gnome.Geary.gschema.xml:8 +msgid "Maximize window" +msgstr "Ablak maximalizálása" + +#: desktop/org.gnome.Geary.gschema.xml:9 +msgid "True if the application window is maximized, false otherwise." +msgstr "Igaz, ha az alkalmazásablak teljes méretű, különben hamis." + +#: desktop/org.gnome.Geary.gschema.xml:14 +msgid "Width of window" +msgstr "Ablak szélessége" + +#: desktop/org.gnome.Geary.gschema.xml:15 +msgid "The last recorded width of the application window." +msgstr "Az alkalmazásablak legutóbb mentett szélessége." + +#: desktop/org.gnome.Geary.gschema.xml:20 +msgid "Height of window" +msgstr "Ablak magassága" + +#: desktop/org.gnome.Geary.gschema.xml:21 +msgid "The last recorded height of the application window." +msgstr "Az alkalmazásablak legutóbb mentett magassága." + +#: desktop/org.gnome.Geary.gschema.xml:26 +msgid "Position of folder list pane" +msgstr "A mappalista ablaktábla pozíciója" + +#: desktop/org.gnome.Geary.gschema.xml:27 +msgid "Position of the folder list Paned grabber." +msgstr "A mappalista ablaktábla fogantyújának helyzete." + +#: desktop/org.gnome.Geary.gschema.xml:32 +msgid "Position of folder list pane when horizontal" +msgstr "A mappalista ablaktábla pozíciója, ha vízszintes" + +#: desktop/org.gnome.Geary.gschema.xml:33 +msgid "" +"Position of the folder list Paned grabber in the horizontal orientation." +msgstr "A mappalista ablaktábla fogantyújának helyzete vízszintes tájolásban." + +#: desktop/org.gnome.Geary.gschema.xml:38 +msgid "Position of folder list pane when vertical" +msgstr "A mappalista ablaktábla pozíciója, ha függőleges" + +#: desktop/org.gnome.Geary.gschema.xml:39 +msgid "Position of the folder list Paned grabber in the vertical orientation." +msgstr "A mappalista ablaktábla fogantyújának helyzete függőleges tájolásban." + +#: desktop/org.gnome.Geary.gschema.xml:44 +msgid "Orientation of the folder list pane" +msgstr "A mappalista ablaktábla tájolása" + +#: desktop/org.gnome.Geary.gschema.xml:45 +msgid "True if the folder list Paned is in the horizontal orientation." +msgstr "Igaz, ha a mappalista ablaktábla vízszintesen tájolt." + +#: desktop/org.gnome.Geary.gschema.xml:50 +msgid "Position of message list pane" +msgstr "A levéllista ablaktábla pozíciója" + +#: desktop/org.gnome.Geary.gschema.xml:51 +msgid "Position of the message list Paned grabber." +msgstr "A levéllista ablaktábla fogantyújának helyzete." + +#: desktop/org.gnome.Geary.gschema.xml:56 +msgid "Autoselect next message" +msgstr "Következő levél automatikus kiválasztása" + +#: desktop/org.gnome.Geary.gschema.xml:57 +msgid "True if we should autoselect the next available conversation." +msgstr "" +"Igaz, ha a következő elérhető beszélgetést automatikusan ki kell választani." + +#: desktop/org.gnome.Geary.gschema.xml:62 +msgid "Display message previews" +msgstr "Beszélgetés előnézetek megjelenítése" + +#: desktop/org.gnome.Geary.gschema.xml:63 +msgid "True if we should display a short preview of each message." +msgstr "Igaz, ha minden levélről jelenjen meg egy rövid előnézet." + +#: desktop/org.gnome.Geary.gschema.xml:68 +msgid "Languages that shall be used in the spell checker" +msgstr "A nyelvek, amelyek használva lesznek a helyesírás-ellenőrzőben" + +#: desktop/org.gnome.Geary.gschema.xml:69 +msgid "List of the languages to use in the spell checker." +msgstr "A helyesírás-ellenőrzőben használandó nyelvek listája." + +#: desktop/org.gnome.Geary.gschema.xml:74 +msgid "Languages that are displayed in the spell checker popover" +msgstr "A nyelvek, melyek megjelennek a helyesírás-ellenőrző felugróban" + +#: desktop/org.gnome.Geary.gschema.xml:75 +msgid "" +"List of languages that are always displayed in the popover of the spell " +"checker." +msgstr "" +"A nyelvek listája, amely mindig megjelenik a helyesírás-ellenőrző " +"felugrójában." + +#: desktop/org.gnome.Geary.gschema.xml:80 +msgid "Enable notification sounds" +msgstr "Értesítő hangok engedélyezése" + +#: desktop/org.gnome.Geary.gschema.xml:81 +msgid "True to play sounds for notifications and sending." +msgstr "Igaz, ha játsszon le értesítési és levélküldési hangokat." + +#: desktop/org.gnome.Geary.gschema.xml:86 +msgid "Show notifications for new mail" +msgstr "Értesítések megjelenítése új levél érkezésekor" + +#: desktop/org.gnome.Geary.gschema.xml:87 +msgid "True to show notification bubbles." +msgstr "Igaz, ha jelenítsen meg értesítési buborékokat." + +#: desktop/org.gnome.Geary.gschema.xml:92 +msgid "Notify of new mail at startup" +msgstr "Értesítés az új levelekről indításkor" + +#: desktop/org.gnome.Geary.gschema.xml:93 +msgid "True to notify of new mail at startup." +msgstr "Igaz, ha értesítsen az új levekről indításkor." + +#: desktop/org.gnome.Geary.gschema.xml:98 +msgid "Ask when opening an attachment" +msgstr "Kérdezzen egy melléklet megnyitásakor" + +#: desktop/org.gnome.Geary.gschema.xml:99 +msgid "True to ask when opening an attachment." +msgstr "Igaz, ha kérdezzen egy melléklet megnyitásakor." + +#: desktop/org.gnome.Geary.gschema.xml:104 +msgid "Whether to compose emails in HTML" +msgstr "Az e-mailek írása HTML-ben történjen-e" + +#: desktop/org.gnome.Geary.gschema.xml:105 +msgid "True to compose emails in HTML; false for plain text." +msgstr "" +"Igaz, ha a levelek HTML-ben legyenek megírva; hamis esetén egyszerű szöveg." + +#: desktop/org.gnome.Geary.gschema.xml:110 +msgid "Advisory strategy for full-text searching" +msgstr "Tanácsadó stratégia a teljes szöveges keresésnél" + +#: desktop/org.gnome.Geary.gschema.xml:111 +msgid "" +"Acceptable values are “exact”, “conservative”, “aggressive”, and “horizon”." +msgstr "" +"Az elfogadható értékek: „exact” (pontos), „conservative” (konzervatív), " +"„aggressive” (agresszív) és „horizon” (horizont)." + +#: desktop/org.gnome.Geary.gschema.xml:116 +msgid "Zoom of conversation viewer" +msgstr "A beszélgetésmegjelenítő nagyítása" + +#: desktop/org.gnome.Geary.gschema.xml:117 +msgid "The zoom to apply on the conservation view." +msgstr "A beszélgetés nézeten alkalmazandó nagyítás." + +#: desktop/org.gnome.Geary.gschema.xml:122 +msgid "Size of detached composer window" +msgstr "A leválasztott levélíró ablak mérete" + +#: desktop/org.gnome.Geary.gschema.xml:123 +msgid "The last recorded size of the detached composer window." +msgstr "A leválasztott levélíró ablak legutóbb elmentett mérete." + +#: desktop/org.gnome.Geary.gschema.xml:128 +msgid "Base URL to look up contact avatars" +msgstr "Alap URL a kapcsolati avatárok kereséséhez" + +#: desktop/org.gnome.Geary.gschema.xml:129 +msgid "" +"A Gravatar or Libravatar compatible URL, set to the empty string to disable." +msgstr "" +"Egy Gravatar vagy Libravatar kompatibilis URL. Állítsa üres karakterláncra a " +"letiltáshoz." + +#: desktop/org.gnome.Geary.gschema.xml:134 +msgid "Whether we migrated the old settings" +msgstr "A régi beállítások migrálva lettek-e" + +#: desktop/org.gnome.Geary.gschema.xml:135 +msgid "" +"False to check for the old “org.yorba.geary”-schema and copy its values." +msgstr "" +"Hamis a régi „org.yorba.geary” séma ellenőrzéséhez, és az értékeinek " +"másolásához." + +#. Translators: In-app notification label, when +#. the app had a problem pinning an otherwise +#. untrusted TLS certificate +#: src/client/accounts/accounts-editor.vala:203 +msgid "Failed to store certificate" +msgstr "A tanúsítvány tárolása meghiúsult" + +#. Translators: Label for adding an email account +#. account for a generic IMAP service provider. +#: src/client/accounts/accounts-editor-add-pane.vala:109 +msgid "All others" +msgstr "Az összes többi" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:196 +#: src/client/accounts/accounts-editor-servers-pane.vala:316 +msgid "Check your receiving login and password" +msgstr "Ellenőrizze a fogadó bejelentkezési nevet és jelszót" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:211 +#: src/client/accounts/accounts-editor-servers-pane.vala:329 +msgid "Check your receiving server details" +msgstr "Ellenőrizze a fogadó kiszolgáló részleteit" + +#. Translators: In-app notification label +#. There was an SMTP auth error, but IMAP already +#. succeeded, so the user probably needs to +#. specify custom creds here +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:233 +#: src/client/accounts/accounts-editor-servers-pane.vala:350 +msgid "Check your sending login and password" +msgstr "Ellenőrizze a küldő bejelentkezési nevet és jelszót" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:247 +#: src/client/accounts/accounts-editor-servers-pane.vala:363 +msgid "Check your sending server details" +msgstr "Ellenőrizze a küldő kiszolgáló részleteit" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:262 +msgid "Check your email address and password" +msgstr "Ellenőrizze az e-mail címét és jelszavát" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:273 +msgid "Could not connect, check your network" +msgstr "Nem sikerült kapcsolódni, ellenőrizze a hálózatot" + +#. Translators: In-app notification label for a +#. generic error creating an account +#: src/client/accounts/accounts-editor-add-pane.vala:286 +msgid "An unexpected problem occurred" +msgstr "Váratlan probléma történt" + +#. Translators: In-app notification label, the +#. string substitution is a more detailed reason. +#: src/client/accounts/accounts-editor-add-pane.vala:304 +#, c-format +msgid "Account not created: %s" +msgstr "A fiók nincs létrehozva: %s" + +#. Translators: Label for the person's actual name when adding +#. an account +#: src/client/accounts/accounts-editor-add-pane.vala:551 +msgid "Your name" +msgstr "Az Ön neve" + +#. Translators: Label used for the address part of an +#. email address when editing a user's sender address +#. preferences for an account. +#: src/client/accounts/accounts-editor-add-pane.vala:568 +#: src/client/accounts/accounts-editor-edit-pane.vala:501 +msgid "Email address" +msgstr "E-mail cím" -#: ../desktop/geary-attach.contract.in.h:2 -msgid "Send files using Geary" -msgstr "Fájlok küldése a Geary használatával" +#. Translators: Placeholder for the default sender address +#. when adding an account +#. Translators: This is used as a placeholder for the +#. address part of an email address when editing a user's +#. sender address preferences for an account. +#: src/client/accounts/accounts-editor-add-pane.vala:571 +#: src/client/accounts/accounts-editor-edit-pane.vala:469 +msgid "person@example.com" +msgstr "szemely@example.com" + +#. Translators: Label for an IMAP/SMTP service login/user name +#. when adding an account +#. Translators: Label for the user's login name for an +#. IMAP, SMTP, etc service +#: src/client/accounts/accounts-editor-add-pane.vala:585 +#: src/client/accounts/accounts-editor-servers-pane.vala:880 +msgid "Login name" +msgstr "Bejelentkezési név" + +#. Translators: Label for the user's password for an IMAP, +#. SMTP, etc service +#: src/client/accounts/accounts-editor-add-pane.vala:599 +#: src/client/accounts/accounts-editor-servers-pane.vala:999 +#: ui/password-dialog.glade:108 +msgid "Password" +msgstr "Jelszó" -#: ../src/client/accounts/account-dialog-add-edit-pane.vala:51 -#: ../src/client/components/stock.vala:31 -#: ../ui/conversation-email-menus.ui.h:10 -msgid "_Save" -msgstr "_Mentés" +#. Translators: Label for the IMAP server hostname when +#. adding an account. +#. Translators: This label describes the host name or IP +#. address and port used by an account's IMAP service. +#: src/client/accounts/accounts-editor-add-pane.vala:621 +#: src/client/accounts/accounts-editor-servers-pane.vala:727 +msgid "IMAP server" +msgstr "IMAP-kiszolgáló" + +#. Translators: Placeholder for the IMAP server hostname +#. when adding an account. +#: src/client/accounts/accounts-editor-add-pane.vala:624 +msgid "imap.example.com" +msgstr "imap.example.com" -#: ../src/client/accounts/account-dialog-add-edit-pane.vala:51 -#: ../src/client/components/stock.vala:22 -msgid "_Add" -msgstr "_Hozzáadás" +#. Translators: Label for the SMTP server hostname when +#. adding an account. +#. Translators: This label describes the host name or IP +#. address and port used by an account's SMTP service. +#: src/client/accounts/accounts-editor-add-pane.vala:630 +#: src/client/accounts/accounts-editor-servers-pane.vala:733 +msgid "SMTP server" +msgstr "SMTP-kiszolgáló" + +#. Translators: Placeholder for the SMTP server hostname +#. when adding an account. +#: src/client/accounts/accounts-editor-add-pane.vala:633 +msgid "smtp.example.com" +msgstr "smtp.example.com" -#. reset/clear widgets -#: ../src/client/accounts/account-dialog-edit-alternate-emails-pane.vala:120 +#. Translators: Label in the account editor for the user's +#. custom name for an account. +#: src/client/accounts/accounts-editor-edit-pane.vala:278 +#: ui/accounts_editor_remove_pane.ui:123 +msgid "Account name" +msgstr "Fiók neve" + +#. Translators: Tooltip used to undo changing +#. the name of an account. The string +#. substitution is the old name of the +#. account. +#: src/client/accounts/accounts-editor-edit-pane.vala:312 +#, c-format +msgid "Change account name back to “%s”" +msgstr "Fiók nevének visszaváltoztatása erre: „%s”" + +#. Translators: Tooltip for adding a new email sender/from +#. address's address to an account +#: src/client/accounts/accounts-editor-edit-pane.vala:336 +msgid "Add a new sender email address" +msgstr "Új küldő e-mail cím hozzáadása" + +#. Translators: Label used to indicate the user has +#. provided no display name for one of their sender +#. email addresses in their account settings. +#: src/client/accounts/accounts-editor-edit-pane.vala:417 +msgid "Name not set" +msgstr "Név nincs beállítva" + +#. Translators: This is used as a placeholder for the +#. display name for an email address when editing a user's +#. sender address preferences for an account. +#: src/client/accounts/accounts-editor-edit-pane.vala:456 +msgid "Sender Name" +msgstr "Küldő neve" + +#: src/client/accounts/accounts-editor-edit-pane.vala:479 +msgid "Remove" +msgstr "Eltávolítás" + +#. Translators: Label used for the display name part of an +#. email address when editing a user's sender address +#. preferences for an account. +#: src/client/accounts/accounts-editor-edit-pane.vala:494 +msgid "Sender name" +msgstr "Küldő neve" + +#. Translators: Label used as the undo tooltip after adding an +#. new sender email address to an account. The string +#. substitution is the email address added. +#: src/client/accounts/accounts-editor-edit-pane.vala:561 +#, c-format +msgid "Remove “%s”" +msgstr "„%s” eltávolítása" + +#. Translators: Label used as the undo tooltip after editing a +#. sender address for an account. The string substitution is +#. the email address edited. +#: src/client/accounts/accounts-editor-edit-pane.vala:601 +#, c-format +msgid "Undo changes to “%s”" +msgstr "„%s” változtatásainak visszavonása" + +#. Translators: Label used as the undo tooltip after removing +#. a sender address from an account. The string substitution +#. is the email address edited. +#: src/client/accounts/accounts-editor-edit-pane.vala:688 +#, c-format +msgid "Add “%s” back" +msgstr "„%s” újra hozzáadása" + +#. Translators: Label used as the undo tooltip after removing +#. a sender address from an account. The string substitution +#. is the email address edited. +#: src/client/accounts/accounts-editor-edit-pane.vala:730 +msgid "Undo signature changes" +msgstr "Aláírás-változtatások visszavonása" + +#. Translators: This label describes the account +#. preference for the length of time (weeks, months or +#. years) that past email should be downloaded. +#: src/client/accounts/accounts-editor-edit-pane.vala:778 +msgid "Download mail" +msgstr "Levél letöltése" + +#. Translators: Tooltip for undoing a change +#. to the length of time that past email +#. should be downloaded for an account. The +#. string substitution is the duration, +#. e.g. "1 month back". +#: src/client/accounts/accounts-editor-edit-pane.vala:810 #, c-format -msgid "Additional addresses for %s" -msgstr "További címek a következőhöz: %s" +msgid "Change download period back to: %s" +msgstr "Letöltési időszak visszaváltoztatása erre: %s" -#. Sets min size. -#: ../src/client/accounts/account-dialog.vala:21 -msgid "Accounts" -msgstr "Fiókok" - -#. Copyright 2016 Software Freedom Conservancy Inc. -#. * -#. * This software is licensed under the GNU Lesser General Public License -#. * (version 2.1 or later). See the COPYING file in this distribution. -#. -#. Page for adding or editing an account. -#. / Placeholder text indicating that the user should type their first name and last name -#: ../src/client/accounts/add-edit-page.vala:10 -msgid "First Last" -msgstr "Last First" - -#: ../src/client/accounts/add-edit-page.vala:235 -msgid "Welcome to Geary." -msgstr "Üdvözli a Geary!" - -#: ../src/client/accounts/add-edit-page.vala:235 -msgid "Enter your account information to get started." -msgstr "Adja meg a fiókinformációit a kezdéshez." +#: src/client/accounts/accounts-editor-edit-pane.vala:831 +msgid "Everything" +msgstr "Minden" -#: ../src/client/accounts/add-edit-page.vala:255 +#: src/client/accounts/accounts-editor-edit-pane.vala:835 msgid "2 weeks back" msgstr "2 hétre visszamenőleg" -#. IDs are # of days -#: ../src/client/accounts/add-edit-page.vala:256 +#: src/client/accounts/accounts-editor-edit-pane.vala:839 msgid "1 month back" msgstr "1 hónapra visszamenőleg" -#: ../src/client/accounts/add-edit-page.vala:257 +#: src/client/accounts/accounts-editor-edit-pane.vala:843 msgid "3 months back" msgstr "3 hónapra visszamenőleg" -#: ../src/client/accounts/add-edit-page.vala:258 +#: src/client/accounts/accounts-editor-edit-pane.vala:847 msgid "6 months back" msgstr "6 hónapra visszamenőleg" -#: ../src/client/accounts/add-edit-page.vala:259 +#: src/client/accounts/accounts-editor-edit-pane.vala:851 msgid "1 year back" msgstr "1 évre visszamenőleg" -#: ../src/client/accounts/add-edit-page.vala:260 +#: src/client/accounts/accounts-editor-edit-pane.vala:855 msgid "2 years back" msgstr "2 évre visszamenőleg" -#: ../src/client/accounts/add-edit-page.vala:261 +#: src/client/accounts/accounts-editor-edit-pane.vala:859 msgid "4 years back" msgstr "4 évre visszamenőleg" -#. Separator -#: ../src/client/accounts/add-edit-page.vala:263 -msgid "Everything" -msgstr "Minden" +#: src/client/accounts/accounts-editor-edit-pane.vala:865 +#, c-format +msgid "%d day back" +msgid_plural "%d days back" +msgstr[0] "%d napra visszamenőleg" +msgstr[1] "%d napra visszamenőleg" + +#: src/client/accounts/accounts-editor-list-pane.vala:243 +msgid "Undo" +msgstr "Visszavonás" + +#: src/client/accounts/accounts-editor-list-pane.vala:251 +msgid "Redo" +msgstr "Újra" + +#: src/client/accounts/accounts-editor-list-pane.vala:345 +#: src/client/accounts/accounts-editor-list-pane.vala:433 +#: src/client/accounts/accounts-editor-row.vala:278 +msgid "Gmail" +msgstr "Gmail" -#: ../src/client/accounts/add-edit-page.vala:283 -msgid "Edit" -msgstr "Szerkesztés" - -#: ../src/client/accounts/add-edit-page.vala:285 -msgid "Preview" -msgstr "Előnézet" - -#: ../src/client/accounts/add-edit-page.vala:751 -msgid "Remem_ber passwords" -msgstr "_Jelszavak megjegyzése" - -#: ../src/client/accounts/add-edit-page.vala:758 ../ui/login.glade.h:7 -msgid "Remem_ber password" -msgstr "_Jelszó megjegyzése" - -#: ../src/client/accounts/add-edit-page.vala:792 -msgid "Unable to validate:\n" -msgstr "Nem sikerült érvényesíteni:\n" - -#: ../src/client/accounts/add-edit-page.vala:794 -msgid " • Invalid account nickname.\n" -msgstr " • Érvénytelen fiók becenév.\n" - -#: ../src/client/accounts/add-edit-page.vala:797 -msgid " • Email address already added to Geary.\n" -msgstr " • Az e-mail cím már hozzá lett adva a Geary programhoz.\n" - -#: ../src/client/accounts/add-edit-page.vala:801 -msgid " • IMAP connection error.\n" -msgstr " • IMAP kapcsolódási hiba.\n" - -#: ../src/client/accounts/add-edit-page.vala:804 -msgid " • IMAP username or password incorrect.\n" -msgstr " • Az IMAP felhasználónév vagy jelszó helytelen.\n" - -#: ../src/client/accounts/add-edit-page.vala:807 -msgid " • SMTP connection error.\n" -msgstr " • SMTP kapcsolódási hiba.\n" - -#: ../src/client/accounts/add-edit-page.vala:810 -msgid " • SMTP username or password incorrect.\n" -msgstr " • Az SMTP felhasználónév vagy jelszó helytelen.\n" - -#: ../src/client/accounts/add-edit-page.vala:814 -msgid " • Connection error.\n" -msgstr " • Kapcsolódási hiba.\n" - -#: ../src/client/accounts/add-edit-page.vala:818 -msgid " • Username or password incorrect.\n" -msgstr " • A felhasználónév vagy jelszó helytelen.\n" +#: src/client/accounts/accounts-editor-list-pane.vala:349 +#: src/client/accounts/accounts-editor-list-pane.vala:437 +#: src/client/accounts/accounts-editor-row.vala:282 +msgid "Outlook.com" +msgstr "Outlook.com" -#: ../src/client/application/geary-application.vala:22 +#: src/client/accounts/accounts-editor-list-pane.vala:353 +#: src/client/accounts/accounts-editor-list-pane.vala:441 +#: src/client/accounts/accounts-editor-row.vala:286 +msgid "Yahoo" +msgstr "Yahoo" + +#. Translators: Tooltip for accounts that have been +#. loaded but disabled by the user. +#: src/client/accounts/accounts-editor-list-pane.vala:371 +msgid "This account has been disabled" +msgstr "Ezt a fiókot letiltották" + +#. Translators: Tooltip for accounts that have been +#. loaded but because of some error are not able to be +#. used. +#: src/client/accounts/accounts-editor-list-pane.vala:380 +msgid "This account has encountered a problem and is unavailable" +msgstr "Ennél a fióknál probléma történt és nem érhető el" + +#. Translators: Label for adding a generic email account +#: src/client/accounts/accounts-editor-list-pane.vala:430 +msgid "Other email providers" +msgstr "Egyéb e-mail szolgáltatók" + +#. Translators: Notification shown after removing an +#. account. The string substitution is the name of the +#. account. +#: src/client/accounts/accounts-editor-list-pane.vala:547 +#, c-format +msgid "Account “%s” removed" +msgstr "A(z) „%s” fiók eltávolítva" + +#. Translators: Notification shown after removing an account +#. is undone. The string substitution is the name of the +#. account. +#: src/client/accounts/accounts-editor-list-pane.vala:554 +#, c-format +msgid "Account “%s” restored" +msgstr "A(z) „%s” fiók visszaállítva" + +#. Translators: Tooltip for dragging list items +#: src/client/accounts/accounts-editor-row.vala:49 +msgid "Drag to move this item" +msgstr "Húzza az elem mozgatásához" + +#. Translators: Label describes the service provider +#. hosting the email account, e.g. Gmail, Yahoo, or some +#. other generic IMAP service. +#: src/client/accounts/accounts-editor-row.vala:294 +msgid "Service provider" +msgstr "Szolgáltató" + +#. Translators: This label describes what form of transport +#. security (TLS, StartTLS, etc) used by an account's IMAP or SMTP +#. service. +#: src/client/accounts/accounts-editor-row.vala:467 +msgid "Connection security" +msgstr "Kapcsolat biztonsága" + +#. Translators: Label used when no auth scheme is used +#. by an account's IMAP or SMTP service. +#: src/client/accounts/accounts-editor-row.vala:478 +#: src/client/accounts/accounts-editor-servers-pane.vala:752 +#: src/client/accounts/accounts-editor-servers-pane.vala:964 +#: src/engine/api/geary-special-folder-type.vala:58 +msgid "None" +msgstr "Nincs" + +#: src/client/accounts/accounts-editor-row.vala:485 +msgid "StartTLS" +msgstr "StartTLS" + +#: src/client/accounts/accounts-editor-row.vala:492 +msgid "TLS" +msgstr "TLS" + +#. Translators: Label for source of SMTP authentication +#. credentials (none, use IMAP, custom) when adding a new +#. account +#. Button label for retrying when a login error has occurred +#: src/client/accounts/accounts-editor-row.vala:533 ui/main-window.ui:455 +msgid "Login" +msgstr "Bejelentkezés" + +#. Translators: ComboBox value for source of SMTP +#. authentication credentials (none) when adding a new +#. account +#: src/client/accounts/accounts-editor-row.vala:540 +msgid "No login needed" +msgstr "Nem szükséges bejelentkezés" + +#. Translators: ComboBox value for source of SMTP +#. authentication credentials (use IMAP) when adding a new +#. account +#: src/client/accounts/accounts-editor-row.vala:548 +msgid "Use same login as receiving" +msgstr "Ugyanazon bejelentkezési név használata mint fogadáshoz" + +#. Translators: ComboBox value for source of SMTP +#. authentication credentials (custom) when adding a new +#. account +#: src/client/accounts/accounts-editor-row.vala:556 +msgid "Use a different login" +msgstr "Eltérő bejelentkezési név használata" + +#. Translators: In-app notification label, the +#. string substitution is a more detailed reason. +#: src/client/accounts/accounts-editor-servers-pane.vala:377 +#, c-format +msgid "Account not updated: %s" +msgstr "A fiók nincs frissítve: %s" + +#. Translators: This label describes the program that +#. created the account, e.g. an SSO service like GOA, or +#. locally by Geary. +#: src/client/accounts/accounts-editor-servers-pane.vala:540 +msgid "Account source" +msgstr "Fiók forrása" + +#: src/client/accounts/accounts-editor-servers-pane.vala:552 +msgid "GNOME Online Accounts" +msgstr "GNOME Online fiókok" + +#. Translators: This label describes an account +#. preference. +#: src/client/accounts/accounts-editor-servers-pane.vala:611 +msgid "Save drafts on server" +msgstr "Piszkozatok mentése a kiszolgálón" + +#. Translators: This label describes an account +#. preference. +#: src/client/accounts/accounts-editor-servers-pane.vala:666 +msgid "Save sent email on server" +msgstr "Elküldött e-mail mentése a kiszolgálón" + +#. Add a suffix for OAuth2 auth so people know they +#. shouldn't expect to be prompted for a password +#. Translators: Label used when an account's IMAP or +#. SMTP service uses OAuth2. The string replacement is +#. the service's login name. +#: src/client/accounts/accounts-editor-servers-pane.vala:950 +#, c-format +msgid "%s using OAuth2" +msgstr "%s OAuth2 használatával" + +#: src/client/accounts/accounts-editor-servers-pane.vala:960 +msgid "Use receiving server login" +msgstr "Fogadó kiszolgáló bejelentkezési nevének használata" + +#: src/client/application/geary-application.vala:24 msgid "Copyright 2016 Software Freedom Conservancy Inc." msgstr "Copyright 2016 Software Freedom Conservancy Inc." -#: ../src/client/application/geary-application.vala:23 -msgid "Copyright 2016-2017 Geary Development Team." -msgstr "Copyright 2016-2017 Geary fejlesztőcsapat." +#: src/client/application/geary-application.vala:25 +msgid "Copyright 2016-2019 Geary Development Team." +msgstr "Copyright 2016-2019 A Geary fejlesztőcsapat." -#: ../src/client/application/geary-application.vala:25 +#: src/client/application/geary-application.vala:27 msgid "Visit the Geary web site" msgstr "Keresse fel a Geary weboldalát" -#: ../src/client/application/geary-application.vala:466 +#: src/client/application/geary-application.vala:454 #, c-format msgid "About %s" msgstr "%s névjegye" @@ -281,321 +787,115 @@ #. Translators: add your name and email address to receive #. credit in the About dialog For example: Yamada Taro #. -#: ../src/client/application/geary-application.vala:470 +#: src/client/application/geary-application.vala:458 msgid "translator-credits" msgstr "" "Kelemen Gábor \n" "Lukács Bence \n" -"Úr Balázs \n" -"Meskó Balázs " +"Meskó Balázs \n" +"Úr Balázs " -#: ../src/client/application/geary-args.vala:10 +#: src/client/application/geary-args.vala:10 msgid "Start Geary with hidden main window" msgstr "A Geary indítása rejtett főablakkal" -#: ../src/client/application/geary-args.vala:11 +#: src/client/application/geary-args.vala:11 msgid "Output debugging information" msgstr "Hibakeresési információk kiírása" -#: ../src/client/application/geary-args.vala:12 +#: src/client/application/geary-args.vala:12 msgid "Log conversation monitoring" msgstr "Beszélgetésfigyelés naplózása" -#: ../src/client/application/geary-args.vala:13 +#: src/client/application/geary-args.vala:13 msgid "Log network deserialization" msgstr "Hálózati visszafejtés naplózása" -#: ../src/client/application/geary-args.vala:14 +#: src/client/application/geary-args.vala:14 msgid "Log network activity" msgstr "Hálózati tevékenység naplózása" #. / The IMAP replay queue is how changes on the server are replicated on the client. #. / It could also be called the IMAP events queue. -#: ../src/client/application/geary-args.vala:17 +#: src/client/application/geary-args.vala:17 msgid "Log IMAP replay queue" msgstr "IMAP eseménysor naplózása" #. / Serialization is how commands and responses are converted into a stream of bytes for #. / network transmission -#: ../src/client/application/geary-args.vala:20 +#: src/client/application/geary-args.vala:20 msgid "Log network serialization" msgstr "Hálózati sorosítás naplózása" -#: ../src/client/application/geary-args.vala:21 +#: src/client/application/geary-args.vala:21 msgid "Log periodic activity" msgstr "Időszakos tevékenység naplózása" -#: ../src/client/application/geary-args.vala:22 +#: src/client/application/geary-args.vala:22 msgid "Log database queries (generates lots of messages)" msgstr "Adatbázis lekérdezések naplózása (sok üzenetet állít elő)" #. / "Normalization" can also be called "synchronization" -#: ../src/client/application/geary-args.vala:24 +#: src/client/application/geary-args.vala:24 msgid "Log folder normalization" msgstr "Mappa-normalizálás naplózása" -#: ../src/client/application/geary-args.vala:25 +#: src/client/application/geary-args.vala:25 msgid "Allow inspection of WebView" msgstr "A webes nézet megfigyelésének engedélyezése" -#: ../src/client/application/geary-args.vala:26 +#: src/client/application/geary-args.vala:26 msgid "Revoke all server certificates with TLS warnings" msgstr "Minden kiszolgáló tanúsítvány visszavonása TLS figyelmeztetésekkel" -#: ../src/client/application/geary-args.vala:27 +#: src/client/application/geary-args.vala:27 msgid "Perform a graceful quit" msgstr "Elegáns kilépés végrehajtása" -#: ../src/client/application/geary-args.vala:28 +#: src/client/application/geary-args.vala:28 msgid "Display program version" msgstr "A program verziójának megjelenítése" #. This gives a command-line hint on how to open new composer windows with mailto: -#: ../src/client/application/geary-args.vala:53 +#: src/client/application/geary-args.vala:53 #, c-format msgid "Use %s to open a new composer window" msgstr "%s használata egy új levélíró ablak megnyitásához" -#: ../src/client/application/geary-args.vala:56 +#: src/client/application/geary-args.vala:56 msgid "Please report comments, suggestions and bugs to:" msgstr "Kérjük írja le véleményét, észrevételeit és a hibákat ide:" #. i18n: Command line arguments are invalid -#: ../src/client/application/geary-args.vala:63 +#: src/client/application/geary-args.vala:63 #, c-format msgid "Failed to parse command line options: %s\n" msgstr "A parancssori kapcsolók feldolgozása meghiúsult: %s\n" -#: ../src/client/application/geary-args.vala:74 +#: src/client/application/geary-args.vala:74 #, c-format msgid "Unrecognized command line option “%s”\n" msgstr "Ismeretlen parancssori kapcsoló: „%s”\n" -#: ../src/client/application/geary-controller.vala:57 -msgid "Delete conversation" -msgstr "Beszélgetés törlése" - -#: ../src/client/application/geary-controller.vala:58 -msgid "Delete conversation (Shift+Delete)" -msgstr "Beszélgetés törlése (Shift+Delete)" - -#: ../src/client/application/geary-controller.vala:59 -msgid "Delete conversations (Shift+Delete)" -msgstr "Beszélgetések törlése (Shift+Delete)" - -#. This refers to the action ("move email to the trash"), not the Trash folder itself -#: ../src/client/application/geary-controller.vala:63 -msgid "Move conversation to Trash (Delete, Backspace)" -msgstr "Beszélgetés áthelyezése a kukába (Delete, Backspace)" - -#: ../src/client/application/geary-controller.vala:64 -msgid "Move conversations to Trash (Delete, Backspace)" -msgstr "Beszélgetések áthelyezése a kukába (Delete, Backspace)" - -#. This refers to the action ("archive an email"), not the Archive folder itself -#: ../src/client/application/geary-controller.vala:68 -msgid "_Archive" -msgstr "_Archiválás" - -#: ../src/client/application/geary-controller.vala:69 -msgid "Archive conversation (A)" -msgstr "Beszélgetés archiválása (A)" - -#: ../src/client/application/geary-controller.vala:70 -msgid "Archive conversations (A)" -msgstr "Beszélgetések archiválása (A)" - -#: ../src/client/application/geary-controller.vala:73 -msgid "Mark as S_pam" -msgstr "Megjelölés levél_szemétként" - -#: ../src/client/application/geary-controller.vala:74 -msgid "Mark as not S_pam" -msgstr "Megjelölés nem levél_szemétként" - -#: ../src/client/application/geary-controller.vala:76 -#: ../src/client/application/geary-controller.vala:448 -msgid "Mark conversation" -msgstr "Beszélgetés megjelölése" - -#: ../src/client/application/geary-controller.vala:77 -msgid "Mark conversations" -msgstr "Beszélgetések megjelölése" - -#: ../src/client/application/geary-controller.vala:78 -msgid "Add label to conversation" -msgstr "Címke hozzáadása a beszélgetéshez" - -#: ../src/client/application/geary-controller.vala:79 -msgid "Add label to conversations" -msgstr "Címke hozzáadása a beszélgetésekhez" - -#: ../src/client/application/geary-controller.vala:80 -#: ../src/client/application/geary-controller.vala:487 -msgid "Move conversation" -msgstr "Beszélgetés áthelyezése" - -#: ../src/client/application/geary-controller.vala:81 -msgid "Move conversations" -msgstr "Beszélgetések áthelyezése" - -#: ../src/client/application/geary-controller.vala:450 -msgid "_Mark as…" -msgstr "_Megjelölés mint…" - -#: ../src/client/application/geary-controller.vala:456 -msgid "Mark as _Read" -msgstr "Megjelölés _olvasottként" - -#: ../src/client/application/geary-controller.vala:462 -msgid "Mark as _Unread" -msgstr "Megjelölés ol_vasatlanként" - -#: ../src/client/application/geary-controller.vala:468 -msgid "_Star" -msgstr "_Csillagozás" - -#: ../src/client/application/geary-controller.vala:473 -msgid "U_nstar" -msgstr "C_sillagozás megszüntetése" +#. Translators: File name used in save chooser when saving +#. attachments that do not otherwise have a name. +#: src/client/application/geary-controller.vala:58 +msgid "Untitled" +msgstr "Névtelen" -#: ../src/client/application/geary-controller.vala:483 -msgid "Add label" -msgstr "Címke hozzáadása" - -#: ../src/client/application/geary-controller.vala:484 -msgid "_Label" -msgstr "_Címke" - -#: ../src/client/application/geary-controller.vala:488 -msgid "_Move" -msgstr "Át_helyezés" - -#: ../src/client/application/geary-controller.vala:492 -msgid "Compose new message (Ctrl+N, N)" -msgstr "Új levél írása (Ctrl+N, N)" - -#: ../src/client/application/geary-controller.vala:496 -#: ../ui/conversation-email-menus.ui.h:1 -msgid "_Reply" -msgstr "_Válasz" - -#: ../src/client/application/geary-controller.vala:497 -msgid "Reply (Ctrl+R, R)" -msgstr "Válasz (Ctrl+R, R)" - -#: ../src/client/application/geary-controller.vala:501 -msgid "R_eply All" -msgstr "Válasz _mindenkinek" - -#: ../src/client/application/geary-controller.vala:502 -msgid "Reply all (Ctrl+Shift+R, Shift+R)" -msgstr "Válasz mindenkinek (Ctrl+Shift+R, Shift+R)" - -#: ../src/client/application/geary-controller.vala:507 -#: ../ui/conversation-email-menus.ui.h:3 -msgid "_Forward" -msgstr "T_ovábbítás" - -#: ../src/client/application/geary-controller.vala:508 -msgid "Forward (Ctrl+L, F)" -msgstr "Továbbítás (Ctrl+L, F)" - -#: ../src/client/application/geary-controller.vala:538 -msgid "Empty _Spam…" -msgstr "_Levélszemét ürítése…" - -#: ../src/client/application/geary-controller.vala:542 -msgid "Empty _Trash…" -msgstr "_Kuka ürítése…" - -#. No callback is connected, since we bind the toggle button to the search bar visibility -#: ../src/client/application/geary-controller.vala:574 -msgid "Toggle search bar" -msgstr "Keresősáv ki- vagy bekapcsolása" - -#. No callback is connected, since we bind the toggle button to the find bar visibility -#: ../src/client/application/geary-controller.vala:579 -msgid "Toggle find bar" -msgstr "Keresősáv ki- vagy bekapcsolása" - -#: ../src/client/application/geary-controller.vala:757 -msgid "Unable to store server trust exception" -msgstr "Nem lehet eltárolni a kiszolgáló megbízhatósági kivételét" - -#: ../src/client/application/geary-controller.vala:992 -msgid "Your settings are insecure" -msgstr "A beállításai nem biztonságosak" - -#: ../src/client/application/geary-controller.vala:993 -msgid "" -"Your IMAP and/or SMTP settings do not specify SSL or TLS. This means your " -"username and password could be read by another person on the network. Are " -"you sure you want to do this?" -msgstr "" -"Az IMAP és/vagy SMTP beállításai nem tartalmaznak SSL vagy TLS titkosítást. " -"Ez azt jelenti, hogy a felhasználónevét és a jelszavát mások elolvashatják a " -"hálózaton. Biztos benne, hogy ezt szeretné tenni?" - -#: ../src/client/application/geary-controller.vala:994 -msgid "Co_ntinue" -msgstr "Fo_lytatás" - -#: ../src/client/application/geary-controller.vala:1041 -msgid "Error connecting to the server" -msgstr "Hiba a kiszolgálóhoz kapcsolódás közben" - -#: ../src/client/application/geary-controller.vala:1042 -msgid "" -"Geary encountered an error while connecting to the server. Please try again " -"in a few moments." -msgstr "" -"A Geary hibába ütközött a kiszolgálóhoz történő kapcsolódáskor. Próbálja " -"újra néhány másodperc múlva." - -#. / Displayed in the space-limited status bar when a message fails to be sent due to error. -#: ../src/client/application/geary-controller.vala:1079 -#: ../src/client/components/status-bar.vala:29 -msgid "Error sending email" -msgstr "Hiba a levél küldésekor" - -#: ../src/client/application/geary-controller.vala:1080 -msgid "" -"Geary encountered an error sending an email. If the problem persists, " -"please manually delete the email from your Outbox folder." -msgstr "" -"A Geary hibára futott a levél küldésekor. Ha a probléma állandó, törölje " -"kézzel a levelet a Kimenő levelek mappájából." - -#. Displayed in the space-limited status bar when a message fails to be uploaded -#. to Sent Mail after being sent. -#: ../src/client/application/geary-controller.vala:1084 -#: ../src/client/components/status-bar.vala:33 -msgid "Error saving sent mail" -msgstr "Hiba az elküldött levél mentésekor" - -#: ../src/client/application/geary-controller.vala:1085 -msgid "" -"Geary encountered an error saving a sent message to Sent Mail. The message " -"will stay in your Outbox folder until you delete it." -msgstr "" -"A Geary hibára futott az elküldött levél mentésekor az Elküldött levelek " -"mappába. Az üzenet a Kimenő levelek mappájában fog maradni, amíg ki nem " -"törli azt." - -#: ../src/client/application/geary-controller.vala:1154 +#: src/client/application/geary-controller.vala:937 msgid "Labels" msgstr "Címkék" #. give the user two options: reset the Account local store, or exit Geary. A third #. could be done to leave the Account in an unopened state, but we don't currently #. have provisions for that. -#: ../src/client/application/geary-controller.vala:1166 +#: src/client/application/geary-controller.vala:950 #, c-format msgid "Unable to open the database for %s" msgstr "Nem sikerült megnyitni az adatbázist ehhez: %s" -#: ../src/client/application/geary-controller.vala:1167 +#: src/client/application/geary-controller.vala:951 #, c-format msgid "" "There was an error opening the local mail database for this account. This is " @@ -619,20 +919,20 @@ "Az adatbázis újjáépítése törölni fogja az összes helyi levelet és azok " "mellékleteit. A kiszolgálón lévő levelek nem érintettek." -#: ../src/client/application/geary-controller.vala:1169 +#: src/client/application/geary-controller.vala:953 msgid "_Rebuild" msgstr "Ú_jjáépítés" -#: ../src/client/application/geary-controller.vala:1169 +#: src/client/application/geary-controller.vala:953 msgid "E_xit" msgstr "_Kilépés" -#: ../src/client/application/geary-controller.vala:1178 +#: src/client/application/geary-controller.vala:962 #, c-format msgid "Unable to rebuild database for “%s”" msgstr "Nem sikerült újjáépíteni az adatbázist ehhez: „%s”" -#: ../src/client/application/geary-controller.vala:1179 +#: src/client/application/geary-controller.vala:963 #, c-format msgid "" "Error during rebuild:\n" @@ -643,69 +943,15 @@ "\n" "%s" -#. some other problem opening the account ... as with other flow path, can't run -#. Geary today with an account in unopened state, so have to exit -#: ../src/client/application/geary-controller.vala:1201 -#: ../src/client/application/geary-controller.vala:1211 -#: ../src/client/application/geary-controller.vala:1222 -#, c-format -msgid "Unable to open local mailbox for %s" -msgstr "Nem sikerült megnyitni a helyi postafiókot ehhez: %s" - -#: ../src/client/application/geary-controller.vala:1202 -#, c-format -msgid "" -"There was an error opening the local mail database for this account. This is " -"possibly due to a file permissions problem.\n" -"\n" -"Please check that you have read/write permissions for all files in this " -"directory:\n" -"\n" -"%s" -msgstr "" -"Hiba történt a fiók helyi levéladatbázisának megnyitásakor. Ez valószínűleg " -"a fájl jogosultsági problémája miatt volt.\n" -"\n" -"Ellenőrizze, hogy minden fájlra van-e olvasási/írási jogosultsága ebben a " -"könyvtárban:\n" -"\n" -"%s" - -#: ../src/client/application/geary-controller.vala:1212 -msgid "" -"The version number of the local mail database is formatted for a newer " -"version of Geary. Unfortunately, the database cannot be “rolled back” to " -"work with this version of Geary.\n" -"\n" -"Please install the latest version of Geary and try again." -msgstr "" -"A helyi levéladatbázis verziószáma a Geary újabb verziójához van kialakítva. " -"Sajnos az adatbázist nem lehet „visszagörgetni”, hogy működni tudjon a Geary " -"ezen verziójával.\n" -"\n" -"Telepítse a Geary legújabb verzióját és próbálja újra." - -#: ../src/client/application/geary-controller.vala:1223 -msgid "" -"There was an error opening the local account. This is probably due to " -"connectivity issues.\n" -"\n" -"Please check your network connection and restart Geary." -msgstr "" -"Hiba történt a helyi fiók megnyitásakor. Ez valószínűleg kapcsolódási " -"problémák miatt volt.\n" -"\n" -"Ellenőrizze a hálózati kapcsolatot és indítsa újra a Geary programot." - -#: ../src/client/application/geary-controller.vala:1999 +#: src/client/application/geary-controller.vala:1818 msgid "Undo move (Ctrl+Z)" msgstr "Áthelyezés visszavonása (Ctrl+Z)" -#: ../src/client/application/geary-controller.vala:2009 +#: src/client/application/geary-controller.vala:1828 msgid "Are you sure you want to open these attachments?" msgstr "Biztosan meg szeretné nyitni ezeket a mellékleteket?" -#: ../src/client/application/geary-controller.vala:2010 +#: src/client/application/geary-controller.vala:1829 msgid "" "Attachments may cause damage to your system if opened. Only open files from " "trusted sources." @@ -713,16 +959,22 @@ "A mellékletek kárt okozhatnak a rendszerében, ha meg vannak nyitva. Csak " "megbízható forrásokból nyisson meg fájlokat." -#: ../src/client/application/geary-controller.vala:2011 +#: src/client/application/geary-controller.vala:1830 msgid "Don’t _ask me again" msgstr "Ne _kérdezze újra" -#: ../src/client/application/geary-controller.vala:2121 +#. Translators: Dialog primary label when prompting to +#. overwrite a file. The string substitution is the file'sx +#. name. +#: src/client/application/geary-controller.vala:1959 #, c-format msgid "A file named “%s” already exists. Do you want to replace it?" msgstr "Már létezik „%s” nevű fájl. Le akarja cserélni?" -#: ../src/client/application/geary-controller.vala:2123 +#. Translators: Dialog secondary label when prompting to +#. overwrite a file. The string substitution is the parent +#. folder's name. +#: src/client/application/geary-controller.vala:1966 #, c-format msgid "" "The file already exists in “%s”. Replacing it will overwrite its contents." @@ -730,184 +982,463 @@ "A fájl már létezik a(z) „%s” helyen. Lecserélésével a tartalma felül lesz " "írva." -#: ../src/client/application/geary-controller.vala:2126 +#: src/client/application/geary-controller.vala:1970 msgid "_Replace" msgstr "_Csere" -#. Find out what to do with the inline composers. -#. TODO: Remove this in favor of automatically saving drafts -#: ../src/client/application/geary-controller.vala:2374 -msgid "Close open draft messages?" -msgstr "Bezárja a megnyitott piszkozat leveleket?" +#: src/client/application/geary-controller.vala:2246 +msgid "Close the draft message?" +msgid_plural "Close all draft messages?" +msgstr[0] "Bezárja a piszkozatlevelet?" +msgstr[1] "Bezárja az összes piszkozatlevelet?" -#: ../src/client/application/geary-controller.vala:2496 +#: src/client/application/geary-controller.vala:2372 #, c-format msgid "Empty all email from your %s folder?" msgstr "Minden levelet kiürít a(z) %s mappából?" -#: ../src/client/application/geary-controller.vala:2497 +#: src/client/application/geary-controller.vala:2373 msgid "This removes the email from Geary and your email server." msgstr "" "Ez eltávolítja a levelet a Geary programból és a levelezőkiszolgálóról." -#: ../src/client/application/geary-controller.vala:2498 +#: src/client/application/geary-controller.vala:2374 msgid "This cannot be undone." msgstr "Ez nem vonható vissza." -#: ../src/client/application/geary-controller.vala:2499 +#: src/client/application/geary-controller.vala:2375 #, c-format msgid "Empty %s" msgstr "%s ürítése" -#: ../src/client/application/geary-controller.vala:2516 +#: src/client/application/geary-controller.vala:2392 #, c-format msgid "Error emptying %s" msgstr "Hiba a(z) %s ürítésekor" -#: ../src/client/application/geary-controller.vala:2546 +#: src/client/application/geary-controller.vala:2424 msgid "Do you want to permanently delete this message?" msgid_plural "Do you want to permanently delete these messages?" msgstr[0] "Véglegesen törölni szeretné ezt a levelet?" msgstr[1] "Véglegesen törölni szeretné ezeket a leveleket?" -#: ../src/client/application/geary-controller.vala:2548 +#: src/client/application/geary-controller.vala:2426 msgid "Delete" msgstr "Törlés" -#: ../src/client/application/geary-controller.vala:2580 -msgid "Undo archive (Ctrl+Z)" -msgstr "Archiválás visszavonása (Ctrl+Z)" - -#: ../src/client/application/geary-controller.vala:2595 +#: src/client/application/geary-controller.vala:2440 msgid "Undo trash (Ctrl+Z)" msgstr "Kukába helyezés visszavonása (Ctrl+Z)" -#: ../src/client/application/geary-controller.vala:2649 +#: src/client/application/geary-controller.vala:2490 +msgid "Undo archive (Ctrl+Z)" +msgstr "Archiválás visszavonása (Ctrl+Z)" + +#: src/client/application/geary-controller.vala:2535 msgid "Undo (Ctrl+Z)" msgstr "Visszavonás (Ctrl+Z)" -#: ../src/client/application/geary-controller.vala:2780 +#. Translators: The label for an in-app notification. The +#. string substitution is a list of recipients of the email. +#: src/client/application/geary-controller.vala:2616 +#, c-format +msgid "Successfully sent mail to %s." +msgstr "A levél sikeresen elküldve ide: %s." + +#: src/client/application/geary-controller.vala:2698 msgid "Failed to open default text editor." msgstr "Nem sikerült megnyitni az alapértelmezett szövegszerkesztőt." -#: ../src/client/components/main-window.vala:389 +#. Translators: Tooltip used when an entry requires a valid +#. email address to be entered, but one is not provided. +#: src/client/components/components-validator.vala:378 +msgid "An email address is required" +msgstr "Egy e-mail cím szükséges" + +#. Translators: Tooltip used when an entry requires a valid +#. email address to be entered, but the address is invalid. +#: src/client/components/components-validator.vala:382 +msgid "Not a valid email address" +msgstr "Nem érvénytelen e-mail cím" + +#. Translators: Tooltip used when an entry requires a valid, +#. resolvable server name to be entered, but one is not +#. provided. +#: src/client/components/components-validator.vala:428 +msgid "A server name is required" +msgstr "Egy kiszolgálónév szükséges" + +#. Translators: Tooltip used when an entry requires a valid +#. server name to be entered, but it was unable to be +#. looked-up in the DNS. +#: src/client/components/components-validator.vala:433 +msgid "Could not look up server name" +msgstr "Nem sikerült megkeresni a kiszolgáló nevét" + +#: src/client/components/main-toolbar.vala:151 +msgid "Mark conversation" +msgid_plural "Mark conversations" +msgstr[0] "Beszélgetés megjelölése" +msgstr[1] "Beszélgetések megjelölése" + +#: src/client/components/main-toolbar.vala:156 +msgid "Add label to conversation" +msgid_plural "Add label to conversations" +msgstr[0] "Címke hozzáadása a beszélgetéshez" +msgstr[1] "Címke hozzáadása a beszélgetésekhez" + +#: src/client/components/main-toolbar.vala:161 +msgid "Move conversation" +msgid_plural "Move conversations" +msgstr[0] "Beszélgetés áthelyezése" +msgstr[1] "Beszélgetések áthelyezése" + +#: src/client/components/main-toolbar.vala:166 +msgid "Archive conversation (A)" +msgid_plural "Archive conversations (A)" +msgstr[0] "Beszélgetés archiválása (A)" +msgstr[1] "Beszélgetések archiválása (A)" + +#: src/client/components/main-toolbar.vala:175 +msgid "Move conversation to Trash (Delete, Backspace)" +msgid_plural "Move conversations to Trash (Delete, Backspace)" +msgstr[0] "Beszélgetés áthelyezése a kukába (Delete, Backspace)" +msgstr[1] "Beszélgetések áthelyezése a kukába (Delete, Backspace)" + +#: src/client/components/main-toolbar.vala:183 +msgid "Delete conversation (Shift+Delete)" +msgid_plural "Delete conversations (Shift+Delete)" +msgstr[0] "Beszélgetés törlése (Shift+Delete)" +msgstr[1] "Beszélgetések törlése (Shift+Delete)" + +#: src/client/components/main-window.vala:503 #, c-format msgid "%s (%d)" msgstr "%s (%d)" -#: ../src/client/components/search-bar.vala:8 -#: ../src/client/folder-list/folder-list-search-branch.vala:38 -#: ../src/engine/api/geary-special-folder-type.vala:51 +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:47 +#, c-format +msgid "Problem connecting to incoming server for %s" +msgstr "Hiba a(z) %s bejövő kiszolgálóhoz kapcsolódáskor" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:49 +#: src/client/components/main-window-info-bar.vala:58 +#, c-format +msgid "" +"Could not connect to %s, check your Internet access and the server name and " +"try again" +msgstr "" +"Nem sikerült a(z) %s kiszolgálóhoz kapcsolódás, ellenőrizze az " +"internetkapcsolatát és a kiszolgáló nevét, majd próbálja újra" + +#. Translators: Tooltip label for Retry button +#. Button tooltip for retrying an account problem +#: src/client/components/main-window-info-bar.vala:51 +#: src/client/components/main-window-info-bar.vala:60 +#: src/client/components/main-window-info-bar.vala:69 +#: src/client/components/main-window-info-bar.vala:78 +#: src/client/components/main-window-info-bar.vala:87 +#: src/client/components/main-window-info-bar.vala:96 +#: src/client/components/main-window-info-bar.vala:136 ui/main-window.ui:265 +msgid "Try reconnecting" +msgstr "Próbáljon meg újrakapcsolódni" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:56 +#, c-format +msgid "Problem connecting to outgoing server for %s" +msgstr "Hiba a(z) %s kimenő kiszolgálóhoz kapcsolódáskor" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:65 +#: src/client/components/main-window-info-bar.vala:83 +#, c-format +msgid "Problem communicating with incoming server for %s" +msgstr "Hiba a(z) %s bejövő kiszolgálóval történő kommunikáció során" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:67 +#: src/client/components/main-window-info-bar.vala:76 +#, c-format +msgid "Network error talking to %s, check your Internet access and try again" +msgstr "" +"Hálózati hiba a(z) %s kiszolgálóval történő kommunikáció során, ellenőrizze " +"az internetkapcsolatát, és próbálja újra" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:74 +#: src/client/components/main-window-info-bar.vala:91 +msgid "Problem communicating with outgoing mail server" +msgstr "Hiba a kimenő levél kiszolgálójával történő kommunikáció során" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:85 +#, c-format +msgid "" +"Geary did not understand a message from %s or vice versa, please file a bug " +"report" +msgstr "" +"A Geary nem értette a(z) %s kiszolgáló üzenetét, vagy fordítva, adjon be " +"hibajelentést" + +#. Translators: First string substitution is the server +#. name, second is the account name +#: src/client/components/main-window-info-bar.vala:94 +#, c-format +msgid "" +"Could not communicate with %s for %s, check the server name and try again in " +"a moment" +msgstr "" +"Nem sikerült kommunikálni a(z) %s kiszolgálóval a(z) %s fiók esetén, " +"ellenőrizze a kiszolgálónevet, és próbálja újra" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:101 +#, c-format +msgid "Incoming mail server password required for %s" +msgstr "A bejövő levél kiszolgálójának jelszava szükséges a(z) %s fiókhoz" + +#: src/client/components/main-window-info-bar.vala:102 +msgid "Messages cannot be received without the correct password." +msgstr "A levelek nem kérhetők le a helyes jelszó nélkül." + +#. Translators: Tooltip label for Retry button +#: src/client/components/main-window-info-bar.vala:104 +msgid "Retry receiving email, you will be prompted for a password" +msgstr "E-mail fogadásának újrapróbálása, a jelszava bekérésre fog kerülni" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:109 +#, c-format +msgid "Outgoing mail server password required for %s" +msgstr "A kimenő levél kiszolgálójának jelszava szükséges a(z) %s fiókhoz" + +#: src/client/components/main-window-info-bar.vala:110 +msgid "Messages cannot be sent without the correct password." +msgstr "A levelek nem küldhetőek el a helyes jelszó nélkül." + +#. Translators: Tooltip label for Retry button +#: src/client/components/main-window-info-bar.vala:112 +msgid "Retry sending queued messages, you will be prompted for a password" +msgstr "" +"Próbálja meg újra elküldeni a postázandó leveleket, a jelszó bekérésre fog " +"kerülni" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:117 +#, c-format +msgid "Incoming mail server security is not trusted for %s" +msgstr "" +"A bejövő levél kiszolgálójának biztonsága nem megbízható a(z) %s fióknál" + +#: src/client/components/main-window-info-bar.vala:118 +msgid "Messages will not be received until checked." +msgstr "A levelek nem lesznek lekérve az ellenőrzésig." + +#. Translators: Tooltip label for Retry button +#: src/client/components/main-window-info-bar.vala:120 +#: src/client/components/main-window-info-bar.vala:128 +msgid "Check security details" +msgstr "Ellenőrizze a biztonság részleteit" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:125 +#, c-format +msgid "Outgoing mail server security is not trusted for %s" +msgstr "" +"A kimenő levél kiszolgálójának biztonsága nem megbízható a(z) %s fióknál" + +#: src/client/components/main-window-info-bar.vala:126 +msgid "Messages cannot be sent until checked." +msgstr "A leveleket nem lehet elküldeni az ellenőrzésig." + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:133 +#, c-format +msgid "A problem occurred checking mail for %s" +msgstr "Hiba történt a(z) %s fiók leveleinek ellenőrzésekor" + +#: src/client/components/main-window-info-bar.vala:134 +#: src/client/components/main-window-info-bar.vala:142 +msgid "Something went wrong, please file a bug report if the problem persists" +msgstr "" +"Valami hiba történt, jelentse be hibaként, ha a probléma továbbra is fennáll" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:141 +#, c-format +msgid "A problem occurred sending mail for %s" +msgstr "Hiba történt a(z) %s fióknak történő levélküldéskor" + +#. Translators: Tooltip label for Retry button +#: src/client/components/main-window-info-bar.vala:144 +msgid "Retry sending queued messages" +msgstr "Postázandó levelek elküldésének újrapróbálása" + +#: src/client/components/main-window-info-bar.vala:155 +msgid "A database problem has occurred" +msgstr "Adatbázis-probléma történt" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:157 +#, c-format +msgid "Messages for %s must be downloaded again." +msgstr "A(z) %s leveleit újra le kell tölteni." + +#: src/client/components/main-window-info-bar.vala:170 +msgid "Geary has encountered a problem" +msgstr "A Geary problémát észlelt" + +#: src/client/components/main-window-info-bar.vala:171 +msgid "" +"Please check the technical details and report the problem if it persists." +msgstr "" +"Ellenőrizze a műszaki részleteket, és jelentse, ha a probléma továbbra is " +"fennáll" + +#: src/client/components/main-window-info-bar.vala:179 +msgid "_Details" +msgstr "_Részletek" + +#. Button tooltip for displaying technical details about an account problem +#: src/client/components/main-window-info-bar.vala:180 ui/main-window.ui:250 +msgid "View technical details about the error" +msgstr "A hiba műszaki részleteinek megtekintése" + +#: src/client/components/main-window-info-bar.vala:184 +msgid "_Retry" +msgstr "Ú_jra" + +#: src/client/components/search-bar.vala:8 +#: src/client/folder-list/folder-list-search-branch.vala:38 +#: src/engine/api/geary-special-folder-type.vala:51 msgid "Search" msgstr "Keresés" #. Search entry. -#: ../src/client/components/search-bar.vala:23 +#: src/client/components/search-bar.vala:23 msgid "Search all mail in account for keywords (Ctrl+S)" msgstr "A fiók minden levélének keresése kulcsszavakra (Ctrl+S)" -#: ../src/client/components/search-bar.vala:100 +#: src/client/components/search-bar.vala:101 #, c-format msgid "Indexing %s account" msgstr "%s fiók indexelése" -#: ../src/client/components/search-bar.vala:111 -#: ../src/client/folder-list/folder-list-search-branch.vala:39 +#: src/client/components/search-bar.vala:112 +#: src/client/folder-list/folder-list-search-branch.vala:39 #, c-format msgid "Search %s account" msgstr "%s fiók keresése" #. / Displayed in the space-limited status bar while a message is in the process of being sent. -#: ../src/client/components/status-bar.vala:26 +#: src/client/components/status-bar.vala:26 msgid "Sending…" msgstr "Küldés…" -#: ../src/client/components/stock.vala:18 ../ui/account_cannot_remove.glade.h:3 +#. / Displayed in the space-limited status bar when a message fails to be sent due to error. +#: src/client/components/status-bar.vala:29 +msgid "Error sending email" +msgstr "Hiba a levél küldésekor" + +#. Displayed in the space-limited status bar when a message fails to be uploaded +#. to Sent Mail after being sent. +#: src/client/components/status-bar.vala:33 +msgid "Error saving sent mail" +msgstr "Hiba az elküldött levél mentésekor" + +#: src/client/components/stock.vala:18 msgid "_OK" msgstr "_OK" -#: ../src/client/components/stock.vala:19 ../ui/edit_alternate_emails.glade.h:3 -#: ../ui/password-dialog.glade.h:5 ../ui/remove_confirm.glade.h:5 +#: src/client/components/stock.vala:19 ui/password-dialog.glade:196 msgid "_Cancel" msgstr "Mé_gse" -#: ../src/client/components/stock.vala:21 ../ui/gtk/menus.ui.h:6 +#: src/client/components/stock.vala:21 msgid "_About" msgstr "_Névjegy" -#: ../src/client/components/stock.vala:23 +#: src/client/components/stock.vala:22 +msgid "_Add" +msgstr "_Hozzáadás" + +#: src/client/components/stock.vala:23 msgid "_Close" msgstr "_Bezárás" -#: ../src/client/components/stock.vala:24 +#: src/client/components/stock.vala:24 msgid "_Discard" msgstr "El_dobás" -#: ../src/client/components/stock.vala:25 ../ui/gtk/menus.ui.h:5 +#: src/client/components/stock.vala:25 ui/main-toolbar-menus.ui:56 msgid "_Help" msgstr "_Súgó" -#: ../src/client/components/stock.vala:26 ../ui/conversation-email-menus.ui.h:9 +#: src/client/components/stock.vala:26 ui/conversation-email-menus.ui:77 msgid "_Open" msgstr "_Megnyitás" -#: ../src/client/components/stock.vala:27 ../ui/gtk/menus.ui.h:3 +#: src/client/components/stock.vala:27 ui/main-toolbar-menus.ui:46 msgid "_Preferences" msgstr "_Beállítások" -#: ../src/client/components/stock.vala:28 ../ui/conversation-email-menus.ui.h:7 +#. Translators: Menu item to print a single, specific message +#: src/client/components/stock.vala:28 ui/conversation-email-menus.ui:64 msgid "_Print…" msgstr "_Nyomtatás…" -#: ../src/client/components/stock.vala:29 ../ui/gtk/menus.ui.h:7 +#: src/client/components/stock.vala:29 msgid "_Quit" msgstr "_Kilépés" -#: ../src/client/components/stock.vala:30 ../ui/remove_confirm.glade.h:6 +#: src/client/components/stock.vala:30 msgid "_Remove" msgstr "_Eltávolítás" -#: ../src/client/components/stock.vala:32 +#: src/client/components/stock.vala:31 ui/conversation-email-menus.ui:83 +msgid "_Save" +msgstr "_Mentés" + +#: src/client/components/stock.vala:32 msgid "_Keep" msgstr "_Megtartás" -#: ../src/client/composer/composer-link-popover.vala:150 +#: src/client/composer/composer-link-popover.vala:149 msgid "Link URL is not correctly formatted, e.g. http://example.com" msgstr "A hivatkozás URL nem helyesen formázott, például http://example.com" -#: ../src/client/composer/composer-link-popover.vala:157 +#: src/client/composer/composer-link-popover.vala:156 msgid "Invalid link URL" msgstr "Érvénytelen hivatkozás URL" -#: ../src/client/composer/composer-link-popover.vala:157 +#: src/client/composer/composer-link-popover.vala:156 msgid "Invalid email address" msgstr "Érvénytelen e-mail cím" -#: ../src/client/composer/composer-widget.vala:154 +#: src/client/composer/composer-widget.vala:158 msgid "Saved" msgstr "Elmentve" -#: ../src/client/composer/composer-widget.vala:155 +#: src/client/composer/composer-widget.vala:159 msgid "Saving" msgstr "Mentés" -#: ../src/client/composer/composer-widget.vala:156 +#: src/client/composer/composer-widget.vala:160 msgid "Error saving" msgstr "Hiba a mentéskor" -#: ../src/client/composer/composer-widget.vala:157 +#: src/client/composer/composer-widget.vala:161 msgid "Press Backspace to delete quote" msgstr "Nyomja meg a Backspace billentyűt az idézet törléséhez" -#: ../src/client/composer/composer-widget.vala:158 -msgid "New Message" -msgstr "Új levél" - #. Translators: This is list of keywords, separated by pipe ("|") #. characters, that suggest an attachment; since this is full-word #. checking, include all variants of each word. No spaces are #. allowed. -#: ../src/client/composer/composer-widget.vala:167 +#: src/client/composer/composer-widget.vala:170 msgid "" "attach|attaching|attaches|attachment|attachments|attached|enclose|enclosed|" "enclosing|encloses|enclosure|enclosures" @@ -922,28 +1453,37 @@ "önéletrajzot|önéletrajzom|önéletrajzomat|.doc|.docx|.pdf|.xls|.xlsx|.ppt|." "pptx|.rtf|.pps|.odt|.ods|.odp|.odg|.odb" -#: ../src/client/composer/composer-widget.vala:1122 -#: ../src/client/composer/composer-widget.vala:1141 -msgid "Do you want to discard this message?" -msgstr "El szeretné dobni ezt a levelet?" +#. Translators: This dialog text is displayed to the +#. user when closing a composer where the options are +#. Keep, Discard or Cancel. +#: src/client/composer/composer-widget.vala:1131 +msgid "Do you want to keep or discard this draft message?" +msgstr "Megtartja vagy eldobja ezt a piszkozatlevelet?" + +#. Translators: This dialog text is displayed to the +#. user when closing a composer where the options are +#. only Discard or Cancel. +#: src/client/composer/composer-widget.vala:1159 +msgid "Do you want to discard this draft message?" +msgstr "El szeretné dobni ezt a piszkozatlevelet?" -#: ../src/client/composer/composer-widget.vala:1245 +#: src/client/composer/composer-widget.vala:1276 msgid "Send message with an empty subject and body?" msgstr "Elküldi a levelet üres tárggyal és törzzsel?" -#: ../src/client/composer/composer-widget.vala:1247 +#: src/client/composer/composer-widget.vala:1278 msgid "Send message with an empty subject?" msgstr "Elküldi a levelet üres tárggyal?" -#: ../src/client/composer/composer-widget.vala:1249 +#: src/client/composer/composer-widget.vala:1280 msgid "Send message with an empty body?" msgstr "Elküldi a levelet üres törzzsel?" -#: ../src/client/composer/composer-widget.vala:1253 +#: src/client/composer/composer-widget.vala:1284 msgid "Send message without an attachment?" msgstr "Elküldi a levelet melléklet nélkül?" -#: ../src/client/composer/composer-widget.vala:1515 +#: src/client/composer/composer-widget.vala:1589 #, c-format msgid "“%s” already attached for delivery." msgstr "„%s” már csatolva van a küldeményhez." @@ -953,170 +1493,262 @@ #. description of the document type, the second will #. be a human-friendly size string. For example: #. Document (100.9MB) -#: ../src/client/composer/composer-widget.vala:1523 -#: ../src/client/conversation-viewer/conversation-email.vala:138 +#: src/client/composer/composer-widget.vala:1597 +#: src/client/conversation-viewer/conversation-email.vala:173 #, c-format msgid "%s (%s)" msgstr "%s (%s)" -#: ../src/client/composer/composer-widget.vala:1560 +#: src/client/composer/composer-widget.vala:1634 #, c-format msgid "“%s” could not be found." msgstr "„%s” nem található." -#: ../src/client/composer/composer-widget.vala:1566 +#: src/client/composer/composer-widget.vala:1640 #, c-format msgid "“%s” is a folder." msgstr "„%s” egy mappa." -#: ../src/client/composer/composer-widget.vala:1572 +#: src/client/composer/composer-widget.vala:1646 #, c-format msgid "“%s” is an empty file." msgstr "„%s” egy üres fájl." -#: ../src/client/composer/composer-widget.vala:1585 +#: src/client/composer/composer-widget.vala:1659 #, c-format msgid "“%s” could not be opened for reading." msgstr "„%s” nem nyitható meg olvasásra." -#: ../src/client/composer/composer-widget.vala:1593 +#: src/client/composer/composer-widget.vala:1667 msgid "Cannot add attachment" msgstr "Nem sikerült mellékletet hozzáadni" -#: ../src/client/composer/composer-widget.vala:1645 -msgid "To: " -msgstr "Címzett: " - -#: ../src/client/composer/composer-widget.vala:1648 -msgid "Cc: " -msgstr "Másolat: " - -#: ../src/client/composer/composer-widget.vala:1651 -msgid "Bcc: " -msgstr "Titkos másolat: " +#. Translators: Human-readable version of the RFC 822 To header +#: src/client/composer/composer-widget.vala:1717 +#: src/client/conversation-viewer/conversation-email.vala:975 +#: src/engine/rfc822/rfc822-utils.vala:298 ui/conversation-message.ui:313 +msgid "To:" +msgstr "Címzett:" + +#. Translators: Human-readable version of the RFC 822 CC header +#: src/client/composer/composer-widget.vala:1723 +#: src/client/conversation-viewer/conversation-email.vala:980 +#: src/engine/rfc822/rfc822-utils.vala:303 ui/conversation-message.ui:358 +msgid "Cc:" +msgstr "Másolat:" + +#. Translators: Human-readable version of the RFC 822 BCC header +#: src/client/composer/composer-widget.vala:1729 +#: src/client/conversation-viewer/conversation-email.vala:985 +#: ui/conversation-message.ui:403 +msgid "Bcc:" +msgstr "Titkos másolat:" -#: ../src/client/composer/composer-widget.vala:1654 +#. Translators: Human-readable version of the RFC 822 Reply-To header +#: src/client/composer/composer-widget.vala:1735 msgid "Reply-To: " msgstr "Válaszcím: " -#: ../src/client/composer/composer-widget.vala:1786 +#: src/client/composer/composer-widget.vala:1875 msgid "Select Color" msgstr "Szín kiválasztása" -#. Displayed in the From dropdown to indicate an "alternate email address" -#. for an account. The first printf argument will be the alternate email -#. address, and the second will be the account's primary email address. -#: ../src/client/composer/composer-widget.vala:1986 +#. Displayed in the From dropdown to indicate an +#. "alternate email address" for an account. The first +#. printf argument will be the alternate email address, +#. and the second will be the account's primary email +#. address. +#: src/client/composer/composer-widget.vala:2065 #, c-format msgid "%1$s via %2$s" msgstr "%1$s ezen keresztül: %2$s" #. Composer label (with mnemonic underscore) for the account selector #. when choosing what address to send a message from. -#: ../src/client/composer/composer-widget.vala:2028 +#: src/client/composer/composer-widget.vala:2126 msgid "_From:" msgstr "_Feladó:" #. Translators: This is the name of the file chooser filter #. when inserting an image in the composer. -#: ../src/client/composer/composer-widget.vala:2252 +#: src/client/composer/composer-widget.vala:2352 msgid "Images" msgstr "Képek" -#: ../src/client/composer/spell-check-popover.vala:117 +#: src/client/composer/composer-window.vala:14 +msgid "New Message" +msgstr "Új levél" + +#: src/client/composer/spell-check-popover.vala:117 msgid "Remove this language from the preferred list" msgstr "Nyelv eltávolítása az előnyben részesített listáról" -#: ../src/client/composer/spell-check-popover.vala:121 +#: src/client/composer/spell-check-popover.vala:121 msgid "Add this language to the preferred list" msgstr "Nyelv hozzáadása az előnyben részesített listához" -#: ../src/client/composer/spell-check-popover.vala:217 +#: src/client/composer/spell-check-popover.vala:217 msgid "Search for more languages" msgstr "Több nyelv keresése" -#: ../src/client/conversation-list/formatted-conversation-data.vala:11 +#: src/client/conversation-list/conversation-list-view.vala:283 +msgid "Delete conversation" +msgstr "Beszélgetés törlése" + +#: src/client/conversation-list/conversation-list-view.vala:286 +#: ui/main-toolbar-menus.ui:5 +msgid "Mark as _Read" +msgstr "Megjelölés _olvasottként" + +#: src/client/conversation-list/conversation-list-view.vala:289 +#: ui/main-toolbar-menus.ui:9 +msgid "Mark as _Unread" +msgstr "Megjelölés ol_vasatlanként" + +#: src/client/conversation-list/conversation-list-view.vala:292 +#: ui/main-toolbar-menus.ui:17 +msgid "U_nstar" +msgstr "C_sillagozás megszüntetése" + +#: src/client/conversation-list/conversation-list-view.vala:294 +#: ui/main-toolbar-menus.ui:13 +msgid "_Star" +msgstr "_Csillagozás" + +#. Translators: Menu item to reply to a specific message. +#: src/client/conversation-list/conversation-list-view.vala:297 +#: ui/conversation-email-menus.ui:9 +msgid "_Reply" +msgstr "_Válasz" + +#: src/client/conversation-list/conversation-list-view.vala:298 +msgid "R_eply All" +msgstr "Válasz _mindenkinek" + +#. Translators: Menu item to forward a specific message. +#: src/client/conversation-list/conversation-list-view.vala:299 +#: ui/conversation-email-menus.ui:21 +msgid "_Forward" +msgstr "T_ovábbítás" + +#: src/client/conversation-list/formatted-conversation-data.vala:11 msgid "Me" msgstr "Én" #. Translators: This is the file type displayed for #. attachments with unknown file types. -#: ../src/client/conversation-viewer/conversation-email.vala:124 +#: src/client/conversation-viewer/conversation-email.vala:159 msgid "Unknown" msgstr "Ismeretlen" -#. Preview headers +#. Translators: Human-readable version of the RFC 822 From header +#: src/client/conversation-viewer/conversation-email.vala:970 +#: src/engine/rfc822/rfc822-utils.vala:289 +msgid "From:" +msgstr "Feladó:" + +#. Translators: Human-readable version of the RFC 822 Date header +#: src/client/conversation-viewer/conversation-email.vala:990 +#: src/engine/rfc822/rfc822-utils.vala:294 +msgid "Date:" +msgstr "Dátum:" + +#. Translators: Human-readable version of the RFC 822 Subject header +#: src/client/conversation-viewer/conversation-email.vala:995 +#: src/engine/rfc822/rfc822-utils.vala:292 +msgid "Subject:" +msgstr "Tárgy:" + +#: src/client/conversation-viewer/conversation-message.vala:65 +msgid "This email address may have been forged" +msgstr "Ez az e-mail cím hamisított lehet" + +#. Compact headers #. Translators: This is displayed in place of the from address #. when the message has no from address. -#: ../src/client/conversation-viewer/conversation-message.vala:331 +#: src/client/conversation-viewer/conversation-message.vala:394 msgid "No sender" msgstr "Nincs feladó" #. Translators: This separates multiple 'from' -#. addresses in the header preview for a message. -#: ../src/client/conversation-viewer/conversation-message.vala:586 +#. addresses in the compact header for a message. +#: src/client/conversation-viewer/conversation-message.vala:767 msgid ", " msgstr ", " #. Translators: This string is used as the HTML IMG ALT #. attribute value when displaying an inline image in an email #. that did not specify a file name. E.g. ImageCannot remove account " -msgstr "" -"Nem sikerült eltávolítani a fiókot " +#: ui/accounts_editor_edit_pane.ui:201 +msgid "Settings" +msgstr "Beállítások" -#: ../ui/account_cannot_remove.glade.h:2 -msgid "" -"A composer window associated with this account is currently open. Send or " -"discard the message and try again." -msgstr "" -"Ezen fiók szerkesztő ablaka jelenleg meg van nyitva. Küldje el vagy vesse el " -"a levelet, és próbálja újra." +#. This is a button in the account settings to show server settings. +#: ui/accounts_editor_edit_pane.ui:243 ui/accounts_editor_servers_pane.ui:8 +msgid "Server Settings" +msgstr "Kiszolgáló beállításai" + +#. This is the remove account button in the account settings. +#: ui/accounts_editor_edit_pane.ui:258 ui/accounts_editor_remove_pane.ui:23 +msgid "Remove Account" +msgstr "Fiók eltávolítása" -#: ../ui/account_list.glade.h:1 -msgid "Add account" -msgstr "Fiók hozzáadása" +#: ui/accounts_editor_edit_pane.ui:262 ui/accounts_editor_remove_pane.ui:27 +msgid "Remove this account from Geary" +msgstr "Fiók eltávolítása a Geary programból" -#: ../ui/account_list.glade.h:2 -msgid "Edit account" -msgstr "Fiók szerkesztése" +#: ui/accounts_editor_list_pane.ui:8 +msgid "Accounts" +msgstr "Fiókok" + +#: ui/accounts_editor_list_pane.ui:63 +msgid "To get started, select an email provider below." +msgstr "Kezdéshez válasszon egy e-mail szolgáltatót lent." + +#: ui/accounts_editor_list_pane.ui:76 +msgid "Welcome to Geary" +msgstr "Üdvözli a Geary" + +#. This title is shown to users when confirming if they want to remove an account. The string substitution is replaced with the account's name. +#: ui/accounts_editor_remove_pane.ui:73 +#, c-format +msgid "Confirm removing: %s" +msgstr "Eltávolítás megerősítése: %s" -#: ../ui/account_list.glade.h:3 +#: ui/accounts_editor_remove_pane.ui:91 +msgid "" +"Removing an account will remove it from Geary and delete locally cached " +"email data from your computer, but not from your service provider." +msgstr "" +"Egy fiók eltávolítása eltávolítja azt a Geary programból, és törli a " +"helyileg gyorsítótárazott e-mail adatokat a számítógépről, de nem törli " +"azokat a szolgáltatótól." + +#: ui/accounts_editor_remove_pane.ui:122 msgid "Remove account" msgstr "Fiók eltávolítása" -#: ../ui/account_spinner.glade.h:1 -msgid "Please wait while Geary validates your account." -msgstr "Kérem várjon, amíg a Geary érvényesíti a fiókját." +#: ui/accounts_editor_servers_pane.ui:17 +msgid "Cancel" +msgstr "Mégse" + +#: ui/accounts_editor_servers_pane.ui:47 +msgid "Apply" +msgstr "Alkalmaz" -#: ../ui/certificate_warning_dialog.glade.h:1 +#: ui/certificate_warning_dialog.glade:7 msgid "Untrusted Connection" msgstr "Nem megbízható kapcsolat" -#: ../ui/certificate_warning_dialog.glade.h:2 +#: ui/certificate_warning_dialog.glade:29 msgid "_Always Trust This Server" msgstr "Min_dig megbízom a kiszolgálóban" -#: ../ui/certificate_warning_dialog.glade.h:3 +#: ui/certificate_warning_dialog.glade:43 msgid "_Trust This Server" msgstr "_Megbízom a kiszolgálóban" -#: ../ui/certificate_warning_dialog.glade.h:4 +#: ui/certificate_warning_dialog.glade:57 msgid "_Don’t Trust This Server" msgstr "_Nem bízom meg a kiszolgálóban" -#: ../ui/composer-headerbar.ui.h:1 +#: ui/composer-headerbar.ui:17 ui/composer-headerbar.ui:174 msgid "Detach (Ctrl+D)" msgstr "Leválasztás (Ctrl+D)" -#: ../ui/composer-headerbar.ui.h:2 +#: ui/composer-headerbar.ui:57 ui/composer-headerbar.ui:83 msgid "Attach File (Ctrl+T)" msgstr "Fájl csatolása (Ctrl+T)" -#: ../ui/composer-headerbar.ui.h:3 +#: ui/composer-headerbar.ui:107 msgid "Include Original Attachments" msgstr "Eredeti mellékletek megtartása" -#: ../ui/composer-headerbar.ui.h:4 -msgid "Send (Ctrl+Enter)" -msgstr "Küldés (Ctrl+Enter)" - -#: ../ui/composer-headerbar.ui.h:5 +#: ui/composer-headerbar.ui:202 msgid "_Send" msgstr "_Küldés" -#: ../ui/composer-headerbar.ui.h:6 +#: ui/composer-headerbar.ui:207 +msgid "Send (Ctrl+Enter)" +msgstr "Küldés (Ctrl+Enter)" + +#: ui/composer-headerbar.ui:230 msgid "Discard and Close" msgstr "Elvetés és bezárás" -#: ../ui/composer-headerbar.ui.h:7 +#: ui/composer-headerbar.ui:254 msgid "Save and Close" msgstr "Mentés és bezárás" #. Note that this button and the Update button will never be shown at the same time to the user. -#: ../ui/composer-link-popover.ui.h:2 +#: ui/composer-link-popover.ui:41 msgid "Insert the new link with this URL" msgstr "Az új hivatkozás beszúrása ezzel az URL-lel" -#: ../ui/composer-link-popover.ui.h:3 +#: ui/composer-link-popover.ui:52 msgid "Link URL" msgstr "Hivatkozás URL" #. Note that this button and the Insert button will never be shown at the same time to the user. -#: ../ui/composer-link-popover.ui.h:5 +#: ui/composer-link-popover.ui:66 msgid "Update this link’s URL" msgstr "Hivatkozás URL-ének frissítése" -#: ../ui/composer-link-popover.ui.h:6 +#: ui/composer-link-popover.ui:86 msgid "Delete this link" msgstr "Hivatkozás törlése" -#: ../ui/composer-link-popover.ui.h:7 +#: ui/composer-link-popover.ui:106 msgid "Open this link" msgstr "Hivatkozás megnyitása" -#: ../ui/composer-menus.ui.h:1 +#: ui/composer-menus.ui:7 msgid "S_ans Serif" msgstr "T_alpatlan betűkészlet" -#: ../ui/composer-menus.ui.h:2 +#: ui/composer-menus.ui:12 msgid "S_erif" msgstr "Talpas b_etűkészlet" -#: ../ui/composer-menus.ui.h:3 +#: ui/composer-menus.ui:17 msgid "_Fixed Width" msgstr "_Rögzített szélességű" -#: ../ui/composer-menus.ui.h:4 +#: ui/composer-menus.ui:24 msgid "_Small" msgstr "Ki_csi" -#: ../ui/composer-menus.ui.h:5 +#: ui/composer-menus.ui:29 msgid "_Medium" msgstr "_Közepes" -#: ../ui/composer-menus.ui.h:6 +#: ui/composer-menus.ui:34 msgid "Lar_ge" msgstr "_Nagy" -#: ../ui/composer-menus.ui.h:7 +#: ui/composer-menus.ui:41 msgid "C_olor" msgstr "_Szín" -#: ../ui/composer-menus.ui.h:8 +#: ui/composer-menus.ui:47 ui/composer-menus.ui:62 msgid "_Rich Text" msgstr "_Rich Text" -#: ../ui/composer-menus.ui.h:9 +#: ui/composer-menus.ui:53 ui/composer-menus.ui:68 msgid "Show Extended Fields" msgstr "Kiterjesztett mezők megjelenítése" -#: ../ui/composer-menus.ui.h:10 +#: ui/composer-menus.ui:78 msgid "_Undo" msgstr "_Visszavonás" -#: ../ui/composer-menus.ui.h:11 +#: ui/composer-menus.ui:82 msgid "_Redo" msgstr "Új_ra" -#: ../ui/composer-menus.ui.h:12 +#: ui/composer-menus.ui:88 ui/composer-menus.ui:106 msgid "Cu_t" msgstr "_Kivágás" -#: ../ui/composer-menus.ui.h:13 ../ui/conversation-message-menus.ui.h:7 +#: ui/composer-menus.ui:92 ui/composer-menus.ui:110 +#: ui/conversation-message-menus.ui:37 msgid "_Copy" msgstr "_Másolás" -#: ../ui/composer-menus.ui.h:14 +#: ui/composer-menus.ui:96 ui/composer-menus.ui:114 msgid "_Paste" msgstr "_Beillesztés" -#: ../ui/composer-menus.ui.h:15 -msgctxt "Clipboard paste with rich text" -msgid "Paste _With Formatting" -msgstr "Beillesztés _formázással" +#: ui/composer-menus.ui:100 +msgctxt "Clipboard paste as plain text" +msgid "Paste _Without Formatting" +msgstr "Beillesztés formázás _nélkül" -#: ../ui/composer-menus.ui.h:16 +#: ui/composer-menus.ui:120 msgid "Select _All" msgstr "Ö_sszes kijelölése" -#: ../ui/composer-menus.ui.h:17 ../ui/conversation-message-menus.ui.h:9 +#: ui/composer-menus.ui:127 ui/conversation-message-menus.ui:49 msgid "_Inspect…" msgstr "_Vizsgálat…" #. Address(es) e-mail is to be sent to -#: ../ui/composer-widget.ui.h:2 +#: ui/composer-widget.ui:56 msgid "_To" msgstr "_Címzett" -#: ../ui/composer-widget.ui.h:3 +#: ui/composer-widget.ui:75 msgid "_Cc" msgstr "_Másolat" -#: ../ui/composer-widget.ui.h:4 +#: ui/composer-widget.ui:130 msgid "_Subject" msgstr "_Tárgy" -#: ../ui/composer-widget.ui.h:5 +#: ui/composer-widget.ui:149 msgid "_Bcc" msgstr "Titk_os másolat" -#: ../ui/composer-widget.ui.h:6 +#: ui/composer-widget.ui:179 msgid "_Reply-To" msgstr "_Válaszcím" #. Geary account mail will be sent from -#: ../ui/composer-widget.ui.h:8 +#: ui/composer-widget.ui:208 msgid "From" msgstr "Feladó" -#: ../ui/composer-widget.ui.h:9 +#: ui/composer-widget.ui:293 msgid "Drop files here" msgstr "Ejtse ide a fájlokat" -#: ../ui/composer-widget.ui.h:10 +#: ui/composer-widget.ui:309 msgid "To add them as attachments" msgstr "Mellékletként való hozzáadáshoz" -#: ../ui/composer-widget.ui.h:11 +#: ui/composer-widget.ui:353 msgid "Undo last edit (Ctrl+Z)" msgstr "Utolsó szerkesztés visszavonása (Ctrl+Z)" -#: ../ui/composer-widget.ui.h:12 +#: ui/composer-widget.ui:377 msgid "Redo last edit (Ctrl+Shift+Z)" msgstr "Utolsó szerkesztés ismétlése (Ctrl+Shift+Z)" -#: ../ui/composer-widget.ui.h:13 +#: ui/composer-widget.ui:415 msgid "Bold (Ctrl+B)" msgstr "Félkövér (Ctrl+B)" -#: ../ui/composer-widget.ui.h:14 +#: ui/composer-widget.ui:439 msgid "Italic (Ctrl+I)" msgstr "Dőlt (Ctrl+I)" -#: ../ui/composer-widget.ui.h:15 +#: ui/composer-widget.ui:463 msgid "Underline (Ctrl+U)" msgstr "Aláhúzott (Ctrl+U)" -#: ../ui/composer-widget.ui.h:16 +#: ui/composer-widget.ui:487 msgid "Strikethrough (Ctrl+K)" msgstr "Áthúzott (Ctrl+K)" -#: ../ui/composer-widget.ui.h:17 +#: ui/composer-widget.ui:525 +msgid "Insert unordered list" +msgstr "Felsorolás beszúrása" + +#: ui/composer-widget.ui:549 +msgid "Insert ordered list" +msgstr "Számozott lista beszúrása" + +#: ui/composer-widget.ui:587 msgid "Quote text (Ctrl+])" msgstr "Szöveg idézése (Ctrl+])" -#: ../ui/composer-widget.ui.h:18 +#: ui/composer-widget.ui:611 msgid "Unquote text (Ctrl+[)" msgstr "Szöveg idézésének visszavonása (Ctrl+[)" -#: ../ui/composer-widget.ui.h:19 +#: ui/composer-widget.ui:649 msgid "Insert or update selection link (Ctrl+L)" msgstr "Kijelölés hivatkozásának beszúrása vagy frissítése (Ctrl+L)" -#: ../ui/composer-widget.ui.h:20 +#: ui/composer-widget.ui:673 msgid "Insert an image (Ctrl+G)" msgstr "Kép beszúrása (Ctrl+G)" -#: ../ui/composer-widget.ui.h:21 +#: ui/composer-widget.ui:707 msgid "Remove selection formatting (Ctrl+Space)" msgstr "Kijelölés formázásának eltávolítása (Ctrl+Space)" -#: ../ui/composer-widget.ui.h:22 +#: ui/composer-widget.ui:731 msgid "Select spell checking languages" msgstr "Helyesírás-ellenőrzés nyelvének kiválasztása" -#: ../ui/conversation-email.ui.h:1 +#: ui/conversation-email.ui:27 msgid "Save all attachments" msgstr "Minden melléklet mentése" #. Note: The application will never show this button at the same time as unstar_button, one will always be hidden. -#: ../ui/conversation-email.ui.h:3 +#: ui/conversation-email.ui:50 msgid "Mark this message as starred" msgstr "Levél megjelölése csillagozottként" #. Note: The application will never show this button at the same time as star_button, one will always be hidden. -#: ../ui/conversation-email.ui.h:5 +#: ui/conversation-email.ui:72 msgid "Mark this message as not starred" msgstr "Levél megjelölése nem csillagozottként" -#: ../ui/conversation-email.ui.h:6 +#: ui/conversation-email.ui:95 msgid "Display the message menu" msgstr "A levél menü megjelenítése" -#: ../ui/conversation-email.ui.h:7 +#: ui/conversation-email.ui:161 msgid "Open selected attachments" msgstr "Kiválasztott mellékletek megnyitása" -#: ../ui/conversation-email.ui.h:8 +#: ui/conversation-email.ui:178 msgid "Save selected attachments" msgstr "Kiválasztott mellékletek mentése" -#: ../ui/conversation-email.ui.h:9 +#: ui/conversation-email.ui:195 msgid "Select all attachments" msgstr "Minden melléklet kiválasztása" -#: ../ui/conversation-email.ui.h:10 +#: ui/conversation-email.ui:240 msgid "Edit Draft" msgstr "Piszkozat szerkesztése" -#: ../ui/conversation-email.ui.h:11 +#: ui/conversation-email.ui:267 msgid "Draft message" msgstr "Piszkozat" -#: ../ui/conversation-email.ui.h:12 +#: ui/conversation-email.ui:283 msgid "This message has not yet been sent." msgstr "Ez a levél még nem lett elküldve." -#: ../ui/conversation-email.ui.h:13 -msgid "Try Again" -msgstr "Próbálja újra" - -#: ../ui/conversation-email.ui.h:14 +#: ui/conversation-email.ui:329 msgid "Message not saved" msgstr "A levél nincs mentve" -#: ../ui/conversation-email.ui.h:15 +#: ui/conversation-email.ui:345 msgid "This message was sent, but has not been saved to your account." msgstr "" "Ez a levél sikeresen el lett küldve, de nem sikerült elmenteni a fiókjába." -#: ../ui/conversation-email-menus.ui.h:2 +#. Translators: Menu item to reply to a specific message. +#: ui/conversation-email-menus.ui:15 msgid "Reply to _All" msgstr "_Válasz mindenkinek" -#: ../ui/conversation-email-menus.ui.h:4 +#. Translators: Menu item to mark a specific message as +#. read. +#: ui/conversation-email-menus.ui:30 msgid "_Mark Read" msgstr "_Megjelölés olvasottként" -#: ../ui/conversation-email-menus.ui.h:5 +#: ui/conversation-email-menus.ui:36 msgid "_Mark Unread" msgstr "_Megjelölés olvasatlanként" -#: ../ui/conversation-email-menus.ui.h:6 +#. Translators: Menu item to mark all messages in a +#. conversation from this one as unread. +#: ui/conversation-email-menus.ui:42 msgid "Mark Unread From _Here" msgstr "Megjelölés olvasatlanként _innentől" -#: ../ui/conversation-email-menus.ui.h:8 +#. Translators: Menu item to move a single, specific message +#. to the trash +#: ui/conversation-email-menus.ui:50 +msgid "_Trash" +msgstr "_Kuka" + +#. Translators: Menu item to delete a single, specific message +#: ui/conversation-email-menus.ui:57 +msgid "_Delete…" +msgstr "_Törlés…" + +#. Translators: Menu item to view the source for a message +#: ui/conversation-email-menus.ui:69 msgid "_View Source" msgstr "_Forrás megtekintése" -#: ../ui/conversation-email-menus.ui.h:11 +#: ui/conversation-email-menus.ui:87 msgid "_Save All" msgstr "_Mind mentése" -#: ../ui/conversation-message-menus.ui.h:1 +#: ui/conversation-message-menus.ui:7 msgid "_Open Link" msgstr "Hivatkozás _megnyitása" -#: ../ui/conversation-message-menus.ui.h:2 +#: ui/conversation-message-menus.ui:11 msgid "Copy Link _Address" msgstr "Hivatkozás_cím másolása" -#: ../ui/conversation-message-menus.ui.h:3 +#: ui/conversation-message-menus.ui:17 msgid "Send New _Message…" msgstr "Új _levél küldése…" -#: ../ui/conversation-message-menus.ui.h:4 +#: ui/conversation-message-menus.ui:21 msgid "Copy Email _Address" msgstr "_E-mail cím másolása" -#: ../ui/conversation-message-menus.ui.h:5 +#: ui/conversation-message-menus.ui:27 msgid "Save _Image As…" msgstr "_Kép mentése másként…" -#: ../ui/conversation-message-menus.ui.h:6 +#: ui/conversation-message-menus.ui:33 msgid "_Select All" msgstr "Ö_sszes kijelölése" -#: ../ui/conversation-message-menus.ui.h:8 +#: ui/conversation-message-menus.ui:43 msgid "Search for messages from" msgstr "Levelek keresése innen" -#: ../ui/conversation-message.ui.h:1 +#: ui/conversation-message.ui:64 msgid "From " msgstr "Feladó: " -#: ../ui/conversation-message.ui.h:2 +#: ui/conversation-message.ui:80 ui/conversation-message.ui:179 msgid "1/1/1970\t" msgstr "1/1/1970\t" -#: ../ui/conversation-message.ui.h:3 +#: ui/conversation-message.ui:103 msgid "Preview body text." msgstr "Levéltörzs előnézete." -#: ../ui/conversation-message.ui.h:4 +#: ui/conversation-message.ui:203 msgid "Sent by:" msgstr "Feladó:" -#: ../ui/conversation-message.ui.h:5 +#: ui/conversation-message.ui:248 msgid "Reply to:" msgstr "Válasz:" -#: ../ui/conversation-message.ui.h:6 +#: ui/conversation-message.ui:292 msgid "Subject" msgstr "Tárgy" -#: ../ui/conversation-message.ui.h:7 -msgid "To:" -msgstr "Címzett:" - -#: ../ui/conversation-message.ui.h:8 -msgid "Cc:" -msgstr "Másolat:" - -#: ../ui/conversation-message.ui.h:9 -msgid "Bcc:" -msgstr "Titkos másolat:" - -#: ../ui/conversation-message.ui.h:10 +#: ui/conversation-message.ui:502 msgid "Show Images" msgstr "Képek megjelenítése" -#: ../ui/conversation-message.ui.h:11 +#: ui/conversation-message.ui:515 msgid "Always Show From Sender" msgstr "Mindig jelenítse meg a feladótól" -#: ../ui/conversation-message.ui.h:12 +#: ui/conversation-message.ui:543 msgid "Remote images not shown" msgstr "A távoli képek nincsenek megjelenítve" -#: ../ui/conversation-message.ui.h:13 +#: ui/conversation-message.ui:560 msgid "Only show remote images from senders you trust." msgstr "A távoli képek megjelenítése csak a megbízható feladóktól." -#: ../ui/conversation-message.ui.h:14 +#: ui/conversation-message.ui:693 msgid "But actually goes to:" msgstr "De valójában ide:" -#: ../ui/conversation-message.ui.h:15 +#: ui/conversation-message.ui:724 msgid "The link appears to go to:" msgstr "Úgy tűnik, ez a hivatkozás ide mutat:" -#: ../ui/conversation-message.ui.h:16 +#: ui/conversation-message.ui:736 msgid "Deceptive link found" msgstr "Megtévesztő hivatkozás található" -#: ../ui/conversation-message.ui.h:17 +#: ui/conversation-message.ui:751 msgid "The email sender may be leading you to the wrong web site." msgstr "Az e-mail küldője talán a rossz weboldalra vezeti Önt." -#: ../ui/conversation-message.ui.h:18 +#: ui/conversation-message.ui:764 msgid "If unsure, contact the sender and ask before continuing." msgstr "" "Ha bizonytalan, vegye fel a kapcsolatot a küldővel, és kérdezzen rá a " "folytatás előtt." -#: ../ui/conversation-viewer.ui.h:1 +#: ui/conversation-viewer.ui:60 msgid "Find in conversation" msgstr "Keresés a beszélgetésben" -#: ../ui/conversation-viewer.ui.h:2 +#: ui/conversation-viewer.ui:74 msgid "Find the previous occurrence of the search string." msgstr "Előző keresése a beszélgetésben." -#: ../ui/conversation-viewer.ui.h:3 +#: ui/conversation-viewer.ui:95 msgid "Find the next occurrence of the search string." msgstr "Következő keresése a beszélgetésben." -#: ../ui/edit_alternate_emails.glade.h:1 -msgid "Remove email address" -msgstr "E-mail cím eltávolítása" - -#: ../ui/edit_alternate_emails.glade.h:2 -msgid "" -"Some email services require additional addresses be configured on the " -"server. Contact your email provider for more information." -msgstr "" -"Néhány e-mail szolgáltatás további címek beállítását igényli a kiszolgálón. " -"További információkért lépjen kapcsolatba az e-mail szolgáltatójával." - -#: ../ui/edit_alternate_emails.glade.h:4 -msgid "_Update" -msgstr "_Frissítés" - -#: ../ui/find_bar.glade.h:1 +#: ui/find_bar.glade:66 msgid "Find:" msgstr "Keresés:" -#: ../ui/find_bar.glade.h:2 +#: ui/find_bar.glade:89 msgid "_Previous" msgstr "_Előző" -#: ../ui/find_bar.glade.h:3 +#: ui/find_bar.glade:107 msgid "_Next" msgstr "_Következő" -#: ../ui/find_bar.glade.h:4 +#: ui/find_bar.glade:125 msgid "_Case sensitive" msgstr "K_is- és nagybetűk megkülönböztetése" -#: ../ui/find_bar.glade.h:5 +#: ui/find_bar.glade:145 msgid "label" msgstr "címke" -#: ../ui/gtk/help-overlay.ui.h:1 +#: ui/gtk/help-overlay.ui:9 msgid "Conversation Shortcuts" msgstr "Beszélgetési gyorsbillentyűk" -#: ../ui/gtk/help-overlay.ui.h:2 +#: ui/gtk/help-overlay.ui:13 ui/gtk/help-overlay.ui:254 msgctxt "shortcut window" msgid "General" msgstr "Általános" -#: ../ui/gtk/help-overlay.ui.h:3 +#: ui/gtk/help-overlay.ui:17 msgctxt "shortcut window" msgid "Move focus to the next/previous pane" msgstr "Fókusz átvitele a következő/előző panelra" -#: ../ui/gtk/help-overlay.ui.h:4 +#: ui/gtk/help-overlay.ui:24 msgctxt "shortcut window" msgid "Move focus to conversation list" msgstr "Fókusz átvitele a beszélgetések listájára" -#: ../ui/gtk/help-overlay.ui.h:5 +#: ui/gtk/help-overlay.ui:31 msgctxt "shortcut window" msgid "Detach composer window" msgstr "Levélíró ablak leválasztása" -#: ../ui/gtk/help-overlay.ui.h:6 +#: ui/gtk/help-overlay.ui:38 msgctxt "shortcut window" msgid "Close composer window" msgstr "Levélíró ablak bezárása" -#: ../ui/gtk/help-overlay.ui.h:7 +#: ui/gtk/help-overlay.ui:45 msgctxt "shortcut window" msgid "Show keyboard shortcuts" msgstr "Gyorsbillentyűk megjelenítése" -#: ../ui/gtk/help-overlay.ui.h:8 +#: ui/gtk/help-overlay.ui:52 msgctxt "shortcut window" msgid "Show help" msgstr "Súgó megjelenítése" -#: ../ui/gtk/help-overlay.ui.h:9 +#: ui/gtk/help-overlay.ui:59 msgctxt "shortcut window" msgid "Quit the application" msgstr "Kilépés az alkalmazásból" -#: ../ui/gtk/help-overlay.ui.h:10 +#: ui/gtk/help-overlay.ui:68 msgctxt "shortcut window" msgid "Search" msgstr "Keresés" -#: ../ui/gtk/help-overlay.ui.h:11 +#: ui/gtk/help-overlay.ui:72 msgctxt "shortcut window" msgid "Jump to search box" msgstr "Ugrás a keresődobozra" -#: ../ui/gtk/help-overlay.ui.h:12 +#: ui/gtk/help-overlay.ui:79 msgctxt "shortcut window" msgid "Find in current conversation" msgstr "Keresés a jelenlegi beszélgetésben" -#: ../ui/gtk/help-overlay.ui.h:13 +#: ui/gtk/help-overlay.ui:86 msgctxt "shortcut window" msgid "Find next/previous in current conversation" msgstr "Következő/előző keresése a beszélgetésben" -#: ../ui/gtk/help-overlay.ui.h:14 +#: ui/gtk/help-overlay.ui:95 ui/gtk/help-overlay.ui:274 msgctxt "shortcut window" msgid "Actions" msgstr "Műveletek" -#: ../ui/gtk/help-overlay.ui.h:15 +#: ui/gtk/help-overlay.ui:99 msgctxt "shortcut window" msgid "Compose a new message" msgstr "Új levél írása" -#: ../ui/gtk/help-overlay.ui.h:16 +#: ui/gtk/help-overlay.ui:106 msgctxt "shortcut window" msgid "Reply to sender " msgstr "Válasz a feladónak " -#: ../ui/gtk/help-overlay.ui.h:17 +#: ui/gtk/help-overlay.ui:113 msgctxt "shortcut window" msgid "Reply to all" msgstr "Válasz mindenkinek" -#: ../ui/gtk/help-overlay.ui.h:18 +#: ui/gtk/help-overlay.ui:120 msgctxt "shortcut window" msgid "Forward" msgstr "Továbbítás" -#: ../ui/gtk/help-overlay.ui.h:19 +#: ui/gtk/help-overlay.ui:127 msgctxt "shortcut window" msgid "Archive" msgstr "Archiválás" -#: ../ui/gtk/help-overlay.ui.h:20 +#: ui/gtk/help-overlay.ui:134 msgctxt "shortcut window" msgid "Move to trash" msgstr "Áthelyezés a Kukába" -#: ../ui/gtk/help-overlay.ui.h:21 +#: ui/gtk/help-overlay.ui:141 msgctxt "shortcut window" msgid "Toggle spam" msgstr "Levélszemét jelölés be/ki" -#: ../ui/gtk/help-overlay.ui.h:22 +#: ui/gtk/help-overlay.ui:148 msgctxt "shortcut window" msgid "Move the conversation" msgstr "Beszélgetés áthelyezése" -#: ../ui/gtk/help-overlay.ui.h:23 +#: ui/gtk/help-overlay.ui:155 msgctxt "shortcut window" msgid "Label the conversation" msgstr "Beszélgetés címkézése" -#: ../ui/gtk/help-overlay.ui.h:24 +#: ui/gtk/help-overlay.ui:163 msgctxt "shortcut window" msgid "Mark read" msgstr "Megjelölés olvasottként" -#: ../ui/gtk/help-overlay.ui.h:25 +#: ui/gtk/help-overlay.ui:170 msgctxt "shortcut window" msgid "Mark unread" msgstr "Megjelölés olvasatlanként" -#: ../ui/gtk/help-overlay.ui.h:26 +#: ui/gtk/help-overlay.ui:179 msgctxt "shortcut window" msgid "View" msgstr "Nézet" -#: ../ui/gtk/help-overlay.ui.h:27 +#: ui/gtk/help-overlay.ui:183 msgctxt "shortcut window" msgid "Zoom in" msgstr "Nagyítás" -#: ../ui/gtk/help-overlay.ui.h:28 +#: ui/gtk/help-overlay.ui:190 msgctxt "shortcut window" msgid "Zoom out" msgstr "Kicsinyítés" -#: ../ui/gtk/help-overlay.ui.h:29 +#: ui/gtk/help-overlay.ui:197 msgctxt "shortcut window" msgid "Reset zoom" msgstr "Nagyítás visszaállítása" -#: ../ui/gtk/help-overlay.ui.h:30 +#: ui/gtk/help-overlay.ui:206 msgctxt "shortcut window" msgid "Additional Shortcuts" msgstr "További gyorsbillentyűk" -#: ../ui/gtk/help-overlay.ui.h:31 +#: ui/gtk/help-overlay.ui:210 msgctxt "shortcut window" msgid "Star" msgstr "Csillagozás" -#: ../ui/gtk/help-overlay.ui.h:32 +#: ui/gtk/help-overlay.ui:217 msgctxt "shortcut window" msgid "Unstar" msgstr "Csillagozás megszüntetése" -#: ../ui/gtk/help-overlay.ui.h:33 +#: ui/gtk/help-overlay.ui:224 msgctxt "shortcut window" msgid "Delete" msgstr "Törlés" -#: ../ui/gtk/help-overlay.ui.h:34 +#: ui/gtk/help-overlay.ui:231 msgctxt "shortcut window" msgid "Jump to next (older) conversation" msgstr "Ugrás a következő (régebbi) beszélgetésre" -#: ../ui/gtk/help-overlay.ui.h:35 +#: ui/gtk/help-overlay.ui:238 msgctxt "shortcut window" msgid "Jump to previous (newer) conversation" msgstr "Ugrás az előző (újabb) beszélgetésre" -#: ../ui/gtk/help-overlay.ui.h:36 +#: ui/gtk/help-overlay.ui:250 msgid "Composer Shortcuts" msgstr "Szerkesztő gyorsbillentyűk" -#: ../ui/gtk/help-overlay.ui.h:37 +#: ui/gtk/help-overlay.ui:258 msgctxt "shortcut window" msgid "Quote text" msgstr "Szöveg idézése" -#: ../ui/gtk/help-overlay.ui.h:38 +#: ui/gtk/help-overlay.ui:265 msgctxt "shortcut window" msgid "Unquote text" msgstr "Szöveg idézésének visszavonása" -#: ../ui/gtk/help-overlay.ui.h:39 +#: ui/gtk/help-overlay.ui:278 msgctxt "shortcut window" msgid "Send" msgstr "Küldés" -#: ../ui/gtk/help-overlay.ui.h:40 +#: ui/gtk/help-overlay.ui:285 msgctxt "shortcut window" msgid "Add attachment" msgstr "Melléklet hozzáadása" -#: ../ui/gtk/help-overlay.ui.h:41 +#: ui/gtk/help-overlay.ui:294 msgctxt "shortcut window" msgid "Rich text mode" msgstr "Rich text mód" -#: ../ui/gtk/help-overlay.ui.h:42 +#: ui/gtk/help-overlay.ui:298 msgctxt "shortcut window" msgid "Bold text" msgstr "Szöveg félkövérré tétele" -#: ../ui/gtk/help-overlay.ui.h:43 +#: ui/gtk/help-overlay.ui:305 msgctxt "shortcut window" msgid "Italicize text" msgstr "Szöveg dőltté tétele" -#: ../ui/gtk/help-overlay.ui.h:44 +#: ui/gtk/help-overlay.ui:312 msgctxt "shortcut window" msgid "Underline text" msgstr "Szöveg aláhúzása" -#: ../ui/gtk/help-overlay.ui.h:45 +#: ui/gtk/help-overlay.ui:319 msgctxt "shortcut window" msgid "Strike text" msgstr "Szöveg áthúzása" -#: ../ui/gtk/help-overlay.ui.h:46 +#: ui/gtk/help-overlay.ui:326 msgctxt "shortcut window" msgid "Insert a link" msgstr "Hivatkozás beszúrása" -#: ../ui/gtk/help-overlay.ui.h:47 +#: ui/gtk/help-overlay.ui:333 msgctxt "shortcut window" msgid "Remove formatting" msgstr "Formázás eltávolítása" -#: ../ui/gtk/menus.ui.h:2 -msgid "A_ccounts" -msgstr "F_iókok" +#: ui/main-toolbar.ui:23 +msgctxt "tooltip" +msgid "Compose Message" +msgstr "Levél írása" -#: ../ui/gtk/menus.ui.h:4 -msgid "_Keyboard Shortcuts" -msgstr "Gyorsbille_ntyűk" +#: ui/main-toolbar.ui:52 +msgid "Toggle search bar" +msgstr "Keresősáv ki- vagy bekapcsolása" -#: ../ui/login.glade.h:1 -msgid "email@example.com" -msgstr "email@example.com" +#: ui/main-toolbar.ui:112 +msgid "Reply" +msgstr "Válasz" -#: ../ui/login.glade.h:2 ../ui/password-dialog.glade.h:3 -msgid "Password" -msgstr "Jelszó" +#: ui/main-toolbar.ui:135 +msgid "Reply All" +msgstr "Válasz mindenkinek" -#: ../ui/login.glade.h:3 -msgid "E_mail address" -msgstr "E-mail cím" +#: ui/main-toolbar.ui:158 +msgid "Forward" +msgstr "Továbbítás" -#: ../ui/login.glade.h:4 -msgid "_Password" -msgstr "_Jelszó" - -#: ../ui/login.glade.h:5 -msgid "S_ervice" -msgstr "_Szolgáltatás" - -#: ../ui/login.glade.h:6 -msgid "N_ame" -msgstr "_Név" - -#: ../ui/login.glade.h:8 -msgid "N_ickname" -msgstr "_Becenév" - -#: ../ui/login.glade.h:9 -msgid "Work, Home, etc." -msgstr "Munkahelyi, otthoni, stb." - -#: ../ui/login.glade.h:10 -msgid "_Save sent mail" -msgstr "Elküldött levél _mentése" - -#: ../ui/login.glade.h:11 -msgid "Addi_tional email addresses…" -msgstr "_További e-mail címek…" - -#: ../ui/login.glade.h:12 -msgid "IMAP settings" -msgstr "IMAP beállítások" - -#: ../ui/login.glade.h:13 -msgid "Se_rver" -msgstr "_Kiszolgáló" +#: ui/main-toolbar.ui:264 +msgid "Toggle find bar" +msgstr "Keresősáv ki- vagy bekapcsolása" -#: ../ui/login.glade.h:14 -msgid "imap.example.com" -msgstr "imap.example.com" +#: ui/main-toolbar.ui:306 +msgid "_Archive" +msgstr "_Archiválás" -#: ../ui/login.glade.h:15 -msgid "P_ort" -msgstr "_Port" +#: ui/main-toolbar-menus.ui:21 +msgid "Mark as S_pam" +msgstr "Megjelölés levél_szemétként" -#: ../ui/login.glade.h:16 -msgid "smtp.example.com" -msgstr "smtp.example.com" +#: ui/main-toolbar-menus.ui:25 +msgid "Mark as not S_pam" +msgstr "Megjelölés nem levél_szemétként" -#: ../ui/login.glade.h:17 -msgid "Ser_ver" -msgstr "Ki_szolgáló" - -#: ../ui/login.glade.h:18 -msgid "Por_t" -msgstr "Por_t" - -#: ../ui/login.glade.h:19 -msgid "SMTP settings" -msgstr "SMTP beállítások" - -#: ../ui/login.glade.h:20 -msgid "User_name" -msgstr "_Felhasználónév" - -#: ../ui/login.glade.h:21 -msgid "Pass_word" -msgstr "_Jelszó" - -#: ../ui/login.glade.h:22 -msgid "SMTP username" -msgstr "SMTP felhasználónév" - -#: ../ui/login.glade.h:23 -msgid "SMTP password" -msgstr "SMTP jelszó" - -#: ../ui/login.glade.h:24 -msgid "_Username" -msgstr "_Felhasználónév" - -#: ../ui/login.glade.h:25 -msgid "IMAP username" -msgstr "IMAP felhasználónév" - -#: ../ui/login.glade.h:26 -msgid "IMAP password" -msgstr "IMAP jelszó" - -#: ../ui/login.glade.h:27 -msgid "Encr_yption" -msgstr "_Titkosítás" - -#: ../ui/login.glade.h:28 -msgid "Encrypt_ion" -msgstr "T_itkosítás" - -#: ../ui/login.glade.h:30 -msgid "SSL/TLS" -msgstr "SSL/TLS" - -#: ../ui/login.glade.h:31 -msgid "STARTTLS" -msgstr "STARTTLS" - -#: ../ui/login.glade.h:32 -msgid "No authentication re_quired" -msgstr "Nincs szükség _hitelesítésre" - -#: ../ui/login.glade.h:33 -msgid "Use IMAP cre_dentials" -msgstr "IMAP hitelesítési _adatok használata" - -#: ../ui/login.glade.h:34 -msgid "Composer" -msgstr "Szerkesztő" - -#: ../ui/login.glade.h:35 -msgid "Save dra_fts on server" -msgstr "_Piszkozatok mentése a kiszolgálón" - -#: ../ui/login.glade.h:36 -msgid "Si_gn emails (HTML allowed):" -msgstr "E-mailek _aláírása (HTML engedélyezett):" - -#: ../ui/login.glade.h:37 -msgid "Storage" -msgstr "Tároló" - -#: ../ui/login.glade.h:38 -msgid "_Download mail" -msgstr "Levél l_etöltése" - -#: ../ui/main-toolbar.ui.h:1 -msgid "Empty Spam or Trash folders" -msgstr "Levélszemét vagy kuka mappák ürítése" +#: ui/main-toolbar-menus.ui:32 +msgid "Empty _Spam…" +msgstr "_Levélszemét ürítése…" + +#: ui/main-toolbar-menus.ui:36 +msgid "Empty _Trash…" +msgstr "_Kuka ürítése…" + +#: ui/main-toolbar-menus.ui:42 +msgid "_Accounts" +msgstr "_Fiókok" + +#: ui/main-toolbar-menus.ui:50 +msgid "_Keyboard Shortcuts" +msgstr "Gyorsbille_ntyűk" + +#: ui/main-toolbar-menus.ui:61 +msgid "_About Geary" +msgstr "A Geary _névjegye" + +#. Infobar title when one or more accounts are offline +#: ui/main-window.ui:183 +msgid "Working offline" +msgstr "Kapcsolat nélküli munka" + +#. Label and tooltip for offline infobar +#: ui/main-window.ui:197 +msgid "" +"Your computer does not appear to be connected to the Internet.\n" +"You will not be able to send or receive email until it is re-connected." +msgstr "" +"Úgy tűnik, hogy a számítógépe nem kapcsolódik az internethez.\n" +"Nem lesz képes leveleket küldeni vagy fogadni, amíg nem kapcsolódik újra." + +#. Label and tooltip for offline infobar +#: ui/main-window.ui:200 +msgid "You will not be able to send or receive email until re-connected." +msgstr "" +"Nem lesz képes leveleket küldeni vagy fogadni, amíg nem kapcsolódik újra." + +#. Button label for displaying technical details about an account problem +#. Dialog title for displaying technical details of a problem. Same as the button that invokes it. +#: ui/main-window.ui:247 ui/problem-details-dialog.ui:13 +msgid "Details" +msgstr "Részletek" + +#. Button label for retrying an account problem +#: ui/main-window.ui:261 +msgid "Retry" +msgstr "Újra" + +#. Infobar title when one or more accounts have encounted an error +#: ui/main-window.ui:294 +msgid "Account problem" +msgstr "Fiókprobléma" + +#. Label and tooltip for account service problem infobar +#: ui/main-window.ui:308 +msgid "" +"Geary encountered a problem connecting to an account.\n" +"Please check your Internet connection, the server configuration and try " +"again." +msgstr "" +"A Geary problémába ütközött egy fiókhoz való kapcsolódáskor.\n" +"Ellenőrizze az internetkapcsolatát és a kiszolgáló beállítását, majd " +"próbálja újra." + +#. Label and tooltip for account service problem infobar +#: ui/main-window.ui:311 +msgid "Geary encountered a problem connecting to an account." +msgstr "A Geary problémába ütközött egy fiókhoz való kapcsolódáskor." + +#. Button label for retrying TLS cert validation +#: ui/main-window.ui:358 +msgid "Check" +msgstr "Ellenőrzés" + +#. Button tooltip for retrying TLS cert validation +#: ui/main-window.ui:362 +msgid "Check the security details for the connection" +msgstr "A kapcsolat biztonsági részleteinek ellenőrzése" + +#. Infobar title when one or more accounts have a TLS cert validation error +#: ui/main-window.ui:391 +msgid "Security problem" +msgstr "Biztonsági probléma" + +#. Label and tooltip for TLS cert validation error infobar +#: ui/main-window.ui:405 +msgid "" +"An account has reported an untrusted server.\n" +"Please check the server configuration and try again." +msgstr "" +"Egy fiók nem megbízható kiszolgálót jelentett.\n" +"Ellenőrizze a kiszolgáló beállításai, és próbálja újra." + +#. Label and tooltip for TLS cert validation error infobar +#: ui/main-window.ui:408 +msgid "An account has reported an untrusted server." +msgstr "Egy fiók nem megbízható kiszolgálót jelentett." + +#. Button tooltip for retrying when a login error has occurred +#: ui/main-window.ui:459 +msgid "Retry login, you will be prompted for your password" +msgstr "Bejelentkezés újrapróbálása, a jelszava bekérésre fog kerülni" + +#. Infobar title when one or more accounts have a login error +#: ui/main-window.ui:488 +msgid "Login problem" +msgstr "Bejelentkezési probléma" + +#. Label and tooltip for authentication problem infobar +#: ui/main-window.ui:502 +msgid "" +"An account has reported an incorrect login or password.\n" +"Please check your login name and try again." +msgstr "" +"Egy fiók helytelen bejelentkezési nevet vagy jelszót jelentett.\n" +"Ellenőrizze a bejelentkezési nevét, és próbálja újra." + +#. Label and tooltip for authentication problem infobar +#: ui/main-window.ui:505 +msgid "An account has reported an incorrect login or password." +msgstr "Egy fiók helytelen bejelentkezési nevet vagy jelszót jelentett." -#: ../ui/password-dialog.glade.h:1 +#: ui/password-dialog.glade:74 msgid "SMTP Credentials" msgstr "SMTP hitelesítési adatok" -#: ../ui/password-dialog.glade.h:2 +#: ui/password-dialog.glade:91 msgid "Username" msgstr "Felhasználónév" -#: ../ui/password-dialog.glade.h:4 +#: ui/password-dialog.glade:152 msgid "_Remember password" msgstr "Jelszó _megjegyzése" -#: ../ui/password-dialog.glade.h:6 +#: ui/password-dialog.glade:210 msgid "_Authenticate" msgstr "_Hitelesítés" -#: ../ui/preferences-dialog.ui.h:1 +#: ui/preferences-dialog.ui:38 msgid "Reading" msgstr "Olvasás" -#: ../ui/preferences-dialog.ui.h:2 +#: ui/preferences-dialog.ui:51 msgid "_Automatically select next message" msgstr "Következő levél _automatikus kiválasztása" -#: ../ui/preferences-dialog.ui.h:3 +#: ui/preferences-dialog.ui:70 msgid "_Display conversation preview" msgstr "_Beszélgetés előnézetének megjelenítése" -#: ../ui/preferences-dialog.ui.h:4 +#: ui/preferences-dialog.ui:89 msgid "Use _three pane view" msgstr "_Három ablaktábla nézet használata" -#: ../ui/preferences-dialog.ui.h:5 +#: ui/preferences-dialog.ui:113 msgid "Notifications" msgstr "Értesítések" -#: ../ui/preferences-dialog.ui.h:6 +#: ui/preferences-dialog.ui:126 msgid "_Play notification sounds" msgstr "Értesítő hangok ­_lejátszása" -#: ../ui/preferences-dialog.ui.h:7 +#: ui/preferences-dialog.ui:145 msgid "Show _notifications for new mail" msgstr "É_rtesítések megjelenítése új levél érkezésekor" -#: ../ui/preferences-dialog.ui.h:8 -msgid "Always _watch for new mail" -msgstr "Mindig _figyeljen az új levél érkezésére" - -#: ../ui/preferences-dialog.ui.h:9 -msgid "Geary will run in the background and notify of new mail" -msgstr "A Geary a háttérben fog futni és értesíteni fog új levél érkezésekor" +#: ui/preferences-dialog.ui:164 +msgid "_Watch for new mail when closed" +msgstr "Zárt állapotban is _figyeljen az új levél érkezésére" + +#: ui/preferences-dialog.ui:168 +msgid "Geary will keep running after all windows are closed" +msgstr "A Geary továbbra is futni fog az összes ablak bezárása után" -#: ../ui/preferences-dialog.ui.h:10 +#: ui/preferences-dialog.ui:195 msgid "Preferences" msgstr "Beállítások" -#: ../ui/remove_confirm.glade.h:1 +#. Button label for copying technical information to the clipboard +#: ui/problem-details-dialog.ui:17 +msgid "Copy to Clipboard" +msgstr "Másolás a vágólapra" + +#. Button tooltip for copying technical information to the clipboard +#: ui/problem-details-dialog.ui:21 msgid "" -"Are you sure you want to remove this " -"account? " +"Copy technical details to clipboard for pasting into an email or bug report" msgstr "" -"Biztosan el szeretné távolítani ezt a " -"fiókot? " +"Műszaki részletek vágólapra másolása, e-mailbe vagy hibajelentésbe történő " +"beillesztéshez" -#: ../ui/remove_confirm.glade.h:2 +#: ui/problem-details-dialog.ui:73 msgid "" -"All email associated with this account will be removed from your computer. " -"This will not affect email on the server." +"If the problem is serious or persists, please copy and send these details to " +"the mailing list " +"or file a new " +"bug report." msgstr "" -"A fiókhoz tartozó összes e-mail el lesz távolítva a számítógépről. Ez nem " -"fogja érinteni a kiszolgálón lévő e-maileket." - -#: ../ui/remove_confirm.glade.h:3 -msgid "Nickname:" -msgstr "Becenév:" +"Ha a probléma komoly, és továbbra is fennáll, akkor másolja ki és küldje el " +"a részleteket a levelezőlistára vagy adjon fel egy új hibajelentést." -#: ../ui/remove_confirm.glade.h:4 -msgid "Email address:" -msgstr "E-mail cím:" +#: ui/problem-details-dialog.ui:89 +msgid "Details:" +msgstr "Részletek:" -#: ../ui/upgrade_dialog.glade.h:1 +#: ui/upgrade_dialog.glade:60 msgid "Geary update in progress…" msgstr "Geary frissítés folyamatban…" - -#~ msgid "Mail Client" -#~ msgstr "Levelezőkliens" diff -Nru geary-0.12.4/po/id.po geary-3.32.0/po/id.po --- geary-0.12.4/po/id.po 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/po/id.po 2019-03-17 13:39:29.000000000 +0000 @@ -9,11 +9,10 @@ msgid "" msgstr "" "Project-Id-Version: geary master\n" -"Report-Msgid-Bugs-To: https://bugzilla.gnome.org/enter_bug.cgi?" -"product=geary&keywords=I18N+L10N&component=internationalization\n" -"POT-Creation-Date: 2017-10-02 14:41+0000\n" -"PO-Revision-Date: 2017-10-04 17:26+0700\n" -"Last-Translator: Kukuh Syafaat \n" +"Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/geary/issues\n" +"POT-Creation-Date: 2018-10-24 11:36+0000\n" +"PO-Revision-Date: 2018-11-15 23:24+0700\n" +"Last-Translator: Kukuh Syafaat \n" "Language-Team: Indonesian (http://www.transifex.com/projects/p/geary/" "language/id/)\n" "Language: id\n" @@ -21,29 +20,56 @@ "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Poedit 2.0.3\n" +"X-Generator: Poedit 2.0.6\n" + +#: desktop/geary-attach.contract.desktop.in:3 +msgid "Send by email" +msgstr "Kirim melalui surel" + +#. Translators: Do NOT translate or transliterate this text (this is an icon file name)! +#: desktop/geary-attach.contract.desktop.in:5 +msgid "mail-send" +msgstr "mail-send" + +#: desktop/geary-attach.contract.desktop.in:6 +msgid "Send files using Geary" +msgstr "Kirim berkas memakai Geary" #. Translators: The application name -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:2 -#: ../desktop/org.gnome.Geary.desktop.in.h:1 -#: ../desktop/geary-autostart.desktop.in.h:1 +#: desktop/geary-autostart.desktop.in:3 +#: desktop/org.gnome.Geary.appdata.xml.in:11 +#: desktop/org.gnome.Geary.desktop.in:3 msgid "Geary" msgstr "Geary" -#. Translators: The development team's name -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:4 -msgid "Geary Development Team" -msgstr "Tim Pengembang Geary" +#: desktop/geary-autostart.desktop.in:4 desktop/org.gnome.Geary.desktop.in:4 +msgid "Email" +msgstr "Surel" #. Translators: The application's summary / tagline -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:6 -#: ../desktop/org.gnome.Geary.desktop.in.h:4 -#: ../desktop/geary-autostart.desktop.in.h:4 -#: ../src/client/application/geary-application.vala:21 +#: desktop/geary-autostart.desktop.in:5 +#: desktop/org.gnome.Geary.appdata.xml.in:15 +#: desktop/org.gnome.Geary.desktop.in:5 +#: src/client/application/geary-application.vala:21 msgid "Send and receive email" msgstr "Kirim dan terima surel" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:7 +#. Translators: These are desktop search terms. Do not translate semicolons, end line with a semicolon. +#: desktop/geary-autostart.desktop.in:7 +msgid "Email;E-mail;Mail;" +msgstr "Surel;Surat;Email;E-mail;Mail;" + +#. Translators: Do NOT translate or transliterate this text (this is an icon file name)! +#: desktop/geary-autostart.desktop.in:9 desktop/org.gnome.Geary.desktop.in:9 +msgid "org.gnome.Geary" +msgstr "org.gnome.Geary" + +#. Translators: The development team's name +#: desktop/org.gnome.Geary.appdata.xml.in:13 +msgid "Geary Development Team" +msgstr "Tim Pengembang Geary" + +#: desktop/org.gnome.Geary.appdata.xml.in:17 msgid "" "Geary is an email application built around conversations, for the GNOME 3 " "desktop. It allows you to read, find and send email with a straightforward, " @@ -53,7 +79,7 @@ "desktop GNOME 3. Memungkinkan Anda membaca, mencari, dan mengirim surel " "dengan antar muka yang modern, mudah." -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:8 +#: desktop/org.gnome.Geary.appdata.xml.in:22 msgid "" "Conversations allow you to read a complete discussion without having to find " "and click from message to message." @@ -61,98 +87,259 @@ "Percakapan memungkinkan Anda membaca diskusi lengkap tanpa perlu mencari dan " "mengklik dari pesan ke pesan." -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:9 +#: desktop/org.gnome.Geary.appdata.xml.in:26 msgid "Geary’s features include:" msgstr "Fitur Geary termasuk:" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:10 +#: desktop/org.gnome.Geary.appdata.xml.in:28 msgid "Quick email account setup" msgstr "Penyiapan akun surel cepat" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:11 +#: desktop/org.gnome.Geary.appdata.xml.in:29 msgid "Shows related messages together in conversations" msgstr "Menampilkan pesan-pesan yang berhubungan bersama-sama dalam percakapan" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:12 +#: desktop/org.gnome.Geary.appdata.xml.in:30 msgid "Fast, full text and keyword search" msgstr "Pencarian kata kunci dan teks penuh yang cepat" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:13 +#: desktop/org.gnome.Geary.appdata.xml.in:31 msgid "Full-featured HTML and plain text message composer" msgstr "Penyusun pesan teks polos dan HTML berfitur lengkap" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:14 +#: desktop/org.gnome.Geary.appdata.xml.in:32 msgid "Desktop notification of new mail" msgstr "Pemberitahuan desktop atas surat baru" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:15 +#: desktop/org.gnome.Geary.appdata.xml.in:33 msgid "Compatible with GMail, Yahoo! Mail, Outlook.com and other IMAP servers" msgstr "" "Kompatibel dengan GMail, Yahoo! Mail, Outlook.com, dan server IMAP lain" #. Translators: A screenshot description. -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:17 +#: desktop/org.gnome.Geary.appdata.xml.in:47 msgid "Geary displaying a conversation" msgstr "Geary menampilkan percakapan" #. Translators: A screenshot description. -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:19 +#: desktop/org.gnome.Geary.appdata.xml.in:52 msgid "Geary showing the rich text composer" msgstr "Geary menunjukkan penyusun rich text" -#: ../desktop/org.gnome.Geary.desktop.in.h:2 -#: ../desktop/geary-autostart.desktop.in.h:2 -msgid "Email" -msgstr "Surel" - -#: ../desktop/org.gnome.Geary.desktop.in.h:3 -msgid "Geary Email" -msgstr "Surel Geary" - #. Translators: These are desktop search terms. Do not translate semicolons, end line with a semicolon. -#: ../desktop/org.gnome.Geary.desktop.in.h:6 +#: desktop/org.gnome.Geary.desktop.in:7 msgid "Mail;E-mail;IMAP;GMail;Yahoo;Hotmail;Outlook;" msgstr "Surat;Surel;Mail;E-mail;IMAP;GMail;Yahoo;Hotmail;Outlook;" -#: ../desktop/org.gnome.Geary.desktop.in.h:7 ../ui/gtk/menus.ui.h:1 +#: desktop/org.gnome.Geary.desktop.in:21 ui/gtk/menus.ui:7 msgid "Compose Message" msgstr "Susun Pesan" -#: ../desktop/geary-autostart.desktop.in.h:3 -msgid "Geary Mail" -msgstr "Surat Geary" +#: desktop/org.gnome.Geary.gschema.xml:8 +msgid "Maximize window" +msgstr "Maksimalkan jendela" -#: ../desktop/geary-autostart.desktop.in.h:5 -msgid "Email;E-mail;Mail;" -msgstr "Surel;Surat;Email;E-mail;Mail;" +#: desktop/org.gnome.Geary.gschema.xml:9 +msgid "True if the application window is maximized, false otherwise." +msgstr "True jika jendela aplikasi dimaksimalkan, false jika sebaliknya." -#: ../desktop/geary-attach.contract.in.h:1 -msgid "Send by email" -msgstr "Kirim melalui surel" +#: desktop/org.gnome.Geary.gschema.xml:14 +msgid "Width of window" +msgstr "Lebar jendela" -#: ../desktop/geary-attach.contract.in.h:2 -msgid "Send files using Geary" -msgstr "Kirim berkas memakai Geary" +#: desktop/org.gnome.Geary.gschema.xml:15 +msgid "The last recorded width of the application window." +msgstr "Lebar yang tersimpan dari jendela pustaka aplikasi sebelumnya." + +#: desktop/org.gnome.Geary.gschema.xml:20 +msgid "Height of window" +msgstr "Tinggi jendela" + +#: desktop/org.gnome.Geary.gschema.xml:21 +msgid "The last recorded height of the application window." +msgstr "Tinggi yang tersimpan dari jendela pustaka aplikasi sebelumnya." + +#: desktop/org.gnome.Geary.gschema.xml:26 +msgid "Position of folder list pane" +msgstr "Posisi panel daftar folder" + +#: desktop/org.gnome.Geary.gschema.xml:27 +msgid "Position of the folder list Paned grabber." +msgstr "Posisi daftar folder Paned grabber." + +#: desktop/org.gnome.Geary.gschema.xml:32 +msgid "Position of folder list pane when horizontal" +msgstr "Posisi panel daftar folder saat horisontal" + +#: desktop/org.gnome.Geary.gschema.xml:33 +msgid "" +"Position of the folder list Paned grabber in the horizontal orientation." +msgstr "Posisi daftar folder Paned grabber dalam orientasi horizontal." + +#: desktop/org.gnome.Geary.gschema.xml:38 +msgid "Position of folder list pane when vertical" +msgstr "Posisi panel daftar folder saat vertikal" + +#: desktop/org.gnome.Geary.gschema.xml:39 +msgid "Position of the folder list Paned grabber in the vertical orientation." +msgstr "Posisi daftar folder Paned grabber dalam orientasi vertikal." + +#: desktop/org.gnome.Geary.gschema.xml:44 +msgid "Orientation of the folder list pane" +msgstr "Orientasi panel daftar folder" + +#: desktop/org.gnome.Geary.gschema.xml:45 +msgid "True if the folder list Paned is in the horizontal orientation." +msgstr "True jika daftar folder Paned berada dalam orientasi horizontal." + +#: desktop/org.gnome.Geary.gschema.xml:50 +msgid "Position of message list pane" +msgstr "Posisi panel daftar pesan" + +#: desktop/org.gnome.Geary.gschema.xml:51 +msgid "Position of the message list Paned grabber." +msgstr "Posisi dari daftar pesan Paned grabber." + +#: desktop/org.gnome.Geary.gschema.xml:56 +msgid "Autoselect next message" +msgstr "Otomatis pilih pesan selanjutnya" + +#: desktop/org.gnome.Geary.gschema.xml:57 +msgid "True if we should autoselect the next available conversation." +msgstr "True jika kita harus memilih ulang percakapan yang ada selanjutnya." + +#: desktop/org.gnome.Geary.gschema.xml:62 +msgid "Display message previews" +msgstr "Tampilkan pratinjau pesan" + +#: desktop/org.gnome.Geary.gschema.xml:63 +msgid "True if we should display a short preview of each message." +msgstr "True jika kita harus menampilkan pratinjau singkat setiap pesan." + +#: desktop/org.gnome.Geary.gschema.xml:68 +msgid "Languages that shall be used in the spell checker" +msgstr "Bahasa yang akan digunakan dalam pemeriksa ejaan" -#: ../src/client/accounts/account-dialog-add-edit-pane.vala:51 -#: ../src/client/components/stock.vala:31 -#: ../ui/conversation-email-menus.ui.h:10 +#: desktop/org.gnome.Geary.gschema.xml:69 +msgid "List of the languages to use in the spell checker." +msgstr "Daftar bahasa yang akan digunakan di pemeriksa ejaan." + +#: desktop/org.gnome.Geary.gschema.xml:74 +msgid "Languages that are displayed in the spell checker popover" +msgstr "Bahasa yang ditampilkan dalam popover pemeriksa ejaan" + +#: desktop/org.gnome.Geary.gschema.xml:75 +msgid "" +"List of languages that are always displayed in the popover of the spell " +"checker." +msgstr "Daftar bahasa yang selalu ditampilkan di popover pemeriksa ejaan." + +#: desktop/org.gnome.Geary.gschema.xml:80 +msgid "Enable notification sounds" +msgstr "Aktifkan suara pemberitahuan" + +#: desktop/org.gnome.Geary.gschema.xml:81 +msgid "True to play sounds for notifications and sending." +msgstr "True untuk memutar suara untuk pemberitahuan dan pengiriman." + +#: desktop/org.gnome.Geary.gschema.xml:86 +msgid "Show notifications for new mail" +msgstr "Tampilkan pemberitahuan bagi surat baru" + +#: desktop/org.gnome.Geary.gschema.xml:87 +msgid "True to show notification bubbles." +msgstr "True untuk menunjukkan gelembung pemberitahuan." + +#: desktop/org.gnome.Geary.gschema.xml:92 +msgid "Notify of new mail at startup" +msgstr "Beritahu surel baru saat awal mula" + +#: desktop/org.gnome.Geary.gschema.xml:93 +msgid "True to notify of new mail at startup." +msgstr "True untuk memberitahukan surel baru saat memulai." + +#: desktop/org.gnome.Geary.gschema.xml:98 +msgid "Ask when opening an attachment" +msgstr "Tanyakan saat membuka lampiran" + +#: desktop/org.gnome.Geary.gschema.xml:99 +msgid "True to ask when opening an attachment." +msgstr "True untuk bertanya saat membuka lampiran." + +#: desktop/org.gnome.Geary.gschema.xml:104 +msgid "Whether to compose emails in HTML" +msgstr "Apakah untuk menulis surel dalam HTML" + +#: desktop/org.gnome.Geary.gschema.xml:105 +msgid "True to compose emails in HTML; false for plain text." +msgstr "True untuk menulis surel dalam HTML; false untuk teks biasa." + +#: desktop/org.gnome.Geary.gschema.xml:110 +msgid "Advisory strategy for full-text searching" +msgstr "Strategi penasehat untuk penelusuran teks lengkap" + +#: desktop/org.gnome.Geary.gschema.xml:111 +msgid "" +"Acceptable values are “exact”, “conservative”, “aggressive”, and “horizon”." +msgstr "" +"Nilai yang dapat diterima \"exact\" (tepat), \"conservative\" (konservatif), " +"\"aggressive\" (agresif), dan \"horizon\" (horizon)." + +#: desktop/org.gnome.Geary.gschema.xml:116 +msgid "Zoom of conversation viewer" +msgstr "Zoom penampil percakapan" + +#: desktop/org.gnome.Geary.gschema.xml:117 +msgid "The zoom to apply on the conservation view." +msgstr "Zum untuk diterapkan pada tampilan konservasi." + +#: desktop/org.gnome.Geary.gschema.xml:122 +msgid "Size of detached composer window" +msgstr "Ukuran jendela penyusun yang terpisah" + +#: desktop/org.gnome.Geary.gschema.xml:123 +msgid "The last recorded size of the detached composer window." +msgstr "Ukuran yang terekam terakhir dari jendela komposer yang terpisah." + +#: desktop/org.gnome.Geary.gschema.xml:128 +msgid "Base URL to look up contact avatars" +msgstr "URL dasar untuk mencari avatar kontak" + +#: desktop/org.gnome.Geary.gschema.xml:129 +msgid "" +"A Gravatar or Libravatar compatible URL, set to the empty string to disable." +msgstr "" +"URL yang kompatibel dengan Gravatar atau Libravatar, disetel ke string " +"kosong untuk dinonaktifkan." + +#: desktop/org.gnome.Geary.gschema.xml:134 +msgid "Whether we migrated the old settings" +msgstr "Apakah kita memigrasikan pengaturan lama" + +#: desktop/org.gnome.Geary.gschema.xml:135 +msgid "" +"False to check for the old “org.yorba.geary”-schema and copy its values." +msgstr "" +"False untuk memeriksa \"org.yorba.geary\"-schema lama dan salin nilainya." + +#: src/client/accounts/account-dialog-add-edit-pane.vala:54 +#: src/client/components/stock.vala:31 ui/conversation-email-menus.ui:83 msgid "_Save" msgstr "_Simpan" -#: ../src/client/accounts/account-dialog-add-edit-pane.vala:51 -#: ../src/client/components/stock.vala:22 +#: src/client/accounts/account-dialog-add-edit-pane.vala:54 +#: src/client/components/stock.vala:22 msgid "_Add" msgstr "T_ambah" #. reset/clear widgets -#: ../src/client/accounts/account-dialog-edit-alternate-emails-pane.vala:120 +#: src/client/accounts/account-dialog-edit-alternate-emails-pane.vala:120 #, c-format msgid "Additional addresses for %s" msgstr "Alamat tambahan bagi %s" #. Sets min size. -#: ../src/client/accounts/account-dialog.vala:21 +#: src/client/accounts/account-dialog.vala:28 msgid "Accounts" msgstr "Akun" @@ -163,119 +350,119 @@ #. #. Page for adding or editing an account. #. / Placeholder text indicating that the user should type their first name and last name -#: ../src/client/accounts/add-edit-page.vala:10 +#: src/client/accounts/add-edit-page.vala:10 msgid "First Last" msgstr "NamaDepan NamaBelakang" -#: ../src/client/accounts/add-edit-page.vala:235 +#: src/client/accounts/add-edit-page.vala:240 msgid "Welcome to Geary." msgstr "Selamat datang ke Geary." -#: ../src/client/accounts/add-edit-page.vala:235 +#: src/client/accounts/add-edit-page.vala:240 msgid "Enter your account information to get started." msgstr "Masukkan info akun Anda untuk memulai." -#: ../src/client/accounts/add-edit-page.vala:255 +#: src/client/accounts/add-edit-page.vala:260 msgid "2 weeks back" msgstr "2 minggu ke belakang" #. IDs are # of days -#: ../src/client/accounts/add-edit-page.vala:256 +#: src/client/accounts/add-edit-page.vala:261 msgid "1 month back" msgstr "1 bulan ke belakang" -#: ../src/client/accounts/add-edit-page.vala:257 +#: src/client/accounts/add-edit-page.vala:262 msgid "3 months back" msgstr "3 bulan ke belakang" -#: ../src/client/accounts/add-edit-page.vala:258 +#: src/client/accounts/add-edit-page.vala:263 msgid "6 months back" msgstr "6 bulan ke belakang" -#: ../src/client/accounts/add-edit-page.vala:259 +#: src/client/accounts/add-edit-page.vala:264 msgid "1 year back" msgstr "1 tahun ke belakang" -#: ../src/client/accounts/add-edit-page.vala:260 +#: src/client/accounts/add-edit-page.vala:265 msgid "2 years back" msgstr "2 tahun ke belakang" -#: ../src/client/accounts/add-edit-page.vala:261 +#: src/client/accounts/add-edit-page.vala:266 msgid "4 years back" msgstr "4 tahun ke belakang" #. Separator -#: ../src/client/accounts/add-edit-page.vala:263 +#: src/client/accounts/add-edit-page.vala:268 msgid "Everything" msgstr "Semua" -#: ../src/client/accounts/add-edit-page.vala:283 +#: src/client/accounts/add-edit-page.vala:288 msgid "Edit" msgstr "Sunting" -#: ../src/client/accounts/add-edit-page.vala:285 +#: src/client/accounts/add-edit-page.vala:290 msgid "Preview" msgstr "Pratinjau" -#: ../src/client/accounts/add-edit-page.vala:751 +#: src/client/accounts/add-edit-page.vala:778 msgid "Remem_ber passwords" msgstr "I_ngat sandi" -#: ../src/client/accounts/add-edit-page.vala:758 ../ui/login.glade.h:7 +#: src/client/accounts/add-edit-page.vala:785 ui/login.glade:233 msgid "Remem_ber password" msgstr "I_ngat sandi" -#: ../src/client/accounts/add-edit-page.vala:792 +#: src/client/accounts/add-edit-page.vala:819 msgid "Unable to validate:\n" msgstr "Tak dapat mengesahkan:\n" -#: ../src/client/accounts/add-edit-page.vala:794 +#: src/client/accounts/add-edit-page.vala:821 msgid " • Invalid account nickname.\n" msgstr " • Nama panggilan akun tidak sah.\n" -#: ../src/client/accounts/add-edit-page.vala:797 +#: src/client/accounts/add-edit-page.vala:824 msgid " • Email address already added to Geary.\n" msgstr "" " • Alamat surel telah ditambahkan ke Geary.\n" "\n" -#: ../src/client/accounts/add-edit-page.vala:801 +#: src/client/accounts/add-edit-page.vala:828 msgid " • IMAP connection error.\n" msgstr " • Galat koneksi IMAP.\n" -#: ../src/client/accounts/add-edit-page.vala:804 +#: src/client/accounts/add-edit-page.vala:831 msgid " • IMAP username or password incorrect.\n" msgstr " • Nama pengguna atau sandi IMAP salah.\n" -#: ../src/client/accounts/add-edit-page.vala:807 +#: src/client/accounts/add-edit-page.vala:834 msgid " • SMTP connection error.\n" msgstr " • Koneksi SMTP galat.\n" -#: ../src/client/accounts/add-edit-page.vala:810 +#: src/client/accounts/add-edit-page.vala:837 msgid " • SMTP username or password incorrect.\n" msgstr " • Nama pengguna atau sandi SMTP salah.\n" -#: ../src/client/accounts/add-edit-page.vala:814 +#: src/client/accounts/add-edit-page.vala:841 msgid " • Connection error.\n" msgstr " • Galat koneksi.\n" -#: ../src/client/accounts/add-edit-page.vala:818 +#: src/client/accounts/add-edit-page.vala:845 msgid " • Username or password incorrect.\n" msgstr " • Nama pengguna atau sandi salah.\n" -#: ../src/client/application/geary-application.vala:22 +#: src/client/application/geary-application.vala:22 msgid "Copyright 2016 Software Freedom Conservancy Inc." msgstr "Hak Cipta 2016 Software Freedom Conservancy Inc." -#: ../src/client/application/geary-application.vala:23 -msgid "Copyright 2016-2017 Geary Development Team." -msgstr "Hak Cipta 2016-2017 Tim Pengembang Geary." +#: src/client/application/geary-application.vala:23 +msgid "Copyright 2016-2018 Geary Development Team." +msgstr "Hak Cipta 2016-2018 Tim Pengembang Geary." -#: ../src/client/application/geary-application.vala:25 +#: src/client/application/geary-application.vala:25 msgid "Visit the Geary web site" msgstr "Kunjungi situs web Geary" -#: ../src/client/application/geary-application.vala:466 +#: src/client/application/geary-application.vala:416 #, c-format msgid "About %s" msgstr "Tentang %s" @@ -283,252 +470,110 @@ #. Translators: add your name and email address to receive #. credit in the About dialog For example: Yamada Taro #. -#: ../src/client/application/geary-application.vala:470 +#: src/client/application/geary-application.vala:420 msgid "translator-credits" msgstr "" "Andika Triwidada , 2012, 2013, 2016, 2017\n" "Dani Pratomo , 2012.\n" -"Kukuh Syafaat , 2017." +"Kukuh Syafaat , 2017, 2018." -#: ../src/client/application/geary-args.vala:10 +#: src/client/application/geary-args.vala:10 msgid "Start Geary with hidden main window" msgstr "Mulai Geary dengan jendela utama tersembunyi" -#: ../src/client/application/geary-args.vala:11 +#: src/client/application/geary-args.vala:11 msgid "Output debugging information" msgstr "Keluaran informasi pengawakutuan" -#: ../src/client/application/geary-args.vala:12 +#: src/client/application/geary-args.vala:12 msgid "Log conversation monitoring" msgstr "Catat pemantauan percakapan" -#: ../src/client/application/geary-args.vala:13 +#: src/client/application/geary-args.vala:13 msgid "Log network deserialization" msgstr "Catat deserialisasi jaringan" -#: ../src/client/application/geary-args.vala:14 +#: src/client/application/geary-args.vala:14 msgid "Log network activity" msgstr "Catat aktivitas jaringan" #. / The IMAP replay queue is how changes on the server are replicated on the client. #. / It could also be called the IMAP events queue. -#: ../src/client/application/geary-args.vala:17 +#: src/client/application/geary-args.vala:17 msgid "Log IMAP replay queue" msgstr "Catat antrian putar ulang IMAP" #. / Serialization is how commands and responses are converted into a stream of bytes for #. / network transmission -#: ../src/client/application/geary-args.vala:20 +#: src/client/application/geary-args.vala:20 msgid "Log network serialization" msgstr "Catat serialisasi jaringan" -#: ../src/client/application/geary-args.vala:21 +#: src/client/application/geary-args.vala:21 msgid "Log periodic activity" msgstr "Catat aktivitas periodik" -#: ../src/client/application/geary-args.vala:22 +#: src/client/application/geary-args.vala:22 msgid "Log database queries (generates lots of messages)" msgstr "Catat kuiri basis data (menimbulkan banyak pesan)" #. / "Normalization" can also be called "synchronization" -#: ../src/client/application/geary-args.vala:24 +#: src/client/application/geary-args.vala:24 msgid "Log folder normalization" msgstr "Catat normalisasi map" -#: ../src/client/application/geary-args.vala:25 +#: src/client/application/geary-args.vala:25 msgid "Allow inspection of WebView" msgstr "Izinkan inspeksi WebView" -#: ../src/client/application/geary-args.vala:26 +#: src/client/application/geary-args.vala:26 msgid "Revoke all server certificates with TLS warnings" msgstr "Cabut semua sertifikat server yang memiliki peringatan TLS" -#: ../src/client/application/geary-args.vala:27 +#: src/client/application/geary-args.vala:27 msgid "Perform a graceful quit" msgstr "Keluar secara anggun" -#: ../src/client/application/geary-args.vala:28 +#: src/client/application/geary-args.vala:28 msgid "Display program version" msgstr "Tampilkan versi program" #. This gives a command-line hint on how to open new composer windows with mailto: -#: ../src/client/application/geary-args.vala:53 +#: src/client/application/geary-args.vala:53 #, c-format msgid "Use %s to open a new composer window" msgstr "Pakai %s untuk membuka jendela penyusun baru" -#: ../src/client/application/geary-args.vala:56 +#: src/client/application/geary-args.vala:56 msgid "Please report comments, suggestions and bugs to:" msgstr "Harap kirimkan komentar, saran, dan kutu ke:" #. i18n: Command line arguments are invalid -#: ../src/client/application/geary-args.vala:63 +#: src/client/application/geary-args.vala:63 #, c-format msgid "Failed to parse command line options: %s\n" msgstr "Gagal mengurai opsi baris perintah: %s\n" -#: ../src/client/application/geary-args.vala:74 +#: src/client/application/geary-args.vala:74 #, c-format msgid "Unrecognized command line option “%s”\n" msgstr "Opsi baris perintah tidak dikenal \"%s\"\n" -#: ../src/client/application/geary-controller.vala:57 -msgid "Delete conversation" -msgstr "Hapus percakapan" - -#: ../src/client/application/geary-controller.vala:58 -msgid "Delete conversation (Shift+Delete)" -msgstr "Hapus percakapan (Shift+Delete)" - -#: ../src/client/application/geary-controller.vala:59 -msgid "Delete conversations (Shift+Delete)" -msgstr "Hapus percakapan (Shift+Delete)" - -#. This refers to the action ("move email to the trash"), not the Trash folder itself -#: ../src/client/application/geary-controller.vala:63 -msgid "Move conversation to Trash (Delete, Backspace)" -msgstr "Pindahkan percakapan ke Tong Sampah (Delete, Backspace)" - -#: ../src/client/application/geary-controller.vala:64 -msgid "Move conversations to Trash (Delete, Backspace)" -msgstr "Pindahkan percakapan ke Tong Sampah (Delete, Backspace)" - -#. This refers to the action ("archive an email"), not the Archive folder itself -#: ../src/client/application/geary-controller.vala:68 -msgid "_Archive" -msgstr "_Arsip" - -#: ../src/client/application/geary-controller.vala:69 -msgid "Archive conversation (A)" -msgstr "Arsipkan percakapan (A)" - -#: ../src/client/application/geary-controller.vala:70 -msgid "Archive conversations (A)" -msgstr "Arsipkan percakapan (A)" - -#: ../src/client/application/geary-controller.vala:73 -msgid "Mark as S_pam" -msgstr "Tandai sebagai S_pam" - -#: ../src/client/application/geary-controller.vala:74 -msgid "Mark as not S_pam" -msgstr "Tandai sebagai bukan S_pam" - -#: ../src/client/application/geary-controller.vala:76 -#: ../src/client/application/geary-controller.vala:448 -msgid "Mark conversation" -msgstr "Tandai percakapan" - -#: ../src/client/application/geary-controller.vala:77 -msgid "Mark conversations" -msgstr "Tandai percakapan" - -#: ../src/client/application/geary-controller.vala:78 -msgid "Add label to conversation" -msgstr "Tambahkan label ke percakapan" - -#: ../src/client/application/geary-controller.vala:79 -msgid "Add label to conversations" -msgstr "Tambahkan label ke percakapan" - -#: ../src/client/application/geary-controller.vala:80 -#: ../src/client/application/geary-controller.vala:487 -msgid "Move conversation" -msgstr "Pindahkan percakapan" - -#: ../src/client/application/geary-controller.vala:81 -msgid "Move conversations" -msgstr "Pindahkan percakapan" - -#: ../src/client/application/geary-controller.vala:450 -msgid "_Mark as…" -msgstr "_Tandai sebagai…" - -#: ../src/client/application/geary-controller.vala:456 -msgid "Mark as _Read" -msgstr "Tandai Sudah Di_baca" - -#: ../src/client/application/geary-controller.vala:462 -msgid "Mark as _Unread" -msgstr "Tandai _Belum Dibaca" - -#: ../src/client/application/geary-controller.vala:468 -msgid "_Star" -msgstr "_Bintangi" - -#: ../src/client/application/geary-controller.vala:473 -msgid "U_nstar" -msgstr "Hapus bi_ntang" - -#: ../src/client/application/geary-controller.vala:483 -msgid "Add label" -msgstr "Tambah label" - -#: ../src/client/application/geary-controller.vala:484 -msgid "_Label" -msgstr "_Label" - -#: ../src/client/application/geary-controller.vala:488 -msgid "_Move" -msgstr "_Pindahkan" - -#: ../src/client/application/geary-controller.vala:492 -msgid "Compose new message (Ctrl+N, N)" -msgstr "Susun pesan baru (Ctrl+N, N)" - -#: ../src/client/application/geary-controller.vala:496 -#: ../ui/conversation-email-menus.ui.h:1 -msgid "_Reply" -msgstr "B_alas" - -#: ../src/client/application/geary-controller.vala:497 -msgid "Reply (Ctrl+R, R)" -msgstr "Balas (Ctrl+R, R)" - -#: ../src/client/application/geary-controller.vala:501 -msgid "R_eply All" -msgstr "Balas S_emua" - -#: ../src/client/application/geary-controller.vala:502 -msgid "Reply all (Ctrl+Shift+R, Shift+R)" -msgstr "Balas semua (Ctrl+Shift+R, Shift+R)" - -#: ../src/client/application/geary-controller.vala:507 -#: ../ui/conversation-email-menus.ui.h:3 -msgid "_Forward" -msgstr "_Teruskan" +#. Translators: File name used in save chooser when saving +#. attachments that do not otherwise have a name. +#: src/client/application/geary-controller.vala:68 +msgid "Untitled" +msgstr "Tak ada judul" -#: ../src/client/application/geary-controller.vala:508 -msgid "Forward (Ctrl+L, F)" -msgstr "Maju (Ctrl+L, F)" - -#: ../src/client/application/geary-controller.vala:538 -msgid "Empty _Spam…" -msgstr "Kosongkan _Spam…" - -#: ../src/client/application/geary-controller.vala:542 -msgid "Empty _Trash…" -msgstr "Kosongkan _Tong Sampah…" - -#. No callback is connected, since we bind the toggle button to the search bar visibility -#: ../src/client/application/geary-controller.vala:574 -msgid "Toggle search bar" -msgstr "Jungkitkan bilah pencarian" - -#. No callback is connected, since we bind the toggle button to the find bar visibility -#: ../src/client/application/geary-controller.vala:579 -msgid "Toggle find bar" -msgstr "Jungkitkan bilah pencarian" - -#: ../src/client/application/geary-controller.vala:757 +#: src/client/application/geary-controller.vala:721 msgid "Unable to store server trust exception" msgstr "Tak bisa menyimpan pengecualian kepercayaan server" -#: ../src/client/application/geary-controller.vala:992 +#: src/client/application/geary-controller.vala:972 msgid "Your settings are insecure" msgstr "Pengaturan Anda tak aman" -#: ../src/client/application/geary-controller.vala:993 +#: src/client/application/geary-controller.vala:973 msgid "" "Your IMAP and/or SMTP settings do not specify SSL or TLS. This means your " "username and password could be read by another person on the network. Are " @@ -538,29 +583,17 @@ "nama pengguna dan sandi Anda bisa dibaca orang lain di jaringan ini. Anda " "yakin ingin melakukan ini?" -#: ../src/client/application/geary-controller.vala:994 +#: src/client/application/geary-controller.vala:974 msgid "Co_ntinue" msgstr "La_njutkan" -#: ../src/client/application/geary-controller.vala:1041 -msgid "Error connecting to the server" -msgstr "Kesalahan saat menyambung ke server" - -#: ../src/client/application/geary-controller.vala:1042 -msgid "" -"Geary encountered an error while connecting to the server. Please try again " -"in a few moments." -msgstr "" -"Geary menemui galat ketika menyambung ke server. Harap coba lagi setelah " -"beberapa saat." - #. / Displayed in the space-limited status bar when a message fails to be sent due to error. -#: ../src/client/application/geary-controller.vala:1079 -#: ../src/client/components/status-bar.vala:29 +#: src/client/application/geary-controller.vala:1078 +#: src/client/components/status-bar.vala:29 msgid "Error sending email" msgstr "Galat saat mengirim surel" -#: ../src/client/application/geary-controller.vala:1080 +#: src/client/application/geary-controller.vala:1079 msgid "" "Geary encountered an error sending an email. If the problem persists, " "please manually delete the email from your Outbox folder." @@ -570,12 +603,12 @@ #. Displayed in the space-limited status bar when a message fails to be uploaded #. to Sent Mail after being sent. -#: ../src/client/application/geary-controller.vala:1084 -#: ../src/client/components/status-bar.vala:33 +#: src/client/application/geary-controller.vala:1083 +#: src/client/components/status-bar.vala:33 msgid "Error saving sent mail" msgstr "Galat saat menyimpan surel terkirim" -#: ../src/client/application/geary-controller.vala:1085 +#: src/client/application/geary-controller.vala:1084 msgid "" "Geary encountered an error saving a sent message to Sent Mail. The message " "will stay in your Outbox folder until you delete it." @@ -583,19 +616,19 @@ "Gary menemui galat ketika menyimpan sebuah surel terkirim ke Sent Mail. " "Pesan akan tetap berada di folder Kotak Keluar Anda sampai Anda menghapusnya." -#: ../src/client/application/geary-controller.vala:1154 +#: src/client/application/geary-controller.vala:1151 msgid "Labels" msgstr "Label" #. give the user two options: reset the Account local store, or exit Geary. A third #. could be done to leave the Account in an unopened state, but we don't currently #. have provisions for that. -#: ../src/client/application/geary-controller.vala:1166 +#: src/client/application/geary-controller.vala:1163 #, c-format msgid "Unable to open the database for %s" msgstr "Tak bisa membuka basis data bagi %s" -#: ../src/client/application/geary-controller.vala:1167 +#: src/client/application/geary-controller.vala:1164 #, c-format msgid "" "There was an error opening the local mail database for this account. This is " @@ -619,20 +652,20 @@ "Membangun ulang basis data akan menghancurkan semua surel lokal dan " "lampirannya. Surat pada server Anda tak akan terpengaruh." -#: ../src/client/application/geary-controller.vala:1169 +#: src/client/application/geary-controller.vala:1166 msgid "_Rebuild" msgstr "_Bangun Ulang" -#: ../src/client/application/geary-controller.vala:1169 +#: src/client/application/geary-controller.vala:1166 msgid "E_xit" msgstr "_Keluar" -#: ../src/client/application/geary-controller.vala:1178 +#: src/client/application/geary-controller.vala:1175 #, c-format msgid "Unable to rebuild database for “%s”" msgstr "Tak bisa membangun ulang basis data bagi \"%s\"" -#: ../src/client/application/geary-controller.vala:1179 +#: src/client/application/geary-controller.vala:1176 #, c-format msgid "" "Error during rebuild:\n" @@ -645,14 +678,14 @@ #. some other problem opening the account ... as with other flow path, can't run #. Geary today with an account in unopened state, so have to exit -#: ../src/client/application/geary-controller.vala:1201 -#: ../src/client/application/geary-controller.vala:1211 -#: ../src/client/application/geary-controller.vala:1222 +#: src/client/application/geary-controller.vala:1198 +#: src/client/application/geary-controller.vala:1208 +#: src/client/application/geary-controller.vala:1219 #, c-format msgid "Unable to open local mailbox for %s" msgstr "Tak bisa membuka kotak surat bagi %s" -#: ../src/client/application/geary-controller.vala:1202 +#: src/client/application/geary-controller.vala:1199 #, c-format msgid "" "There was an error opening the local mail database for this account. This is " @@ -671,7 +704,7 @@ "\n" "%s" -#: ../src/client/application/geary-controller.vala:1212 +#: src/client/application/geary-controller.vala:1209 msgid "" "The version number of the local mail database is formatted for a newer " "version of Geary. Unfortunately, the database cannot be “rolled back” to " @@ -685,7 +718,7 @@ "\n" "Harap pasang versi Geary terbaru dan coba lagi." -#: ../src/client/application/geary-controller.vala:1223 +#: src/client/application/geary-controller.vala:1220 msgid "" "There was an error opening the local account. This is probably due to " "connectivity issues.\n" @@ -696,15 +729,15 @@ "\n" "Harap periksa koneksi jaringan Anda dan start ulang Geary." -#: ../src/client/application/geary-controller.vala:1999 +#: src/client/application/geary-controller.vala:2038 msgid "Undo move (Ctrl+Z)" msgstr "Tak jadi pindah (Ctrl+Z)" -#: ../src/client/application/geary-controller.vala:2009 +#: src/client/application/geary-controller.vala:2048 msgid "Are you sure you want to open these attachments?" msgstr "Anda yakin ingin membuka lampiran-lampiran ini?" -#: ../src/client/application/geary-controller.vala:2010 +#: src/client/application/geary-controller.vala:2049 msgid "" "Attachments may cause damage to your system if opened. Only open files from " "trusted sources." @@ -712,197 +745,442 @@ "Lampiran berpotensi bahaya jika dibuka. Hanya buka berkas dari sumber " "terpercaya." -#: ../src/client/application/geary-controller.vala:2011 +#: src/client/application/geary-controller.vala:2050 msgid "Don’t _ask me again" msgstr "Jangan tanya s_aya lagi" -#: ../src/client/application/geary-controller.vala:2121 +#. Translators: Dialog primary label when prompting to +#. overwrite a file. The string substitution is the file'sx +#. name. +#: src/client/application/geary-controller.vala:2179 #, c-format msgid "A file named “%s” already exists. Do you want to replace it?" msgstr "Berkas bernama \"%s\" sudah ada. Anda ingin menimpanya?" -#: ../src/client/application/geary-controller.vala:2123 +#. Translators: Dialog secondary label when prompting to +#. overwrite a file. The string substitution is the parent +#. folder's name. +#: src/client/application/geary-controller.vala:2186 #, c-format msgid "" "The file already exists in “%s”. Replacing it will overwrite its contents." msgstr "Berkas telah ada di \"%s\". Kalau ditimpa isi sebelumnya hilang." -#: ../src/client/application/geary-controller.vala:2126 +#: src/client/application/geary-controller.vala:2190 msgid "_Replace" msgstr "Timp_a" -#. Find out what to do with the inline composers. -#. TODO: Remove this in favor of automatically saving drafts -#: ../src/client/application/geary-controller.vala:2374 -msgid "Close open draft messages?" -msgstr "Tutup draf pesan yang terbuka?" +#: src/client/application/geary-controller.vala:2460 +msgid "Close the draft message?" +msgid_plural "Close all draft messages?" +msgstr[0] "Tutup semua draf pesan?" -#: ../src/client/application/geary-controller.vala:2496 +#: src/client/application/geary-controller.vala:2586 #, c-format msgid "Empty all email from your %s folder?" msgstr "Kosongkan semua surel dari folder %s Anda?" -#: ../src/client/application/geary-controller.vala:2497 +#: src/client/application/geary-controller.vala:2587 msgid "This removes the email from Geary and your email server." msgstr "Ini menghapus surel dari Geary dan server surel Anda." -#: ../src/client/application/geary-controller.vala:2498 +#: src/client/application/geary-controller.vala:2588 msgid "This cannot be undone." msgstr "Ini tidak bisa dibatalkan." -#: ../src/client/application/geary-controller.vala:2499 +#: src/client/application/geary-controller.vala:2589 #, c-format msgid "Empty %s" msgstr "Kosongkan %s" -#: ../src/client/application/geary-controller.vala:2516 +#: src/client/application/geary-controller.vala:2606 #, c-format msgid "Error emptying %s" msgstr "Galat saat mengosongkan %s" -#: ../src/client/application/geary-controller.vala:2546 +#: src/client/application/geary-controller.vala:2638 msgid "Do you want to permanently delete this message?" msgid_plural "Do you want to permanently delete these messages?" msgstr[0] "Anda mau membuang pesan ini secara permanen?" -#: ../src/client/application/geary-controller.vala:2548 +#: src/client/application/geary-controller.vala:2640 msgid "Delete" msgstr "Hapus" -#: ../src/client/application/geary-controller.vala:2580 -msgid "Undo archive (Ctrl+Z)" -msgstr "Tak jadi arsipkan (Ctrl+Z)" - -#: ../src/client/application/geary-controller.vala:2595 +#: src/client/application/geary-controller.vala:2654 msgid "Undo trash (Ctrl+Z)" msgstr "Tak jadi buang ke tong sampah (Ctrl+Z)" -#: ../src/client/application/geary-controller.vala:2649 +#: src/client/application/geary-controller.vala:2704 +msgid "Undo archive (Ctrl+Z)" +msgstr "Tak jadi arsipkan (Ctrl+Z)" + +#: src/client/application/geary-controller.vala:2749 msgid "Undo (Ctrl+Z)" msgstr "Tak jadi (Ctrl+Z)" -#: ../src/client/application/geary-controller.vala:2780 +#. Translators: The label for an in-app notification. The +#. string substitution is a list of recipients of the email. +#: src/client/application/geary-controller.vala:2826 +#, c-format +msgid "Successfully sent mail to %s." +msgstr "Berhasil mengirim surel ke %s." + +#: src/client/application/geary-controller.vala:2907 msgid "Failed to open default text editor." msgstr "Gagal membuka penyunting teks utama." -#: ../src/client/components/main-window.vala:389 +#. Tooltips +#: src/client/components/main-toolbar.vala:69 +msgid "Delete conversation (Shift+Delete)" +msgstr "Hapus percakapan (Shift+Delete)" + +#: src/client/components/main-toolbar.vala:70 +msgid "Delete conversations (Shift+Delete)" +msgstr "Hapus percakapan (Shift+Delete)" + +#: src/client/components/main-toolbar.vala:71 +msgid "Move conversation to Trash (Delete, Backspace)" +msgstr "Pindahkan percakapan ke Tong Sampah (Delete, Backspace)" + +#: src/client/components/main-toolbar.vala:72 +msgid "Move conversations to Trash (Delete, Backspace)" +msgstr "Pindahkan percakapan ke Tong Sampah (Delete, Backspace)" + +#: src/client/components/main-toolbar.vala:73 +msgid "Archive conversation (A)" +msgstr "Arsipkan percakapan (A)" + +#: src/client/components/main-toolbar.vala:74 +msgid "Archive conversations (A)" +msgstr "Arsipkan percakapan (A)" + +#: src/client/components/main-toolbar.vala:75 +msgid "Mark conversation" +msgstr "Tandai percakapan" + +#: src/client/components/main-toolbar.vala:76 +msgid "Mark conversations" +msgstr "Tandai percakapan" + +#: src/client/components/main-toolbar.vala:77 +msgid "Add label to conversation" +msgstr "Tambahkan label ke percakapan" + +#: src/client/components/main-toolbar.vala:78 +msgid "Add label to conversations" +msgstr "Tambahkan label ke percakapan" + +#: src/client/components/main-toolbar.vala:79 +msgid "Move conversation" +msgstr "Pindahkan percakapan" + +#: src/client/components/main-toolbar.vala:80 +msgid "Move conversations" +msgstr "Pindahkan percakapan" + +#: src/client/components/main-window.vala:440 #, c-format msgid "%s (%d)" msgstr "%s (%d)" -#: ../src/client/components/search-bar.vala:8 -#: ../src/client/folder-list/folder-list-search-branch.vala:38 -#: ../src/engine/api/geary-special-folder-type.vala:51 +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:54 +#, c-format +msgid "Problem connecting to incoming server for %s" +msgstr "Masalah saat menyambung ke server masuk untuk %s" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:56 +#: src/client/components/main-window-info-bar.vala:64 +#, c-format +msgid "" +"Could not connect to %s, check your Internet access and the server name and " +"try again" +msgstr "" +"Tidak dapat terhubung ke %s, periksa akses Internet dan nama server dan coba " +"lagi" + +#: src/client/components/main-window-info-bar.vala:57 +#: src/client/components/main-window-info-bar.vala:66 +msgid "Retry connecting now" +msgstr "Coba lagi sambungkan sekarang" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:62 +#, c-format +msgid "Problem connecting to outgoing server for %s" +msgstr "Masalah saat menyambung ke server keluar untuk %s" + +#: src/client/components/main-window-info-bar.vala:65 +msgid "Try reconnecting now" +msgstr "Coba hubungkan kembali sekarang" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:71 +#, c-format +msgid "Problem with connection to incoming server for %s" +msgstr "Masalah dengan koneksi ke server masuk untuk %s" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:73 +#: src/client/components/main-window-info-bar.vala:81 +#, c-format +msgid "Network error talking to %s, check your Internet access and try again" +msgstr "" +"Terjadi kesalahan jaringan saat berbicara dengan %s, periksa akses Internet " +"Anda dan coba lagi" + +#: src/client/components/main-window-info-bar.vala:74 +#: src/client/components/main-window-info-bar.vala:82 +#: src/client/components/main-window-info-bar.vala:90 +#: src/client/components/main-window-info-bar.vala:98 +#: src/client/components/main-window-info-bar.vala:119 +msgid "Try reconnecting" +msgstr "Coba sambungkan kembali" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:79 +#, c-format +msgid "Problem with connection to outgoing server for %s" +msgstr "Masalah koneksi ke server keluar untuk %s" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:87 +#, c-format +msgid "Problem communicating with incoming server for %s" +msgstr "Masalah berkomunikasi dengan server masuk untuk %s" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:89 +#, c-format +msgid "" +"Geary did not understand a message from %s or vice versa, please file a bug " +"report" +msgstr "" +"Geary tidak mengerti pesan dari %s atau sebaliknya, mohon laporkan kutu" + +#: src/client/components/main-window-info-bar.vala:94 +msgid "Problem communicating with outgoing mail server" +msgstr "Masalah berkomunikasi dengan server surat keluar" + +#. Translators: First string substitution is the server +#. name, second is the account name +#: src/client/components/main-window-info-bar.vala:97 +#, c-format +msgid "" +"Could not communicate with %s for %s, check the server name and try again in " +"a moment" +msgstr "" +"Tidak dapat berkomunikasi dengan %s untuk %s, periksa nama server dan coba " +"lagi dalam sekejap" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:103 +#, c-format +msgid "Incoming mail server password required for %s" +msgstr "Kata sandi server surat masuk diperlukan untuk %s" + +#: src/client/components/main-window-info-bar.vala:104 +msgid "Messages cannot be received without the correct password." +msgstr "Pesan tidak bisa diterima tanpa kata sandi yang benar." + +#: src/client/components/main-window-info-bar.vala:105 +msgid "Retry receiving email, you will be prompted for a password" +msgstr "Coba lagi menerima surel, Anda akan diminta memasukkan kata sandi" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:110 +#, c-format +msgid "Outgoing mail server password required for %s" +msgstr "Kata sandi server surat keluar diperlukan untuk %s" + +#: src/client/components/main-window-info-bar.vala:111 +msgid "Messages cannot be sent without the correct password." +msgstr "Pesan tidak dapat dikirim tanpa kata sandi yang benar." + +#: src/client/components/main-window-info-bar.vala:112 +msgid "Retry sending queued messages, you will be prompted for a password" +msgstr "" +"Coba lagi mengirim pesan antrian, Anda akan diminta memasukkan kata sandi" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:117 +#, c-format +msgid "A problem occurred checking mail for %s" +msgstr "Terjadi masalah saat memeriksa surel untuk %s" + +#: src/client/components/main-window-info-bar.vala:118 +#: src/client/components/main-window-info-bar.vala:125 +msgid "Something went wrong, please file a bug report if the problem persists" +msgstr "Ada yang tidak beres, kirimkan laporan kutu jika masalah berlanjut" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:124 +#, c-format +msgid "A problem occurred sending mail for %s" +msgstr "Terjadi masalah saat mengirim surel untuk %s" + +#: src/client/components/main-window-info-bar.vala:126 +msgid "Retry sending queued messages" +msgstr "Coba lagi mengirim pesan antrian" + +#: src/client/components/main-window-info-bar.vala:137 +msgid "A database problem has occurred" +msgstr "Masalah basis data telah terjadi" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:139 +#, c-format +msgid "Messages for %s must be downloaded again." +msgstr "Pesan untuk %s harus diunduh lagi." + +#: src/client/components/main-window-info-bar.vala:152 +msgid "Geary has encountered a problem" +msgstr "Geary mengalami masalah" + +#: src/client/components/main-window-info-bar.vala:153 +msgid "" +"Please check the technical details and report the problem if it persists." +msgstr "Silakan periksa rincian teknis dan laporkan masalahnya jika tetap ada." + +#: src/client/components/main-window-info-bar.vala:161 +msgid "_Details" +msgstr "_Detail" + +#: src/client/components/main-window-info-bar.vala:162 +msgid "View technical details about the error" +msgstr "Lihat rincian teknis tentang kesalahan tersebut" + +#: src/client/components/main-window-info-bar.vala:166 +msgid "_Retry" +msgstr "_Ulangi" + +#: src/client/components/main-window-info-bar.vala:253 +msgid "Details" +msgstr "Detail" + +#: src/client/components/main-window-info-bar.vala:266 +#: src/client/components/stock.vala:23 +msgid "_Close" +msgstr "_Tutup" + +#: src/client/components/main-window-info-bar.vala:270 +msgid "Copy to Clipboard" +msgstr "Salin ke Papan Klip" + +#: src/client/components/main-window-info-bar.vala:273 +msgid "" +"Copy technical details to clipboard for pasting into an email or bug report" +msgstr "" +"Salin rincian teknis ke papan klip untuk menempelkan surel atau laporan kutu" + +#: src/client/components/search-bar.vala:8 +#: src/client/folder-list/folder-list-search-branch.vala:38 +#: src/engine/api/geary-special-folder-type.vala:51 msgid "Search" msgstr "Cari" #. Search entry. -#: ../src/client/components/search-bar.vala:23 +#: src/client/components/search-bar.vala:23 msgid "Search all mail in account for keywords (Ctrl+S)" msgstr "Cari kata kunci pada semua surat dalam akun (Ctrl+S)" -#: ../src/client/components/search-bar.vala:100 +#: src/client/components/search-bar.vala:100 #, c-format msgid "Indexing %s account" msgstr "Mengindeks akun %s" -#: ../src/client/components/search-bar.vala:111 -#: ../src/client/folder-list/folder-list-search-branch.vala:39 +#: src/client/components/search-bar.vala:111 +#: src/client/folder-list/folder-list-search-branch.vala:39 #, c-format msgid "Search %s account" msgstr "Cari akun %s" #. / Displayed in the space-limited status bar while a message is in the process of being sent. -#: ../src/client/components/status-bar.vala:26 +#: src/client/components/status-bar.vala:26 msgid "Sending…" msgstr "Mengirim…" -#: ../src/client/components/stock.vala:18 ../ui/account_cannot_remove.glade.h:3 +#: src/client/components/stock.vala:18 ui/account_cannot_remove.glade:74 msgid "_OK" msgstr "_OK" -#: ../src/client/components/stock.vala:19 ../ui/edit_alternate_emails.glade.h:3 -#: ../ui/password-dialog.glade.h:5 ../ui/remove_confirm.glade.h:5 +#: src/client/components/stock.vala:19 ui/edit_alternate_emails.glade:160 +#: ui/password-dialog.glade:196 ui/remove_confirm.glade:155 msgid "_Cancel" msgstr "Ba_tal" -#: ../src/client/components/stock.vala:21 ../ui/gtk/menus.ui.h:6 +#: src/client/components/stock.vala:21 ui/gtk/menus.ui:34 msgid "_About" msgstr "Tent_ang" -#: ../src/client/components/stock.vala:23 -msgid "_Close" -msgstr "_Tutup" - -#: ../src/client/components/stock.vala:24 +#: src/client/components/stock.vala:24 msgid "_Discard" msgstr "_Buang" -#: ../src/client/components/stock.vala:25 ../ui/gtk/menus.ui.h:5 +#: src/client/components/stock.vala:25 ui/gtk/menus.ui:29 msgid "_Help" msgstr "_Bantuan" -#: ../src/client/components/stock.vala:26 ../ui/conversation-email-menus.ui.h:9 +#: src/client/components/stock.vala:26 ui/conversation-email-menus.ui:77 msgid "_Open" msgstr "_Buka" -#: ../src/client/components/stock.vala:27 ../ui/gtk/menus.ui.h:3 +#: src/client/components/stock.vala:27 ui/gtk/menus.ui:17 msgid "_Preferences" msgstr "_Preferensi" -#: ../src/client/components/stock.vala:28 ../ui/conversation-email-menus.ui.h:7 +#. Translators: Menu item to print a single, specific message +#: src/client/components/stock.vala:28 ui/conversation-email-menus.ui:64 msgid "_Print…" msgstr "_Cetak…" -#: ../src/client/components/stock.vala:29 ../ui/gtk/menus.ui.h:7 +#: src/client/components/stock.vala:29 ui/gtk/menus.ui:38 msgid "_Quit" msgstr "_Keluar" -#: ../src/client/components/stock.vala:30 ../ui/remove_confirm.glade.h:6 +#: src/client/components/stock.vala:30 ui/remove_confirm.glade:170 msgid "_Remove" msgstr "Singki_rkan" -#: ../src/client/components/stock.vala:32 +#: src/client/components/stock.vala:32 msgid "_Keep" msgstr "_Pertahankan" -#: ../src/client/composer/composer-link-popover.vala:150 +#: src/client/composer/composer-link-popover.vala:149 msgid "Link URL is not correctly formatted, e.g. http://example.com" msgstr "URL tautan tidak diformat secara benar, mis. http://contoh.com" -#: ../src/client/composer/composer-link-popover.vala:157 +#: src/client/composer/composer-link-popover.vala:156 msgid "Invalid link URL" msgstr "URL tautan tidak valid" -#: ../src/client/composer/composer-link-popover.vala:157 +#: src/client/composer/composer-link-popover.vala:156 msgid "Invalid email address" msgstr "Alamat surel tidak valid" -#: ../src/client/composer/composer-widget.vala:154 +#: src/client/composer/composer-widget.vala:158 msgid "Saved" msgstr "Disimpan" -#: ../src/client/composer/composer-widget.vala:155 +#: src/client/composer/composer-widget.vala:159 msgid "Saving" msgstr "Menyimpan" -#: ../src/client/composer/composer-widget.vala:156 +#: src/client/composer/composer-widget.vala:160 msgid "Error saving" msgstr "Galat saat menyimpan" -#: ../src/client/composer/composer-widget.vala:157 +#: src/client/composer/composer-widget.vala:161 msgid "Press Backspace to delete quote" msgstr "Tekan Backspace untuk menghapus kutip" -#: ../src/client/composer/composer-widget.vala:158 -msgid "New Message" -msgstr "Pesan Baru" - #. Translators: This is list of keywords, separated by pipe ("|") #. characters, that suggest an attachment; since this is full-word #. checking, include all variants of each word. No spaces are #. allowed. -#: ../src/client/composer/composer-widget.vala:167 +#: src/client/composer/composer-widget.vala:170 msgid "" "attach|attaching|attaches|attachment|attachments|attached|enclose|enclosed|" "enclosing|encloses|enclosure|enclosures" @@ -911,28 +1189,37 @@ "attachment|attachments|attached|enclose|enclosed|enclosing|encloses|" "enclosure|enclosures" -#: ../src/client/composer/composer-widget.vala:1122 -#: ../src/client/composer/composer-widget.vala:1141 -msgid "Do you want to discard this message?" -msgstr "Anda mau membuang pesan ini?" +#. Translators: This dialog text is displayed to the +#. user when closing a composer where the options are +#. Keep, Discard or Cancel. +#: src/client/composer/composer-widget.vala:1102 +msgid "Do you want to keep or discard this draft message?" +msgstr "Anda mau menyimpan atau membuang draf pesan ini?" + +#. Translators: This dialog text is displayed to the +#. user when closing a composer where the options are +#. only Discard or Cancel. +#: src/client/composer/composer-widget.vala:1130 +msgid "Do you want to discard this draft message?" +msgstr "Anda mau membuang draf pesan ini?" -#: ../src/client/composer/composer-widget.vala:1245 +#: src/client/composer/composer-widget.vala:1247 msgid "Send message with an empty subject and body?" msgstr "Kirim pesan dengan subjek dan isi kosong?" -#: ../src/client/composer/composer-widget.vala:1247 +#: src/client/composer/composer-widget.vala:1249 msgid "Send message with an empty subject?" msgstr "Kirim pesan dengan subjek kosong?" -#: ../src/client/composer/composer-widget.vala:1249 +#: src/client/composer/composer-widget.vala:1251 msgid "Send message with an empty body?" msgstr "Kirim pesan tanpa isi?" -#: ../src/client/composer/composer-widget.vala:1253 +#: src/client/composer/composer-widget.vala:1255 msgid "Send message without an attachment?" msgstr "Kirim pesan tanpa lampiran?" -#: ../src/client/composer/composer-widget.vala:1515 +#: src/client/composer/composer-widget.vala:1560 #, c-format msgid "“%s” already attached for delivery." msgstr "\"%s\" sudah dilampirkan untuk pengiriman." @@ -942,170 +1229,243 @@ #. description of the document type, the second will #. be a human-friendly size string. For example: #. Document (100.9MB) -#: ../src/client/composer/composer-widget.vala:1523 -#: ../src/client/conversation-viewer/conversation-email.vala:138 +#: src/client/composer/composer-widget.vala:1568 +#: src/client/conversation-viewer/conversation-email.vala:136 #, c-format msgid "%s (%s)" msgstr "%s (%s)" -#: ../src/client/composer/composer-widget.vala:1560 +#: src/client/composer/composer-widget.vala:1605 #, c-format msgid "“%s” could not be found." msgstr "\"%s\" tidak ditemukan." -#: ../src/client/composer/composer-widget.vala:1566 +#: src/client/composer/composer-widget.vala:1611 #, c-format msgid "“%s” is a folder." msgstr "\"%s\" adalah sebuah folder." -#: ../src/client/composer/composer-widget.vala:1572 +#: src/client/composer/composer-widget.vala:1617 #, c-format msgid "“%s” is an empty file." msgstr "\"%s\" adalah berkas kosong." -#: ../src/client/composer/composer-widget.vala:1585 +#: src/client/composer/composer-widget.vala:1630 #, c-format msgid "“%s” could not be opened for reading." msgstr "\"%s\" tak bisa dibuka untuk dibaca." -#: ../src/client/composer/composer-widget.vala:1593 +#: src/client/composer/composer-widget.vala:1638 msgid "Cannot add attachment" msgstr "Tak bisa menambah lampiran" -#: ../src/client/composer/composer-widget.vala:1645 +#: src/client/composer/composer-widget.vala:1687 msgid "To: " msgstr "Kepada: " -#: ../src/client/composer/composer-widget.vala:1648 +#: src/client/composer/composer-widget.vala:1690 msgid "Cc: " msgstr "Cc: " -#: ../src/client/composer/composer-widget.vala:1651 +#: src/client/composer/composer-widget.vala:1693 msgid "Bcc: " msgstr "Bcc: " -#: ../src/client/composer/composer-widget.vala:1654 +#: src/client/composer/composer-widget.vala:1696 msgid "Reply-To: " msgstr "Balas-Ke: " -#: ../src/client/composer/composer-widget.vala:1786 +#: src/client/composer/composer-widget.vala:1834 msgid "Select Color" msgstr "Pilih Warna" #. Displayed in the From dropdown to indicate an "alternate email address" #. for an account. The first printf argument will be the alternate email #. address, and the second will be the account's primary email address. -#: ../src/client/composer/composer-widget.vala:1986 +#: src/client/composer/composer-widget.vala:2024 #, c-format msgid "%1$s via %2$s" msgstr "%1$s via %2$s" #. Composer label (with mnemonic underscore) for the account selector #. when choosing what address to send a message from. -#: ../src/client/composer/composer-widget.vala:2028 +#: src/client/composer/composer-widget.vala:2082 msgid "_From:" msgstr "_Dari:" #. Translators: This is the name of the file chooser filter #. when inserting an image in the composer. -#: ../src/client/composer/composer-widget.vala:2252 +#: src/client/composer/composer-widget.vala:2307 msgid "Images" msgstr "Citra" -#: ../src/client/composer/spell-check-popover.vala:117 +#: src/client/composer/composer-window.vala:14 +msgid "New Message" +msgstr "Pesan Baru" + +#: src/client/composer/spell-check-popover.vala:117 msgid "Remove this language from the preferred list" msgstr "Hapus bahasa ini dari daftar yang disukai" -#: ../src/client/composer/spell-check-popover.vala:121 +#: src/client/composer/spell-check-popover.vala:121 msgid "Add this language to the preferred list" msgstr "Tambahkan bahasa ini ke daftar yang disukai" -#: ../src/client/composer/spell-check-popover.vala:217 +#: src/client/composer/spell-check-popover.vala:217 msgid "Search for more languages" msgstr "Cari lebih banyak bahasa lagi" -#: ../src/client/conversation-list/formatted-conversation-data.vala:11 +#: src/client/conversation-list/conversation-list-view.vala:283 +msgid "Delete conversation" +msgstr "Hapus percakapan" + +#: src/client/conversation-list/conversation-list-view.vala:286 +#: ui/main-toolbar-menus.ui:16 +msgid "Mark as _Read" +msgstr "Tandai Sudah Di_baca" + +#: src/client/conversation-list/conversation-list-view.vala:289 +#: ui/main-toolbar-menus.ui:20 +msgid "Mark as _Unread" +msgstr "Tandai _Belum Dibaca" + +#: src/client/conversation-list/conversation-list-view.vala:292 +#: ui/main-toolbar-menus.ui:28 +msgid "U_nstar" +msgstr "Hapus bi_ntang" + +#: src/client/conversation-list/conversation-list-view.vala:294 +#: ui/main-toolbar-menus.ui:24 +msgid "_Star" +msgstr "_Bintangi" + +#. Translators: Menu item to reply to a specific message. +#: src/client/conversation-list/conversation-list-view.vala:297 +#: ui/conversation-email-menus.ui:9 +msgid "_Reply" +msgstr "B_alas" + +#: src/client/conversation-list/conversation-list-view.vala:298 +msgid "R_eply All" +msgstr "Balas S_emua" + +#. Translators: Menu item to forward a specific message. +#: src/client/conversation-list/conversation-list-view.vala:299 +#: ui/conversation-email-menus.ui:21 +msgid "_Forward" +msgstr "_Teruskan" + +#: src/client/conversation-list/formatted-conversation-data.vala:11 msgid "Me" msgstr "Aku" #. Translators: This is the file type displayed for #. attachments with unknown file types. -#: ../src/client/conversation-viewer/conversation-email.vala:124 +#: src/client/conversation-viewer/conversation-email.vala:122 msgid "Unknown" msgstr "Tak dikenal" +#: src/client/conversation-viewer/conversation-email.vala:811 +msgid "From:" +msgstr "Dari:" + +#: src/client/conversation-viewer/conversation-email.vala:815 +#: ui/conversation-message.ui:313 +msgid "To:" +msgstr "Ke:" + +#: src/client/conversation-viewer/conversation-email.vala:819 +#: ui/conversation-message.ui:358 +msgid "Cc:" +msgstr "Cc:" + +#: src/client/conversation-viewer/conversation-email.vala:823 +#: ui/conversation-message.ui:403 +msgid "Bcc:" +msgstr "Bcc:" + +#: src/client/conversation-viewer/conversation-email.vala:827 +msgid "Date:" +msgstr "Tanggal:" + +#: src/client/conversation-viewer/conversation-email.vala:831 +msgid "Subject:" +msgstr "Subjek:" + +#: src/client/conversation-viewer/conversation-message.vala:60 +msgid "This email address may have been forged" +msgstr "Alamat surel ini mungkin telah dipalsukan" + #. Preview headers #. Translators: This is displayed in place of the from address #. when the message has no from address. -#: ../src/client/conversation-viewer/conversation-message.vala:331 +#: src/client/conversation-viewer/conversation-message.vala:330 msgid "No sender" msgstr "Tidak ada pengirim" #. Translators: This separates multiple 'from' #. addresses in the header preview for a message. -#: ../src/client/conversation-viewer/conversation-message.vala:586 +#: src/client/conversation-viewer/conversation-message.vala:589 msgid ", " msgstr ", " #. Translators: This string is used as the HTML IMG ALT #. attribute value when displaying an inline image in an email #. that did not specify a file name. E.g. ImageCannot remove account " -msgstr "Tak dapat menghapus akun" +msgstr "Tak dapat menghapus akun " -#: ../ui/account_cannot_remove.glade.h:2 +#: ui/account_cannot_remove.glade:56 msgid "" "A composer window associated with this account is currently open. Send or " "discard the message and try again." @@ -1715,434 +2093,444 @@ "Jendela penyusun pesan dari akun ini sedang terbuka. Kirim atau buang pesan " "itu dan coba lagi." -#: ../ui/account_list.glade.h:1 +#: ui/account_list.glade:69 msgid "Add account" msgstr "Tambah akun" -#: ../ui/account_list.glade.h:2 +#: ui/account_list.glade:82 msgid "Edit account" msgstr "Sunting akun" -#: ../ui/account_list.glade.h:3 +#: ui/account_list.glade:95 msgid "Remove account" msgstr "Hapus akun" -#: ../ui/account_spinner.glade.h:1 +#: ui/account_spinner.glade:41 msgid "Please wait while Geary validates your account." msgstr "Silakan tunggu sementara Geary memvalidasi akun Anda." -#: ../ui/certificate_warning_dialog.glade.h:1 +#: ui/certificate_warning_dialog.glade:7 msgid "Untrusted Connection" msgstr "Koneksi Tak Terpercaya" -#: ../ui/certificate_warning_dialog.glade.h:2 +#: ui/certificate_warning_dialog.glade:29 msgid "_Always Trust This Server" msgstr "Sel_alu Percayai Server Ini" -#: ../ui/certificate_warning_dialog.glade.h:3 +#: ui/certificate_warning_dialog.glade:43 msgid "_Trust This Server" msgstr "_Percayai Server Ini" -#: ../ui/certificate_warning_dialog.glade.h:4 +#: ui/certificate_warning_dialog.glade:57 msgid "_Don’t Trust This Server" msgstr "_Jangan Percayai Server Ini" -#: ../ui/composer-headerbar.ui.h:1 +#: ui/composer-headerbar.ui:17 ui/composer-headerbar.ui:174 msgid "Detach (Ctrl+D)" msgstr "Lepas (Ctrl+D)" -#: ../ui/composer-headerbar.ui.h:2 +#: ui/composer-headerbar.ui:57 ui/composer-headerbar.ui:83 msgid "Attach File (Ctrl+T)" msgstr "Lampirkan Berkas (Ctrl+T)" -#: ../ui/composer-headerbar.ui.h:3 +#: ui/composer-headerbar.ui:107 msgid "Include Original Attachments" msgstr "Sertakan Lampiran Asli" -#: ../ui/composer-headerbar.ui.h:4 -msgid "Send (Ctrl+Enter)" -msgstr "Kirim (Ctrl+Enter)" - -#: ../ui/composer-headerbar.ui.h:5 +#: ui/composer-headerbar.ui:202 msgid "_Send" msgstr "_Kirim" -#: ../ui/composer-headerbar.ui.h:6 +#: ui/composer-headerbar.ui:207 +msgid "Send (Ctrl+Enter)" +msgstr "Kirim (Ctrl+Enter)" + +#: ui/composer-headerbar.ui:230 msgid "Discard and Close" msgstr "Buang dan Tutup" -#: ../ui/composer-headerbar.ui.h:7 +#: ui/composer-headerbar.ui:254 msgid "Save and Close" msgstr "Simpan dan Tutup" #. Note that this button and the Update button will never be shown at the same time to the user. -#: ../ui/composer-link-popover.ui.h:2 +#: ui/composer-link-popover.ui:41 msgid "Insert the new link with this URL" msgstr "Sisipkan tautan baru dengan URL ini" -#: ../ui/composer-link-popover.ui.h:3 +#: ui/composer-link-popover.ui:52 msgid "Link URL" msgstr "URL Tautan" #. Note that this button and the Insert button will never be shown at the same time to the user. -#: ../ui/composer-link-popover.ui.h:5 +#: ui/composer-link-popover.ui:66 msgid "Update this link’s URL" msgstr "Mutakhirkan URL tautan ini" -#: ../ui/composer-link-popover.ui.h:6 +#: ui/composer-link-popover.ui:86 msgid "Delete this link" msgstr "Hapus tautan ini" -#: ../ui/composer-link-popover.ui.h:7 +#: ui/composer-link-popover.ui:106 msgid "Open this link" msgstr "Buka tautan ini" -#: ../ui/composer-menus.ui.h:1 +#: ui/composer-menus.ui:7 msgid "S_ans Serif" msgstr "S_ans Serif" -#: ../ui/composer-menus.ui.h:2 +#: ui/composer-menus.ui:12 msgid "S_erif" msgstr "S_erif" -#: ../ui/composer-menus.ui.h:3 +#: ui/composer-menus.ui:17 msgid "_Fixed Width" msgstr "Lebar _Tetap" -#: ../ui/composer-menus.ui.h:4 +#: ui/composer-menus.ui:24 msgid "_Small" msgstr "_Kecil" -#: ../ui/composer-menus.ui.h:5 +#: ui/composer-menus.ui:29 msgid "_Medium" msgstr "_Sedang" -#: ../ui/composer-menus.ui.h:6 +#: ui/composer-menus.ui:34 msgid "Lar_ge" msgstr "_Besar" -#: ../ui/composer-menus.ui.h:7 +#: ui/composer-menus.ui:41 msgid "C_olor" msgstr "_Warna" -#: ../ui/composer-menus.ui.h:8 +#: ui/composer-menus.ui:47 ui/composer-menus.ui:62 msgid "_Rich Text" msgstr "_Rich Text" -#: ../ui/composer-menus.ui.h:9 +#: ui/composer-menus.ui:53 ui/composer-menus.ui:68 msgid "Show Extended Fields" msgstr "Tampilkan Ruas Yang Diperluas" -#: ../ui/composer-menus.ui.h:10 +#: ui/composer-menus.ui:78 msgid "_Undo" msgstr "Tak _Jadi" -#: ../ui/composer-menus.ui.h:11 +#: ui/composer-menus.ui:82 msgid "_Redo" msgstr "Jadi _Lagi" -#: ../ui/composer-menus.ui.h:12 +#: ui/composer-menus.ui:88 ui/composer-menus.ui:106 msgid "Cu_t" msgstr "Po_tong" -#: ../ui/composer-menus.ui.h:13 ../ui/conversation-message-menus.ui.h:7 +#: ui/composer-menus.ui:92 ui/composer-menus.ui:110 +#: ui/conversation-message-menus.ui:37 msgid "_Copy" msgstr "Sali_n" -#: ../ui/composer-menus.ui.h:14 +#: ui/composer-menus.ui:96 ui/composer-menus.ui:114 msgid "_Paste" msgstr "Tem_pel" -#: ../ui/composer-menus.ui.h:15 -msgctxt "Clipboard paste with rich text" -msgid "Paste _With Formatting" -msgstr "Tempel Dengan _Format" +#: ui/composer-menus.ui:100 +msgctxt "Clipboard paste as plain text" +msgid "Paste _Without Formatting" +msgstr "Tempel _Tanpa Format" -#: ../ui/composer-menus.ui.h:16 +#: ui/composer-menus.ui:120 msgid "Select _All" msgstr "Pilih Semu_a" -#: ../ui/composer-menus.ui.h:17 ../ui/conversation-message-menus.ui.h:9 +#: ui/composer-menus.ui:127 ui/conversation-message-menus.ui:49 msgid "_Inspect…" msgstr "Per_iksa…" #. Address(es) e-mail is to be sent to -#: ../ui/composer-widget.ui.h:2 +#: ui/composer-widget.ui:56 msgid "_To" msgstr "_Ke" -#: ../ui/composer-widget.ui.h:3 +#: ui/composer-widget.ui:75 msgid "_Cc" msgstr "_Cc" -#: ../ui/composer-widget.ui.h:4 +#: ui/composer-widget.ui:130 msgid "_Subject" msgstr "_Perihal" -#: ../ui/composer-widget.ui.h:5 +#: ui/composer-widget.ui:149 msgid "_Bcc" msgstr "_Bcc" -#: ../ui/composer-widget.ui.h:6 +#: ui/composer-widget.ui:179 msgid "_Reply-To" msgstr "B_alas-Ke" #. Geary account mail will be sent from -#: ../ui/composer-widget.ui.h:8 +#: ui/composer-widget.ui:208 msgid "From" msgstr "Dari" -#: ../ui/composer-widget.ui.h:9 +#: ui/composer-widget.ui:293 msgid "Drop files here" msgstr "Jatuhkan berkas di sini" -#: ../ui/composer-widget.ui.h:10 +#: ui/composer-widget.ui:309 msgid "To add them as attachments" msgstr "Untuk menambahkan mereka sebagai lampiran" -#: ../ui/composer-widget.ui.h:11 +#: ui/composer-widget.ui:348 msgid "Undo last edit (Ctrl+Z)" msgstr "Batalkan penyuntingan terakhir (Ctrl+Z)" -#: ../ui/composer-widget.ui.h:12 +#: ui/composer-widget.ui:372 msgid "Redo last edit (Ctrl+Shift+Z)" msgstr "Ulangi penyuntingan terakhir (Ctrl+Shift+Z)" -#: ../ui/composer-widget.ui.h:13 +#: ui/composer-widget.ui:410 msgid "Bold (Ctrl+B)" msgstr "Tebal (Ctrl+B)" -#: ../ui/composer-widget.ui.h:14 +#: ui/composer-widget.ui:434 msgid "Italic (Ctrl+I)" msgstr "Miring (Ctrl+I)" -#: ../ui/composer-widget.ui.h:15 +#: ui/composer-widget.ui:458 msgid "Underline (Ctrl+U)" msgstr "Garis bawah (Ctrl+U)" -#: ../ui/composer-widget.ui.h:16 +#: ui/composer-widget.ui:482 msgid "Strikethrough (Ctrl+K)" msgstr "Coret (Ctrl+K)" -#: ../ui/composer-widget.ui.h:17 +#: ui/composer-widget.ui:520 +msgid "Insert unordered list" +msgstr "Masukkan daftar tidak berurutan" + +#: ui/composer-widget.ui:544 +msgid "Insert ordered list" +msgstr "Masukkan daftar berurutan" + +#: ui/composer-widget.ui:582 msgid "Quote text (Ctrl+])" msgstr "Kutip teks (Ctrl+])" -#: ../ui/composer-widget.ui.h:18 +#: ui/composer-widget.ui:606 msgid "Unquote text (Ctrl+[)" msgstr "Tidak mengutip teks (Ctrl+[)" -#: ../ui/composer-widget.ui.h:19 +#: ui/composer-widget.ui:644 msgid "Insert or update selection link (Ctrl+L)" msgstr "Sisipkan atau mutakhirkan tautan yang dipilih (Ctrl+L)" -#: ../ui/composer-widget.ui.h:20 +#: ui/composer-widget.ui:668 msgid "Insert an image (Ctrl+G)" msgstr "Sisipkan citra (Ctrl+G)" -#: ../ui/composer-widget.ui.h:21 +#: ui/composer-widget.ui:702 msgid "Remove selection formatting (Ctrl+Space)" msgstr "Hapus format bagian yang dipilih (Ctrl+Space)" -#: ../ui/composer-widget.ui.h:22 +#: ui/composer-widget.ui:726 msgid "Select spell checking languages" msgstr "Pilih bahasa pemeriksaan ejaan" -#: ../ui/conversation-email.ui.h:1 +#: ui/conversation-email.ui:27 msgid "Save all attachments" msgstr "Simpan semua lampiran" #. Note: The application will never show this button at the same time as unstar_button, one will always be hidden. -#: ../ui/conversation-email.ui.h:3 +#: ui/conversation-email.ui:50 msgid "Mark this message as starred" msgstr "Tandai pesan ini sebagai dibintangi" #. Note: The application will never show this button at the same time as star_button, one will always be hidden. -#: ../ui/conversation-email.ui.h:5 +#: ui/conversation-email.ui:72 msgid "Mark this message as not starred" msgstr "Tandai pesan ini sebagai tidak dibintangi" -#: ../ui/conversation-email.ui.h:6 +#: ui/conversation-email.ui:95 msgid "Display the message menu" msgstr "Tampilkan menu pesan" -#: ../ui/conversation-email.ui.h:7 +#: ui/conversation-email.ui:161 msgid "Open selected attachments" msgstr "Buka lampiran yang dipilih" -#: ../ui/conversation-email.ui.h:8 +#: ui/conversation-email.ui:178 msgid "Save selected attachments" msgstr "Simpan lampiran yang dipilih" -#: ../ui/conversation-email.ui.h:9 +#: ui/conversation-email.ui:195 msgid "Select all attachments" msgstr "Pilih semua lampiran" -#: ../ui/conversation-email.ui.h:10 +#: ui/conversation-email.ui:240 msgid "Edit Draft" msgstr "Sunting Draf" -#: ../ui/conversation-email.ui.h:11 +#: ui/conversation-email.ui:267 msgid "Draft message" msgstr "Draf pesan" -#: ../ui/conversation-email.ui.h:12 +#: ui/conversation-email.ui:283 msgid "This message has not yet been sent." msgstr "Pesan ini belum dikirim." -#: ../ui/conversation-email.ui.h:13 -msgid "Try Again" -msgstr "Coba Lagi" - -#: ../ui/conversation-email.ui.h:14 +#: ui/conversation-email.ui:329 msgid "Message not saved" msgstr "Pesan tidak disimpan" -#: ../ui/conversation-email.ui.h:15 +#: ui/conversation-email.ui:345 msgid "This message was sent, but has not been saved to your account." msgstr "Pesan ini terkirim, tapi belum disimpan ke akun Anda." -#: ../ui/conversation-email-menus.ui.h:2 +#. Translators: Menu item to reply to a specific message. +#: ui/conversation-email-menus.ui:15 msgid "Reply to _All" msgstr "Balas ke Semu_a" -#: ../ui/conversation-email-menus.ui.h:4 +#. Translators: Menu item to mark a specific message as +#. read. +#: ui/conversation-email-menus.ui:30 msgid "_Mark Read" msgstr "Tan_dai sudah Dibaca" -#: ../ui/conversation-email-menus.ui.h:5 +#: ui/conversation-email-menus.ui:36 msgid "_Mark Unread" msgstr "Tandai Belu_m Dibaca" -#: ../ui/conversation-email-menus.ui.h:6 +#. Translators: Menu item to mark all messages in a +#. conversation from this one as unread. +#: ui/conversation-email-menus.ui:42 msgid "Mark Unread From _Here" msgstr "Tandai Belum Dibaca Dari _Sini" -#: ../ui/conversation-email-menus.ui.h:8 +#. Translators: Menu item to move a single, specific message +#. to the trash +#: ui/conversation-email-menus.ui:50 +msgid "_Trash" +msgstr "_Sampah" + +#. Translators: Menu item to delete a single, specific message +#: ui/conversation-email-menus.ui:57 +msgid "_Delete…" +msgstr "_Hapus…" + +#. Translators: Menu item to view the source for a message +#: ui/conversation-email-menus.ui:69 msgid "_View Source" msgstr "Lihat Su_mber" -#: ../ui/conversation-email-menus.ui.h:11 +#: ui/conversation-email-menus.ui:87 msgid "_Save All" msgstr "Simpan Semu_a" -#: ../ui/conversation-message-menus.ui.h:1 +#: ui/conversation-message-menus.ui:7 msgid "_Open Link" msgstr "_Buka Tautan" -#: ../ui/conversation-message-menus.ui.h:2 +#: ui/conversation-message-menus.ui:11 msgid "Copy Link _Address" msgstr "Salin _Alamat Tautan" -#: ../ui/conversation-message-menus.ui.h:3 +#: ui/conversation-message-menus.ui:17 msgid "Send New _Message…" msgstr "Kiri_m Pesan Baru…" -#: ../ui/conversation-message-menus.ui.h:4 +#: ui/conversation-message-menus.ui:21 msgid "Copy Email _Address" msgstr "Salin _Alamat Surel" -#: ../ui/conversation-message-menus.ui.h:5 +#: ui/conversation-message-menus.ui:27 msgid "Save _Image As…" msgstr "Simpan C_itra Sebagai…" -#: ../ui/conversation-message-menus.ui.h:6 +#: ui/conversation-message-menus.ui:33 msgid "_Select All" msgstr "Pilih _Semua" -#: ../ui/conversation-message-menus.ui.h:8 +#: ui/conversation-message-menus.ui:43 msgid "Search for messages from" msgstr "Cari pesan dari" -#: ../ui/conversation-message.ui.h:1 +#: ui/conversation-message.ui:64 msgid "From " msgstr "Dari " -#: ../ui/conversation-message.ui.h:2 +#: ui/conversation-message.ui:80 ui/conversation-message.ui:179 msgid "1/1/1970\t" msgstr "1/1/1970\t" -#: ../ui/conversation-message.ui.h:3 +#: ui/conversation-message.ui:103 msgid "Preview body text." msgstr "Pratinjau tubuh teks." -#: ../ui/conversation-message.ui.h:4 +#: ui/conversation-message.ui:203 msgid "Sent by:" msgstr "Dikirim oleh:" -#: ../ui/conversation-message.ui.h:5 +#: ui/conversation-message.ui:248 msgid "Reply to:" msgstr "Balas ke:" -#: ../ui/conversation-message.ui.h:6 +#: ui/conversation-message.ui:292 msgid "Subject" msgstr "Perihal" -#: ../ui/conversation-message.ui.h:7 -msgid "To:" -msgstr "Ke:" - -#: ../ui/conversation-message.ui.h:8 -msgid "Cc:" -msgstr "Cc:" - -#: ../ui/conversation-message.ui.h:9 -msgid "Bcc:" -msgstr "Bcc:" - -#: ../ui/conversation-message.ui.h:10 +#: ui/conversation-message.ui:502 msgid "Show Images" msgstr "Tampilkan Gambar" -#: ../ui/conversation-message.ui.h:11 +#: ui/conversation-message.ui:515 msgid "Always Show From Sender" msgstr "Selalu Tampilkan Dari Pengirim" -#: ../ui/conversation-message.ui.h:12 +#: ui/conversation-message.ui:543 msgid "Remote images not shown" msgstr "Citra remote tidak ditampilkan" -#: ../ui/conversation-message.ui.h:13 +#: ui/conversation-message.ui:560 msgid "Only show remote images from senders you trust." msgstr "Hanya tampilkan citra remote dari pengirim yang Anda percayai." -#: ../ui/conversation-message.ui.h:14 +#: ui/conversation-message.ui:692 msgid "But actually goes to:" msgstr "Tapi sebenarnya ke:" -#: ../ui/conversation-message.ui.h:15 +#: ui/conversation-message.ui:723 msgid "The link appears to go to:" msgstr "Tautan ini nampaknya menuju ke:" -#: ../ui/conversation-message.ui.h:16 +#: ui/conversation-message.ui:735 msgid "Deceptive link found" msgstr "Ditemukan tautan yang menipu" -#: ../ui/conversation-message.ui.h:17 +#: ui/conversation-message.ui:750 msgid "The email sender may be leading you to the wrong web site." msgstr "Pengirim surel mungkin mengecoh Anda ke situs web yang salah." -#: ../ui/conversation-message.ui.h:18 +#: ui/conversation-message.ui:763 msgid "If unsure, contact the sender and ask before continuing." msgstr "Bila tidak yakin, hubungi pengirim dan tanyakan sebelum melanjutkan." -#: ../ui/conversation-viewer.ui.h:1 +#: ui/conversation-viewer.ui:60 msgid "Find in conversation" msgstr "Cari dalam percakapan" -#: ../ui/conversation-viewer.ui.h:2 +#: ui/conversation-viewer.ui:74 msgid "Find the previous occurrence of the search string." msgstr "Cari kemunculan sebelumnya dari kalimat yang dicari." -#: ../ui/conversation-viewer.ui.h:3 +#: ui/conversation-viewer.ui:95 msgid "Find the next occurrence of the search string." msgstr "Cari kemunculan selanjutnya dari kalimat yang dicari." -#: ../ui/edit_alternate_emails.glade.h:1 +#: ui/edit_alternate_emails.glade:112 msgid "Remove email address" msgstr "Hapus alamat surel" -#: ../ui/edit_alternate_emails.glade.h:2 +#: ui/edit_alternate_emails.glade:136 msgid "" "Some email services require additional addresses be configured on the " "server. Contact your email provider for more information." @@ -2150,476 +2538,532 @@ "Beberapa layanan surel memerlukan alamat-alamat tambahan ditata pada server. " "Kontak penyedia surel Anda untuk informasi lebih jauh." -#: ../ui/edit_alternate_emails.glade.h:4 +#: ui/edit_alternate_emails.glade:175 msgid "_Update" msgstr "M_utakhirkan" -#: ../ui/find_bar.glade.h:1 +#: ui/find_bar.glade:66 msgid "Find:" msgstr "Cari:" -#: ../ui/find_bar.glade.h:2 +#: ui/find_bar.glade:89 msgid "_Previous" msgstr "Se_belumnya" -#: ../ui/find_bar.glade.h:3 +#: ui/find_bar.glade:107 msgid "_Next" msgstr "Se_lanjutnya" -#: ../ui/find_bar.glade.h:4 +#: ui/find_bar.glade:125 msgid "_Case sensitive" msgstr "Peka huruf besar ke_cil" -#: ../ui/find_bar.glade.h:5 +#: ui/find_bar.glade:145 msgid "label" msgstr "label" -#: ../ui/gtk/help-overlay.ui.h:1 +#: ui/gtk/help-overlay.ui:9 msgid "Conversation Shortcuts" msgstr "Pintasan Percakapan" -#: ../ui/gtk/help-overlay.ui.h:2 +#: ui/gtk/help-overlay.ui:13 ui/gtk/help-overlay.ui:254 msgctxt "shortcut window" msgid "General" msgstr "Umum" -#: ../ui/gtk/help-overlay.ui.h:3 +#: ui/gtk/help-overlay.ui:17 msgctxt "shortcut window" msgid "Move focus to the next/previous pane" msgstr "Pindahkan fokus ke panel selanjutnya/sebelumnya" -#: ../ui/gtk/help-overlay.ui.h:4 +#: ui/gtk/help-overlay.ui:24 msgctxt "shortcut window" msgid "Move focus to conversation list" msgstr "Pindahkan fokus ke daftar percakapan" -#: ../ui/gtk/help-overlay.ui.h:5 +#: ui/gtk/help-overlay.ui:31 msgctxt "shortcut window" msgid "Detach composer window" msgstr "Copot jendela penyusun" -#: ../ui/gtk/help-overlay.ui.h:6 +#: ui/gtk/help-overlay.ui:38 msgctxt "shortcut window" msgid "Close composer window" msgstr "Tutup jendela penyusun" -#: ../ui/gtk/help-overlay.ui.h:7 +#: ui/gtk/help-overlay.ui:45 msgctxt "shortcut window" msgid "Show keyboard shortcuts" msgstr "Tampilkan pintasan papan tik" -#: ../ui/gtk/help-overlay.ui.h:8 +#: ui/gtk/help-overlay.ui:52 msgctxt "shortcut window" msgid "Show help" msgstr "Tampilkan bantuan" -#: ../ui/gtk/help-overlay.ui.h:9 +#: ui/gtk/help-overlay.ui:59 msgctxt "shortcut window" msgid "Quit the application" msgstr "Keluar aplikasi" -#: ../ui/gtk/help-overlay.ui.h:10 +#: ui/gtk/help-overlay.ui:68 msgctxt "shortcut window" msgid "Search" msgstr "Cari" -#: ../ui/gtk/help-overlay.ui.h:11 +#: ui/gtk/help-overlay.ui:72 msgctxt "shortcut window" msgid "Jump to search box" msgstr "Lompat ke kotak pencarian" -#: ../ui/gtk/help-overlay.ui.h:12 +#: ui/gtk/help-overlay.ui:79 msgctxt "shortcut window" msgid "Find in current conversation" msgstr "Cari dalam percakapan saat ini" -#: ../ui/gtk/help-overlay.ui.h:13 +#: ui/gtk/help-overlay.ui:86 msgctxt "shortcut window" msgid "Find next/previous in current conversation" msgstr "Cari selanjutnya/sebelumnya dalam percakapan saat ini" -#: ../ui/gtk/help-overlay.ui.h:14 +#: ui/gtk/help-overlay.ui:95 ui/gtk/help-overlay.ui:274 msgctxt "shortcut window" msgid "Actions" msgstr "Aksi" -#: ../ui/gtk/help-overlay.ui.h:15 +#: ui/gtk/help-overlay.ui:99 msgctxt "shortcut window" msgid "Compose a new message" msgstr "Susun suatu pesan baru" -#: ../ui/gtk/help-overlay.ui.h:16 +#: ui/gtk/help-overlay.ui:106 msgctxt "shortcut window" msgid "Reply to sender " msgstr "Balas ke pengirim " -#: ../ui/gtk/help-overlay.ui.h:17 +#: ui/gtk/help-overlay.ui:113 msgctxt "shortcut window" msgid "Reply to all" msgstr "Balas ke semua" -#: ../ui/gtk/help-overlay.ui.h:18 +#: ui/gtk/help-overlay.ui:120 msgctxt "shortcut window" msgid "Forward" msgstr "Teruskan" -#: ../ui/gtk/help-overlay.ui.h:19 +#: ui/gtk/help-overlay.ui:127 msgctxt "shortcut window" msgid "Archive" msgstr "Arsip" -#: ../ui/gtk/help-overlay.ui.h:20 +#: ui/gtk/help-overlay.ui:134 msgctxt "shortcut window" msgid "Move to trash" msgstr "Pindah ke tong sampah" -#: ../ui/gtk/help-overlay.ui.h:21 +#: ui/gtk/help-overlay.ui:141 msgctxt "shortcut window" msgid "Toggle spam" msgstr "Jungkitkan spam" -#: ../ui/gtk/help-overlay.ui.h:22 +#: ui/gtk/help-overlay.ui:148 msgctxt "shortcut window" msgid "Move the conversation" msgstr "Pindahkan percakapan" -#: ../ui/gtk/help-overlay.ui.h:23 +#: ui/gtk/help-overlay.ui:155 msgctxt "shortcut window" msgid "Label the conversation" msgstr "Labeli ke percakapan" -#: ../ui/gtk/help-overlay.ui.h:24 +#: ui/gtk/help-overlay.ui:163 msgctxt "shortcut window" msgid "Mark read" msgstr "Tandai sudah dibaca" -#: ../ui/gtk/help-overlay.ui.h:25 +#: ui/gtk/help-overlay.ui:170 msgctxt "shortcut window" msgid "Mark unread" msgstr "Tandai belum dibaca" -#: ../ui/gtk/help-overlay.ui.h:26 +#: ui/gtk/help-overlay.ui:179 msgctxt "shortcut window" msgid "View" msgstr "Tilik" -#: ../ui/gtk/help-overlay.ui.h:27 +#: ui/gtk/help-overlay.ui:183 msgctxt "shortcut window" msgid "Zoom in" msgstr "Perbesar" -#: ../ui/gtk/help-overlay.ui.h:28 +#: ui/gtk/help-overlay.ui:190 msgctxt "shortcut window" msgid "Zoom out" msgstr "Perkecil" -#: ../ui/gtk/help-overlay.ui.h:29 +#: ui/gtk/help-overlay.ui:197 msgctxt "shortcut window" msgid "Reset zoom" msgstr "Reset zum" -#: ../ui/gtk/help-overlay.ui.h:30 +#: ui/gtk/help-overlay.ui:206 msgctxt "shortcut window" msgid "Additional Shortcuts" msgstr "Pintasan Tambahan" -#: ../ui/gtk/help-overlay.ui.h:31 +#: ui/gtk/help-overlay.ui:210 msgctxt "shortcut window" msgid "Star" msgstr "Bintangi" -#: ../ui/gtk/help-overlay.ui.h:32 +#: ui/gtk/help-overlay.ui:217 msgctxt "shortcut window" msgid "Unstar" msgstr "Hapus bintang" -#: ../ui/gtk/help-overlay.ui.h:33 +#: ui/gtk/help-overlay.ui:224 msgctxt "shortcut window" msgid "Delete" msgstr "Hapus" -#: ../ui/gtk/help-overlay.ui.h:34 +#: ui/gtk/help-overlay.ui:231 msgctxt "shortcut window" msgid "Jump to next (older) conversation" msgstr "Lompat ke lokasi selanjutnya (lebih lama)" -#: ../ui/gtk/help-overlay.ui.h:35 +#: ui/gtk/help-overlay.ui:238 msgctxt "shortcut window" msgid "Jump to previous (newer) conversation" msgstr "Lompat ke lokasi sebelumnya (lebih baru)" -#: ../ui/gtk/help-overlay.ui.h:36 +#: ui/gtk/help-overlay.ui:250 msgid "Composer Shortcuts" msgstr "Pintasan Penyusun" -#: ../ui/gtk/help-overlay.ui.h:37 +#: ui/gtk/help-overlay.ui:258 msgctxt "shortcut window" msgid "Quote text" msgstr "Kutip teks" -#: ../ui/gtk/help-overlay.ui.h:38 +#: ui/gtk/help-overlay.ui:265 msgctxt "shortcut window" msgid "Unquote text" msgstr "Tidak mengutip teks" -#: ../ui/gtk/help-overlay.ui.h:39 +#: ui/gtk/help-overlay.ui:278 msgctxt "shortcut window" msgid "Send" msgstr "Kirim" -#: ../ui/gtk/help-overlay.ui.h:40 +#: ui/gtk/help-overlay.ui:285 msgctxt "shortcut window" msgid "Add attachment" msgstr "Tambah lampiran" -#: ../ui/gtk/help-overlay.ui.h:41 +#: ui/gtk/help-overlay.ui:294 msgctxt "shortcut window" msgid "Rich text mode" msgstr "Mode rich text" -#: ../ui/gtk/help-overlay.ui.h:42 +#: ui/gtk/help-overlay.ui:298 msgctxt "shortcut window" msgid "Bold text" msgstr "Teks tebal" -#: ../ui/gtk/help-overlay.ui.h:43 +#: ui/gtk/help-overlay.ui:305 msgctxt "shortcut window" msgid "Italicize text" msgstr "Miringkan teks" -#: ../ui/gtk/help-overlay.ui.h:44 +#: ui/gtk/help-overlay.ui:312 msgctxt "shortcut window" msgid "Underline text" msgstr "Garisbawahi teks" -#: ../ui/gtk/help-overlay.ui.h:45 +#: ui/gtk/help-overlay.ui:319 msgctxt "shortcut window" msgid "Strike text" msgstr "Coret teks" -#: ../ui/gtk/help-overlay.ui.h:46 +#: ui/gtk/help-overlay.ui:326 msgctxt "shortcut window" msgid "Insert a link" msgstr "Sisipkan tautan" -#: ../ui/gtk/help-overlay.ui.h:47 +#: ui/gtk/help-overlay.ui:333 msgctxt "shortcut window" msgid "Remove formatting" msgstr "Buang format" -#: ../ui/gtk/menus.ui.h:2 +#: ui/gtk/menus.ui:13 msgid "A_ccounts" msgstr "A_kun" -#: ../ui/gtk/menus.ui.h:4 +#: ui/gtk/menus.ui:23 msgid "_Keyboard Shortcuts" msgstr "Pintasan Papan Ti_k" -#: ../ui/login.glade.h:1 +#: ui/login.glade:88 msgid "email@example.com" msgstr "email@example.com" -#: ../ui/login.glade.h:2 ../ui/password-dialog.glade.h:3 +#: ui/login.glade:107 ui/password-dialog.glade:108 msgid "Password" msgstr "Sandi" -#: ../ui/login.glade.h:3 +#: ui/login.glade:123 msgid "E_mail address" msgstr "Ala_mat surel" -#: ../ui/login.glade.h:4 +#: ui/login.glade:144 ui/login.glade:635 msgid "_Password" msgstr "_Sandi" -#: ../ui/login.glade.h:5 +#: ui/login.glade:178 msgid "S_ervice" msgstr "_Layanan" -#: ../ui/login.glade.h:6 +#: ui/login.glade:199 msgid "N_ame" msgstr "N_ama" -#: ../ui/login.glade.h:8 +#: ui/login.glade:256 msgid "N_ickname" msgstr "Nama pangg_ilan" -#: ../ui/login.glade.h:9 +#: ui/login.glade:280 msgid "Work, Home, etc." msgstr "Kantor, Rumah, dsb." -#: ../ui/login.glade.h:10 +#: ui/login.glade:291 msgid "_Save sent mail" msgstr "_Simpan surat terkirim" -#: ../ui/login.glade.h:11 +#: ui/login.glade:309 msgid "Addi_tional email addresses…" msgstr "Alamat surel _tambahan…" -#: ../ui/login.glade.h:12 +#: ui/login.glade:353 msgid "IMAP settings" msgstr "Pengaturan IMAP" -#: ../ui/login.glade.h:13 +#: ui/login.glade:372 msgid "Se_rver" msgstr "Se_rver" -#: ../ui/login.glade.h:14 +#: ui/login.glade:393 msgid "imap.example.com" msgstr "imap.example.com" -#: ../ui/login.glade.h:15 +#: ui/login.glade:409 msgid "P_ort" msgstr "P_ort" -#: ../ui/login.glade.h:16 +#: ui/login.glade:448 msgid "smtp.example.com" msgstr "smtp.example.com" -#: ../ui/login.glade.h:17 +#: ui/login.glade:480 msgid "Ser_ver" msgstr "Ser_ver" -#: ../ui/login.glade.h:18 +#: ui/login.glade:501 msgid "Por_t" msgstr "Por_t" -#: ../ui/login.glade.h:19 +#: ui/login.glade:522 msgid "SMTP settings" msgstr "Pengaturan SMTP" -#: ../ui/login.glade.h:20 +#: ui/login.glade:541 msgid "User_name" msgstr "_Nama pengguna" -#: ../ui/login.glade.h:21 +#: ui/login.glade:562 msgid "Pass_word" msgstr "San_di" -#: ../ui/login.glade.h:22 +#: ui/login.glade:582 msgid "SMTP username" msgstr "Nama pengguna SMTP" -#: ../ui/login.glade.h:23 +#: ui/login.glade:598 msgid "SMTP password" msgstr "Sandi SMTP" -#: ../ui/login.glade.h:24 +#: ui/login.glade:614 msgid "_Username" msgstr "Nama pengg_una" -#: ../ui/login.glade.h:25 +#: ui/login.glade:655 msgid "IMAP username" msgstr "Nama pengguna IMAP" -#: ../ui/login.glade.h:26 +#: ui/login.glade:671 msgid "IMAP password" msgstr "Sandi IMAP" -#: ../ui/login.glade.h:27 +#: ui/login.glade:688 msgid "Encr_yption" msgstr "Enkrip_si" -#: ../ui/login.glade.h:28 +#: ui/login.glade:711 msgid "Encrypt_ion" msgstr "Enkrips_i" -#: ../ui/login.glade.h:30 +#: ui/login.glade:733 ui/login.glade:751 msgid "SSL/TLS" msgstr "SSL/TLS" -#: ../ui/login.glade.h:31 +#: ui/login.glade:734 ui/login.glade:752 msgid "STARTTLS" msgstr "STARTTLS" -#: ../ui/login.glade.h:32 +#: ui/login.glade:764 msgid "No authentication re_quired" msgstr "Otenti_kasi tidak dibutuhkan" -#: ../ui/login.glade.h:33 +#: ui/login.glade:781 msgid "Use IMAP cre_dentials" msgstr "Pakai kre_densial IMAP" -#: ../ui/login.glade.h:34 +#: ui/login.glade:888 msgid "Composer" msgstr "Penyusun" -#: ../ui/login.glade.h:35 +#: ui/login.glade:901 msgid "Save dra_fts on server" msgstr "Simpan dra_f pada server" -#: ../ui/login.glade.h:36 +#: ui/login.glade:918 msgid "Si_gn emails (HTML allowed):" msgstr "Tandatan_gani surel (boleh HTML):" -#: ../ui/login.glade.h:37 +#: ui/login.glade:976 msgid "Storage" msgstr "Penyimpanan" -#: ../ui/login.glade.h:38 +#: ui/login.glade:998 msgid "_Download mail" msgstr "Un_duh surat" -#: ../ui/main-toolbar.ui.h:1 +#: ui/main-toolbar.ui:51 +msgid "Toggle search bar" +msgstr "Jungkitkan bilah pencarian" + +#: ui/main-toolbar.ui:72 msgid "Empty Spam or Trash folders" msgstr "Kosongkan folder Spam atau Sampah" -#: ../ui/password-dialog.glade.h:1 +#: ui/main-toolbar.ui:112 +msgid "Reply" +msgstr "Balas" + +#: ui/main-toolbar.ui:135 +msgid "Reply All" +msgstr "Balas Semua" + +#: ui/main-toolbar.ui:158 +msgid "Forward" +msgstr "Teruskan" + +#: ui/main-toolbar.ui:264 +msgid "Toggle find bar" +msgstr "Jungkitkan bilah pencarian" + +#: ui/main-toolbar.ui:306 +msgid "_Archive" +msgstr "_Arsip" + +#: ui/main-toolbar-menus.ui:5 +msgid "Empty _Spam…" +msgstr "Kosongkan _Spam…" + +#: ui/main-toolbar-menus.ui:9 +msgid "Empty _Trash…" +msgstr "Kosongkan _Tong Sampah…" + +#: ui/main-toolbar-menus.ui:32 +msgid "Mark as S_pam" +msgstr "Tandai sebagai S_pam" + +#: ui/main-toolbar-menus.ui:36 +msgid "Mark as not S_pam" +msgstr "Tandai sebagai bukan S_pam" + +#: ui/main-window-info-bar.ui:97 +msgid "" +"If the problem is serious or persists, please copy and send these details to " +"the mailing list " +"or file a new " +"bug report." +msgstr "" +"Jika masalahnya serius atau berlanjut, salin dan kirim rincian ini ke milis atau kirim laporan kutu baru." + +#: ui/main-window-info-bar.ui:113 +msgid "Details:" +msgstr "Detail:" + +#: ui/password-dialog.glade:74 msgid "SMTP Credentials" msgstr "Kredensial SMTP" -#: ../ui/password-dialog.glade.h:2 +#: ui/password-dialog.glade:91 msgid "Username" msgstr "Nama pengguna" -#: ../ui/password-dialog.glade.h:4 +#: ui/password-dialog.glade:152 msgid "_Remember password" msgstr "_Ingat sandi" -#: ../ui/password-dialog.glade.h:6 +#: ui/password-dialog.glade:210 msgid "_Authenticate" msgstr "Otentik_asikan" -#: ../ui/preferences-dialog.ui.h:1 +#: ui/preferences-dialog.ui:38 msgid "Reading" msgstr "Membaca" -#: ../ui/preferences-dialog.ui.h:2 +#: ui/preferences-dialog.ui:51 msgid "_Automatically select next message" msgstr "Otom_atis pilih pesan selanjutnya" -#: ../ui/preferences-dialog.ui.h:3 +#: ui/preferences-dialog.ui:70 msgid "_Display conversation preview" msgstr "_Tampilkan pratinjau percakapan" -#: ../ui/preferences-dialog.ui.h:4 +#: ui/preferences-dialog.ui:89 msgid "Use _three pane view" msgstr "Pakai tilikan _tiga panel" -#: ../ui/preferences-dialog.ui.h:5 +#: ui/preferences-dialog.ui:113 msgid "Notifications" msgstr "Pemberitahuan" -#: ../ui/preferences-dialog.ui.h:6 +#: ui/preferences-dialog.ui:126 msgid "_Play notification sounds" msgstr "Bunyikan suara _pemberitahuan" -#: ../ui/preferences-dialog.ui.h:7 +#: ui/preferences-dialog.ui:145 msgid "Show _notifications for new mail" msgstr "Tu_njukkan pemberitahuan bagi surat baru" -#: ../ui/preferences-dialog.ui.h:8 -msgid "Always _watch for new mail" -msgstr "Selalu a_wasi surat baru" - -#: ../ui/preferences-dialog.ui.h:9 -msgid "Geary will run in the background and notify of new mail" -msgstr "Geary akan berjalan di latar dan memberitahukan adanya surel baru" +#: ui/preferences-dialog.ui:164 +msgid "_Watch for new mail when closed" +msgstr "A_wasi surat baru ketika ditutup" + +#: ui/preferences-dialog.ui:168 +msgid "Geary will keep running after all windows are closed" +msgstr "Geary akan terus berjalan setelah semua jendela ditutup" -#: ../ui/preferences-dialog.ui.h:10 +#: ui/preferences-dialog.ui:195 msgid "Preferences" msgstr "Preferensi" -#: ../ui/remove_confirm.glade.h:1 +#: ui/remove_confirm.glade:43 msgid "" "Are you sure you want to remove this " "account? " @@ -2627,7 +3071,7 @@ "Anda yakin ingin menghapus akun ini? " -#: ../ui/remove_confirm.glade.h:2 +#: ui/remove_confirm.glade:58 msgid "" "All email associated with this account will be removed from your computer. " "This will not affect email on the server." @@ -2635,17 +3079,72 @@ "Semua surel terkait akun ini akan dihapus dari komputer Anda. Ini tidak " "mempengaruhi surel pada server." -#: ../ui/remove_confirm.glade.h:3 +#: ui/remove_confirm.glade:80 msgid "Nickname:" msgstr "Nama panggilan:" -#: ../ui/remove_confirm.glade.h:4 +#: ui/remove_confirm.glade:94 msgid "Email address:" msgstr "Alamat surel:" -#: ../ui/upgrade_dialog.glade.h:1 +#: ui/upgrade_dialog.glade:60 msgid "Geary update in progress…" msgstr "Pemutakhiran Geary sedang berlangsung…" +#~ msgid "Default attachments directory" +#~ msgstr "Direktori lampiran bawaan" + +#~ msgid "Location used when opening and saving attachments." +#~ msgstr "Lokasi yang digunakan saat membuka dan menyimpan lampiran." + +#~ msgid "Default print output directory" +#~ msgstr "Direktori keluaran cetak bawaan" + +#~ msgid "Location used when printing to a file." +#~ msgstr "Lokasi yang digunakan saat mencetak ke berkas." + +#~ msgid "Geary will run in the background and notify of new mail" +#~ msgstr "Geary akan berjalan di latar dan memberitahukan adanya surel baru" + +#~ msgid "Geary Email" +#~ msgstr "Surel Geary" + #~ msgid "Mail Client" #~ msgstr "Klien Surat" + +#~ msgid "Geary Mail" +#~ msgstr "Surat Geary" + +#~ msgid "_Mark as…" +#~ msgstr "_Tandai sebagai…" + +#~ msgid "Add label" +#~ msgstr "Tambah label" + +#~ msgid "_Label" +#~ msgstr "_Label" + +#~ msgid "_Move" +#~ msgstr "_Pindahkan" + +#~ msgid "Compose new message (Ctrl+N, N)" +#~ msgstr "Susun pesan baru (Ctrl+N, N)" + +#~ msgid "Reply (Ctrl+R, R)" +#~ msgstr "Balas (Ctrl+R, R)" + +#~ msgid "Reply all (Ctrl+Shift+R, Shift+R)" +#~ msgstr "Balas semua (Ctrl+Shift+R, Shift+R)" + +#~ msgid "Forward (Ctrl+L, F)" +#~ msgstr "Maju (Ctrl+L, F)" + +#~ msgid "" +#~ "Geary encountered an error while connecting to the server. Please try " +#~ "again in a few moments." +#~ msgstr "" +#~ "Geary menemui galat ketika menyambung ke server. Harap coba lagi setelah " +#~ "beberapa saat." + +#~ msgid "Try Again" +#~ msgstr "Coba Lagi" diff -Nru geary-0.12.4/po/it.po geary-3.32.0/po/it.po --- geary-0.12.4/po/it.po 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/po/it.po 2019-03-17 13:39:29.000000000 +0000 @@ -17,10 +17,9 @@ msgid "" msgstr "" "Project-Id-Version: geary master\n" -"Report-Msgid-Bugs-To: https://bugzilla.gnome.org/enter_bug.cgi?" -"product=geary&keywords=I18N+L10N&component=internationalization\n" -"POT-Creation-Date: 2017-11-16 05:25+0000\n" -"PO-Revision-Date: 2018-01-28 11:07+0100\n" +"Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/geary/issues\n" +"POT-Creation-Date: 2018-06-13 23:57+0000\n" +"PO-Revision-Date: 2018-06-29 09:18+0200\n" "Last-Translator: Federico Bruni \n" "Language-Team: Italiano <>\n" "Language: it\n" @@ -30,27 +29,50 @@ "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Gtranslator 2.91.7\n" +#: desktop/geary-attach.contract.desktop.in:3 +msgid "Send by email" +msgstr "Invia per email" + +#. Translators: Do NOT translate or transliterate this text (this is an icon file name)! +#: desktop/geary-attach.contract.desktop.in:5 +msgid "mail-send" +msgstr "mail-send" + #. Translators: The application name -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:2 -#: ../desktop/org.gnome.Geary.desktop.in.h:1 -#: ../desktop/geary-autostart.desktop.in.h:1 +#: desktop/geary-autostart.desktop.in:3 +#: desktop/org.gnome.Geary.appdata.xml.in:11 +#: desktop/org.gnome.Geary.desktop.in:3 msgid "Geary" msgstr "Geary" -#. Translators: The development team's name -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:4 -msgid "Geary Development Team" -msgstr "Gruppo sviluppatori di Geary" +#: desktop/geary-autostart.desktop.in:4 desktop/org.gnome.Geary.desktop.in:4 +msgid "Email" +msgstr "Email" #. Translators: The application's summary / tagline -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:6 -#: ../desktop/org.gnome.Geary.desktop.in.h:4 -#: ../desktop/geary-autostart.desktop.in.h:4 -#: ../src/client/application/geary-application.vala:21 +#: desktop/geary-autostart.desktop.in:5 +#: desktop/org.gnome.Geary.appdata.xml.in:15 +#: desktop/org.gnome.Geary.desktop.in:5 +#: src/client/application/geary-application.vala:21 msgid "Send and receive email" msgstr "Invia e riceve email" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:7 +#. Translators: These are desktop search terms. Do not translate semicolons, end line with a semicolon. +#: desktop/geary-autostart.desktop.in:7 +msgid "Email;E-mail;Mail;" +msgstr "Email;E-mail;Mail;Posta;" + +#. Translators: Do NOT translate or transliterate this text (this is an icon file name)! +#: desktop/geary-autostart.desktop.in:9 desktop/org.gnome.Geary.desktop.in:9 +msgid "org.gnome.Geary" +msgstr "org.gnome.Geary" + +#. Translators: The development team's name +#: desktop/org.gnome.Geary.appdata.xml.in:13 +msgid "Geary Development Team" +msgstr "Gruppo sviluppatori di Geary" + +#: desktop/org.gnome.Geary.appdata.xml.in:17 msgid "" "Geary is an email application built around conversations, for the GNOME 3 " "desktop. It allows you to read, find and send email with a straightforward, " @@ -60,7 +82,7 @@ "l'ambiente desktop GNOME 3. Permette di leggere, trovare e inviare email con " "un'interfaccia semplice e moderna." -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:8 +#: desktop/org.gnome.Geary.appdata.xml.in:22 msgid "" "Conversations allow you to read a complete discussion without having to find " "and click from message to message." @@ -68,98 +90,284 @@ "Le conversazioni permettono di leggere un'intera discussione senza dover " "cercare e fare clic su ogni messaggio." -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:9 +#: desktop/org.gnome.Geary.appdata.xml.in:26 msgid "Geary’s features include:" msgstr "Le funzionalità di Geary comprendono:" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:10 +#: desktop/org.gnome.Geary.appdata.xml.in:28 msgid "Quick email account setup" msgstr "Rapida configurazione dell'account di posta" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:11 +#: desktop/org.gnome.Geary.appdata.xml.in:29 msgid "Shows related messages together in conversations" msgstr "Mostra messaggi correlati insieme in conversazioni" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:12 +#: desktop/org.gnome.Geary.appdata.xml.in:30 msgid "Fast, full text and keyword search" -msgstr "Ricerca rapida \"full text\" e per parole chiave" +msgstr "Ricerca rapida a testo intero (\"full text\") e per parole chiave" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:13 +#: desktop/org.gnome.Geary.appdata.xml.in:31 msgid "Full-featured HTML and plain text message composer" msgstr "Compositore dei messaggi in HTML e testo semplice" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:14 +#: desktop/org.gnome.Geary.appdata.xml.in:32 msgid "Desktop notification of new mail" msgstr "Notifiche desktop della nuova posta" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:15 +#: desktop/org.gnome.Geary.appdata.xml.in:33 msgid "Compatible with GMail, Yahoo! Mail, Outlook.com and other IMAP servers" msgstr "Compatibile con GMail, Yahoo! Mail, Outlook.com e altri server IMAP" #. Translators: A screenshot description. -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:17 -#| msgid "_Display conversation preview" +#: desktop/org.gnome.Geary.appdata.xml.in:47 msgid "Geary displaying a conversation" msgstr "L'applicazione che mostra una conversazione" #. Translators: A screenshot description. -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:19 +#: desktop/org.gnome.Geary.appdata.xml.in:52 msgid "Geary showing the rich text composer" msgstr "L'applicazione che mostra il compositore di testo formattato" -#: ../desktop/org.gnome.Geary.desktop.in.h:2 -#: ../desktop/geary-autostart.desktop.in.h:2 -msgid "Email" -msgstr "Email" - -#: ../desktop/org.gnome.Geary.desktop.in.h:3 -msgid "Geary Email" -msgstr "Geary Email" - #. Translators: These are desktop search terms. Do not translate semicolons, end line with a semicolon. -#: ../desktop/org.gnome.Geary.desktop.in.h:6 +#: desktop/org.gnome.Geary.desktop.in:7 msgid "Mail;E-mail;IMAP;GMail;Yahoo;Hotmail;Outlook;" msgstr "Mail;E-mail;IMAP;GMail;Yahoo;Hotmail;Outlook;" -#: ../desktop/org.gnome.Geary.desktop.in.h:7 ../ui/gtk/menus.ui.h:1 +#: desktop/org.gnome.Geary.desktop.in:21 ui/gtk/menus.ui:7 msgid "Compose Message" msgstr "Componi messaggio" -#: ../desktop/geary-autostart.desktop.in.h:3 -msgid "Geary Mail" -msgstr "Geary Mail" +#: desktop/org.gnome.Geary.gschema.xml:8 +#| msgid "Save all attachments" +msgid "Default attachments directory" +msgstr "Directory predefinita degli allegati" -#: ../desktop/geary-autostart.desktop.in.h:5 -msgid "Email;E-mail;Mail;" -msgstr "Email;E-mail;Mail;Posta;" +#: desktop/org.gnome.Geary.gschema.xml:9 +msgid "Location used when opening and saving attachments." +msgstr "Posizione usata per l'apertura e il salvataggio di allegati." -#: ../desktop/geary-attach.contract.in.h:1 -msgid "Send by email" -msgstr "Invia per email" +#: desktop/org.gnome.Geary.gschema.xml:14 +msgid "Default print output directory" +msgstr "Directory predefinita per la stampa dell'output" + +#: desktop/org.gnome.Geary.gschema.xml:15 +msgid "Location used when printing to a file." +msgstr "Posizione usata per la stampa su file." + +#: desktop/org.gnome.Geary.gschema.xml:20 +msgid "Maximize window" +msgstr "Massimizza finestra" + +#: desktop/org.gnome.Geary.gschema.xml:21 +msgid "True if the application window is maximized, false otherwise." +msgstr "" +"Vero se la finestra dell'applicazione è massimizzata, falso altrimenti." + +#: desktop/org.gnome.Geary.gschema.xml:26 +msgid "Width of window" +msgstr "Larghezza della finestra" + +#: desktop/org.gnome.Geary.gschema.xml:27 +msgid "The last recorded width of the application window." +msgstr "L'ultima larghezza salvata della finestra dell'applicazione." + +#: desktop/org.gnome.Geary.gschema.xml:32 +msgid "Height of window" +msgstr "Altezza della finestra" + +#: desktop/org.gnome.Geary.gschema.xml:33 +msgid "The last recorded height of the application window." +msgstr "L'ultima altezza salvata della finestra dell'applicazione." + +#: desktop/org.gnome.Geary.gschema.xml:38 +msgid "Position of folder list pane" +msgstr "Posizione del riquadro dell'elenco cartelle" + +#: desktop/org.gnome.Geary.gschema.xml:39 +msgid "Position of the folder list Paned grabber." +msgstr "Posizione della maniglia del riquadro elenco cartelle." + +#: desktop/org.gnome.Geary.gschema.xml:44 +msgid "Position of folder list pane when horizontal" +msgstr "Posizione del riquadro dell'elenco cartelle se orizzontale" + +#: desktop/org.gnome.Geary.gschema.xml:45 +msgid "" +"Position of the folder list Paned grabber in the horizontal orientation." +msgstr "" +"Posizione della maniglia del riquadro elenco cartelle nell'orientamento " +"orizzontale." + +#: desktop/org.gnome.Geary.gschema.xml:50 +msgid "Position of folder list pane when vertical" +msgstr "Posizione del riquadro dell'elenco cartelle se verticale" + +#: desktop/org.gnome.Geary.gschema.xml:51 +msgid "Position of the folder list Paned grabber in the vertical orientation." +msgstr "" +"Posizione della maniglia del riquadro elenco cartelle nell'orientamento " +"verticale." + +#: desktop/org.gnome.Geary.gschema.xml:56 +msgid "Orientation of the folder list pane" +msgstr "Orientamento del riquadro dell'elenco cartelle" + +#: desktop/org.gnome.Geary.gschema.xml:57 +msgid "True if the folder list Paned is in the horizontal orientation." +msgstr "Vero se l'elenco cartelle agganciato è nell'orientamento orizzontale." + +#: desktop/org.gnome.Geary.gschema.xml:62 +msgid "Position of message list pane" +msgstr "Posizione del riquadro dell'elenco messaggi" -#: ../desktop/geary-attach.contract.in.h:2 -msgid "Send files using Geary" -msgstr "Invia file usando Geary" - -#: ../src/client/accounts/account-dialog-add-edit-pane.vala:51 -#: ../src/client/components/stock.vala:31 -#: ../ui/conversation-email-menus.ui.h:10 +#: desktop/org.gnome.Geary.gschema.xml:63 +msgid "Position of the message list Paned grabber." +msgstr "Posizione della maniglia del riquadro elenco messaggi." + +#: desktop/org.gnome.Geary.gschema.xml:68 +#| msgid "_Automatically select next message" +msgid "Autoselect next message" +msgstr "Selezione automatica del messaggio successivo" + +#: desktop/org.gnome.Geary.gschema.xml:69 +msgid "True if we should autoselect the next available conversation." +msgstr "" +"Vero se occorre selezionare automaticamente la conversazione disponibile " +"successiva." + +#: desktop/org.gnome.Geary.gschema.xml:74 +#| msgid "_Display conversation preview" +msgid "Display message previews" +msgstr "Visualizza le anteprime dei messaggi" + +#: desktop/org.gnome.Geary.gschema.xml:75 +msgid "True if we should display a short preview of each message." +msgstr "Vero se occorre mostrare una breve anteprima di ciascun messaggio." + +#: desktop/org.gnome.Geary.gschema.xml:80 +msgid "Languages that shall be used in the spell checker" +msgstr "Lingue da usare nel controllo ortografico" + +#: desktop/org.gnome.Geary.gschema.xml:81 +msgid "List of the languages to use in the spell checker." +msgstr "Elenco di lingue da usare nel controllo ortografico." + +#: desktop/org.gnome.Geary.gschema.xml:86 +msgid "Languages that are displayed in the spell checker popover" +msgstr "" +"Lingue visualizzate nella finestra a comparsa del controllo ortografico" + +#: desktop/org.gnome.Geary.gschema.xml:87 +msgid "" +"List of languages that are always displayed in the popover of the spell " +"checker." +msgstr "" +"Elenco di lingue sempre visualizzate nella finestra a comparsa del controllo " +"ortografico." + +#: desktop/org.gnome.Geary.gschema.xml:92 +#| msgid "_Play notification sounds" +msgid "Enable notification sounds" +msgstr "Abilita avvisi sonori" + +#: desktop/org.gnome.Geary.gschema.xml:93 +msgid "True to play sounds for notifications and sending." +msgstr "Vero per riprodurre suoni di notifica e invio." + +#: desktop/org.gnome.Geary.gschema.xml:98 +#| msgid "Show _notifications for new mail" +msgid "Show notifications for new mail" +msgstr "Mostrare notifiche per la nuova posta" + +#: desktop/org.gnome.Geary.gschema.xml:99 +msgid "True to show notification bubbles." +msgstr "Vero per mostrare le nuvolette di notifica." + +#: desktop/org.gnome.Geary.gschema.xml:104 +msgid "Notify of new mail at startup" +msgstr "Notifica di nuova posta all'avvio" + +#: desktop/org.gnome.Geary.gschema.xml:105 +#| msgid "Desktop notification of new mail" +msgid "True to notify of new mail at startup." +msgstr "Vero per avvisare all'avvio dell'arrivo di nuova posta." + +#: desktop/org.gnome.Geary.gschema.xml:110 +msgid "Ask when opening an attachment" +msgstr "Chiedere prima di aprire un allegato" + +#: desktop/org.gnome.Geary.gschema.xml:111 +#| msgid "To add them as attachments" +msgid "True to ask when opening an attachment." +msgstr "Vero per chiedere prima di aprire un allegato" + +#: desktop/org.gnome.Geary.gschema.xml:116 +msgid "Whether to compose emails in HTML" +msgstr "Se comporre le email in HTML" + +#: desktop/org.gnome.Geary.gschema.xml:117 +msgid "True to compose emails in HTML; false for plain text." +msgstr "Vero per comporre le email in HTML; falso per il testo semplice." + +#: desktop/org.gnome.Geary.gschema.xml:122 +msgid "Advisory strategy for full-text searching" +msgstr "Strategia di ricerca a testo intero" + +#: desktop/org.gnome.Geary.gschema.xml:123 +msgid "" +"Acceptable values are “exact”, “conservative”, “aggressive”, and “horizon”." +msgstr "" +"I valori possibili sono “exact”, “conservative”, “aggressive” e “horizon”." + +#: desktop/org.gnome.Geary.gschema.xml:128 +#| msgctxt "shortcut window" +#| msgid "Move focus to conversation list" +msgid "Zoom of conversation viewer" +msgstr "Ingrandimento del visualizzatore delle conversazioni" + +#: desktop/org.gnome.Geary.gschema.xml:129 +msgid "The zoom to apply on the conservation view." +msgstr "L'ingrandimento da applicare al visualizzatore delle conversazioni." + +#: desktop/org.gnome.Geary.gschema.xml:134 +#| msgctxt "shortcut window" +#| msgid "Detach composer window" +msgid "Size of detached composer window" +msgstr "Dimensione della finestra di composizione staccata" + +#: desktop/org.gnome.Geary.gschema.xml:135 +msgid "The last recorded size of the detached composer window." +msgstr "L'ultima dimensione salvata della finestra di composizione staccata." + +#: desktop/org.gnome.Geary.gschema.xml:140 +msgid "Whether we migrated the old settings" +msgstr "Se le vecchie impostazioni sono state migrate" + +#: desktop/org.gnome.Geary.gschema.xml:141 +msgid "" +"False to check for the old “org.yorba.geary”-schema and copy its values." +msgstr "" +"Falso per cercare il vecchio schema “org.yorba.geary” e copiare i suoi " +"valori." + +#: src/client/accounts/account-dialog-add-edit-pane.vala:54 +#: src/client/components/stock.vala:31 ui/conversation-email-menus.ui:57 msgid "_Save" msgstr "_Salva" -#: ../src/client/accounts/account-dialog-add-edit-pane.vala:51 -#: ../src/client/components/stock.vala:22 +#: src/client/accounts/account-dialog-add-edit-pane.vala:54 +#: src/client/components/stock.vala:22 msgid "_Add" msgstr "A_ggiungi" #. reset/clear widgets -#: ../src/client/accounts/account-dialog-edit-alternate-emails-pane.vala:120 +#: src/client/accounts/account-dialog-edit-alternate-emails-pane.vala:120 #, c-format msgid "Additional addresses for %s" msgstr "Indirizzi aggiuntivi per %s" #. Sets min size. -#: ../src/client/accounts/account-dialog.vala:21 +#: src/client/accounts/account-dialog.vala:28 msgid "Accounts" msgstr "Account" @@ -170,117 +378,117 @@ #. #. Page for adding or editing an account. #. / Placeholder text indicating that the user should type their first name and last name -#: ../src/client/accounts/add-edit-page.vala:10 +#: src/client/accounts/add-edit-page.vala:10 msgid "First Last" msgstr "Nome Cognome" -#: ../src/client/accounts/add-edit-page.vala:235 +#: src/client/accounts/add-edit-page.vala:240 msgid "Welcome to Geary." msgstr "Benvenuti in Geary." -#: ../src/client/accounts/add-edit-page.vala:235 +#: src/client/accounts/add-edit-page.vala:240 msgid "Enter your account information to get started." msgstr "Inserire le informazioni del proprio account per iniziare." -#: ../src/client/accounts/add-edit-page.vala:255 +#: src/client/accounts/add-edit-page.vala:260 msgid "2 weeks back" msgstr "2 settimane fa" #. IDs are # of days -#: ../src/client/accounts/add-edit-page.vala:256 +#: src/client/accounts/add-edit-page.vala:261 msgid "1 month back" msgstr "1 mese fa" -#: ../src/client/accounts/add-edit-page.vala:257 +#: src/client/accounts/add-edit-page.vala:262 msgid "3 months back" msgstr "3 mesi fa" -#: ../src/client/accounts/add-edit-page.vala:258 +#: src/client/accounts/add-edit-page.vala:263 msgid "6 months back" msgstr "6 mesi fa" -#: ../src/client/accounts/add-edit-page.vala:259 +#: src/client/accounts/add-edit-page.vala:264 msgid "1 year back" msgstr "1 anno fa" -#: ../src/client/accounts/add-edit-page.vala:260 +#: src/client/accounts/add-edit-page.vala:265 msgid "2 years back" msgstr "2 anni fa" -#: ../src/client/accounts/add-edit-page.vala:261 +#: src/client/accounts/add-edit-page.vala:266 msgid "4 years back" msgstr "4 anni fa" #. Separator -#: ../src/client/accounts/add-edit-page.vala:263 +#: src/client/accounts/add-edit-page.vala:268 msgid "Everything" msgstr "Tutto" -#: ../src/client/accounts/add-edit-page.vala:283 +#: src/client/accounts/add-edit-page.vala:288 msgid "Edit" msgstr "Modifica" -#: ../src/client/accounts/add-edit-page.vala:285 +#: src/client/accounts/add-edit-page.vala:290 msgid "Preview" msgstr "Anteprima" -#: ../src/client/accounts/add-edit-page.vala:751 +#: src/client/accounts/add-edit-page.vala:778 msgid "Remem_ber passwords" msgstr "Ric_ordare le password" -#: ../src/client/accounts/add-edit-page.vala:758 ../ui/login.glade.h:7 +#: src/client/accounts/add-edit-page.vala:785 ui/login.glade:233 msgid "Remem_ber password" msgstr "Ric_ordare la password" -#: ../src/client/accounts/add-edit-page.vala:792 +#: src/client/accounts/add-edit-page.vala:819 msgid "Unable to validate:\n" msgstr "Impossibile da convalidare:\n" -#: ../src/client/accounts/add-edit-page.vala:794 +#: src/client/accounts/add-edit-page.vala:821 msgid " • Invalid account nickname.\n" msgstr " • Soprannome dell'account non valido.\n" -#: ../src/client/accounts/add-edit-page.vala:797 +#: src/client/accounts/add-edit-page.vala:824 msgid " • Email address already added to Geary.\n" msgstr " • Indirizzo email già presente nell'applicazione.\n" -#: ../src/client/accounts/add-edit-page.vala:801 +#: src/client/accounts/add-edit-page.vala:828 msgid " • IMAP connection error.\n" msgstr " • Errore di connessione IMAP.\n" -#: ../src/client/accounts/add-edit-page.vala:804 +#: src/client/accounts/add-edit-page.vala:831 msgid " • IMAP username or password incorrect.\n" msgstr " • Nome utente o password IMAP non corretti.\n" -#: ../src/client/accounts/add-edit-page.vala:807 +#: src/client/accounts/add-edit-page.vala:834 msgid " • SMTP connection error.\n" msgstr " • Errore di connessione SMTP.\n" -#: ../src/client/accounts/add-edit-page.vala:810 +#: src/client/accounts/add-edit-page.vala:837 msgid " • SMTP username or password incorrect.\n" msgstr " • Nome utente o password SMTP non corretti.\n" -#: ../src/client/accounts/add-edit-page.vala:814 +#: src/client/accounts/add-edit-page.vala:841 msgid " • Connection error.\n" msgstr " • Errore di connessione.\n" -#: ../src/client/accounts/add-edit-page.vala:818 +#: src/client/accounts/add-edit-page.vala:845 msgid " • Username or password incorrect.\n" msgstr " • Nome utente o password non corretti.\n" -#: ../src/client/application/geary-application.vala:22 +#: src/client/application/geary-application.vala:22 msgid "Copyright 2016 Software Freedom Conservancy Inc." msgstr "Copyright 2016 Software Freedom Conservancy Inc." -#: ../src/client/application/geary-application.vala:23 +#: src/client/application/geary-application.vala:23 msgid "Copyright 2016-2017 Geary Development Team." msgstr "Copyright 2016-2017 gruppo sviluppatori di Geary." -#: ../src/client/application/geary-application.vala:25 +#: src/client/application/geary-application.vala:25 msgid "Visit the Geary web site" msgstr "Visita il sito web di Geary" -#: ../src/client/application/geary-application.vala:466 +#: src/client/application/geary-application.vala:413 #, c-format msgid "About %s" msgstr "Informazioni su %s" @@ -288,252 +496,104 @@ #. Translators: add your name and email address to receive #. credit in the About dialog For example: Yamada Taro #. -#: ../src/client/application/geary-application.vala:470 +#: src/client/application/geary-application.vala:417 msgid "translator-credits" msgstr "" "Daniele Napolitano\n" -"Federico Bruni\n" +"Federico Bruni \n" "Gianvito Cavasoli " -#: ../src/client/application/geary-args.vala:10 +#: src/client/application/geary-args.vala:10 msgid "Start Geary with hidden main window" msgstr "Avvia l'applicazione nascondendo la finestra principale" -#: ../src/client/application/geary-args.vala:11 +#: src/client/application/geary-args.vala:11 msgid "Output debugging information" msgstr "Emette informazioni di debug" -#: ../src/client/application/geary-args.vala:12 +#: src/client/application/geary-args.vala:12 msgid "Log conversation monitoring" msgstr "Registra il monitoraggio delle conversazioni" -#: ../src/client/application/geary-args.vala:13 +#: src/client/application/geary-args.vala:13 msgid "Log network deserialization" msgstr "Registra deserializzazione di rete" -#: ../src/client/application/geary-args.vala:14 +#: src/client/application/geary-args.vala:14 msgid "Log network activity" msgstr "Registra attività di rete" #. / The IMAP replay queue is how changes on the server are replicated on the client. #. / It could also be called the IMAP events queue. -#: ../src/client/application/geary-args.vala:17 +#: src/client/application/geary-args.vala:17 msgid "Log IMAP replay queue" msgstr "Registra la coda di eventi IMAP" #. / Serialization is how commands and responses are converted into a stream of bytes for #. / network transmission -#: ../src/client/application/geary-args.vala:20 +#: src/client/application/geary-args.vala:20 msgid "Log network serialization" msgstr "Registra serializzazione di rete" -#: ../src/client/application/geary-args.vala:21 +#: src/client/application/geary-args.vala:21 msgid "Log periodic activity" msgstr "Registra attività periodiche" -#: ../src/client/application/geary-args.vala:22 +#: src/client/application/geary-args.vala:22 msgid "Log database queries (generates lots of messages)" msgstr "Registra le ricerche nel database (genera numerosi messaggi)" #. / "Normalization" can also be called "synchronization" -#: ../src/client/application/geary-args.vala:24 +#: src/client/application/geary-args.vala:24 msgid "Log folder normalization" msgstr "Registra la sincronizzazione della cartella" -#: ../src/client/application/geary-args.vala:25 +#: src/client/application/geary-args.vala:25 msgid "Allow inspection of WebView" msgstr "Consente l'ispezione WebView" -#: ../src/client/application/geary-args.vala:26 +#: src/client/application/geary-args.vala:26 msgid "Revoke all server certificates with TLS warnings" msgstr "Revoca tutti i certificati del server con avvisi TLS" -#: ../src/client/application/geary-args.vala:27 +#: src/client/application/geary-args.vala:27 msgid "Perform a graceful quit" msgstr "Esegui un'uscita non forzata" -#: ../src/client/application/geary-args.vala:28 +#: src/client/application/geary-args.vala:28 msgid "Display program version" msgstr "Visualizza la versione del programma" #. This gives a command-line hint on how to open new composer windows with mailto: -#: ../src/client/application/geary-args.vala:53 +#: src/client/application/geary-args.vala:53 #, c-format msgid "Use %s to open a new composer window" msgstr "Usa %s per aprire una nuova finestra di composizione" -#: ../src/client/application/geary-args.vala:56 +#: src/client/application/geary-args.vala:56 msgid "Please report comments, suggestions and bugs to:" msgstr "Inviare commenti, suggerimenti e segnalazioni di bug a:" #. i18n: Command line arguments are invalid -#: ../src/client/application/geary-args.vala:63 +#: src/client/application/geary-args.vala:63 #, c-format msgid "Failed to parse command line options: %s\n" msgstr "Analisi delle opzioni a riga di comando non riuscita: %s\n" -#: ../src/client/application/geary-args.vala:74 +#: src/client/application/geary-args.vala:74 #, c-format msgid "Unrecognized command line option “%s”\n" msgstr "Opzione a riga di comando «%s» non riconosciuta\n" -#: ../src/client/application/geary-controller.vala:57 -msgid "Delete conversation" -msgstr "Elimina conversazione" - -#: ../src/client/application/geary-controller.vala:58 -msgid "Delete conversation (Shift+Delete)" -msgstr "Elimina conversazione (Maiusc+Canc)" - -#: ../src/client/application/geary-controller.vala:59 -msgid "Delete conversations (Shift+Delete)" -msgstr "Elimina conversazioni (Maiusc+Canc)" - -#. This refers to the action ("move email to the trash"), not the Trash folder itself -#: ../src/client/application/geary-controller.vala:63 -msgid "Move conversation to Trash (Delete, Backspace)" -msgstr "Sposta la conversazione nel cestino (Canc, Backspace)" - -#: ../src/client/application/geary-controller.vala:64 -msgid "Move conversations to Trash (Delete, Backspace)" -msgstr "Sposta le conversazioni nel cestino (Canc, Backspace)" - -#. This refers to the action ("archive an email"), not the Archive folder itself -#: ../src/client/application/geary-controller.vala:68 -msgid "_Archive" -msgstr "_Archivia" - -#: ../src/client/application/geary-controller.vala:69 -msgid "Archive conversation (A)" -msgstr "Archivia conversazione (A)" - -#: ../src/client/application/geary-controller.vala:70 -msgid "Archive conversations (A)" -msgstr "Archivia conversazioni (A)" - -#: ../src/client/application/geary-controller.vala:73 -msgid "Mark as S_pam" -msgstr "Segna come i_ndesiderata" - -#: ../src/client/application/geary-controller.vala:74 -msgid "Mark as not S_pam" -msgstr "Segna come non i_ndesiderata" - -#: ../src/client/application/geary-controller.vala:76 -#: ../src/client/application/geary-controller.vala:448 -msgid "Mark conversation" -msgstr "Segna conversazione" - -#: ../src/client/application/geary-controller.vala:77 -msgid "Mark conversations" -msgstr "Segna conversazioni" - -#: ../src/client/application/geary-controller.vala:78 -msgid "Add label to conversation" -msgstr "Aggiunge etichetta alla conversazione" - -#: ../src/client/application/geary-controller.vala:79 -msgid "Add label to conversations" -msgstr "Aggiunge etichetta alle conversazioni" - -#: ../src/client/application/geary-controller.vala:80 -#: ../src/client/application/geary-controller.vala:487 -msgid "Move conversation" -msgstr "Sposta conversazione" - -#: ../src/client/application/geary-controller.vala:81 -msgid "Move conversations" -msgstr "Sposta conversazioni" - -#: ../src/client/application/geary-controller.vala:450 -msgid "_Mark as…" -msgstr "_Segna come…" - -#: ../src/client/application/geary-controller.vala:456 -msgid "Mark as _Read" -msgstr "Segna come _letto" - -#: ../src/client/application/geary-controller.vala:462 -msgid "Mark as _Unread" -msgstr "Segna come _non letto" - -#: ../src/client/application/geary-controller.vala:468 -msgid "_Star" -msgstr "_Speciale" - -#: ../src/client/application/geary-controller.vala:473 -msgid "U_nstar" -msgstr "_Non speciale" - -#: ../src/client/application/geary-controller.vala:483 -msgid "Add label" -msgstr "Aggiunge etichetta" - -#: ../src/client/application/geary-controller.vala:484 -msgid "_Label" -msgstr "_Etichetta" - -#: ../src/client/application/geary-controller.vala:488 -msgid "_Move" -msgstr "_Sposta" - -#: ../src/client/application/geary-controller.vala:492 -msgid "Compose new message (Ctrl+N, N)" -msgstr "Compone un nuovo messaggio (Ctrl+N, N)" - -#: ../src/client/application/geary-controller.vala:496 -#: ../ui/conversation-email-menus.ui.h:1 -msgid "_Reply" -msgstr "_Rispondi" - -#: ../src/client/application/geary-controller.vala:497 -msgid "Reply (Ctrl+R, R)" -msgstr "Risponde (Ctrl+R, R)" - -#: ../src/client/application/geary-controller.vala:501 -msgid "R_eply All" -msgstr "R_ispondi a tutti" - -#: ../src/client/application/geary-controller.vala:502 -msgid "Reply all (Ctrl+Shift+R, Shift+R)" -msgstr "Risponde a tutti (Ctrl+Maiusc+R, Maiusc+R)" - -#: ../src/client/application/geary-controller.vala:507 -#: ../ui/conversation-email-menus.ui.h:3 -msgid "_Forward" -msgstr "_Inoltra" - -#: ../src/client/application/geary-controller.vala:508 -msgid "Forward (Ctrl+L, F)" -msgstr "Inoltra (Ctrl+L, F)" - -#: ../src/client/application/geary-controller.vala:538 -msgid "Empty _Spam…" -msgstr "Svuota _indesiderata…" - -#: ../src/client/application/geary-controller.vala:542 -msgid "Empty _Trash…" -msgstr "Svuota _cestino…" - -#. No callback is connected, since we bind the toggle button to the search bar visibility -#: ../src/client/application/geary-controller.vala:574 -msgid "Toggle search bar" -msgstr "Attiva/Disattiva barra di ricerca" - -#. No callback is connected, since we bind the toggle button to the find bar visibility -#: ../src/client/application/geary-controller.vala:579 -msgid "Toggle find bar" -msgstr "Attiva/Disattiva barra di ricerca nella conversazione" - -#: ../src/client/application/geary-controller.vala:757 +#: src/client/application/geary-controller.vala:717 msgid "Unable to store server trust exception" msgstr "Impossibile salvare l'eccezione di fiducia nel server" -#: ../src/client/application/geary-controller.vala:992 +#: src/client/application/geary-controller.vala:968 msgid "Your settings are insecure" msgstr "Le impostazioni non sono sicure" -#: ../src/client/application/geary-controller.vala:993 +#: src/client/application/geary-controller.vala:969 msgid "" "Your IMAP and/or SMTP settings do not specify SSL or TLS. This means your " "username and password could be read by another person on the network. Are " @@ -543,29 +603,17 @@ "che nome utente e password potrebbero essere letti da altre persone sulla " "rete. Sicuri di volerlo?" -#: ../src/client/application/geary-controller.vala:994 +#: src/client/application/geary-controller.vala:970 msgid "Co_ntinue" msgstr "Co_ntinua" -#: ../src/client/application/geary-controller.vala:1041 -msgid "Error connecting to the server" -msgstr "Errore di connessione col server" - -#: ../src/client/application/geary-controller.vala:1042 -msgid "" -"Geary encountered an error while connecting to the server. Please try again " -"in a few moments." -msgstr "" -"L'applicazione ha riscontrato un errore durante la connessione col server. " -"Riprovare in un secondo momento." - #. / Displayed in the space-limited status bar when a message fails to be sent due to error. -#: ../src/client/application/geary-controller.vala:1079 -#: ../src/client/components/status-bar.vala:29 +#: src/client/application/geary-controller.vala:1074 +#: src/client/components/status-bar.vala:29 msgid "Error sending email" msgstr "Errore durante l'invio dell'email" -#: ../src/client/application/geary-controller.vala:1080 +#: src/client/application/geary-controller.vala:1075 msgid "" "Geary encountered an error sending an email. If the problem persists, " "please manually delete the email from your Outbox folder." @@ -575,33 +623,33 @@ #. Displayed in the space-limited status bar when a message fails to be uploaded #. to Sent Mail after being sent. -#: ../src/client/application/geary-controller.vala:1084 -#: ../src/client/components/status-bar.vala:33 +#: src/client/application/geary-controller.vala:1079 +#: src/client/components/status-bar.vala:33 msgid "Error saving sent mail" -msgstr "Errore durante il salvataggio dell'email inviata" +msgstr "Errore durante il salvataggio della posta inviata" -#: ../src/client/application/geary-controller.vala:1085 +#: src/client/application/geary-controller.vala:1080 msgid "" "Geary encountered an error saving a sent message to Sent Mail. The message " "will stay in your Outbox folder until you delete it." msgstr "" -"Si è verificato un errore durante il salvataggio di un'email inviata. Il " -"messaggio resterà nella cartella della posta in uscita finché non viene " -"eliminato." +"Si è verificato un errore durante il salvataggio di un'email nella posta " +"inviata. Il messaggio resterà nella cartella della posta in uscita finché " +"non viene eliminato manualmente." -#: ../src/client/application/geary-controller.vala:1154 +#: src/client/application/geary-controller.vala:1147 msgid "Labels" msgstr "Etichette" #. give the user two options: reset the Account local store, or exit Geary. A third #. could be done to leave the Account in an unopened state, but we don't currently #. have provisions for that. -#: ../src/client/application/geary-controller.vala:1166 +#: src/client/application/geary-controller.vala:1159 #, c-format msgid "Unable to open the database for %s" msgstr "Impossibile aprire il database per %s" -#: ../src/client/application/geary-controller.vala:1167 +#: src/client/application/geary-controller.vala:1160 #, c-format msgid "" "There was an error opening the local mail database for this account. This is " @@ -627,20 +675,20 @@ "allegati. Tale operazione non ha effetti sulla posta presente sul server." "" -#: ../src/client/application/geary-controller.vala:1169 +#: src/client/application/geary-controller.vala:1162 msgid "_Rebuild" msgstr "_Ricostruisci" -#: ../src/client/application/geary-controller.vala:1169 +#: src/client/application/geary-controller.vala:1162 msgid "E_xit" msgstr "_Esci" -#: ../src/client/application/geary-controller.vala:1178 +#: src/client/application/geary-controller.vala:1171 #, c-format msgid "Unable to rebuild database for “%s”" msgstr "Impossibile ricostruire il database per «%s»" -#: ../src/client/application/geary-controller.vala:1179 +#: src/client/application/geary-controller.vala:1172 #, c-format msgid "" "Error during rebuild:\n" @@ -653,14 +701,14 @@ #. some other problem opening the account ... as with other flow path, can't run #. Geary today with an account in unopened state, so have to exit -#: ../src/client/application/geary-controller.vala:1201 -#: ../src/client/application/geary-controller.vala:1211 -#: ../src/client/application/geary-controller.vala:1222 +#: src/client/application/geary-controller.vala:1194 +#: src/client/application/geary-controller.vala:1204 +#: src/client/application/geary-controller.vala:1215 #, c-format msgid "Unable to open local mailbox for %s" msgstr "Impossibile aprire la casella di posta locale per %s" -#: ../src/client/application/geary-controller.vala:1202 +#: src/client/application/geary-controller.vala:1195 #, c-format msgid "" "There was an error opening the local mail database for this account. This is " @@ -679,7 +727,7 @@ "\n" "%s" -#: ../src/client/application/geary-controller.vala:1212 +#: src/client/application/geary-controller.vala:1205 msgid "" "The version number of the local mail database is formatted for a newer " "version of Geary. Unfortunately, the database cannot be “rolled back” to " @@ -694,7 +742,7 @@ "\n" "Si consiglia di installare l'ultima versione dell'applicazione e riprovare." -#: ../src/client/application/geary-controller.vala:1223 +#: src/client/application/geary-controller.vala:1216 msgid "" "There was an error opening the local account. This is probably due to " "connectivity issues.\n" @@ -706,15 +754,15 @@ "\n" "Controllare la connessione di rete e riavviare l'applicazione." -#: ../src/client/application/geary-controller.vala:2008 +#: src/client/application/geary-controller.vala:2018 msgid "Undo move (Ctrl+Z)" msgstr "Annulla lo spostamento (Ctrl+Z)" -#: ../src/client/application/geary-controller.vala:2018 +#: src/client/application/geary-controller.vala:2028 msgid "Are you sure you want to open these attachments?" msgstr "Sicuri di voler aprire questi allegati?" -#: ../src/client/application/geary-controller.vala:2019 +#: src/client/application/geary-controller.vala:2029 msgid "" "Attachments may cause damage to your system if opened. Only open files from " "trusted sources." @@ -722,201 +770,448 @@ "Gli allegati possono danneggiare il sistema se aperti. Aprire solo file " "provenienti da fonti fidate." -#: ../src/client/application/geary-controller.vala:2020 +#: src/client/application/geary-controller.vala:2030 msgid "Don’t _ask me again" msgstr "_Non chiederlo più" -#: ../src/client/application/geary-controller.vala:2130 +#: src/client/application/geary-controller.vala:2126 #, c-format msgid "A file named “%s” already exists. Do you want to replace it?" msgstr "Il file «%s» esiste già. Sostituirlo?" -#: ../src/client/application/geary-controller.vala:2132 +#: src/client/application/geary-controller.vala:2128 #, c-format msgid "" "The file already exists in “%s”. Replacing it will overwrite its contents." msgstr "" "Il file esiste già in «%s». Sostituendolo il suo contenuto sarà sovrascritto." -#: ../src/client/application/geary-controller.vala:2135 +#: src/client/application/geary-controller.vala:2131 msgid "_Replace" msgstr "_Sostituisci" -#. Find out what to do with the inline composers. -#. TODO: Remove this in favor of automatically saving drafts -#: ../src/client/application/geary-controller.vala:2383 -msgid "Close open draft messages?" -msgstr "Chiudere le bozze aperte?" +#: src/client/application/geary-controller.vala:2371 +#| msgid "Close open draft messages?" +msgid "Close the draft message?" +msgid_plural "Close all draft messages?" +msgstr[0] "Chiudere la bozza aperta?" +msgstr[1] "Chiudere tutte le bozze aperte?" -#: ../src/client/application/geary-controller.vala:2505 +#: src/client/application/geary-controller.vala:2497 #, c-format msgid "Empty all email from your %s folder?" msgstr "Svuotare tutte le email dalla cartella %s?" -#: ../src/client/application/geary-controller.vala:2506 +#: src/client/application/geary-controller.vala:2498 msgid "This removes the email from Geary and your email server." msgstr "Questo rimuoverà le email dall'applicazione e dal server di posta." -#: ../src/client/application/geary-controller.vala:2507 +#: src/client/application/geary-controller.vala:2499 msgid "This cannot be undone." msgstr "Impossibile annullare questa azione." -#: ../src/client/application/geary-controller.vala:2508 +#: src/client/application/geary-controller.vala:2500 #, c-format msgid "Empty %s" msgstr "Svuota %s" -#: ../src/client/application/geary-controller.vala:2525 +#: src/client/application/geary-controller.vala:2517 #, c-format msgid "Error emptying %s" msgstr "Errore nello svuotare %s" -#: ../src/client/application/geary-controller.vala:2555 +#: src/client/application/geary-controller.vala:2549 msgid "Do you want to permanently delete this message?" msgid_plural "Do you want to permanently delete these messages?" msgstr[0] "Eliminare permanentemente questo messaggio?" msgstr[1] "Eliminare permanentemente questi messaggi?" -#: ../src/client/application/geary-controller.vala:2557 +#: src/client/application/geary-controller.vala:2551 msgid "Delete" msgstr "Elimina" -#: ../src/client/application/geary-controller.vala:2589 +#: src/client/application/geary-controller.vala:2583 msgid "Undo archive (Ctrl+Z)" msgstr "Annulla l'archiviazione (Ctrl+Z)" -#: ../src/client/application/geary-controller.vala:2604 +#: src/client/application/geary-controller.vala:2598 msgid "Undo trash (Ctrl+Z)" msgstr "Annulla lo spostamento nel cestino (Ctrl+Z)" -#: ../src/client/application/geary-controller.vala:2658 +#: src/client/application/geary-controller.vala:2654 msgid "Undo (Ctrl+Z)" msgstr "Annulla (Ctrl+Z)" -#: ../src/client/application/geary-controller.vala:2789 +#. Translators: The label for an in-app notification. The +#. string substitution is a list of recipients of the email. +#: src/client/application/geary-controller.vala:2717 +#, c-format +msgid "Successfully sent mail to %s." +msgstr "Posta inviata con successo a %s." + +#: src/client/application/geary-controller.vala:2773 msgid "Failed to open default text editor." msgstr "Apertura dell'editor di testo predefinito non riuscita." -#: ../src/client/components/main-window.vala:389 +#. Tooltips +#: src/client/components/main-toolbar.vala:69 +msgid "Delete conversation (Shift+Delete)" +msgstr "Elimina conversazione (Maiusc+Canc)" + +#: src/client/components/main-toolbar.vala:70 +msgid "Delete conversations (Shift+Delete)" +msgstr "Elimina conversazioni (Maiusc+Canc)" + +#: src/client/components/main-toolbar.vala:71 +msgid "Move conversation to Trash (Delete, Backspace)" +msgstr "Sposta la conversazione nel cestino (Canc, Backspace)" + +#: src/client/components/main-toolbar.vala:72 +msgid "Move conversations to Trash (Delete, Backspace)" +msgstr "Sposta le conversazioni nel cestino (Canc, Backspace)" + +#: src/client/components/main-toolbar.vala:73 +msgid "Archive conversation (A)" +msgstr "Archivia conversazione (A)" + +#: src/client/components/main-toolbar.vala:74 +msgid "Archive conversations (A)" +msgstr "Archivia conversazioni (A)" + +#: src/client/components/main-toolbar.vala:75 +msgid "Mark conversation" +msgstr "Segna conversazione" + +#: src/client/components/main-toolbar.vala:76 +msgid "Mark conversations" +msgstr "Segna conversazioni" + +#: src/client/components/main-toolbar.vala:77 +msgid "Add label to conversation" +msgstr "Aggiunge etichetta alla conversazione" + +#: src/client/components/main-toolbar.vala:78 +msgid "Add label to conversations" +msgstr "Aggiunge etichetta alle conversazioni" + +#: src/client/components/main-toolbar.vala:79 +msgid "Move conversation" +msgstr "Sposta conversazione" + +#: src/client/components/main-toolbar.vala:80 +msgid "Move conversations" +msgstr "Sposta conversazioni" + +#: src/client/components/main-window.vala:417 #, c-format msgid "%s (%d)" msgstr "%s (%d)" -#: ../src/client/components/search-bar.vala:8 -#: ../src/client/folder-list/folder-list-search-branch.vala:38 -#: ../src/engine/api/geary-special-folder-type.vala:51 +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:54 +#, c-format +#| msgid "Error connecting to the server" +msgid "Problem connecting to incoming server for %s" +msgstr "Problema di connessione al server in entrata per %s" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:56 +#: src/client/components/main-window-info-bar.vala:64 +#, c-format +msgid "" +"Could not connect to %s, check your Internet access and the server name and " +"try again" +msgstr "" +"Impossibile connettersi a %s, controllare l'accesso a Internet e il nome del " +"server e provare di nuovo" + +#: src/client/components/main-window-info-bar.vala:57 +#: src/client/components/main-window-info-bar.vala:66 +msgid "Retry connecting now" +msgstr "Ritenta la connessione ora" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:62 +#, c-format +#| msgid "Error connecting to the server" +msgid "Problem connecting to outgoing server for %s" +msgstr "Problema di connessione al server in uscita per %s" + +#: src/client/components/main-window-info-bar.vala:65 +msgid "Try reconnecting now" +msgstr "Tenta la riconnessione ora" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:71 +#, c-format +msgid "Problem with connection to incoming server for %s" +msgstr "Problema di connessione al server in entrata per %s" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:73 +#: src/client/components/main-window-info-bar.vala:81 +#, c-format +msgid "Network error talking to %s, check your Internet access and try again" +msgstr "" +"Errore di rete durante la comunicazione con %s, controllare l'accesso a " +"Internet e riprovare" + +#: src/client/components/main-window-info-bar.vala:74 +#: src/client/components/main-window-info-bar.vala:82 +#: src/client/components/main-window-info-bar.vala:90 +#: src/client/components/main-window-info-bar.vala:98 +#: src/client/components/main-window-info-bar.vala:119 +msgid "Try reconnecting" +msgstr "Tenta la riconnessione" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:79 +#, c-format +#| msgid "Error connecting to the server" +msgid "Problem with connection to outgoing server for %s" +msgstr "Problema di connessione col server in uscita per %s" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:87 +#, c-format +msgid "Problem communicating with incoming server for %s" +msgstr "Problema di comunicazione col server in entrata per %s" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:89 +#, c-format +msgid "" +"Geary did not understand a message from %s or vice versa, please file a bug " +"report" +msgstr "" +"L'applicazione non ha compreso un messaggio di %s o viceversa, aprire una " +"segnalazione bug" + +#: src/client/components/main-window-info-bar.vala:94 +msgid "Problem communicating with outgoing mail server" +msgstr "Problema di comunicazione col server in uscita per %s" + +#. Translators: First string substitution is the server +#. name, second is the account name +#: src/client/components/main-window-info-bar.vala:97 +#, c-format +msgid "" +"Could not communicate with %s for %s, check the server name and try again in " +"a moment" +msgstr "" +"Impossibile comunicare con %s per %s, controllare il nome del server e " +"riprovare" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:103 +#, c-format +msgid "Incoming mail server password required for %s" +msgstr "Richiesta la password per il server di posta in entrata per %s" + +#: src/client/components/main-window-info-bar.vala:104 +msgid "Messages cannot be received without the correct password." +msgstr "I messaggi non possono essere ricevuti senza la password corretta." + +#: src/client/components/main-window-info-bar.vala:105 +msgid "Retry receiving email, you will be prompted for a password" +msgstr "Ritenta la ricezione della posta, verrà richiesta la password" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:110 +#, c-format +msgid "Outgoing mail server password required for %s" +msgstr "Richiesta la password per il server di posta in uscita per %s" + +#: src/client/components/main-window-info-bar.vala:111 +msgid "Messages cannot be sent without the correct password." +msgstr "I messaggi non possono essere ricevuti senza la password corretta." + +#: src/client/components/main-window-info-bar.vala:112 +msgid "Retry sending queued messages, you will be prompted for a password" +msgstr "Ritenta l'invio dei messaggi in coda, verrà richiesta la password" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:117 +#, c-format +msgid "A problem occurred checking mail for %s" +msgstr "Si è verificato un problema nel controllare la posta per %s" + +#: src/client/components/main-window-info-bar.vala:118 +#: src/client/components/main-window-info-bar.vala:125 +msgid "Something went wrong, please file a bug report if the problem persists" +msgstr "" +"Qualcosa è andato storto, aprire una segnalazione bug se il problema persiste" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:124 +#, c-format +msgid "A problem occurred sending mail for %s" +msgstr "Si è verificato un problema nell'inviare la posta per %s" + +#: src/client/components/main-window-info-bar.vala:126 +msgid "Retry sending queued messages" +msgstr "Ritenta l'invio dei messaggi in coda" + +#: src/client/components/main-window-info-bar.vala:137 +msgid "A database problem has occurred" +msgstr "Si è verificato un problema del database" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:139 +#, c-format +msgid "Messages for %s must be downloaded again." +msgstr "I messaggi per %s devono essere scaricati di nuovo." + +#: src/client/components/main-window-info-bar.vala:152 +msgid "Geary has encountered a problem" +msgstr "L'applicazione ha rilevato un problema" + +#: src/client/components/main-window-info-bar.vala:153 +msgid "" +"Please check the technical details and report the problem if it persists." +msgstr "" +"Controllare i dettagli tecnici e segnalare il problema nel caso persista." + +#: src/client/components/main-window-info-bar.vala:161 +msgid "_Details" +msgstr "_Dettagli" + +#: src/client/components/main-window-info-bar.vala:162 +msgid "View technical details about the error" +msgstr "Visualizza i dettagli tecnici relativi all'errore" + +#: src/client/components/main-window-info-bar.vala:166 +msgid "_Retry" +msgstr "_Riprova" + +#: src/client/components/main-window-info-bar.vala:253 +#| msgid "Detach" +msgid "Details" +msgstr "Dettagli" + +#: src/client/components/main-window-info-bar.vala:266 +#: src/client/components/stock.vala:23 +msgid "_Close" +msgstr "_Chiudi" + +#: src/client/components/main-window-info-bar.vala:270 +msgid "Copy to Clipboard" +msgstr "Copia negli appunti" + +#: src/client/components/main-window-info-bar.vala:273 +msgid "" +"Copy technical details to clipboard for pasting into an email or bug report" +msgstr "" +"Copia i dettagli tecnici negli appunti per incollarli in un'email o in una " +"segnalazione bug" + +#: src/client/components/search-bar.vala:8 +#: src/client/folder-list/folder-list-search-branch.vala:38 +#: src/engine/api/geary-special-folder-type.vala:51 msgid "Search" msgstr "Cerca" #. Search entry. -#: ../src/client/components/search-bar.vala:23 +#: src/client/components/search-bar.vala:23 msgid "Search all mail in account for keywords (Ctrl+S)" msgstr "Cerca le parole chiave in tutta la posta dell'account (Ctrl+S)" -#: ../src/client/components/search-bar.vala:100 +#: src/client/components/search-bar.vala:100 #, c-format msgid "Indexing %s account" msgstr "Indicizzazione dell'account %s" -#: ../src/client/components/search-bar.vala:111 -#: ../src/client/folder-list/folder-list-search-branch.vala:39 +#: src/client/components/search-bar.vala:111 +#: src/client/folder-list/folder-list-search-branch.vala:39 #, c-format msgid "Search %s account" msgstr "Cerca l'account %s" #. / Displayed in the space-limited status bar while a message is in the process of being sent. -#: ../src/client/components/status-bar.vala:26 +#: src/client/components/status-bar.vala:26 msgid "Sending…" msgstr "Invio…" -#: ../src/client/components/stock.vala:18 ../ui/account_cannot_remove.glade.h:3 +#: src/client/components/stock.vala:18 ui/account_cannot_remove.glade:74 msgid "_OK" msgstr "_Ok" -#: ../src/client/components/stock.vala:19 ../ui/edit_alternate_emails.glade.h:3 -#: ../ui/password-dialog.glade.h:5 ../ui/remove_confirm.glade.h:5 +#: src/client/components/stock.vala:19 ui/edit_alternate_emails.glade:160 +#: ui/password-dialog.glade:196 ui/remove_confirm.glade:155 msgid "_Cancel" msgstr "A_nnulla" -#: ../src/client/components/stock.vala:21 ../ui/gtk/menus.ui.h:6 +#: src/client/components/stock.vala:21 ui/gtk/menus.ui:34 msgid "_About" msgstr "I_nformazioni" -#: ../src/client/components/stock.vala:23 -msgid "_Close" -msgstr "_Chiudi" - -#: ../src/client/components/stock.vala:24 +#: src/client/components/stock.vala:24 msgid "_Discard" msgstr "_Scarta" -#: ../src/client/components/stock.vala:25 ../ui/gtk/menus.ui.h:5 +#: src/client/components/stock.vala:25 ui/gtk/menus.ui:29 msgid "_Help" msgstr "A_iuto" -#: ../src/client/components/stock.vala:26 ../ui/conversation-email-menus.ui.h:9 +#: src/client/components/stock.vala:26 ui/conversation-email-menus.ui:51 msgid "_Open" msgstr "_Apri" -#: ../src/client/components/stock.vala:27 ../ui/gtk/menus.ui.h:3 +#: src/client/components/stock.vala:27 ui/gtk/menus.ui:17 msgid "_Preferences" msgstr "Preferen_ze" -#: ../src/client/components/stock.vala:28 ../ui/conversation-email-menus.ui.h:7 +#: src/client/components/stock.vala:28 ui/conversation-email-menus.ui:39 msgid "_Print…" msgstr "Stam_pa…" -#: ../src/client/components/stock.vala:29 ../ui/gtk/menus.ui.h:7 +#: src/client/components/stock.vala:29 ui/gtk/menus.ui:38 msgid "_Quit" msgstr "_Chiudi" -#: ../src/client/components/stock.vala:30 ../ui/remove_confirm.glade.h:6 +#: src/client/components/stock.vala:30 ui/remove_confirm.glade:170 msgid "_Remove" msgstr "_Rimuovi" -#: ../src/client/components/stock.vala:32 +#: src/client/components/stock.vala:32 msgid "_Keep" msgstr "_Conserva" -#: ../src/client/composer/composer-link-popover.vala:150 +#: src/client/composer/composer-link-popover.vala:149 msgid "Link URL is not correctly formatted, e.g. http://example.com" msgstr "" "L'URL del collegamento non ha un formato corretto, per esempio http://" "esempio.com" -#: ../src/client/composer/composer-link-popover.vala:157 +#: src/client/composer/composer-link-popover.vala:156 msgid "Invalid link URL" msgstr "URL del collegamento non valido" -#: ../src/client/composer/composer-link-popover.vala:157 +#: src/client/composer/composer-link-popover.vala:156 msgid "Invalid email address" msgstr "Indirizzo email non valido" -#: ../src/client/composer/composer-widget.vala:154 +#: src/client/composer/composer-widget.vala:158 msgid "Saved" msgstr "Salvato" -#: ../src/client/composer/composer-widget.vala:155 +#: src/client/composer/composer-widget.vala:159 msgid "Saving" msgstr "Salvataggio" -#: ../src/client/composer/composer-widget.vala:156 +#: src/client/composer/composer-widget.vala:160 msgid "Error saving" msgstr "Errore durante il salvataggio" -#: ../src/client/composer/composer-widget.vala:157 +#: src/client/composer/composer-widget.vala:161 msgid "Press Backspace to delete quote" msgstr "Premere Backspace per eliminare la citazione" -#: ../src/client/composer/composer-widget.vala:158 -msgid "New Message" -msgstr "Nuovo messaggio" - #. Translators: This is list of keywords, separated by pipe ("|") #. characters, that suggest an attachment; since this is full-word #. checking, include all variants of each word. No spaces are #. allowed. -#: ../src/client/composer/composer-widget.vala:167 +#: src/client/composer/composer-widget.vala:170 msgid "" "attach|attaching|attaches|attachment|attachments|attached|enclose|enclosed|" "enclosing|encloses|enclosure|enclosures" @@ -925,28 +1220,39 @@ "enclosing|encloses|enclosure|enclosures|allegato|allegati|allego|allega|" "allegare|allegando|includo" -#: ../src/client/composer/composer-widget.vala:1122 -#: ../src/client/composer/composer-widget.vala:1141 -msgid "Do you want to discard this message?" -msgstr "Scartare questo messaggio?" +#. Translators: This dialog text is displayed to the +#. user when closing a composer where the options are +#. Keep, Discard or Cancel. +#: src/client/composer/composer-widget.vala:1102 +#| msgid "Do you want to discard this message?" +msgid "Do you want to keep or discard this draft message?" +msgstr "Conservare o scartare questa bozza?" + +#. Translators: This dialog text is displayed to the +#. user when closing a composer where the options are +#. only Discard or Cancel. +#: src/client/composer/composer-widget.vala:1130 +#| msgid "Do you want to discard this message?" +msgid "Do you want to discard this draft message?" +msgstr "Scartare questa bozza?" -#: ../src/client/composer/composer-widget.vala:1245 +#: src/client/composer/composer-widget.vala:1238 msgid "Send message with an empty subject and body?" msgstr "Inviare il messaggio senza l'oggetto e il testo?" -#: ../src/client/composer/composer-widget.vala:1247 +#: src/client/composer/composer-widget.vala:1240 msgid "Send message with an empty subject?" msgstr "Inviare il messaggio senza un oggetto?" -#: ../src/client/composer/composer-widget.vala:1249 +#: src/client/composer/composer-widget.vala:1242 msgid "Send message with an empty body?" msgstr "Inviare il messaggio senza il testo?" -#: ../src/client/composer/composer-widget.vala:1253 +#: src/client/composer/composer-widget.vala:1246 msgid "Send message without an attachment?" msgstr "Inviare il messaggio senza un allegato?" -#: ../src/client/composer/composer-widget.vala:1515 +#: src/client/composer/composer-widget.vala:1551 #, c-format msgid "“%s” already attached for delivery." msgstr "«%s» è già allegato per l'invio." @@ -956,170 +1262,214 @@ #. description of the document type, the second will #. be a human-friendly size string. For example: #. Document (100.9MB) -#: ../src/client/composer/composer-widget.vala:1523 -#: ../src/client/conversation-viewer/conversation-email.vala:138 +#: src/client/composer/composer-widget.vala:1559 +#: src/client/conversation-viewer/conversation-email.vala:136 #, c-format msgid "%s (%s)" msgstr "%s (%s)" -#: ../src/client/composer/composer-widget.vala:1560 +#: src/client/composer/composer-widget.vala:1596 #, c-format msgid "“%s” could not be found." msgstr "«%s» non può essere trovato." -#: ../src/client/composer/composer-widget.vala:1566 +#: src/client/composer/composer-widget.vala:1602 #, c-format msgid "“%s” is a folder." msgstr "«%s» è una cartella." -#: ../src/client/composer/composer-widget.vala:1572 +#: src/client/composer/composer-widget.vala:1608 #, c-format msgid "“%s” is an empty file." msgstr "«%s» è un file vuoto." -#: ../src/client/composer/composer-widget.vala:1585 +#: src/client/composer/composer-widget.vala:1621 #, c-format msgid "“%s” could not be opened for reading." msgstr "«%s» non può essere aperto per la lettura." -#: ../src/client/composer/composer-widget.vala:1593 +#: src/client/composer/composer-widget.vala:1629 msgid "Cannot add attachment" msgstr "Impossibile aggiungere l'allegato" -#: ../src/client/composer/composer-widget.vala:1645 +#: src/client/composer/composer-widget.vala:1678 msgid "To: " msgstr "A: " -#: ../src/client/composer/composer-widget.vala:1648 +#: src/client/composer/composer-widget.vala:1681 msgid "Cc: " msgstr "Cc: " -#: ../src/client/composer/composer-widget.vala:1651 +#: src/client/composer/composer-widget.vala:1684 msgid "Bcc: " msgstr "Ccn: " -#: ../src/client/composer/composer-widget.vala:1654 +#: src/client/composer/composer-widget.vala:1687 msgid "Reply-To: " msgstr "Rispondi a:" -#: ../src/client/composer/composer-widget.vala:1786 +#: src/client/composer/composer-widget.vala:1820 msgid "Select Color" msgstr "Seleziona colore" #. Displayed in the From dropdown to indicate an "alternate email address" #. for an account. The first printf argument will be the alternate email #. address, and the second will be the account's primary email address. -#: ../src/client/composer/composer-widget.vala:1986 +#: src/client/composer/composer-widget.vala:2003 #, c-format msgid "%1$s via %2$s" msgstr "%1$s tramite %2$s" #. Composer label (with mnemonic underscore) for the account selector #. when choosing what address to send a message from. -#: ../src/client/composer/composer-widget.vala:2028 +#: src/client/composer/composer-widget.vala:2061 msgid "_From:" msgstr "_Da:" #. Translators: This is the name of the file chooser filter #. when inserting an image in the composer. -#: ../src/client/composer/composer-widget.vala:2252 +#: src/client/composer/composer-widget.vala:2286 msgid "Images" msgstr "Immagini" -#: ../src/client/composer/spell-check-popover.vala:117 +#: src/client/composer/composer-window.vala:14 +msgid "New Message" +msgstr "Nuovo messaggio" + +#: src/client/composer/spell-check-popover.vala:117 msgid "Remove this language from the preferred list" msgstr "Togli questa lingua dalla lista dei preferiti" -#: ../src/client/composer/spell-check-popover.vala:121 +#: src/client/composer/spell-check-popover.vala:121 msgid "Add this language to the preferred list" msgstr "Aggiungi questa lingua alla lista dei preferiti" -#: ../src/client/composer/spell-check-popover.vala:217 +#: src/client/composer/spell-check-popover.vala:217 msgid "Search for more languages" msgstr "Cerca altre lingue" -#: ../src/client/conversation-list/formatted-conversation-data.vala:11 +#: src/client/conversation-list/conversation-list-view.vala:283 +msgid "Delete conversation" +msgstr "Elimina conversazione" + +#: src/client/conversation-list/conversation-list-view.vala:286 +#: ui/main-toolbar-menus.ui:16 +msgid "Mark as _Read" +msgstr "Segna come _letto" + +#: src/client/conversation-list/conversation-list-view.vala:289 +#: ui/main-toolbar-menus.ui:20 +msgid "Mark as _Unread" +msgstr "Segna come _non letto" + +#: src/client/conversation-list/conversation-list-view.vala:292 +#: ui/main-toolbar-menus.ui:28 +msgid "U_nstar" +msgstr "_Non speciale" + +#: src/client/conversation-list/conversation-list-view.vala:294 +#: ui/main-toolbar-menus.ui:24 +msgid "_Star" +msgstr "_Speciale" + +#: src/client/conversation-list/conversation-list-view.vala:297 +#: ui/conversation-email-menus.ui:8 +msgid "_Reply" +msgstr "_Rispondi" + +#: src/client/conversation-list/conversation-list-view.vala:298 +msgid "R_eply All" +msgstr "R_ispondi a tutti" + +#: src/client/conversation-list/conversation-list-view.vala:299 +#: ui/conversation-email-menus.ui:18 +msgid "_Forward" +msgstr "_Inoltra" + +#: src/client/conversation-list/formatted-conversation-data.vala:11 msgid "Me" msgstr "Io" #. Translators: This is the file type displayed for #. attachments with unknown file types. -#: ../src/client/conversation-viewer/conversation-email.vala:124 +#: src/client/conversation-viewer/conversation-email.vala:122 msgid "Unknown" msgstr "Sconosciuto" +#: src/client/conversation-viewer/conversation-message.vala:60 +msgid "This email address may have been forged" +msgstr "Questo indirizzo email potrebbe essere contraffatto" + #. Preview headers #. Translators: This is displayed in place of the from address #. when the message has no from address. -#: ../src/client/conversation-viewer/conversation-message.vala:331 +#: src/client/conversation-viewer/conversation-message.vala:330 msgid "No sender" msgstr "Nessun mittente" #. Translators: This separates multiple 'from' #. addresses in the header preview for a message. -#: ../src/client/conversation-viewer/conversation-message.vala:586 +#: src/client/conversation-viewer/conversation-message.vala:589 msgid ", " msgstr ", " #. Translators: This string is used as the HTML IMG ALT #. attribute value when displaying an inline image in an email #. that did not specify a file name. E.g. ImageCannot remove account " msgstr "" "Impossibile rimuovere l'account " -#: ../ui/account_cannot_remove.glade.h:2 +#: ui/account_cannot_remove.glade:56 msgid "" "A composer window associated with this account is currently open. Send or " "discard the message and try again." @@ -1743,433 +2112,444 @@ "Una finestra di composizione associata a questo account è attualmente " "aperta. Inviare o scartare il messaggio e provare ancora." -#: ../ui/account_list.glade.h:1 +#: ui/account_list.glade:69 msgid "Add account" msgstr "Aggiungi account" -#: ../ui/account_list.glade.h:2 +#: ui/account_list.glade:82 msgid "Edit account" msgstr "Modifica account" -#: ../ui/account_list.glade.h:3 +#: ui/account_list.glade:95 msgid "Remove account" msgstr "Rimuovi account" -#: ../ui/account_spinner.glade.h:1 +#: ui/account_spinner.glade:41 msgid "Please wait while Geary validates your account." msgstr "Attendere mentre l'applicazione convalida l'account." -#: ../ui/certificate_warning_dialog.glade.h:1 +#: ui/certificate_warning_dialog.glade:7 msgid "Untrusted Connection" msgstr "Connessione non sicura" -#: ../ui/certificate_warning_dialog.glade.h:2 +#: ui/certificate_warning_dialog.glade:29 msgid "_Always Trust This Server" msgstr "Fidarsi _sempre di questo server" -#: ../ui/certificate_warning_dialog.glade.h:3 +#: ui/certificate_warning_dialog.glade:43 msgid "_Trust This Server" msgstr "_Fidarsi di questo server" -#: ../ui/certificate_warning_dialog.glade.h:4 +#: ui/certificate_warning_dialog.glade:57 msgid "_Don’t Trust This Server" msgstr "_Non fidarsi di questo server" -#: ../ui/composer-headerbar.ui.h:1 +#: ui/composer-headerbar.ui:17 ui/composer-headerbar.ui:174 msgid "Detach (Ctrl+D)" -msgstr "Sgancia (Ctrl+D)" +msgstr "Stacca (Ctrl+D)" -#: ../ui/composer-headerbar.ui.h:2 +#: ui/composer-headerbar.ui:57 ui/composer-headerbar.ui:83 msgid "Attach File (Ctrl+T)" msgstr "Allega file (Ctrl+T)" -#: ../ui/composer-headerbar.ui.h:3 +#: ui/composer-headerbar.ui:107 msgid "Include Original Attachments" msgstr "Include gli allegati originali" -#: ../ui/composer-headerbar.ui.h:4 -msgid "Send (Ctrl+Enter)" -msgstr "Invia (Ctrl+Invio)" - -#: ../ui/composer-headerbar.ui.h:5 +#: ui/composer-headerbar.ui:202 msgid "_Send" msgstr "_Invia" -#: ../ui/composer-headerbar.ui.h:6 +#: ui/composer-headerbar.ui:207 +msgid "Send (Ctrl+Enter)" +msgstr "Invia (Ctrl+Invio)" + +#: ui/composer-headerbar.ui:230 msgid "Discard and Close" msgstr "Scarta e chiudi" -#: ../ui/composer-headerbar.ui.h:7 +#: ui/composer-headerbar.ui:254 msgid "Save and Close" msgstr "Salva e chiudi" #. Note that this button and the Update button will never be shown at the same time to the user. -#: ../ui/composer-link-popover.ui.h:2 +#: ui/composer-link-popover.ui:41 msgid "Insert the new link with this URL" msgstr "Inserisci il nuovo collegamento con questo URL" -#: ../ui/composer-link-popover.ui.h:3 +#: ui/composer-link-popover.ui:52 msgid "Link URL" msgstr "URL del collegamento" #. Note that this button and the Insert button will never be shown at the same time to the user. -#: ../ui/composer-link-popover.ui.h:5 +#: ui/composer-link-popover.ui:66 msgid "Update this link’s URL" msgstr "Aggiornare l'URL di questo collegamento" -#: ../ui/composer-link-popover.ui.h:6 +#: ui/composer-link-popover.ui:86 msgid "Delete this link" msgstr "Eliminare questo collegamento" -#: ../ui/composer-link-popover.ui.h:7 +#: ui/composer-link-popover.ui:106 msgid "Open this link" msgstr "Aprire questo collegamento" -#: ../ui/composer-menus.ui.h:1 +#: ui/composer-menus.ui:7 msgid "S_ans Serif" msgstr "S_enza grazie" -#: ../ui/composer-menus.ui.h:2 +#: ui/composer-menus.ui:12 msgid "S_erif" msgstr "Con g_razie" -#: ../ui/composer-menus.ui.h:3 +#: ui/composer-menus.ui:17 msgid "_Fixed Width" msgstr "_Larghezza fissa" -#: ../ui/composer-menus.ui.h:4 +#: ui/composer-menus.ui:24 msgid "_Small" msgstr "_Piccolo" -#: ../ui/composer-menus.ui.h:5 +#: ui/composer-menus.ui:29 msgid "_Medium" msgstr "_Medio" -#: ../ui/composer-menus.ui.h:6 +#: ui/composer-menus.ui:34 msgid "Lar_ge" msgstr "_Grande" -#: ../ui/composer-menus.ui.h:7 +#: ui/composer-menus.ui:41 msgid "C_olor" msgstr "C_olore" -#: ../ui/composer-menus.ui.h:8 +#: ui/composer-menus.ui:47 ui/composer-menus.ui:62 msgid "_Rich Text" msgstr "Testo _formattato" -#: ../ui/composer-menus.ui.h:9 +#: ui/composer-menus.ui:53 ui/composer-menus.ui:68 msgid "Show Extended Fields" msgstr "Mostra i campi estesi" -#: ../ui/composer-menus.ui.h:10 +#: ui/composer-menus.ui:78 msgid "_Undo" msgstr "_Annulla" -#: ../ui/composer-menus.ui.h:11 +#: ui/composer-menus.ui:82 msgid "_Redo" msgstr "_Ripeti" -#: ../ui/composer-menus.ui.h:12 +#: ui/composer-menus.ui:88 ui/composer-menus.ui:106 msgid "Cu_t" msgstr "_Taglia" -#: ../ui/composer-menus.ui.h:13 ../ui/conversation-message-menus.ui.h:7 +#: ui/composer-menus.ui:92 ui/composer-menus.ui:110 +#: ui/conversation-message-menus.ui:37 msgid "_Copy" msgstr "_Copia" -#: ../ui/composer-menus.ui.h:14 +#: ui/composer-menus.ui:96 ui/composer-menus.ui:114 msgid "_Paste" msgstr "_Incolla" -#: ../ui/composer-menus.ui.h:15 +#: ui/composer-menus.ui:100 msgctxt "Clipboard paste with rich text" msgid "Paste _With Formatting" msgstr "Incolla _con formattazione" -#: ../ui/composer-menus.ui.h:16 +#: ui/composer-menus.ui:120 msgid "Select _All" msgstr "Seleziona _tutto" -#: ../ui/composer-menus.ui.h:17 ../ui/conversation-message-menus.ui.h:9 +#: ui/composer-menus.ui:127 ui/conversation-message-menus.ui:49 msgid "_Inspect…" msgstr "_Ispeziona…" #. Address(es) e-mail is to be sent to -#: ../ui/composer-widget.ui.h:2 +#: ui/composer-widget.ui:56 msgid "_To" msgstr "_A" -#: ../ui/composer-widget.ui.h:3 +#: ui/composer-widget.ui:75 msgid "_Cc" msgstr "_Cc" -#: ../ui/composer-widget.ui.h:4 +#: ui/composer-widget.ui:130 msgid "_Subject" msgstr "_Oggetto" -#: ../ui/composer-widget.ui.h:5 +#: ui/composer-widget.ui:149 msgid "_Bcc" msgstr "Cc_n" -#: ../ui/composer-widget.ui.h:6 +#: ui/composer-widget.ui:179 msgid "_Reply-To" msgstr "_Rispondi a" #. Geary account mail will be sent from -#: ../ui/composer-widget.ui.h:8 +#: ui/composer-widget.ui:208 msgid "From" msgstr "Da" -#: ../ui/composer-widget.ui.h:9 +#: ui/composer-widget.ui:293 msgid "Drop files here" msgstr "Trascina i file qui" -#: ../ui/composer-widget.ui.h:10 +#: ui/composer-widget.ui:309 msgid "To add them as attachments" msgstr "Per aggiungerli come allegati" -#: ../ui/composer-widget.ui.h:11 +#: ui/composer-widget.ui:348 msgid "Undo last edit (Ctrl+Z)" msgstr "Annulla l'ultima modifica (Ctrl+Z)" -#: ../ui/composer-widget.ui.h:12 +#: ui/composer-widget.ui:372 msgid "Redo last edit (Ctrl+Shift+Z)" msgstr "Ripeti l'ultima modifica (Ctrl+Shift+Z)" -#: ../ui/composer-widget.ui.h:13 +#: ui/composer-widget.ui:410 msgid "Bold (Ctrl+B)" msgstr "Grassetto (Ctrl+B)" -#: ../ui/composer-widget.ui.h:14 +#: ui/composer-widget.ui:434 msgid "Italic (Ctrl+I)" msgstr "Corsivo (Ctrl+I)" -#: ../ui/composer-widget.ui.h:15 +#: ui/composer-widget.ui:458 msgid "Underline (Ctrl+U)" msgstr "Sottolineato (Ctrl+U)" -#: ../ui/composer-widget.ui.h:16 +#: ui/composer-widget.ui:482 msgid "Strikethrough (Ctrl+K)" msgstr "Barrato (Ctrl+K)" -#: ../ui/composer-widget.ui.h:17 +#: ui/composer-widget.ui:520 +msgid "Insert unordered list" +msgstr "Inserisci elenco non ordinato" + +#: ui/composer-widget.ui:544 +#| msgctxt "shortcut window" +#| msgid "Insert a link" +msgid "Insert ordered list" +msgstr "Inserisci elenco ordinato" + +#: ui/composer-widget.ui:582 msgid "Quote text (Ctrl+])" msgstr "Cita il testo (Ctrl+])" -#: ../ui/composer-widget.ui.h:18 +#: ui/composer-widget.ui:606 msgid "Unquote text (Ctrl+[)" msgstr "Rimuovi la citazione del testo (Ctrl+[)" -#: ../ui/composer-widget.ui.h:19 +#: ui/composer-widget.ui:644 msgid "Insert or update selection link (Ctrl+L)" msgstr "Inserisci o aggiorna il collegamento del testo selezionato (Ctrl+L)" -#: ../ui/composer-widget.ui.h:20 +#: ui/composer-widget.ui:668 msgid "Insert an image (Ctrl+G)" msgstr "Inserisci un'immagine (Ctrl+G)" -#: ../ui/composer-widget.ui.h:21 +#: ui/composer-widget.ui:702 msgid "Remove selection formatting (Ctrl+Space)" msgstr "Rimuovi la formattazione del testo selezionato (Ctrl+Spazio)" -#: ../ui/composer-widget.ui.h:22 +#: ui/composer-widget.ui:726 msgid "Select spell checking languages" msgstr "Seleziona le lingue del controllo ortografico" -#: ../ui/conversation-email.ui.h:1 +#: ui/conversation-email.ui:27 msgid "Save all attachments" msgstr "Salva tutti gli allegati" #. Note: The application will never show this button at the same time as unstar_button, one will always be hidden. -#: ../ui/conversation-email.ui.h:3 +#: ui/conversation-email.ui:50 msgid "Mark this message as starred" msgstr "Contrassegna questo messaggio come speciale" #. Note: The application will never show this button at the same time as star_button, one will always be hidden. -#: ../ui/conversation-email.ui.h:5 +#: ui/conversation-email.ui:72 msgid "Mark this message as not starred" msgstr "Contrassegna questo messaggio come non speciale" -#: ../ui/conversation-email.ui.h:6 +#: ui/conversation-email.ui:95 msgid "Display the message menu" msgstr "Mostra il menu dei messaggi" -#: ../ui/conversation-email.ui.h:7 +#: ui/conversation-email.ui:161 msgid "Open selected attachments" msgstr "Apri gli allegati selezionati" -#: ../ui/conversation-email.ui.h:8 +#: ui/conversation-email.ui:178 msgid "Save selected attachments" msgstr "Salva gli allegati selezionati" -#: ../ui/conversation-email.ui.h:9 +#: ui/conversation-email.ui:195 msgid "Select all attachments" msgstr "Seleziona tutti gli allegati" -#: ../ui/conversation-email.ui.h:10 +#: ui/conversation-email.ui:240 msgid "Edit Draft" msgstr "Modifica bozza" -#: ../ui/conversation-email.ui.h:11 +#: ui/conversation-email.ui:267 msgid "Draft message" msgstr "Bozza" -#: ../ui/conversation-email.ui.h:12 +#: ui/conversation-email.ui:283 msgid "This message has not yet been sent." msgstr "Questo messaggio non è stato ancora inviato." -#: ../ui/conversation-email.ui.h:13 +#: ui/conversation-email.ui:329 msgid "Message not saved" msgstr "Messaggio non salvato" -#: ../ui/conversation-email.ui.h:14 +#: ui/conversation-email.ui:345 msgid "This message was sent, but has not been saved to your account." msgstr "" "Questo messaggio è stato inviato correttamente, ma non è stato possibile " "salvarlo." -#: ../ui/conversation-email-menus.ui.h:2 +#: ui/conversation-email-menus.ui:13 msgid "Reply to _All" msgstr "Rispondi a _tutti" -#: ../ui/conversation-email-menus.ui.h:4 +#: ui/conversation-email-menus.ui:25 msgid "_Mark Read" msgstr "_Segna come letto" -#: ../ui/conversation-email-menus.ui.h:5 +#: ui/conversation-email-menus.ui:29 msgid "_Mark Unread" msgstr "_Segna come da leggere" -#: ../ui/conversation-email-menus.ui.h:6 +#: ui/conversation-email-menus.ui:33 msgid "Mark Unread From _Here" msgstr "Segna come non letto da _qui" -#: ../ui/conversation-email-menus.ui.h:8 +#: ui/conversation-email-menus.ui:43 msgid "_View Source" msgstr "_Visualizza sorgente" -#: ../ui/conversation-email-menus.ui.h:11 +#: ui/conversation-email-menus.ui:61 msgid "_Save All" msgstr "_Salva tutti" -#: ../ui/conversation-message-menus.ui.h:1 +#: ui/conversation-message-menus.ui:7 msgid "_Open Link" msgstr "_Apri collegamento" -#: ../ui/conversation-message-menus.ui.h:2 +#: ui/conversation-message-menus.ui:11 msgid "Copy Link _Address" msgstr "Copia indirizzo _collegamento" -#: ../ui/conversation-message-menus.ui.h:3 +#: ui/conversation-message-menus.ui:17 msgid "Send New _Message…" msgstr "Invia nuovo _messaggio" -#: ../ui/conversation-message-menus.ui.h:4 +#: ui/conversation-message-menus.ui:21 msgid "Copy Email _Address" msgstr "Copia indirizzo _email" -#: ../ui/conversation-message-menus.ui.h:5 +#: ui/conversation-message-menus.ui:27 msgid "Save _Image As…" msgstr "_Salva immagine come…" -#: ../ui/conversation-message-menus.ui.h:6 +#: ui/conversation-message-menus.ui:33 msgid "_Select All" msgstr "Seleziona _tutto" -#: ../ui/conversation-message-menus.ui.h:8 +#: ui/conversation-message-menus.ui:43 msgid "Search for messages from" msgstr "Cerca messaggi inviati da" -#: ../ui/conversation-message.ui.h:1 +#: ui/conversation-message.ui:64 msgid "From " msgstr "Da " -#: ../ui/conversation-message.ui.h:2 +#: ui/conversation-message.ui:80 ui/conversation-message.ui:179 msgid "1/1/1970\t" msgstr "1/1/1970\t" -#: ../ui/conversation-message.ui.h:3 +#: ui/conversation-message.ui:103 msgid "Preview body text." msgstr "Anteprima corpo del testo." -#: ../ui/conversation-message.ui.h:4 +#: ui/conversation-message.ui:203 msgid "Sent by:" msgstr "Inviato da:" -#: ../ui/conversation-message.ui.h:5 +#: ui/conversation-message.ui:248 msgid "Reply to:" msgstr "Rispondi a:" -#: ../ui/conversation-message.ui.h:6 +#: ui/conversation-message.ui:292 msgid "Subject" msgstr "Oggetto" -#: ../ui/conversation-message.ui.h:7 +#: ui/conversation-message.ui:313 msgid "To:" msgstr "A:" -#: ../ui/conversation-message.ui.h:8 +#: ui/conversation-message.ui:358 msgid "Cc:" msgstr "Cc:" -#: ../ui/conversation-message.ui.h:9 +#: ui/conversation-message.ui:403 msgid "Bcc:" msgstr "Ccn:" -#: ../ui/conversation-message.ui.h:10 +#: ui/conversation-message.ui:502 msgid "Show Images" msgstr "Mostra immagini" -#: ../ui/conversation-message.ui.h:11 +#: ui/conversation-message.ui:515 msgid "Always Show From Sender" msgstr "Mostra sempre dal mittente" -#: ../ui/conversation-message.ui.h:12 +#: ui/conversation-message.ui:543 msgid "Remote images not shown" msgstr "Immagini remote non mostrate" -#: ../ui/conversation-message.ui.h:13 +#: ui/conversation-message.ui:560 msgid "Only show remote images from senders you trust." msgstr "Mostra immagini remote solo da mittenti fidati." -#: ../ui/conversation-message.ui.h:14 +#: ui/conversation-message.ui:692 msgid "But actually goes to:" msgstr "Ma in realtà va in:" -#: ../ui/conversation-message.ui.h:15 +#: ui/conversation-message.ui:723 msgid "The link appears to go to:" msgstr "Il collegamento sembra portare a:" -#: ../ui/conversation-message.ui.h:16 +#: ui/conversation-message.ui:735 msgid "Deceptive link found" msgstr "Trovato collegamento ingannevole" -#: ../ui/conversation-message.ui.h:17 +#: ui/conversation-message.ui:750 msgid "The email sender may be leading you to the wrong web site." msgstr "Il mittente dell'email potrebbe condurre al sito web sbagliato." -#: ../ui/conversation-message.ui.h:18 +#: ui/conversation-message.ui:763 msgid "If unsure, contact the sender and ask before continuing." msgstr "" "In caso di dubbio, contattare il mittente e chiedere prima di continuare." -#: ../ui/conversation-viewer.ui.h:1 +#: ui/conversation-viewer.ui:60 msgid "Find in conversation" msgstr "Trova nella conversazione" -#: ../ui/conversation-viewer.ui.h:2 +#: ui/conversation-viewer.ui:74 msgid "Find the previous occurrence of the search string." msgstr "Trova l'occorrenza precedente della stringa di ricerca." -#: ../ui/conversation-viewer.ui.h:3 +#: ui/conversation-viewer.ui:95 msgid "Find the next occurrence of the search string." msgstr "Trova l'occorrenza successiva della stringa di ricerca." -#: ../ui/edit_alternate_emails.glade.h:1 +#: ui/edit_alternate_emails.glade:112 msgid "Remove email address" msgstr "Rimuovi indirizzo email" -#: ../ui/edit_alternate_emails.glade.h:2 +#: ui/edit_alternate_emails.glade:136 msgid "" "Some email services require additional addresses be configured on the " "server. Contact your email provider for more information." @@ -2177,485 +2557,546 @@ "Alcuni servizi email richiedono ulteriori indirizzi per essere configurati " "sul server. Contattare il fornitore dell'email per maggiori informazioni." -#: ../ui/edit_alternate_emails.glade.h:4 +#: ui/edit_alternate_emails.glade:175 msgid "_Update" msgstr "A_ggiorna" -#: ../ui/find_bar.glade.h:1 +#: ui/find_bar.glade:66 msgid "Find:" msgstr "Trova:" -#: ../ui/find_bar.glade.h:2 +#: ui/find_bar.glade:89 msgid "_Previous" msgstr "_Precedente" -#: ../ui/find_bar.glade.h:3 +#: ui/find_bar.glade:107 msgid "_Next" msgstr "_Successivo" -#: ../ui/find_bar.glade.h:4 +#: ui/find_bar.glade:125 msgid "_Case sensitive" msgstr "_Maiuscole/minuscole" -#: ../ui/find_bar.glade.h:5 +#: ui/find_bar.glade:145 msgid "label" msgstr "etichetta" -#: ../ui/gtk/help-overlay.ui.h:1 +#: ui/gtk/help-overlay.ui:9 msgid "Conversation Shortcuts" msgstr "Scorciatoie conversazioni" -#: ../ui/gtk/help-overlay.ui.h:2 +#: ui/gtk/help-overlay.ui:13 ui/gtk/help-overlay.ui:254 msgctxt "shortcut window" msgid "General" msgstr "Generale" -#: ../ui/gtk/help-overlay.ui.h:3 +#: ui/gtk/help-overlay.ui:17 msgctxt "shortcut window" msgid "Move focus to the next/previous pane" msgstr "Sposta lo stato attivo al pannello successivo/precedente" -#: ../ui/gtk/help-overlay.ui.h:4 +#: ui/gtk/help-overlay.ui:24 msgctxt "shortcut window" msgid "Move focus to conversation list" msgstr "Sposta lo stato attivo sull'elenco delle conversazioni" -#: ../ui/gtk/help-overlay.ui.h:5 +#: ui/gtk/help-overlay.ui:31 msgctxt "shortcut window" msgid "Detach composer window" msgstr "Stacca la finestra di composizione" -#: ../ui/gtk/help-overlay.ui.h:6 +#: ui/gtk/help-overlay.ui:38 msgctxt "shortcut window" msgid "Close composer window" msgstr "Chiudi la finestra di composizione" -#: ../ui/gtk/help-overlay.ui.h:7 +#: ui/gtk/help-overlay.ui:45 msgctxt "shortcut window" msgid "Show keyboard shortcuts" msgstr "Mostra le scorciatoie da tastiera" -#: ../ui/gtk/help-overlay.ui.h:8 +#: ui/gtk/help-overlay.ui:52 msgctxt "shortcut window" msgid "Show help" msgstr "Mostra aiuto" -#: ../ui/gtk/help-overlay.ui.h:9 +#: ui/gtk/help-overlay.ui:59 msgctxt "shortcut window" msgid "Quit the application" msgstr "Chiudi l'applicazione" -#: ../ui/gtk/help-overlay.ui.h:10 +#: ui/gtk/help-overlay.ui:68 msgctxt "shortcut window" msgid "Search" msgstr "Cerca" -#: ../ui/gtk/help-overlay.ui.h:11 +#: ui/gtk/help-overlay.ui:72 msgctxt "shortcut window" msgid "Jump to search box" msgstr "Salta alla casella di ricerca" -#: ../ui/gtk/help-overlay.ui.h:12 +#: ui/gtk/help-overlay.ui:79 msgctxt "shortcut window" msgid "Find in current conversation" msgstr "Trova nella conversazione corrente" -#: ../ui/gtk/help-overlay.ui.h:13 +#: ui/gtk/help-overlay.ui:86 msgctxt "shortcut window" msgid "Find next/previous in current conversation" msgstr "Trova successivo/precedente nella conversazione corrente" -#: ../ui/gtk/help-overlay.ui.h:14 +#: ui/gtk/help-overlay.ui:95 ui/gtk/help-overlay.ui:274 msgctxt "shortcut window" msgid "Actions" msgstr "Azioni" -#: ../ui/gtk/help-overlay.ui.h:15 +#: ui/gtk/help-overlay.ui:99 msgctxt "shortcut window" msgid "Compose a new message" msgstr "Componi un nuovo messaggio" -#: ../ui/gtk/help-overlay.ui.h:16 +#: ui/gtk/help-overlay.ui:106 msgctxt "shortcut window" msgid "Reply to sender " msgstr "Rispondi al mittente" -#: ../ui/gtk/help-overlay.ui.h:17 +#: ui/gtk/help-overlay.ui:113 msgctxt "shortcut window" msgid "Reply to all" msgstr "Rispondi a tutti" -#: ../ui/gtk/help-overlay.ui.h:18 +#: ui/gtk/help-overlay.ui:120 msgctxt "shortcut window" msgid "Forward" msgstr "Inoltra" -#: ../ui/gtk/help-overlay.ui.h:19 +#: ui/gtk/help-overlay.ui:127 msgctxt "shortcut window" msgid "Archive" msgstr "Archivia" -#: ../ui/gtk/help-overlay.ui.h:20 +#: ui/gtk/help-overlay.ui:134 msgctxt "shortcut window" msgid "Move to trash" msgstr "Sposta nel cestino" -#: ../ui/gtk/help-overlay.ui.h:21 +#: ui/gtk/help-overlay.ui:141 msgctxt "shortcut window" msgid "Toggle spam" msgstr "Attiva/Disattiva posta indesiderata" -#: ../ui/gtk/help-overlay.ui.h:22 +#: ui/gtk/help-overlay.ui:148 msgctxt "shortcut window" msgid "Move the conversation" msgstr "Sposta la conversazione" -#: ../ui/gtk/help-overlay.ui.h:23 +#: ui/gtk/help-overlay.ui:155 msgctxt "shortcut window" msgid "Label the conversation" msgstr "Etichetta la conversazione" -#: ../ui/gtk/help-overlay.ui.h:24 +#: ui/gtk/help-overlay.ui:163 msgctxt "shortcut window" msgid "Mark read" msgstr "Segna come letto" -#: ../ui/gtk/help-overlay.ui.h:25 +#: ui/gtk/help-overlay.ui:170 msgctxt "shortcut window" msgid "Mark unread" msgstr "Segna come non letto" -#: ../ui/gtk/help-overlay.ui.h:26 +#: ui/gtk/help-overlay.ui:179 msgctxt "shortcut window" msgid "View" msgstr "Vista" -#: ../ui/gtk/help-overlay.ui.h:27 +#: ui/gtk/help-overlay.ui:183 msgctxt "shortcut window" msgid "Zoom in" msgstr "Aumenta ingrandimento" -#: ../ui/gtk/help-overlay.ui.h:28 +#: ui/gtk/help-overlay.ui:190 msgctxt "shortcut window" msgid "Zoom out" msgstr "Diminuisci ingrandimento" -#: ../ui/gtk/help-overlay.ui.h:29 +#: ui/gtk/help-overlay.ui:197 msgctxt "shortcut window" msgid "Reset zoom" msgstr "Azzera ingrandimento" -#: ../ui/gtk/help-overlay.ui.h:30 +#: ui/gtk/help-overlay.ui:206 msgctxt "shortcut window" msgid "Additional Shortcuts" msgstr "Scorciatoie ulteriori" -#: ../ui/gtk/help-overlay.ui.h:31 +#: ui/gtk/help-overlay.ui:210 msgctxt "shortcut window" msgid "Star" msgstr "Speciale" -#: ../ui/gtk/help-overlay.ui.h:32 +#: ui/gtk/help-overlay.ui:217 msgctxt "shortcut window" msgid "Unstar" msgstr "Non speciale" -#: ../ui/gtk/help-overlay.ui.h:33 +#: ui/gtk/help-overlay.ui:224 msgctxt "shortcut window" msgid "Delete" msgstr "Elimina" -#: ../ui/gtk/help-overlay.ui.h:34 +#: ui/gtk/help-overlay.ui:231 msgctxt "shortcut window" msgid "Jump to next (older) conversation" msgstr "Salta alla conversazione successiva (più vecchia)" -#: ../ui/gtk/help-overlay.ui.h:35 +#: ui/gtk/help-overlay.ui:238 msgctxt "shortcut window" msgid "Jump to previous (newer) conversation" msgstr "Salta alla conversazione precedente (più recente)" -#: ../ui/gtk/help-overlay.ui.h:36 +#: ui/gtk/help-overlay.ui:250 msgid "Composer Shortcuts" msgstr "Scorciatoie compositore" -#: ../ui/gtk/help-overlay.ui.h:37 +#: ui/gtk/help-overlay.ui:258 msgctxt "shortcut window" msgid "Quote text" msgstr "Cita il testo" -#: ../ui/gtk/help-overlay.ui.h:38 +#: ui/gtk/help-overlay.ui:265 msgctxt "shortcut window" msgid "Unquote text" msgstr "Rimuovi la citazione del testo" -#: ../ui/gtk/help-overlay.ui.h:39 +#: ui/gtk/help-overlay.ui:278 msgctxt "shortcut window" msgid "Send" msgstr "Invia" -#: ../ui/gtk/help-overlay.ui.h:40 +#: ui/gtk/help-overlay.ui:285 msgctxt "shortcut window" msgid "Add attachment" msgstr "Aggiungi allegato" -#: ../ui/gtk/help-overlay.ui.h:41 +#: ui/gtk/help-overlay.ui:294 msgctxt "shortcut window" msgid "Rich text mode" msgstr "Modalità testo formattato" -#: ../ui/gtk/help-overlay.ui.h:42 +#: ui/gtk/help-overlay.ui:298 msgctxt "shortcut window" msgid "Bold text" msgstr "Testo in grassetto" -#: ../ui/gtk/help-overlay.ui.h:43 +#: ui/gtk/help-overlay.ui:305 msgctxt "shortcut window" msgid "Italicize text" msgstr "Testo in corsivo" -#: ../ui/gtk/help-overlay.ui.h:44 +#: ui/gtk/help-overlay.ui:312 msgctxt "shortcut window" msgid "Underline text" msgstr "Testo sottolineato" -#: ../ui/gtk/help-overlay.ui.h:45 +#: ui/gtk/help-overlay.ui:319 msgctxt "shortcut window" msgid "Strike text" msgstr "Testo barrato" -#: ../ui/gtk/help-overlay.ui.h:46 +#: ui/gtk/help-overlay.ui:326 msgctxt "shortcut window" msgid "Insert a link" msgstr "Inserisci un collegamento" -#: ../ui/gtk/help-overlay.ui.h:47 +#: ui/gtk/help-overlay.ui:333 msgctxt "shortcut window" msgid "Remove formatting" msgstr "Rimuovi formattazione" -#: ../ui/gtk/menus.ui.h:2 +#: ui/gtk/menus.ui:13 msgid "A_ccounts" msgstr "A_ccount" -#: ../ui/gtk/menus.ui.h:4 +#: ui/gtk/menus.ui:23 msgid "_Keyboard Shortcuts" msgstr "Scorciatoie _tastiera" -#: ../ui/login.glade.h:1 +#: ui/login.glade:88 msgid "email@example.com" msgstr "email@esempio.com" -#: ../ui/login.glade.h:2 ../ui/password-dialog.glade.h:3 +#: ui/login.glade:107 ui/password-dialog.glade:108 msgid "Password" msgstr "Password" -#: ../ui/login.glade.h:3 +#: ui/login.glade:123 msgid "E_mail address" msgstr "Indirizzo e_mail" -#: ../ui/login.glade.h:4 +#: ui/login.glade:144 ui/login.glade:635 msgid "_Password" msgstr "Pass_word" -#: ../ui/login.glade.h:5 +#: ui/login.glade:178 msgid "S_ervice" msgstr "S_ervizio" -#: ../ui/login.glade.h:6 +#: ui/login.glade:199 msgid "N_ame" msgstr "N_ome" -#: ../ui/login.glade.h:8 +#: ui/login.glade:256 msgid "N_ickname" msgstr "S_oprannome" -#: ../ui/login.glade.h:9 +#: ui/login.glade:280 msgid "Work, Home, etc." msgstr "Lavoro, casa, ecc." -#: ../ui/login.glade.h:10 +#: ui/login.glade:291 msgid "_Save sent mail" msgstr "_Salvare la posta inviata" -#: ../ui/login.glade.h:11 +#: ui/login.glade:309 msgid "Addi_tional email addresses…" msgstr "Indirizzi email aggiun_tivi…" -#: ../ui/login.glade.h:12 +#: ui/login.glade:353 msgid "IMAP settings" msgstr "Impostazioni IMAP" -#: ../ui/login.glade.h:13 +#: ui/login.glade:372 msgid "Se_rver" msgstr "Se_rver" -#: ../ui/login.glade.h:14 +#: ui/login.glade:393 msgid "imap.example.com" msgstr "imap.esempio.com" -#: ../ui/login.glade.h:15 +#: ui/login.glade:409 msgid "P_ort" msgstr "P_orta" -#: ../ui/login.glade.h:16 +#: ui/login.glade:448 msgid "smtp.example.com" msgstr "smtp.esempio.com" -#: ../ui/login.glade.h:17 +#: ui/login.glade:480 msgid "Ser_ver" msgstr "Ser_ver" -#: ../ui/login.glade.h:18 +#: ui/login.glade:501 msgid "Por_t" msgstr "Por_ta" -#: ../ui/login.glade.h:19 +#: ui/login.glade:522 msgid "SMTP settings" msgstr "Impostazioni SMTP" -#: ../ui/login.glade.h:20 +#: ui/login.glade:541 msgid "User_name" msgstr "_Nome utente" -#: ../ui/login.glade.h:21 +#: ui/login.glade:562 msgid "Pass_word" msgstr "Pass_word" -#: ../ui/login.glade.h:22 +#: ui/login.glade:582 msgid "SMTP username" msgstr "Nome utente SMTP" -#: ../ui/login.glade.h:23 +#: ui/login.glade:598 msgid "SMTP password" msgstr "Password SMTP" -#: ../ui/login.glade.h:24 +#: ui/login.glade:614 msgid "_Username" msgstr "Nome _utente" -#: ../ui/login.glade.h:25 +#: ui/login.glade:655 msgid "IMAP username" msgstr "Nome utente IMAP" -#: ../ui/login.glade.h:26 +#: ui/login.glade:671 msgid "IMAP password" msgstr "Password IMAP" -#: ../ui/login.glade.h:27 +#: ui/login.glade:688 msgid "Encr_yption" msgstr "Ci_fratura" -#: ../ui/login.glade.h:28 +#: ui/login.glade:711 msgid "Encrypt_ion" msgstr "Cifra_tura" -#: ../ui/login.glade.h:30 +#: ui/login.glade:733 ui/login.glade:751 msgid "SSL/TLS" msgstr "SSL/TLS" -#: ../ui/login.glade.h:31 +#: ui/login.glade:734 ui/login.glade:752 msgid "STARTTLS" msgstr "STARTTLS" -#: ../ui/login.glade.h:32 +#: ui/login.glade:764 msgid "No authentication re_quired" msgstr "Nessuna autenticazione necessaria" -#: ../ui/login.glade.h:33 +#: ui/login.glade:781 msgid "Use IMAP cre_dentials" msgstr "Usare le cre_denziali IMAP" -#: ../ui/login.glade.h:34 +#: ui/login.glade:888 msgid "Composer" msgstr "Compositore" -#: ../ui/login.glade.h:35 +#: ui/login.glade:901 msgid "Save dra_fts on server" msgstr "Salvare le boz_ze sul server" -#: ../ui/login.glade.h:36 +#: ui/login.glade:918 msgid "Si_gn emails (HTML allowed):" msgstr "Fi_rmare le email (HTML permesso):" -#: ../ui/login.glade.h:37 +#: ui/login.glade:976 msgid "Storage" msgstr "Archivio" -#: ../ui/login.glade.h:38 +#: ui/login.glade:998 msgid "_Download mail" msgstr "_Scarica posta" -#: ../ui/main-toolbar.ui.h:1 +#: ui/main-toolbar.ui:51 +msgid "Toggle search bar" +msgstr "Attiva/Disattiva barra di ricerca" + +#: ui/main-toolbar.ui:72 msgid "Empty Spam or Trash folders" msgstr "Svuota la cartella indesiderata o il cestino" -#: ../ui/password-dialog.glade.h:1 +#: ui/main-toolbar.ui:112 +#| msgid "_Reply" +msgid "Reply" +msgstr "Rispondi" + +#: ui/main-toolbar.ui:135 +#| msgid "R_eply All" +msgid "Reply All" +msgstr "Rispondi a tutti" + +#: ui/main-toolbar.ui:158 +#| msgctxt "shortcut window" +#| msgid "Forward" +msgid "Forward" +msgstr "Inoltra" + +#: ui/main-toolbar.ui:264 +msgid "Toggle find bar" +msgstr "Attiva/Disattiva barra di ricerca nella conversazione" + +#: ui/main-toolbar.ui:306 +msgid "_Archive" +msgstr "_Archivia" + +#: ui/main-toolbar-menus.ui:5 +msgid "Empty _Spam…" +msgstr "Svuota _indesiderata…" + +#: ui/main-toolbar-menus.ui:9 +msgid "Empty _Trash…" +msgstr "Svuota _cestino…" + +#: ui/main-toolbar-menus.ui:32 +msgid "Mark as S_pam" +msgstr "Segna come i_ndesiderata" + +#: ui/main-toolbar-menus.ui:36 +msgid "Mark as not S_pam" +msgstr "Segna come non i_ndesiderata" + +#: ui/main-window-info-bar.ui:97 +msgid "" +"If the problem is serious or persists, please copy and send these details to " +"the mailing list " +"or file a new " +"bug report." +msgstr "" +"Se il problema è serio o persiste, copiare e inviare questi dettagli alla mailing list o aprire " +"una nuova " +"segnalazione bug." + +#: ui/main-window-info-bar.ui:113 +msgid "Details:" +msgstr "Dettagli:" + +#: ui/password-dialog.glade:74 msgid "SMTP Credentials" msgstr "Credenziali SMTP" -#: ../ui/password-dialog.glade.h:2 +#: ui/password-dialog.glade:91 msgid "Username" msgstr "Nome utente" -#: ../ui/password-dialog.glade.h:4 +#: ui/password-dialog.glade:152 msgid "_Remember password" msgstr "_Ricordare le password" -#: ../ui/password-dialog.glade.h:6 +#: ui/password-dialog.glade:210 msgid "_Authenticate" msgstr "_Autentica" -#: ../ui/preferences-dialog.ui.h:1 +#: ui/preferences-dialog.ui:38 msgid "Reading" msgstr "Lettura" -#: ../ui/preferences-dialog.ui.h:2 +#: ui/preferences-dialog.ui:51 msgid "_Automatically select next message" msgstr "_Selezionare automaticamente il messaggio successivo" -#: ../ui/preferences-dialog.ui.h:3 +#: ui/preferences-dialog.ui:70 msgid "_Display conversation preview" msgstr "_Visualizzare l'anteprima della conversazione" -#: ../ui/preferences-dialog.ui.h:4 +#: ui/preferences-dialog.ui:89 msgid "Use _three pane view" msgstr "Usare la visuale a _tre riquadri" -#: ../ui/preferences-dialog.ui.h:5 +#: ui/preferences-dialog.ui:113 msgid "Notifications" msgstr "Notifiche" -#: ../ui/preferences-dialog.ui.h:6 +#: ui/preferences-dialog.ui:126 msgid "_Play notification sounds" msgstr "Ri_produrre avvisi sonori" -#: ../ui/preferences-dialog.ui.h:7 +#: ui/preferences-dialog.ui:145 msgid "Show _notifications for new mail" msgstr "Mostrare _notifiche per la nuova posta" -#: ../ui/preferences-dialog.ui.h:8 -msgid "Always _watch for new mail" -msgstr "_Controllare sempre per la nuova posta" +#: ui/preferences-dialog.ui:164 +#| msgid "Always _watch for new mail" +msgid "_Watch for new mail when closed" +msgstr "_Controllare la nuova posta dopo la chiusura" -#: ../ui/preferences-dialog.ui.h:9 -msgid "Geary will run in the background and notify of new mail" +#: ui/preferences-dialog.ui:168 +msgid "Geary will keep running after all windows are closed" msgstr "" -"L'applicazione sarà eseguita in background e notificherà l'arrivo di nuova " -"posta" +"L'applicazione resterà in esecuzione anche dopo che tutte le finestre sono " +"state chiuse" -#: ../ui/preferences-dialog.ui.h:10 +#: ui/preferences-dialog.ui:195 msgid "Preferences" msgstr "Preferenze" -#: ../ui/remove_confirm.glade.h:1 +#: ui/remove_confirm.glade:43 msgid "" "Are you sure you want to remove this " "account? " msgstr "" "Rimuovere questo account? " -#: ../ui/remove_confirm.glade.h:2 +#: ui/remove_confirm.glade:58 msgid "" "All email associated with this account will be removed from your computer. " "This will not affect email on the server." @@ -2663,18 +3104,63 @@ "Tutta le email associate a questo account saranno rimosse dal computer. " "Questo non avrà effetti sulle email presenti sul server." -#: ../ui/remove_confirm.glade.h:3 +#: ui/remove_confirm.glade:80 msgid "Nickname:" msgstr "Soprannome:" -#: ../ui/remove_confirm.glade.h:4 +#: ui/remove_confirm.glade:94 msgid "Email address:" msgstr "Indirizzo email:" -#: ../ui/upgrade_dialog.glade.h:1 +#: ui/upgrade_dialog.glade:60 msgid "Geary update in progress…" msgstr "Aggiornamento dell'applicazione in corso…" +#~ msgid "Geary Email" +#~ msgstr "Geary Email" + +#~ msgid "Geary Mail" +#~ msgstr "Geary Mail" + +#~ msgid "Send files using Geary" +#~ msgstr "Invia file usando Geary" + +#~ msgid "_Mark as…" +#~ msgstr "_Segna come…" + +#~ msgid "Add label" +#~ msgstr "Aggiunge etichetta" + +#~ msgid "_Label" +#~ msgstr "_Etichetta" + +#~ msgid "_Move" +#~ msgstr "_Sposta" + +#~ msgid "Compose new message (Ctrl+N, N)" +#~ msgstr "Compone un nuovo messaggio (Ctrl+N, N)" + +#~ msgid "Reply (Ctrl+R, R)" +#~ msgstr "Risponde (Ctrl+R, R)" + +#~ msgid "Reply all (Ctrl+Shift+R, Shift+R)" +#~ msgstr "Risponde a tutti (Ctrl+Maiusc+R, Maiusc+R)" + +#~ msgid "Forward (Ctrl+L, F)" +#~ msgstr "Inoltra (Ctrl+L, F)" + +#~ msgid "" +#~ "Geary encountered an error while connecting to the server. Please try " +#~ "again in a few moments." +#~ msgstr "" +#~ "L'applicazione ha riscontrato un errore durante la connessione col " +#~ "server. Riprovare in un secondo momento." + +#~ msgid "Geary will run in the background and notify of new mail" +#~ msgstr "" +#~ "L'applicazione sarà eseguita in background e notificherà l'arrivo di " +#~ "nuova posta" + #~ msgid "Mail Client" #~ msgstr "Client di posta" @@ -2786,9 +3272,6 @@ #~ msgid "Fixed Width" #~ msgstr "Larghezza fissa" -#~ msgid "Detach" -#~ msgstr "Sgancia" - #~ msgid "_Attach File" #~ msgstr "_Allega file" diff -Nru geary-0.12.4/po/ja.po geary-3.32.0/po/ja.po --- geary-0.12.4/po/ja.po 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/po/ja.po 2019-03-17 13:39:29.000000000 +0000 @@ -4,1506 +4,3683 @@ # This file is distributed under the GNU LGPL, version 2.1. # # Translators: -# masami chikahiro , 2013 -# pikatenor , 2013 +# masami chikahiro , 2013. +# pikatenor , 2013. +# UTUMI Hirosi , 2018. +# sicklylife , 2019. msgid "" msgstr "" "Project-Id-Version: geary-0.4.1\n" -"Report-Msgid-Bugs-To: http://redmine.yorba.org/projects/geary\n" -"POT-Creation-Date: 2013-09-20 12:16-0700\n" -"PO-Revision-Date: 2013-09-20 19:28+0000\n" -"Last-Translator: yorbajim \n" -"Language-Team: Japanese (Japan) (http://www.transifex.com/projects/p/geary/" -"language/ja_JP/)\n" +"Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/geary/issues\n" +"POT-Creation-Date: 2019-03-07 03:56+0000\n" +"PO-Revision-Date: 2019-03-07 19:05+0900\n" +"Last-Translator: sicklylife \n" +"Language-Team: Japanese (Japan) (https://l10n.gnome.org/module/geary/)\n" "Language: ja\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -#: ../../src/client/accounts/add-edit-page.vala:641 -msgid " • Connection error.\n" -msgstr " • 接続エラー\n" +#: desktop/geary-attach.contract.desktop.in:3 +msgid "Send by email" +msgstr "メールで送信" + +#. Translators: Do NOT translate or transliterate this text (this is an icon file name)! +#: desktop/geary-attach.contract.desktop.in:5 +msgid "mail-send" +msgstr "mail-send" + +#: desktop/geary-attach.contract.desktop.in:6 +msgid "Send files using Geary" +msgstr "Geary でファイルを送信" + +#. Translators: The application name +#: desktop/geary-autostart.desktop.in:3 +#: desktop/org.gnome.Geary.appdata.xml.in:11 +#: desktop/org.gnome.Geary.desktop.in:3 +#: src/client/accounts/accounts-editor-servers-pane.vala:555 +msgid "Geary" +msgstr "Geary" + +#: desktop/geary-autostart.desktop.in:4 desktop/org.gnome.Geary.desktop.in:4 +msgid "Email" +msgstr "メール" + +#. Translators: The application's summary / tagline +#: desktop/geary-autostart.desktop.in:5 +#: desktop/org.gnome.Geary.appdata.xml.in:15 +#: desktop/org.gnome.Geary.desktop.in:5 +#: src/client/application/geary-application.vala:23 +msgid "Send and receive email" +msgstr "メールの送信と受信" -#: ../../src/client/accounts/add-edit-page.vala:624 -msgid " • Email address already added to Geary.\n" -msgstr "" +#. Translators: These are desktop search terms. Do not translate semicolons, end line with a semicolon. +#: desktop/geary-autostart.desktop.in:7 +msgid "Email;E-mail;Mail;" +msgstr "Email;E-mail;Mail;メール;Eメール;E-メール;" -#: ../../src/client/accounts/add-edit-page.vala:628 -msgid " • IMAP connection error.\n" -msgstr " • IMAP 接続エラー\n" +#. Translators: Do NOT translate or transliterate this text (this is an icon file name)! +#: desktop/geary-autostart.desktop.in:9 desktop/org.gnome.Geary.desktop.in:9 +msgid "org.gnome.Geary" +msgstr "org.gnome.Geary" + +#. Translators: The development team's name +#: desktop/org.gnome.Geary.appdata.xml.in:13 +msgid "Geary Development Team" +msgstr "Geary 開発チーム" -#: ../../src/client/accounts/add-edit-page.vala:631 -msgid " • IMAP username or password incorrect.\n" -msgstr " • IMAP ユーザー名もしくはパスワードが不正\n" +#: desktop/org.gnome.Geary.appdata.xml.in:17 +msgid "" +"Geary is an email application built around conversations, for the GNOME 3 " +"desktop. It allows you to read, find and send email with a straightforward, " +"modern interface." +msgstr "" +"Geary は会話に重きを置いて構築された、GNOME 3 デスクトップ用のメールアプリ" +"ケーションです。明快かつモダンなインターフェースで、メールを読んだり、探した" +"り、送ったりできます。" -#: ../../src/client/accounts/add-edit-page.vala:621 -msgid " • Invalid account nickname.\n" -msgstr " • アカウントのニックネームが不正\n" +#: desktop/org.gnome.Geary.appdata.xml.in:22 +msgid "" +"Conversations allow you to read a complete discussion without having to find " +"and click from message to message." +msgstr "" +"やり取りしたメッセージを一々探し出すことなく、抜けのないディスカッションを読" +"むことが可能です。" -#: ../../src/client/accounts/add-edit-page.vala:634 -msgid " • SMTP connection error.\n" -msgstr " • SMTP 接続エラー\n" +#: desktop/org.gnome.Geary.appdata.xml.in:26 +msgid "Geary’s features include:" +msgstr "Geary には以下の機能があります" + +#: desktop/org.gnome.Geary.appdata.xml.in:28 +msgid "Quick email account setup" +msgstr "メールアカウントを素早くセットアップ" + +#: desktop/org.gnome.Geary.appdata.xml.in:29 +msgid "Shows related messages together in conversations" +msgstr "関連したメッセージをまとめてスレッドに表示" + +#: desktop/org.gnome.Geary.appdata.xml.in:30 +msgid "Fast, full text and keyword search" +msgstr "高速な全文検索とキーワード検索" + +#: desktop/org.gnome.Geary.appdata.xml.in:31 +msgid "Full-featured HTML and plain text message composer" +msgstr "十分なテキスト/HTML メール作成機能" + +#: desktop/org.gnome.Geary.appdata.xml.in:32 +msgid "Desktop notification of new mail" +msgstr "新着メールのデスクトップ通知" + +#: desktop/org.gnome.Geary.appdata.xml.in:33 +msgid "Compatible with GMail, Yahoo! Mail, Outlook.com and other IMAP servers" +msgstr "" +"Gmail、Yahoo! Mail (Yahoo.com)、Outlook.com、その他の IMAP 対応サービスとの互" +"換" + +#. Translators: A screenshot description. +#: desktop/org.gnome.Geary.appdata.xml.in:47 +msgid "Geary displaying a conversation" +msgstr "スレッド表示中の Geary" + +#. Translators: A screenshot description. +#: desktop/org.gnome.Geary.appdata.xml.in:52 +msgid "Geary showing the rich text composer" +msgstr "HTML メール作成中の Geary" + +#. Translators: These are desktop search terms. Do not translate semicolons, end line with a semicolon. +#: desktop/org.gnome.Geary.desktop.in:7 +msgid "Mail;E-mail;IMAP;GMail;Yahoo;Hotmail;Outlook;" +msgstr "Mail;E-mail;IMAP;GMail;Yahoo;Hotmail;Outlook;メール;Eメール;E-メール;" -#: ../../src/client/accounts/add-edit-page.vala:637 -msgid " • SMTP username or password incorrect.\n" -msgstr " • SMTP ユーザー名もしくはパスワードが不正\n" +#: desktop/org.gnome.Geary.desktop.in:21 +msgid "Compose Message" +msgstr "メッセージを作成" -#: ../../src/client/accounts/add-edit-page.vala:645 -msgid " • Username or password incorrect.\n" -msgstr " • ユーザー名もしくはパスワードが不正\n" +#: desktop/org.gnome.Geary.gschema.xml:8 +msgid "Maximize window" +msgstr "ウィンドウの最大化" -#: ../../src/client/views/conversation-viewer.vala:1203 -msgid " (Invalid?)" +#: desktop/org.gnome.Geary.gschema.xml:9 +msgid "True if the application window is maximized, false otherwise." msgstr "" +"アプリケーションウィンドウが最大化されている場合は true、そうでない場合は " +"false です。" -#: ../../src/client/composer/composer-window.vala:972 -#, c-format -msgid "\"%s\" already attached for delivery." -msgstr "\"%s\" は既に添付されています" - -#: ../../src/client/composer/composer-window.vala:937 -#, c-format -msgid "\"%s\" could not be found." -msgstr "\"%s\" は見つかりませんでした" +#: desktop/org.gnome.Geary.gschema.xml:14 +msgid "Width of window" +msgstr "ウィンドウの幅" -#: ../../src/client/composer/composer-window.vala:965 -#, c-format -msgid "\"%s\" could not be opened for reading." -msgstr "\"%s\" を開くことができませんでした" +#: desktop/org.gnome.Geary.gschema.xml:15 +msgid "The last recorded width of the application window." +msgstr "最後に記録したアプリケーションウィンドウの幅です。" -#: ../../src/client/composer/composer-window.vala:944 -#, c-format -msgid "\"%s\" is a folder." -msgstr "\"%s\" はフォルダーです" +#: desktop/org.gnome.Geary.gschema.xml:20 +msgid "Height of window" +msgstr "ウィンドウの高さ" -#: ../../src/client/composer/composer-window.vala:951 -#, c-format -msgid "\"%s\" is an empty file." -msgstr "\"%s\" は空ファイルです" +#: desktop/org.gnome.Geary.gschema.xml:21 +msgid "The last recorded height of the application window." +msgstr "最後に記録したアプリケーションウィンドウの高さです。" -#. / Date format that shows the weekday (Monday, Tuesday, ...) -#. / See http://developer.gnome.org/glib/2.32/glib-GDateTime.html#g-date-time-format -#: ../../src/client/util/util-date.vala:182 -#, c-format -msgid "%A" +#: desktop/org.gnome.Geary.gschema.xml:26 +msgid "Position of folder list pane" msgstr "" -#. / Verbose datetime format for 24-hour time, i.e. November 8, 2010 16:35 -#. / See http://developer.gnome.org/glib/2.32/glib-GDateTime.html#g-date-time-format -#: ../../src/client/util/util-date.vala:91 -msgid "%B %-e, %Y %-H:%M" -msgstr "%Y年 %B %-e日, %-H:%M" +#: desktop/org.gnome.Geary.gschema.xml:27 +msgid "Position of the folder list Paned grabber." +msgstr "" -#. / Verbose datetime format for 12-hour time, i.e. November 8, 2010 8:42 am -#. / See http://developer.gnome.org/glib/2.32/glib-GDateTime.html#g-date-time-format -#: ../../src/client/util/util-date.vala:88 -msgid "%B %-e, %Y %-l:%M %P" -msgstr "%Y年 %B %-e日, %-l:%M %P" +#: desktop/org.gnome.Geary.gschema.xml:32 +msgid "Position of folder list pane when horizontal" +msgstr "" -#. / Verbose datetime format for the locale default (full month, day and time) -#. / See http://developer.gnome.org/glib/2.32/glib-GDateTime.html#g-date-time-format -#: ../../src/client/util/util-date.vala:94 -msgctxt "Default full date" -msgid "%B %-e, %Y %-l:%M %P" -msgstr "%Y年 %B %-e日, %-l:%M %P" +#: desktop/org.gnome.Geary.gschema.xml:33 +msgid "" +"Position of the folder list Paned grabber in the horizontal orientation." +msgstr "" -#. / Datetime format for 24-hour time, i.e. 16:35 -#. / See http://developer.gnome.org/glib/2.32/glib-GDateTime.html#g-date-time-format -#: ../../src/client/util/util-date.vala:71 -msgid "%H:%M" -msgstr "%H:%M" +#: desktop/org.gnome.Geary.gschema.xml:38 +msgid "Position of folder list pane when vertical" +msgstr "" -#. / Format for the datetime that a message being replied to was received -#. / See http://developer.gnome.org/glib/2.32/glib-GDateTime.html#g-date-time-format -#: ../../src/engine/rfc822/rfc822-utils.vala:165 -msgid "%a, %b %-e, %Y at %-l:%M %p" -msgstr "%Y %b %-e日 (%a), %l:%M %p" +#: desktop/org.gnome.Geary.gschema.xml:39 +msgid "Position of the folder list Paned grabber in the vertical orientation." +msgstr "" -#. / Date format for dates within the current year, i.e. Nov 8 -#. / See http://developer.gnome.org/glib/2.32/glib-GDateTime.html#g-date-time-format -#: ../../src/client/util/util-date.vala:78 -msgid "%b %-e" -msgstr "%b %-e日" +#: desktop/org.gnome.Geary.gschema.xml:44 +msgid "Orientation of the folder list pane" +msgstr "" -#: ../../src/client/folder-list/folder-list-folder-entry.vala:30 -#, c-format -msgid "%d message" -msgid_plural "%d messages" -msgstr[0] "" +#: desktop/org.gnome.Geary.gschema.xml:45 +msgid "True if the folder list Paned is in the horizontal orientation." +msgstr "" -#: ../../src/client/notification/libnotify.vala:72 -#, c-format -msgid "%d new message" -msgid_plural "%d new messages" -msgstr[0] "%d 件の新着メッセージ" +#: desktop/org.gnome.Geary.gschema.xml:50 +msgid "Position of message list pane" +msgstr "" -#: ../../src/client/folder-list/folder-list-search-branch.vala:43 -#, c-format -msgid "%d results" +#: desktop/org.gnome.Geary.gschema.xml:51 +msgid "Position of the message list Paned grabber." msgstr "" -#. / Label displaying number of unread email messages in a folder -#: ../../src/client/folder-list/folder-list-folder-entry.vala:37 -#, c-format -msgid "%d unread" -msgid_plural "%d unread" -msgstr[0] "" +#: desktop/org.gnome.Geary.gschema.xml:56 +msgid "Autoselect next message" +msgstr "次のメッセージを自動的に選択する" -#: ../../src/client/util/util-date.vala:170 -#, c-format -msgid "%dh ago" -msgstr "" +#: desktop/org.gnome.Geary.gschema.xml:57 +msgid "True if we should autoselect the next available conversation." +msgstr "次の利用可能なスレッドを自動的に選択する場合は true にしてください。" -#: ../../src/client/util/util-date.vala:167 -#, c-format -msgid "%dm ago" -msgstr "" +#: desktop/org.gnome.Geary.gschema.xml:62 +msgid "Display message previews" +msgstr "メッセージのプレビューを表示する" -#: ../../src/client/views/conversation-find-bar.vala:222 -#, c-format -msgid "%i matches" -msgstr "" +#: desktop/org.gnome.Geary.gschema.xml:63 +msgid "True if we should display a short preview of each message." +msgstr "各メッセージの短いプレビューを表示する場合は true にしてください。" -#: ../../src/client/views/conversation-find-bar.vala:224 -#, c-format -msgid "%i matches (wrapped)" -msgstr "" +#: desktop/org.gnome.Geary.gschema.xml:68 +msgid "Languages that shall be used in the spell checker" +msgstr "スペルチェックする言語" -#. / Datetime format for 12-hour time, i.e. 8:31 am -#. / See http://developer.gnome.org/glib/2.32/glib-GDateTime.html#g-date-time-format -#: ../../src/client/util/util-date.vala:68 -msgid "%l:%M %P" -msgstr "%l:%M %P" +#: desktop/org.gnome.Geary.gschema.xml:69 +msgid "List of the languages to use in the spell checker." +msgstr "スペルをチェックする言語の一覧です。" -#. / Datetime format for the locale default, i.e. 8:31 am or 16:35, -#. / See http://developer.gnome.org/glib/2.32/glib-GDateTime.html#g-date-time-format -#: ../../src/client/util/util-date.vala:74 -msgctxt "Default clock format" -msgid "%l:%M %P" -msgstr "%l:%M %P" +#: desktop/org.gnome.Geary.gschema.xml:74 +msgid "Languages that are displayed in the spell checker popover" +msgstr "スペルチェッカーのポップオーバーメニューに表示する言語" -#: ../../src/client/notification/libnotify.vala:107 -#, c-format +#: desktop/org.gnome.Geary.gschema.xml:75 msgid "" -"%s\n" -"(%d other new message for %s)" -msgid_plural "" -"%s\n" -"(%d other new messages for %s)" -msgstr[0] "" +"List of languages that are always displayed in the popover of the spell " +"checker." +msgstr "スペルチェッカーのポップオーバーメニューに常に表示する言語の一覧です。" -#. / In the composer, the filename followed by its filesize, i.e. "notes.txt (1.12KB)" -#: ../../src/client/composer/composer-window.vala:981 -#, c-format -msgid "%s (%s)" -msgstr "" +#: desktop/org.gnome.Geary.gschema.xml:80 +msgid "Enable notification sounds" +msgstr "通知音を有効にする" -#: ../../src/client/views/conversation-web-view.vala:289 -#, c-format -msgid "%s - Conversation Inspector" -msgstr "%s - スレッド検証" +#: desktop/org.gnome.Geary.gschema.xml:81 +msgid "True to play sounds for notifications and sending." +msgstr "true にすると通知時と送信時に音を鳴らします。" -#: ../../src/client/notification/libmessagingmenu.vala:75 -#, c-format -msgid "%s - New Messages" -msgstr "%s - 新着メッセージ" +#: desktop/org.gnome.Geary.gschema.xml:86 +msgid "Show notifications for new mail" +msgstr "新着メールの通知を表示する" -#. / The quoted header for a message being replied to (in case the date is not known). -#. / %s will be replaced by the original sender. -#: ../../src/engine/rfc822/rfc822-utils.vala:178 -#, c-format -msgid "%s wrote:" -msgstr "%s wrote:" +#: desktop/org.gnome.Geary.gschema.xml:87 +msgid "True to show notification bubbles." +msgstr "true にするとポップアップ通知を表示します。" -#: ../../src/client/notification/libnotify.vala:75 -#, c-format -msgid "%s, %d new message total" -msgid_plural "%s, %d new messages total" -msgstr[0] "" +#: desktop/org.gnome.Geary.gschema.xml:92 +msgid "Notify of new mail at startup" +msgstr "起動時に新着メールを通知する" -#. / This string represents the divider between two messages: "n messages" and "n unread", -#. / shown in the folder list as a tooltip. Please use your languages conventions for -#. / combining the two, i.e. a comma (",") for English; "6 messages, 3 unread" -#: ../../src/client/folder-list/folder-list-folder-entry.vala:43 -#, c-format -msgid "%s, %s" +#: desktop/org.gnome.Geary.gschema.xml:93 +msgid "True to notify of new mail at startup." +msgstr "true にすると起動時に新着メールを通知します。" + +#: desktop/org.gnome.Geary.gschema.xml:98 +msgid "Ask when opening an attachment" +msgstr "添付ファイルを開くときに確認する" + +#: desktop/org.gnome.Geary.gschema.xml:99 +msgid "True to ask when opening an attachment." +msgstr "true にすると添付ファイルを開くときに確認します。" + +#: desktop/org.gnome.Geary.gschema.xml:104 +msgid "Whether to compose emails in HTML" +msgstr "HTML メールを作成するかどうか" + +#: desktop/org.gnome.Geary.gschema.xml:105 +msgid "True to compose emails in HTML; false for plain text." +msgstr "true にすると HTML メール、false にするとテキストメールを作成します。" + +#: desktop/org.gnome.Geary.gschema.xml:110 +msgid "Advisory strategy for full-text searching" msgstr "" -#: ../../src/client/views/conversation-viewer.vala:246 -#, c-format -msgid "%u conversations selected." -msgstr "%u 件が選択されています" +#: desktop/org.gnome.Geary.gschema.xml:111 +msgid "" +"Acceptable values are “exact”, “conservative”, “aggressive”, and “horizon”." +msgstr "許容できる値は“exact”、“conservative”、“aggressive”、“horizon”です。" -#: ../../src/client/views/conversation-viewer.vala:743 -#, c-format -msgid "%u read messages" -msgstr "%u 件の既読メッセージ" +#: desktop/org.gnome.Geary.gschema.xml:116 +msgid "Zoom of conversation viewer" +msgstr "スレッドビューアーのズーム" + +#: desktop/org.gnome.Geary.gschema.xml:117 +msgid "The zoom to apply on the conservation view." +msgstr "指定した倍率でスレッドビューを拡大または縮小します。" + +#: desktop/org.gnome.Geary.gschema.xml:122 +msgid "Size of detached composer window" +msgstr "分離した作成ウィンドウのサイズ" + +#: desktop/org.gnome.Geary.gschema.xml:123 +msgid "The last recorded size of the detached composer window." +msgstr "最後に記録した、分離した作成ウィンドウのサイズです。" + +#: desktop/org.gnome.Geary.gschema.xml:128 +msgid "Base URL to look up contact avatars" +msgstr "アバター画像検索用のベース URL" -#. / Date format for dates within a different year, i.e. 02/04/10 -#. / See http://developer.gnome.org/glib/2.32/glib-GDateTime.html#g-date-time-format -#: ../../src/client/util/util-date.vala:83 -#, no-c-format -msgid "%x" -msgstr "%x" +#: desktop/org.gnome.Geary.gschema.xml:129 +msgid "" +"A Gravatar or Libravatar compatible URL, set to the empty string to disable." +msgstr "" +"Gravatar または Libravatar 対応の URLです。無効にする場合は空にしてください。" -#: ../../src/client/util/util-email.vala:30 -msgid "(no subject)" -msgstr "(件名なし)" +#: desktop/org.gnome.Geary.gschema.xml:134 +msgid "Whether we migrated the old settings" +msgstr "" -#: ../../src/engine/rfc822/rfc822-utils.vala:211 -msgid "---------- Forwarded message ----------" -msgstr "---------- 転送されたメッセージ ----------" +#: desktop/org.gnome.Geary.gschema.xml:135 +msgid "" +"False to check for the old “org.yorba.geary”-schema and copy its values." +msgstr "" -#: ../../src/client/accounts/add-edit-page.vala:211 -msgid "1 month back" -msgstr "1ヶ月前" +#. Translators: In-app notification label, when +#. the app had a problem pinning an otherwise +#. untrusted TLS certificate +#: src/client/accounts/accounts-editor.vala:203 +msgid "Failed to store certificate" +msgstr "証明書の保存に失敗しました" + +#. Translators: Label for adding an email account +#. account for a generic IMAP service provider. +#: src/client/accounts/accounts-editor-add-pane.vala:109 +msgid "All others" +msgstr "" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:196 +#: src/client/accounts/accounts-editor-servers-pane.vala:316 +msgid "Check your receiving login and password" +msgstr "受信のログイン名とパスワードを確認してください" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:211 +#: src/client/accounts/accounts-editor-servers-pane.vala:329 +msgid "Check your receiving server details" +msgstr "受信サーバーの詳細を確認してください" + +#. Translators: In-app notification label +#. There was an SMTP auth error, but IMAP already +#. succeeded, so the user probably needs to +#. specify custom creds here +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:233 +#: src/client/accounts/accounts-editor-servers-pane.vala:350 +msgid "Check your sending login and password" +msgstr "送信のログイン名とパスワードを確認してください" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:247 +#: src/client/accounts/accounts-editor-servers-pane.vala:363 +msgid "Check your sending server details" +msgstr "送信サーバーの詳細を確認してください" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:262 +msgid "Check your email address and password" +msgstr "メールアドレスとパスワードを確認してください" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:273 +msgid "Could not connect, check your network" +msgstr "接続できなかったため、ネットワークを確認してください" + +#. Translators: In-app notification label for a +#. generic error creating an account +#: src/client/accounts/accounts-editor-add-pane.vala:286 +msgid "An unexpected problem occurred" +msgstr "予想外の問題が発生しました" + +#. Translators: In-app notification label, the +#. string substitution is a more detailed reason. +#: src/client/accounts/accounts-editor-add-pane.vala:304 +#, c-format +msgid "Account not created: %s" +msgstr "アカウントを作成しません: %s" + +#. Translators: Label for the person's actual name when adding +#. an account +#: src/client/accounts/accounts-editor-add-pane.vala:551 +msgid "Your name" +msgstr "名前" + +#. Translators: Label used for the address part of an +#. email address when editing a user's sender address +#. preferences for an account. +#: src/client/accounts/accounts-editor-add-pane.vala:568 +#: src/client/accounts/accounts-editor-edit-pane.vala:501 +msgid "Email address" +msgstr "メールアドレス" + +#. Translators: Placeholder for the default sender address +#. when adding an account +#. Translators: This is used as a placeholder for the +#. address part of an email address when editing a user's +#. sender address preferences for an account. +#: src/client/accounts/accounts-editor-add-pane.vala:571 +#: src/client/accounts/accounts-editor-edit-pane.vala:469 +msgid "person@example.com" +msgstr "person@example.com" + +#. Translators: Label for an IMAP/SMTP service login/user name +#. when adding an account +#. Translators: Label for the user's login name for an +#. IMAP, SMTP, etc service +#: src/client/accounts/accounts-editor-add-pane.vala:585 +#: src/client/accounts/accounts-editor-servers-pane.vala:880 +msgid "Login name" +msgstr "ログイン名" + +#. Translators: Label for the user's password for an IMAP, +#. SMTP, etc service +#: src/client/accounts/accounts-editor-add-pane.vala:599 +#: src/client/accounts/accounts-editor-servers-pane.vala:999 +#: ui/password-dialog.glade:108 +msgid "Password" +msgstr "パスワード" -#: ../../src/client/accounts/add-edit-page.vala:214 -msgid "1 year back" -msgstr "1年前" +#. Translators: Label for the IMAP server hostname when +#. adding an account. +#. Translators: This label describes the host name or IP +#. address and port used by an account's IMAP service. +#: src/client/accounts/accounts-editor-add-pane.vala:621 +#: src/client/accounts/accounts-editor-servers-pane.vala:727 +msgid "IMAP server" +msgstr "IMAP サーバー" + +#. Translators: Placeholder for the IMAP server hostname +#. when adding an account. +#: src/client/accounts/accounts-editor-add-pane.vala:624 +msgid "imap.example.com" +msgstr "imap.example.com" + +#. Translators: Label for the SMTP server hostname when +#. adding an account. +#. Translators: This label describes the host name or IP +#. address and port used by an account's SMTP service. +#: src/client/accounts/accounts-editor-add-pane.vala:630 +#: src/client/accounts/accounts-editor-servers-pane.vala:733 +msgid "SMTP server" +msgstr "SMTP サーバー" + +#. Translators: Placeholder for the SMTP server hostname +#. when adding an account. +#: src/client/accounts/accounts-editor-add-pane.vala:633 +msgid "smtp.example.com" +msgstr "smtp.example.com" + +#. Translators: Label in the account editor for the user's +#. custom name for an account. +#: src/client/accounts/accounts-editor-edit-pane.vala:278 +#: ui/accounts_editor_remove_pane.ui:123 +msgid "Account name" +msgstr "アカウント名" + +#. Translators: Tooltip used to undo changing +#. the name of an account. The string +#. substitution is the old name of the +#. account. +#: src/client/accounts/accounts-editor-edit-pane.vala:312 +#, c-format +msgid "Change account name back to “%s”" +msgstr "変更したアカウント名を“%s”に戻す" + +#. Translators: Tooltip for adding a new email sender/from +#. address's address to an account +#: src/client/accounts/accounts-editor-edit-pane.vala:336 +msgid "Add a new sender email address" +msgstr "新規送信者メールアドレスを追加" + +#. Translators: Label used to indicate the user has +#. provided no display name for one of their sender +#. email addresses in their account settings. +#: src/client/accounts/accounts-editor-edit-pane.vala:417 +msgid "Name not set" +msgstr "名前を設定していません" + +#. Translators: This is used as a placeholder for the +#. display name for an email address when editing a user's +#. sender address preferences for an account. +#: src/client/accounts/accounts-editor-edit-pane.vala:456 +msgid "Sender Name" +msgstr "送信者名" + +#: src/client/accounts/accounts-editor-edit-pane.vala:479 +msgid "Remove" +msgstr "削除" + +#. Translators: Label used for the display name part of an +#. email address when editing a user's sender address +#. preferences for an account. +#: src/client/accounts/accounts-editor-edit-pane.vala:494 +msgid "Sender name" +msgstr "送信者名" + +#. Translators: Label used as the undo tooltip after adding an +#. new sender email address to an account. The string +#. substitution is the email address added. +#: src/client/accounts/accounts-editor-edit-pane.vala:561 +#, c-format +msgid "Remove “%s”" +msgstr "“%s”を削除" + +#. Translators: Label used as the undo tooltip after editing a +#. sender address for an account. The string substitution is +#. the email address edited. +#: src/client/accounts/accounts-editor-edit-pane.vala:601 +#, c-format +msgid "Undo changes to “%s”" +msgstr "変更を“%s”に戻す" + +#. Translators: Label used as the undo tooltip after removing +#. a sender address from an account. The string substitution +#. is the email address edited. +#: src/client/accounts/accounts-editor-edit-pane.vala:688 +#, c-format +msgid "Add “%s” back" +msgstr "“%s”を元に戻す" + +#. Translators: Label used as the undo tooltip after removing +#. a sender address from an account. The string substitution +#. is the email address edited. +#: src/client/accounts/accounts-editor-edit-pane.vala:730 +msgid "Undo signature changes" +msgstr "署名の変更を元に戻す" + +#. Translators: This label describes the account +#. preference for the length of time (weeks, months or +#. years) that past email should be downloaded. +#: src/client/accounts/accounts-editor-edit-pane.vala:778 +msgid "Download mail" +msgstr "メールのダウンロード" + +#. Translators: Tooltip for undoing a change +#. to the length of time that past email +#. should be downloaded for an account. The +#. string substitution is the duration, +#. e.g. "1 month back". +#: src/client/accounts/accounts-editor-edit-pane.vala:810 +#, c-format +msgid "Change download period back to: %s" +msgstr "変更したダウンロード期間を次に戻す: %s" + +#: src/client/accounts/accounts-editor-edit-pane.vala:831 +msgid "Everything" +msgstr "すべて" -#: ../../src/client/accounts/add-edit-page.vala:210 +#: src/client/accounts/accounts-editor-edit-pane.vala:835 msgid "2 weeks back" msgstr "2週間前" -#: ../../src/client/accounts/add-edit-page.vala:212 +#: src/client/accounts/accounts-editor-edit-pane.vala:839 +msgid "1 month back" +msgstr "1ヶ月前" + +#: src/client/accounts/accounts-editor-edit-pane.vala:843 msgid "3 months back" msgstr "3ヶ月前" -#: ../../src/client/accounts/add-edit-page.vala:213 +#: src/client/accounts/accounts-editor-edit-pane.vala:847 msgid "6 months back" msgstr "6ヶ月前" -#: ../../ui/remove_confirm.glade:43 -msgid "" -"Are you sure you want to remove this " -"account? " -msgstr "" -"このアカウントを削除してもよろしいです" -"か? " +#: src/client/accounts/accounts-editor-edit-pane.vala:851 +msgid "1 year back" +msgstr "1年前" -#: ../../ui/account_cannot_remove.glade:40 -msgid "Cannot remove account " -msgstr "" -"アカウントを削除できません" +#: src/client/accounts/accounts-editor-edit-pane.vala:855 +msgid "2 years back" +msgstr "2年前" + +#: src/client/accounts/accounts-editor-edit-pane.vala:859 +msgid "4 years back" +msgstr "4年前" + +#: src/client/accounts/accounts-editor-edit-pane.vala:865 +#, c-format +msgid "%d day back" +msgid_plural "%d days back" +msgstr[0] "%d日前" + +#: src/client/accounts/accounts-editor-list-pane.vala:243 +msgid "Undo" +msgstr "元に戻す" + +#: src/client/accounts/accounts-editor-list-pane.vala:251 +msgid "Redo" +msgstr "やり直す" + +#: src/client/accounts/accounts-editor-list-pane.vala:345 +#: src/client/accounts/accounts-editor-list-pane.vala:433 +#: src/client/accounts/accounts-editor-row.vala:278 +msgid "Gmail" +msgstr "Gmail" -#: ../../ui/account_cannot_remove.glade:56 -msgid "" -"A composer window associated with this account is currently open. Send or " -"discard the message and try again." -msgstr "" -"アカウントと関連づけられた作成画面が既に開いています. メッセージを送信するか" -"破棄してからもう一度試してください" +#: src/client/accounts/accounts-editor-list-pane.vala:349 +#: src/client/accounts/accounts-editor-list-pane.vala:437 +#: src/client/accounts/accounts-editor-row.vala:282 +msgid "Outlook.com" +msgstr "Outlook.com" -#: ../../src/client/geary-controller.vala:1491 -#, c-format -msgid "A file named \"%s\" already exists. Do you want to replace it?" -msgstr "\"%s\" は既に存在します. 上書きしますか?" +#: src/client/accounts/accounts-editor-list-pane.vala:353 +#: src/client/accounts/accounts-editor-list-pane.vala:441 +#: src/client/accounts/accounts-editor-row.vala:286 +msgid "Yahoo" +msgstr "Yahoo" + +#. Translators: Tooltip for accounts that have been +#. loaded but disabled by the user. +#: src/client/accounts/accounts-editor-list-pane.vala:371 +msgid "This account has been disabled" +msgstr "このアカウントは無効になっています" + +#. Translators: Tooltip for accounts that have been +#. loaded but because of some error are not able to be +#. used. +#: src/client/accounts/accounts-editor-list-pane.vala:380 +msgid "This account has encountered a problem and is unavailable" +msgstr "このアカウントは問題が発生したため利用できません" + +#. Translators: Label for adding a generic email account +#: src/client/accounts/accounts-editor-list-pane.vala:430 +msgid "Other email providers" +msgstr "他のメールプロバイダー" + +#. Translators: Notification shown after removing an +#. account. The string substitution is the name of the +#. account. +#: src/client/accounts/accounts-editor-list-pane.vala:547 +#, c-format +msgid "Account “%s” removed" +msgstr "アカウント“%s”を削除しました" + +#. Translators: Notification shown after removing an account +#. is undone. The string substitution is the name of the +#. account. +#: src/client/accounts/accounts-editor-list-pane.vala:554 +#, c-format +msgid "Account “%s” restored" +msgstr "アカウント“%s”を復元しました" + +#. Translators: Tooltip for dragging list items +#: src/client/accounts/accounts-editor-row.vala:49 +msgid "Drag to move this item" +msgstr "このアイテムを移動するにはドラッグしてください" + +#. Translators: Label describes the service provider +#. hosting the email account, e.g. Gmail, Yahoo, or some +#. other generic IMAP service. +#: src/client/accounts/accounts-editor-row.vala:294 +msgid "Service provider" +msgstr "サービスプロバイダー" + +#. Translators: This label describes what form of transport +#. security (TLS, StartTLS, etc) used by an account's IMAP or SMTP +#. service. +#: src/client/accounts/accounts-editor-row.vala:467 +msgid "Connection security" +msgstr "接続のセキュリティ" + +#. Translators: Label used when no auth scheme is used +#. by an account's IMAP or SMTP service. +#: src/client/accounts/accounts-editor-row.vala:478 +#: src/client/accounts/accounts-editor-servers-pane.vala:752 +#: src/client/accounts/accounts-editor-servers-pane.vala:964 +#: src/engine/api/geary-special-folder-type.vala:58 +msgid "None" +msgstr "なし" -#: ../../src/client/geary-controller.vala:245 -msgid "A_ccounts" -msgstr "アカウント(_C)" +#: src/client/accounts/accounts-editor-row.vala:485 +msgid "StartTLS" +msgstr "StartTLS" + +#: src/client/accounts/accounts-editor-row.vala:492 +msgid "TLS" +msgstr "TLS" + +#. Translators: Label for source of SMTP authentication +#. credentials (none, use IMAP, custom) when adding a new +#. account +#. Button label for retrying when a login error has occurred +#: src/client/accounts/accounts-editor-row.vala:533 ui/main-window.ui:455 +msgid "Login" +msgstr "ログイン" + +#. Translators: ComboBox value for source of SMTP +#. authentication credentials (none) when adding a new +#. account +#: src/client/accounts/accounts-editor-row.vala:540 +msgid "No login needed" +msgstr "ログイン不要" + +#. Translators: ComboBox value for source of SMTP +#. authentication credentials (use IMAP) when adding a new +#. account +#: src/client/accounts/accounts-editor-row.vala:548 +msgid "Use same login as receiving" +msgstr "受信と同じ情報を使う" + +#. Translators: ComboBox value for source of SMTP +#. authentication credentials (custom) when adding a new +#. account +#: src/client/accounts/accounts-editor-row.vala:556 +msgid "Use a different login" +msgstr "他の情報を使う" + +#. Translators: In-app notification label, the +#. string substitution is a more detailed reason. +#: src/client/accounts/accounts-editor-servers-pane.vala:377 +#, c-format +msgid "Account not updated: %s" +msgstr "アカウントを更新しません: %s" + +#. Translators: This label describes the program that +#. created the account, e.g. an SSO service like GOA, or +#. locally by Geary. +#: src/client/accounts/accounts-editor-servers-pane.vala:540 +msgid "Account source" +msgstr "アカウントのソース" + +#: src/client/accounts/accounts-editor-servers-pane.vala:552 +msgid "GNOME Online Accounts" +msgstr "GNOME オンラインアカウント" + +#. Translators: This label describes an account +#. preference. +#: src/client/accounts/accounts-editor-servers-pane.vala:611 +msgid "Save drafts on server" +msgstr "下書きをサーバーに保存" + +#. Translators: This label describes an account +#. preference. +#: src/client/accounts/accounts-editor-servers-pane.vala:666 +msgid "Save sent email on server" +msgstr "送信メールをサーバーに保存" + +#. Add a suffix for OAuth2 auth so people know they +#. shouldn't expect to be prompted for a password +#. Translators: Label used when an account's IMAP or +#. SMTP service uses OAuth2. The string replacement is +#. the service's login name. +#: src/client/accounts/accounts-editor-servers-pane.vala:950 +#, c-format +msgid "%s using OAuth2" +msgstr "%s は OAuth2 を使用しています" + +#: src/client/accounts/accounts-editor-servers-pane.vala:960 +msgid "Use receiving server login" +msgstr "受信サーバーの情報を使う" + +#: src/client/application/geary-application.vala:24 +msgid "Copyright 2016 Software Freedom Conservancy Inc." +msgstr "Copyright 2016 Software Freedom Conservancy Inc." + +#: src/client/application/geary-application.vala:25 +msgid "Copyright 2016-2019 Geary Development Team." +msgstr "Copyright 2016-2019 Geary Development Team." + +#: src/client/application/geary-application.vala:27 +msgid "Visit the Geary web site" +msgstr "Geary のウェブサイト" -#: ../../src/client/geary-controller.vala:1227 +#: src/client/application/geary-application.vala:454 #, c-format msgid "About %s" msgstr "%s について" -#: ../../src/client/accounts/account-dialog.vala:21 -msgid "Accounts" -msgstr "アカウント" - -#: ../../ui/account_list.glade:71 -msgid "Add account" +#. Translators: add your name and email address to receive +#. credit in the About dialog For example: Yamada Taro +#. +#: src/client/application/geary-application.vala:458 +msgid "translator-credits" msgstr "" +"masami chikahiro \n" +"Minato Kanzaki \n" +"pikatenor \n" +"sicklylife \n" +"UTUMI Hirosi " + +#: src/client/application/geary-args.vala:10 +msgid "Start Geary with hidden main window" +msgstr "メインウィンドウを隠して Geary を起動する" -#: ../../src/client/geary-controller.vala:304 -msgid "Add label" -msgstr "" +#: src/client/application/geary-args.vala:11 +msgid "Output debugging information" +msgstr "デバッグ情報を出力する" -#: ../../src/client/geary-controller.vala:57 -msgid "Add label to conversation" -msgstr "スレッドにラベルを付ける" +#: src/client/application/geary-args.vala:12 +msgid "Log conversation monitoring" +msgstr "スレッドのモニタリングを記録する" -#: ../../src/client/geary-controller.vala:58 -msgid "Add label to conversations" -msgstr "スレッドにラベルを付ける" +#: src/client/application/geary-args.vala:13 +msgid "Log network deserialization" +msgstr "ネットワークのデシリアライズを記録する" -#: ../../src/engine/api/geary-special-folder-type.vala:39 -msgid "All Mail" -msgstr "すべてのメール" +#: src/client/application/geary-args.vala:14 +msgid "Log network activity" +msgstr "ネットワークのアクティビティを記録する" -#: ../../ui/remove_confirm.glade:58 -msgid "" -"All email associated with this account will be removed from your computer. " -"This will not affect email on the server." -msgstr "" -"このアカウントに関連づけされた全てのメールは、コンピューターから削除されます." -"これは、サーバー上のメールには影響を与えません." +#. / The IMAP replay queue is how changes on the server are replicated on the client. +#. / It could also be called the IMAP events queue. +#: src/client/application/geary-args.vala:17 +msgid "Log IMAP replay queue" +msgstr "IMAP のイベントキューを記録する" -#: ../../src/client/geary-args.vala:24 -msgid "Allow inspection of WebView" -msgstr "WebView での検証を有効にする" +#. / Serialization is how commands and responses are converted into a stream of bytes for +#. / network transmission +#: src/client/application/geary-args.vala:20 +msgid "Log network serialization" +msgstr "ネットワークのストリームを記録する" -#: ../../src/client/views/conversation-viewer.vala:509 -msgid "Always Show From Sender" -msgstr "" +#: src/client/application/geary-args.vala:21 +msgid "Log periodic activity" +msgstr "定期的なアクティビティを記録する" -#: ../../src/engine/api/geary-special-folder-type.vala:54 -msgid "Archive" -msgstr "" +#: src/client/application/geary-args.vala:22 +msgid "Log database queries (generates lots of messages)" +msgstr "データベースのクエリを記録する (多くのメッセージを生成します)" + +#. / "Normalization" can also be called "synchronization" +#: src/client/application/geary-args.vala:24 +msgid "Log folder normalization" +msgstr "フォルダーの同期を記録する" + +#: src/client/application/geary-args.vala:25 +msgid "Allow inspection of WebView" +msgstr "WebView での検証を有効にする" -#: ../../src/client/geary-controller.vala:48 -msgid "Archive conversation (Delete, Backspace, A)" -msgstr "スレッドをアーカイブ (Delete, Backspace, A)" +#: src/client/application/geary-args.vala:26 +msgid "Revoke all server certificates with TLS warnings" +msgstr "TLS 警告のあるサーバー証明書をすべて失効させる" + +#: src/client/application/geary-args.vala:27 +msgid "Perform a graceful quit" +msgstr "正常に終了する" -#: ../../src/client/geary-controller.vala:49 -msgid "Archive conversations (Delete, Backspace, A)" -msgstr "スレッドをアーカイブ (Delete, Backspace, A)" +#: src/client/application/geary-args.vala:28 +msgid "Display program version" +msgstr "プログラムのバージョンを表示する" -#: ../../src/client/geary-controller.vala:1477 +#. This gives a command-line hint on how to open new composer windows with mailto: +#: src/client/application/geary-args.vala:53 #, c-format -msgid "Are you sure you want to open \"%s\"?" -msgstr "\"%s\" を開きますか?" +msgid "Use %s to open a new composer window" +msgstr "%s を使って新規作成ウィンドウを開くことができます" -#: ../../src/client/geary-controller.vala:1478 -msgid "" -"Attachments may cause damage to your system if opened. Only open files from " -"trusted sources." -msgstr "" -"添付ファイルはシステムにダメージを与える恐れがあります. 信頼できる送信元から" -"のみ開くようにしてください." +#: src/client/application/geary-args.vala:56 +msgid "Please report comments, suggestions and bugs to:" +msgstr "コメント、提案、バグ報告はこちら:" -#: ../../src/client/views/conversation-viewer.vala:614 -msgid "Bcc:" -msgstr "Bcc:" +#. i18n: Command line arguments are invalid +#: src/client/application/geary-args.vala:63 +#, c-format +msgid "Failed to parse command line options: %s\n" +msgstr "不明なコマンドラインオプション: %s\n" -#: ../../ui/composer.glade:113 -msgid "Bold (Ctrl+B)" -msgstr "太字(Ctrl+B)" +#: src/client/application/geary-args.vala:74 +#, c-format +msgid "Unrecognized command line option “%s”\n" +msgstr "不明なコマンドラインオプション“%s”\n" -#: ../../ui/composer.glade:69 -msgid "C_olor" -msgstr "色(_O)" +#. Translators: File name used in save chooser when saving +#. attachments that do not otherwise have a name. +#: src/client/application/geary-controller.vala:58 +msgid "Untitled" +msgstr "無題" -#: ../../src/client/composer/composer-window.vala:926 -msgid "Cannot add attachment" -msgstr "添付できません" +#: src/client/application/geary-controller.vala:937 +msgid "Labels" +msgstr "ラベル" -#: ../../src/client/views/conversation-viewer.vala:611 -msgid "Cc:" -msgstr "Cc:" +#. give the user two options: reset the Account local store, or exit Geary. A third +#. could be done to leave the Account in an unopened state, but we don't currently +#. have provisions for that. +#: src/client/application/geary-controller.vala:950 +#, c-format +msgid "Unable to open the database for %s" +msgstr "%s のデータベースを開けません" -#: ../../src/engine/rfc822/rfc822-utils.vala:223 +#: src/client/application/geary-controller.vala:951 #, c-format -msgid "Cc: %s\n" +msgid "" +"There was an error opening the local mail database for this account. This is " +"possibly due to corruption of the database file in this directory:\n" +"\n" +"%s\n" +"\n" +"Geary can rebuild the database and re-synchronize with the server or exit.\n" +"\n" +"Rebuilding the database will destroy all local email and its attachments. " +"The mail on the your server will not be affected." msgstr "" +"このアカウントのローカルメールデータベースを開くときにエラーが発生しました。" +"このディレクトリのデータベースファイルはおそらく破損しています:\n" +"\n" +"%s\n" +"\n" +"Geary はデータベースを再構築し、サーバーと再同期するか終了することができま" +"す。\n" +"\n" +"データベースを再構築すると、すべてのローカルメールと添付ファイルが破棄されま" +"す。サーバー上のメールは影響を受けません。" -#: ../../src/client/dialogs/attachment-dialog.vala:18 -msgid "Choose a file" -msgstr "ファイルを選択" +#: src/client/application/geary-controller.vala:953 +msgid "_Rebuild" +msgstr "再構築(_R)" -#: ../../src/client/geary-controller.vala:553 -msgid "Co_ntinue" -msgstr "継続(_N)" +#: src/client/application/geary-controller.vala:953 +msgid "E_xit" +msgstr "終了(_X)" -#: ../../src/client/geary-application.vala:29 -msgid "Compose Message" -msgstr "メッセージを作成" +#: src/client/application/geary-controller.vala:962 +#, c-format +msgid "Unable to rebuild database for “%s”" +msgstr "“%s”のデータベースを再構築できません" -#: ../../src/client/geary-controller.vala:313 -msgid "Compose new message (Ctrl+N, N)" +#: src/client/application/geary-controller.vala:963 +#, c-format +msgid "" +"Error during rebuild:\n" +"\n" +"%s" msgstr "" +"再構築エラー:\n" +"\n" +"%s" -#: ../../ui/preferences.glade:117 -msgid "Composer" -msgstr "作成画面" +#: src/client/application/geary-controller.vala:1818 +msgid "Undo move (Ctrl+Z)" +msgstr "移動を元に戻す(Ctrl+Z)" + +#: src/client/application/geary-controller.vala:1828 +msgid "Are you sure you want to open these attachments?" +msgstr "これらの添付ファイルを開いてもよろしいですか?" -#: ../../src/client/views/conversation-viewer.vala:897 -msgid "Copy _Email Address" -msgstr "アドレスをコピー(_E)" +#: src/client/application/geary-controller.vala:1829 +msgid "" +"Attachments may cause damage to your system if opened. Only open files from " +"trusted sources." +msgstr "" +"添付ファイルはシステムにダメージを与える可能性があります。送信元が信頼できる" +"場合のみ開くようにしてください。" -#: ../../src/client/views/conversation-viewer.vala:902 -msgid "Copy _Link" -msgstr "リンクのアドレスをコピー(_L)" +#: src/client/application/geary-controller.vala:1830 +msgid "Don’t _ask me again" +msgstr "次回から表示しない(_A)" -#: ../../src/client/geary-application.vala:17 -msgid "Copyright 2011-2013 Yorba Foundation" -msgstr "Copyright 2011-2013 Yorba Foundation" +#. Translators: Dialog primary label when prompting to +#. overwrite a file. The string substitution is the file'sx +#. name. +#: src/client/application/geary-controller.vala:1959 +#, c-format +msgid "A file named “%s” already exists. Do you want to replace it?" +msgstr "“%s”はすでに存在します。置き換えますか?" + +#. Translators: Dialog secondary label when prompting to +#. overwrite a file. The string substitution is the parent +#. folder's name. +#: src/client/application/geary-controller.vala:1966 +#, c-format +msgid "" +"The file already exists in “%s”. Replacing it will overwrite its contents." +msgstr "ファイルはすでに“%s”に存在します。置き換えると内容を上書きします。" -#: ../../ui/composer.glade:21 -msgid "Cu_t" -msgstr "" +#: src/client/application/geary-controller.vala:1970 +msgid "_Replace" +msgstr "置換(_R)" -#: ../../src/client/views/conversation-viewer.vala:620 -msgid "Date:" -msgstr "日時:" +#: src/client/application/geary-controller.vala:2246 +msgid "Close the draft message?" +msgid_plural "Close all draft messages?" +msgstr[0] "下書きメッセージを閉じますか?" -#: ../../src/engine/rfc822/rfc822-utils.vala:217 +#: src/client/application/geary-controller.vala:2372 #, c-format -msgid "Date: %s\n" -msgstr "日時: %s\n" +msgid "Empty all email from your %s folder?" +msgstr "%s フォルダーのすべてのメールを空にしますか?" -#: ../../src/client/geary-controller.vala:43 -msgid "Delete conversation (Delete, Backspace, A)" -msgstr "スレッドを削除 (Delete, Backspace, A)" +#: src/client/application/geary-controller.vala:2373 +msgid "This removes the email from Geary and your email server." +msgstr "メールを Geary とメールサーバーから削除します。" -#: ../../src/client/geary-controller.vala:44 -msgid "Delete conversations (Delete, Backspace, A)" -msgstr "スレッドを削除 (Delete, Backspace, A)" +#: src/client/application/geary-controller.vala:2374 +msgid "This cannot be undone." +msgstr "これは元に戻すことができません。" -#: ../../src/client/geary-args.vala:25 -msgid "Display program version" -msgstr "バージョンを表示" +#: src/client/application/geary-controller.vala:2375 +#, c-format +msgid "Empty %s" +msgstr "%s を空にする" -#: ../../src/client/composer/composer-window.vala:663 -msgid "Do you want to discard the unsaved message?" -msgstr "未保存のメッセージを破棄しますか?" +#: src/client/application/geary-controller.vala:2392 +#, c-format +msgid "Error emptying %s" +msgstr "%s を空にするときにエラーが発生しました" -#: ../../src/client/composer/composer-window.vala:666 -msgid "Do you want to discard this message?" -msgstr "" +#: src/client/application/geary-controller.vala:2424 +msgid "Do you want to permanently delete this message?" +msgid_plural "Do you want to permanently delete these messages?" +msgstr[0] "このメッセージを完全に削除しますか?" -#: ../../src/client/geary-controller.vala:1479 -msgid "Don't _ask me again" -msgstr "次回から表示しない(_A)" +#: src/client/application/geary-controller.vala:2426 +msgid "Delete" +msgstr "削除" -#: ../../src/engine/api/geary-special-folder-type.vala:27 -msgid "Drafts" -msgstr "下書き" +#: src/client/application/geary-controller.vala:2440 +msgid "Undo trash (Ctrl+Z)" +msgstr "ごみ箱から戻す(Ctrl+Z)" -#: ../../ui/composer.glade:419 -msgid "Drop files here" -msgstr "ここにファイルをドロップ" +#: src/client/application/geary-controller.vala:2490 +msgid "Undo archive (Ctrl+Z)" +msgstr "アーカイブから戻す(Ctrl+Z)" -#: ../../ui/login.glade:115 -msgid "E_mail address:" -msgstr "" +#: src/client/application/geary-controller.vala:2535 +msgid "Undo (Ctrl+Z)" +msgstr "元に戻す(Ctrl+Z)" -#: ../../src/client/geary-controller.vala:704 -msgid "E_xit" +#. Translators: The label for an in-app notification. The +#. string substitution is a list of recipients of the email. +#: src/client/application/geary-controller.vala:2616 +#, c-format +msgid "Successfully sent mail to %s." +msgstr "%s にメールを送信しました。" + +#: src/client/application/geary-controller.vala:2698 +msgid "Failed to open default text editor." +msgstr "デフォルトのテキストエディターの起動に失敗しました。" + +#. Translators: Tooltip used when an entry requires a valid +#. email address to be entered, but one is not provided. +#: src/client/components/components-validator.vala:378 +msgid "An email address is required" +msgstr "メールアドレスは必須です" + +#. Translators: Tooltip used when an entry requires a valid +#. email address to be entered, but the address is invalid. +#: src/client/components/components-validator.vala:382 +msgid "Not a valid email address" +msgstr "有効なメールアドレスではありません" + +#. Translators: Tooltip used when an entry requires a valid, +#. resolvable server name to be entered, but one is not +#. provided. +#: src/client/components/components-validator.vala:428 +msgid "A server name is required" +msgstr "サーバー名は必須です" + +#. Translators: Tooltip used when an entry requires a valid +#. server name to be entered, but it was unable to be +#. looked-up in the DNS. +#: src/client/components/components-validator.vala:433 +msgid "Could not look up server name" +msgstr "サーバー名が見つかりませんでした" + +#: src/client/components/main-toolbar.vala:151 +msgid "Mark conversation" +msgid_plural "Mark conversations" +msgstr[0] "スレッドをマーク" + +#: src/client/components/main-toolbar.vala:156 +msgid "Add label to conversation" +msgid_plural "Add label to conversations" +msgstr[0] "スレッドにラベル付け" + +#: src/client/components/main-toolbar.vala:161 +msgid "Move conversation" +msgid_plural "Move conversations" +msgstr[0] "スレッドを移動" + +#: src/client/components/main-toolbar.vala:166 +msgid "Archive conversation (A)" +msgid_plural "Archive conversations (A)" +msgstr[0] "スレッドをアーカイブに移動(A)" + +#: src/client/components/main-toolbar.vala:175 +msgid "Move conversation to Trash (Delete, Backspace)" +msgid_plural "Move conversations to Trash (Delete, Backspace)" +msgstr[0] "スレッドをごみ箱に移動(Delete, Backspace)" + +#: src/client/components/main-toolbar.vala:183 +msgid "Delete conversation (Shift+Delete)" +msgid_plural "Delete conversations (Shift+Delete)" +msgstr[0] "スレッドを削除(Shift+Delete)" + +#: src/client/components/main-window.vala:503 +#, c-format +msgid "%s (%d)" +msgstr "%s (%d)" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:47 +#, c-format +msgid "Problem connecting to incoming server for %s" +msgstr "%s の受信サーバーとの接続時に問題があります" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:49 +#: src/client/components/main-window-info-bar.vala:58 +#, c-format +msgid "" +"Could not connect to %s, check your Internet access and the server name and " +"try again" msgstr "" +"%s に接続できなかったため、インターネット接続とサーバー名を確認して再試行して" +"ください" -#: ../../src/client/views/conversation-viewer.vala:533 -msgid "Edit Draft" +#. Translators: Tooltip label for Retry button +#. Button tooltip for retrying an account problem +#: src/client/components/main-window-info-bar.vala:51 +#: src/client/components/main-window-info-bar.vala:60 +#: src/client/components/main-window-info-bar.vala:69 +#: src/client/components/main-window-info-bar.vala:78 +#: src/client/components/main-window-info-bar.vala:87 +#: src/client/components/main-window-info-bar.vala:96 +#: src/client/components/main-window-info-bar.vala:136 ui/main-window.ui:265 +msgid "Try reconnecting" +msgstr "再接続を試行" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:56 +#, c-format +msgid "Problem connecting to outgoing server for %s" +msgstr "%s の送信サーバーとの接続時に問題があります" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:65 +#: src/client/components/main-window-info-bar.vala:83 +#, c-format +msgid "Problem communicating with incoming server for %s" +msgstr "%s の受信サーバーとの通信に問題があります" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:67 +#: src/client/components/main-window-info-bar.vala:76 +#, c-format +msgid "Network error talking to %s, check your Internet access and try again" +msgstr "" +"%s との接続でネットワークエラーが発生したため、インターネット接続を確認して再" +"試行してください" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:74 +#: src/client/components/main-window-info-bar.vala:91 +msgid "Problem communicating with outgoing mail server" +msgstr "送信メールサーバーとの通信に問題があります" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:85 +#, c-format +msgid "" +"Geary did not understand a message from %s or vice versa, please file a bug " +"report" msgstr "" +"Geary か %s のどちらかがメッセージを理解できないバグに遭遇したため、可能でし" +"たらバグ報告してください" -#: ../../ui/account_list.glade:84 -msgid "Edit account" +#. Translators: First string substitution is the server +#. name, second is the account name +#: src/client/components/main-window-info-bar.vala:94 +#, c-format +msgid "" +"Could not communicate with %s for %s, check the server name and try again in " +"a moment" msgstr "" +"%2$s の %1$s と通信できなかったため、サーバー名を確認して後ほど再試行してくだ" +"さい" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:101 +#, c-format +msgid "Incoming mail server password required for %s" +msgstr "%s の受信メールサーバーのパスワードが必要です" -#: ../../ui/remove_confirm.glade:94 -msgid "Email address:" -msgstr "メールアドレス:" +#: src/client/components/main-window-info-bar.vala:102 +msgid "Messages cannot be received without the correct password." +msgstr "正しいパスワードがないとメッセージを受信できません。" -#: ../../src/client/geary-application.vala:28 -msgid "Email;E-mail;Mail;" +#. Translators: Tooltip label for Retry button +#: src/client/components/main-window-info-bar.vala:104 +msgid "Retry receiving email, you will be prompted for a password" +msgstr "メール受信の再試行にはパスワードが必要です" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:109 +#, c-format +msgid "Outgoing mail server password required for %s" +msgstr "%s の送信メールサーバーのパスワードが必要です" + +#: src/client/components/main-window-info-bar.vala:110 +msgid "Messages cannot be sent without the correct password." +msgstr "正しいパスワードがないとメッセージを送信できません。" + +#. Translators: Tooltip label for Retry button +#: src/client/components/main-window-info-bar.vala:112 +msgid "Retry sending queued messages, you will be prompted for a password" +msgstr "待機メッセージ送信の再試行にはパスワードが必要です" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:117 +#, c-format +msgid "Incoming mail server security is not trusted for %s" +msgstr "%s の受信メールサーバーのセキュリティを信頼していません" + +#: src/client/components/main-window-info-bar.vala:118 +msgid "Messages will not be received until checked." +msgstr "確認するまでメッセージを受信しません。" + +#. Translators: Tooltip label for Retry button +#: src/client/components/main-window-info-bar.vala:120 +#: src/client/components/main-window-info-bar.vala:128 +msgid "Check security details" +msgstr "セキュリティの詳細を確認" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:125 +#, c-format +msgid "Outgoing mail server security is not trusted for %s" +msgstr "%s の送信メールサーバーのセキュリティを信頼していません" + +#: src/client/components/main-window-info-bar.vala:126 +msgid "Messages cannot be sent until checked." +msgstr "確認するまでメッセージを送信できません。" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:133 +#, c-format +msgid "A problem occurred checking mail for %s" +msgstr "%s のメール確認時に問題が発生しました" + +#: src/client/components/main-window-info-bar.vala:134 +#: src/client/components/main-window-info-bar.vala:142 +msgid "Something went wrong, please file a bug report if the problem persists" msgstr "" +"何らかの不具合が発生しているため、問題が解決しない場合はバグ報告してください" -#: ../../ui/preferences.glade:131 -msgid "Enable _spell checking" -msgstr "スペルチェックを有効(_S)" - -#: ../../ui/login.glade:588 -msgid "Encr_yption:" -msgstr "暗号化(_Y):" - -#: ../../ui/login.glade:607 -msgid "Encrypt_ion:" -msgstr "暗号化(_I):" - -#: ../../src/client/accounts/add-edit-page.vala:190 -msgid "Enter your account information to get started." -msgstr "アカウント情報を入力してください" +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:141 +#, c-format +msgid "A problem occurred sending mail for %s" +msgstr "%s のメール送信時に問題が発生しました" -#: ../../src/client/geary-controller.vala:714 +#. Translators: Tooltip label for Retry button +#: src/client/components/main-window-info-bar.vala:144 +msgid "Retry sending queued messages" +msgstr "待機メッセージの送信を再試行" + +#: src/client/components/main-window-info-bar.vala:155 +msgid "A database problem has occurred" +msgstr "データベースに問題が発生しました" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:157 #, c-format +msgid "Messages for %s must be downloaded again." +msgstr "%s のメッセージを再ダウンロードする必要があります。" + +#: src/client/components/main-window-info-bar.vala:170 +msgid "Geary has encountered a problem" +msgstr "Geary に問題が発生しました" + +#: src/client/components/main-window-info-bar.vala:171 msgid "" -"Error during rebuild:\n" -"\n" -"%s" -msgstr "" +"Please check the technical details and report the problem if it persists." +msgstr "問題が解決しない場合は、技術的な詳細情報を確認して報告してください。" -#: ../../src/client/composer/composer-window.vala:43 -msgid "Error saving" +#: src/client/components/main-window-info-bar.vala:179 +msgid "_Details" +msgstr "詳細(_D)" + +#. Button tooltip for displaying technical details about an account problem +#: src/client/components/main-window-info-bar.vala:180 ui/main-window.ui:250 +msgid "View technical details about the error" +msgstr "エラーについての技術的な詳細情報を表示" + +#: src/client/components/main-window-info-bar.vala:184 +msgid "_Retry" +msgstr "再試行(_R)" + +#: src/client/components/search-bar.vala:8 +#: src/client/folder-list/folder-list-search-branch.vala:38 +#: src/engine/api/geary-special-folder-type.vala:51 +msgid "Search" +msgstr "検索" + +#. Search entry. +#: src/client/components/search-bar.vala:23 +msgid "Search all mail in account for keywords (Ctrl+S)" +msgstr "アカウント内のすべてのメールをキーワードで検索(Ctrl+S)" + +#: src/client/components/search-bar.vala:101 +#, c-format +msgid "Indexing %s account" msgstr "" +#: src/client/components/search-bar.vala:112 +#: src/client/folder-list/folder-list-search-branch.vala:39 +#, c-format +msgid "Search %s account" +msgstr "アカウント %s を検索" + +#. / Displayed in the space-limited status bar while a message is in the process of being sent. +#: src/client/components/status-bar.vala:26 +msgid "Sending…" +msgstr "送信中…" + #. / Displayed in the space-limited status bar when a message fails to be sent due to error. -#: ../../src/client/geary-controller.vala:625 -#: ../../src/client/ui/status-bar.vala:28 +#: src/client/components/status-bar.vala:29 msgid "Error sending email" +msgstr "メール送信時にエラーが発生しました" + +#. Displayed in the space-limited status bar when a message fails to be uploaded +#. to Sent Mail after being sent. +#: src/client/components/status-bar.vala:33 +msgid "Error saving sent mail" +msgstr "送信メール保存時にエラーが発生しました" + +#: src/client/components/stock.vala:18 +msgid "_OK" +msgstr "OK(_O)" + +#: src/client/components/stock.vala:19 ui/password-dialog.glade:196 +msgid "_Cancel" +msgstr "キャンセル(_C)" + +#: src/client/components/stock.vala:21 +msgid "_About" +msgstr "このアプリケーションについて(_A)" + +#: src/client/components/stock.vala:22 +msgid "_Add" +msgstr "追加(_A)" + +#: src/client/components/stock.vala:23 +msgid "_Close" +msgstr "閉じる(_C)" + +#: src/client/components/stock.vala:24 +msgid "_Discard" +msgstr "破棄(_D)" + +#: src/client/components/stock.vala:25 ui/main-toolbar-menus.ui:56 +msgid "_Help" +msgstr "ヘルプ(_H)" + +#: src/client/components/stock.vala:26 ui/conversation-email-menus.ui:77 +msgid "_Open" +msgstr "開く(_O)" + +#: src/client/components/stock.vala:27 ui/main-toolbar-menus.ui:46 +msgid "_Preferences" +msgstr "設定(_P)" + +#. Translators: Menu item to print a single, specific message +#: src/client/components/stock.vala:28 ui/conversation-email-menus.ui:64 +msgid "_Print…" +msgstr "印刷(_P)…" + +#: src/client/components/stock.vala:29 +msgid "_Quit" +msgstr "終了(_Q)" + +#: src/client/components/stock.vala:30 +msgid "_Remove" +msgstr "削除(_R)" + +#: src/client/components/stock.vala:31 ui/conversation-email-menus.ui:83 +msgid "_Save" +msgstr "保存(_S)" + +#: src/client/components/stock.vala:32 +msgid "_Keep" +msgstr "保持(_K)" + +#: src/client/composer/composer-link-popover.vala:149 +msgid "Link URL is not correctly formatted, e.g. http://example.com" +msgstr "リンク URL の書式が間違っています (例: http://example.com)" + +#: src/client/composer/composer-link-popover.vala:156 +msgid "Invalid link URL" +msgstr "無効なリンク URL" + +#: src/client/composer/composer-link-popover.vala:156 +msgid "Invalid email address" +msgstr "無効なメールアドレス" + +#: src/client/composer/composer-widget.vala:158 +msgid "Saved" +msgstr "保存済み" + +#: src/client/composer/composer-widget.vala:159 +msgid "Saving" +msgstr "保存中" + +#: src/client/composer/composer-widget.vala:160 +msgid "Error saving" +msgstr "保存エラー" + +#: src/client/composer/composer-widget.vala:161 +msgid "Press Backspace to delete quote" +msgstr "Backspace を押すと引用を削除できます" + +#. Translators: This is list of keywords, separated by pipe ("|") +#. characters, that suggest an attachment; since this is full-word +#. checking, include all variants of each word. No spaces are +#. allowed. +#: src/client/composer/composer-widget.vala:170 +msgid "" +"attach|attaching|attaches|attachment|attachments|attached|enclose|enclosed|" +"enclosing|encloses|enclosure|enclosures" msgstr "" -#: ../../src/client/accounts/add-edit-page.vala:216 -msgid "Everything" -msgstr "すべて" +#. Translators: This dialog text is displayed to the +#. user when closing a composer where the options are +#. Keep, Discard or Cancel. +#: src/client/composer/composer-widget.vala:1131 +msgid "Do you want to keep or discard this draft message?" +msgstr "この下書きメッセージを保持または破棄しますか?" + +#. Translators: This dialog text is displayed to the +#. user when closing a composer where the options are +#. only Discard or Cancel. +#: src/client/composer/composer-widget.vala:1159 +msgid "Do you want to discard this draft message?" +msgstr "この下書きメッセージを破棄しますか?" -#: ../../src/client/views/conversation-viewer.vala:1897 -msgid "Failed to open default text editor." -msgstr "テキストエディターの起動に失敗しました" +#: src/client/composer/composer-widget.vala:1276 +msgid "Send message with an empty subject and body?" +msgstr "メッセージを件名と本文なしで送信しますか?" + +#: src/client/composer/composer-widget.vala:1278 +msgid "Send message with an empty subject?" +msgstr "メッセージを件名なしで送信しますか?" + +#: src/client/composer/composer-widget.vala:1280 +msgid "Send message with an empty body?" +msgstr "メッセージを本文なしで送信しますか?" + +#: src/client/composer/composer-widget.vala:1284 +msgid "Send message without an attachment?" +msgstr "メッセージを添付ファイルなしで送信しますか?" -#: ../../src/client/geary-args.vala:54 +#: src/client/composer/composer-widget.vala:1589 #, c-format -msgid "Failed to parse command line options: %s\n" -msgstr "不明なコマンドラインオプション: %s\n" +msgid "“%s” already attached for delivery." +msgstr "“%s”はすでに添付されています。" -#. / Placeholder text indicating that the user should type their first name and last name -#: ../../src/client/accounts/add-edit-page.vala:10 -msgid "First Last" -msgstr "" +#. / In the composer, the filename followed by its filesize, i.e. "notes.txt (1.12KB)" +#. Translators: The first argument will be a +#. description of the document type, the second will +#. be a human-friendly size string. For example: +#. Document (100.9MB) +#: src/client/composer/composer-widget.vala:1597 +#: src/client/conversation-viewer/conversation-email.vala:173 +#, c-format +msgid "%s (%s)" +msgstr "%s (%s)" -#: ../../ui/composer.glade:182 -msgid "Fixed Width" -msgstr "" +#: src/client/composer/composer-widget.vala:1634 +#, c-format +msgid "“%s” could not be found." +msgstr "“%s”が見つかりませんでした。" -#: ../../src/client/geary-controller.vala:329 -msgid "Forward (Ctrl+L, F)" -msgstr "" +#: src/client/composer/composer-widget.vala:1640 +#, c-format +msgid "“%s” is a folder." +msgstr "“%s”はフォルダーです。" + +#: src/client/composer/composer-widget.vala:1646 +#, c-format +msgid "“%s” is an empty file." +msgstr "“%s”は空ファイルです。" + +#: src/client/composer/composer-widget.vala:1659 +#, c-format +msgid "“%s” could not be opened for reading." +msgstr "“%s”を読み取り用に開けませんでした。" + +#: src/client/composer/composer-widget.vala:1667 +msgid "Cannot add attachment" +msgstr "添付できません" + +#. Translators: Human-readable version of the RFC 822 To header +#: src/client/composer/composer-widget.vala:1717 +#: src/client/conversation-viewer/conversation-email.vala:975 +#: src/engine/rfc822/rfc822-utils.vala:298 ui/conversation-message.ui:313 +msgid "To:" +msgstr "宛先:" + +#. Translators: Human-readable version of the RFC 822 CC header +#: src/client/composer/composer-widget.vala:1723 +#: src/client/conversation-viewer/conversation-email.vala:980 +#: src/engine/rfc822/rfc822-utils.vala:303 ui/conversation-message.ui:358 +msgid "Cc:" +msgstr "Cc:" + +#. Translators: Human-readable version of the RFC 822 BCC header +#: src/client/composer/composer-widget.vala:1729 +#: src/client/conversation-viewer/conversation-email.vala:985 +#: ui/conversation-message.ui:403 +msgid "Bcc:" +msgstr "Bcc:" + +#. Translators: Human-readable version of the RFC 822 Reply-To header +#: src/client/composer/composer-widget.vala:1735 +msgid "Reply-To: " +msgstr "Reply-To: " + +#: src/client/composer/composer-widget.vala:1875 +msgid "Select Color" +msgstr "色を選択" -#: ../../src/client/views/conversation-viewer.vala:605 +#. Displayed in the From dropdown to indicate an +#. "alternate email address" for an account. The first +#. printf argument will be the alternate email address, +#. and the second will be the account's primary email +#. address. +#: src/client/composer/composer-widget.vala:2065 +#, c-format +msgid "%1$s via %2$s" +msgstr "%1$s (%2$s を通して使用)" + +#. Composer label (with mnemonic underscore) for the account selector +#. when choosing what address to send a message from. +#: src/client/composer/composer-widget.vala:2126 +msgid "_From:" +msgstr "差出人(_F):" + +#. Translators: This is the name of the file chooser filter +#. when inserting an image in the composer. +#: src/client/composer/composer-widget.vala:2352 +msgid "Images" +msgstr "画像" + +#: src/client/composer/composer-window.vala:14 +msgid "New Message" +msgstr "新規メッセージ" + +#: src/client/composer/spell-check-popover.vala:117 +msgid "Remove this language from the preferred list" +msgstr "この言語を優先リストから削除" + +#: src/client/composer/spell-check-popover.vala:121 +msgid "Add this language to the preferred list" +msgstr "この言語を優先リストに追加" + +#: src/client/composer/spell-check-popover.vala:217 +msgid "Search for more languages" +msgstr "言語を検索" + +#: src/client/conversation-list/conversation-list-view.vala:283 +msgid "Delete conversation" +msgstr "スレッドを削除" + +#: src/client/conversation-list/conversation-list-view.vala:286 +#: ui/main-toolbar-menus.ui:5 +msgid "Mark as _Read" +msgstr "既読としてマーク(_R)" + +#: src/client/conversation-list/conversation-list-view.vala:289 +#: ui/main-toolbar-menus.ui:9 +msgid "Mark as _Unread" +msgstr "未読としてマーク(_U)" + +#: src/client/conversation-list/conversation-list-view.vala:292 +#: ui/main-toolbar-menus.ui:17 +msgid "U_nstar" +msgstr "星を外す(_N)" + +#: src/client/conversation-list/conversation-list-view.vala:294 +#: ui/main-toolbar-menus.ui:13 +msgid "_Star" +msgstr "星を付ける(_S)" + +#. Translators: Menu item to reply to a specific message. +#: src/client/conversation-list/conversation-list-view.vala:297 +#: ui/conversation-email-menus.ui:9 +msgid "_Reply" +msgstr "返信(_R)" + +#: src/client/conversation-list/conversation-list-view.vala:298 +msgid "R_eply All" +msgstr "全員に返信(_E)" + +#. Translators: Menu item to forward a specific message. +#: src/client/conversation-list/conversation-list-view.vala:299 +#: ui/conversation-email-menus.ui:21 +msgid "_Forward" +msgstr "転送(_F)" + +#: src/client/conversation-list/formatted-conversation-data.vala:11 +msgid "Me" +msgstr "自分" + +#. Translators: This is the file type displayed for +#. attachments with unknown file types. +#: src/client/conversation-viewer/conversation-email.vala:159 +msgid "Unknown" +msgstr "不明" + +#. Translators: Human-readable version of the RFC 822 From header +#: src/client/conversation-viewer/conversation-email.vala:970 +#: src/engine/rfc822/rfc822-utils.vala:289 msgid "From:" msgstr "差出人:" -#: ../../src/engine/rfc822/rfc822-utils.vala:215 -#, c-format -msgid "From: %s\n" -msgstr "差出人: %s\n" +#. Translators: Human-readable version of the RFC 822 Date header +#: src/client/conversation-viewer/conversation-email.vala:990 +#: src/engine/rfc822/rfc822-utils.vala:294 +msgid "Date:" +msgstr "日時:" -#: ../../src/client/util/util-files.vala:22 -msgctxt "Abbreviation for gigabyte" -msgid "GB" -msgstr "GB" +#. Translators: Human-readable version of the RFC 822 Subject header +#: src/client/conversation-viewer/conversation-email.vala:995 +#: src/engine/rfc822/rfc822-utils.vala:292 +msgid "Subject:" +msgstr "件名:" + +#: src/client/conversation-viewer/conversation-message.vala:65 +msgid "This email address may have been forged" +msgstr "これは偽装メールアドレスである可能性があります" + +#. Compact headers +#. Translators: This is displayed in place of the from address +#. when the message has no from address. +#: src/client/conversation-viewer/conversation-message.vala:394 +msgid "No sender" +msgstr "送信者なし" + +#. Translators: This separates multiple 'from' +#. addresses in the compact header for a message. +#: src/client/conversation-viewer/conversation-message.vala:767 +msgid ", " +msgstr ", " + +#. Translators: This string is used as the HTML IMG ALT +#. attribute value when displaying an inline image in an email +#. that did not specify a file name. E.g. Image" +msgstr "差出人 " + +#: ui/conversation-message.ui:80 ui/conversation-message.ui:179 +msgid "1/1/1970\t" +msgstr "" -#: ../../src/client/geary-controller.vala:60 -msgid "Move conversations" -msgstr "スレッドを移動" +#: ui/conversation-message.ui:103 +msgid "Preview body text." +msgstr "本文をプレビューします。" -#: ../../ui/login.glade:176 -msgid "N_ame:" -msgstr "名前(_A):" +#: ui/conversation-message.ui:203 +msgid "Sent by:" +msgstr "送信者:" -#: ../../ui/login.glade:230 -msgid "N_ickname:" -msgstr "ニックネーム(_I):" +#: ui/conversation-message.ui:248 +msgid "Reply to:" +msgstr "返信:" -#: ../../src/client/composer/composer-window.vala:40 -msgid "New Message" -msgstr "新規メッセージ" +#: ui/conversation-message.ui:292 +msgid "Subject" +msgstr "件名" -#: ../../ui/remove_confirm.glade:80 -msgid "Nickname:" -msgstr "ニックネーム:" +#: ui/conversation-message.ui:502 +msgid "Show Images" +msgstr "画像を表示" -#: ../../ui/login.glade:661 -msgid "No authentication re_quired" -msgstr "認証は必要とされていません(_Q)" +#: ui/conversation-message.ui:515 +msgid "Always Show From Sender" +msgstr "この送信者のものは常に表示" -#: ../../src/client/views/conversation-viewer.vala:275 -msgid "No conversations in folder." -msgstr "" +#: ui/conversation-message.ui:543 +msgid "Remote images not shown" +msgstr "リモート画像は表示しません" -#: ../../src/client/views/conversation-viewer.vala:244 -msgid "No conversations selected." -msgstr "メッセージが選択されていません" +#: ui/conversation-message.ui:560 +msgid "Only show remote images from senders you trust." +msgstr "信頼できる送信者のリモート画像のみ表示するようにしてください。" -#: ../../src/client/views/conversation-viewer.vala:273 -msgid "No search results found." -msgstr "" +#: ui/conversation-message.ui:693 +msgid "But actually goes to:" +msgstr "実際の行き先:" -#: ../../src/client/dialogs/password-dialog.vala:128 -#: ../../src/engine/api/geary-special-folder-type.vala:58 -msgid "None" -msgstr "なし" +#: ui/conversation-message.ui:724 +msgid "The link appears to go to:" +msgstr "リンクの表示上の行き先:" -#: ../../ui/preferences.glade:158 -msgid "Notifications" -msgstr "通知" +#: ui/conversation-message.ui:736 +msgid "Deceptive link found" +msgstr "虚偽リンクが見つかりました" -#: ../../src/client/util/util-date.vala:164 -msgid "Now" +#: ui/conversation-message.ui:751 +msgid "The email sender may be leading you to the wrong web site." msgstr "" +"このメールの送信者は不適切なウェブサイトに誘導している可能性があります。" -#. / The quoted header for a message being replied to. -#. / %1$s will be substituted for the date, and %2$s will be substituted for -#. / the original sender. -#: ../../src/engine/rfc822/rfc822-utils.vala:171 -#, c-format -msgid "On %1$s, %2$s wrote:" -msgstr "" +#: ui/conversation-message.ui:764 +msgid "If unsure, contact the sender and ask before continuing." +msgstr "不明な場合は、続行する前に送信者に連絡して確認してください。" -#. / The quoted header for a message being replied to (in case the sender is not known). -#. / %s will be replaced by the original date -#: ../../src/engine/rfc822/rfc822-utils.vala:184 -#, c-format -msgid "On %s:" -msgstr "" +#: ui/conversation-viewer.ui:60 +msgid "Find in conversation" +msgstr "スレッド内を検索" -#: ../../src/client/notification/libnotify.vala:160 -msgid "Open" -msgstr "開く" +#: ui/conversation-viewer.ui:74 +msgid "Find the previous occurrence of the search string." +msgstr "検索文字列が前に出現した場所を見つけます。" -#: ../../src/engine/api/geary-service-provider.vala:61 -msgid "Other" -msgstr "その他" +#: ui/conversation-viewer.ui:95 +msgid "Find the next occurrence of the search string." +msgstr "検索文字列が次に出現する場所を見つけます。" -#: ../../src/engine/api/geary-special-folder-type.vala:48 -msgid "Outbox" -msgstr "送信トレイ" +#: ui/find_bar.glade:66 +msgid "Find:" +msgstr "検索:" -#: ../../src/engine/api/geary-service-provider.vala:58 -msgid "Outlook.com" -msgstr "" +#: ui/find_bar.glade:89 +msgid "_Previous" +msgstr "前へ(_P)" -#: ../../src/client/geary-args.vala:10 -msgid "Output debugging information" -msgstr "デバッグ情報を出力" +#: ui/find_bar.glade:107 +msgid "_Next" +msgstr "次へ(_N)" -#: ../../ui/login.glade:337 -msgid "P_ort:" -msgstr "ポート(_O):" +#: ui/find_bar.glade:125 +msgid "_Case sensitive" +msgstr "大文字小文字を区別(_C)" -#: ../../ui/login.glade:474 -msgid "Pass_word:" -msgstr "パスワード(_W):" +#: ui/find_bar.glade:145 +msgid "label" +msgstr "ラベル" -#: ../../ui/login.glade:101 -msgid "Password" -msgstr "" +#: ui/gtk/help-overlay.ui:9 +msgid "Conversation Shortcuts" +msgstr "スレッドのショートカット" -#: ../../ui/password-dialog.glade:196 -msgid "Password:" -msgstr "パスワード:" +#: ui/gtk/help-overlay.ui:13 ui/gtk/help-overlay.ui:254 +msgctxt "shortcut window" +msgid "General" +msgstr "全般" -#: ../../ui/composer.glade:102 -msgid "Paste _With Formatting" -msgstr "" +#: ui/gtk/help-overlay.ui:17 +msgctxt "shortcut window" +msgid "Move focus to the next/previous pane" +msgstr "フォーカスを次/前のペインに移動する" + +#: ui/gtk/help-overlay.ui:24 +msgctxt "shortcut window" +msgid "Move focus to conversation list" +msgstr "スレッドリストにフォーカスを移動する" + +#: ui/gtk/help-overlay.ui:31 +msgctxt "shortcut window" +msgid "Detach composer window" +msgstr "作成ウィンドウを分離する" + +#: ui/gtk/help-overlay.ui:38 +msgctxt "shortcut window" +msgid "Close composer window" +msgstr "作成ウィンドウを閉じる" + +#: ui/gtk/help-overlay.ui:45 +msgctxt "shortcut window" +msgid "Show keyboard shortcuts" +msgstr "キーボードショートカットを表示する" + +#: ui/gtk/help-overlay.ui:52 +msgctxt "shortcut window" +msgid "Show help" +msgstr "ヘルプを表示する" + +#: ui/gtk/help-overlay.ui:59 +msgctxt "shortcut window" +msgid "Quit the application" +msgstr "アプリケーションを終了する" -#: ../../src/client/dialogs/password-dialog.vala:16 -msgid "Please enter your email password" -msgstr "パスワードを入力してください" +#: ui/gtk/help-overlay.ui:68 +msgctxt "shortcut window" +msgid "Search" +msgstr "検索" -#: ../../src/client/geary-args.vala:47 -msgid "Please report comments, suggestions and bugs to:" -msgstr "コメント・提案、バグ報告はこちら:" +#: ui/gtk/help-overlay.ui:72 +msgctxt "shortcut window" +msgid "Jump to search box" +msgstr "検索ボックスに移動する" + +#: ui/gtk/help-overlay.ui:79 +msgctxt "shortcut window" +msgid "Find in current conversation" +msgstr "現在のスレッド内を検索する" + +#: ui/gtk/help-overlay.ui:86 +msgctxt "shortcut window" +msgid "Find next/previous in current conversation" +msgstr "現在のスレッド内で次/前を検索する" + +#: ui/gtk/help-overlay.ui:95 ui/gtk/help-overlay.ui:274 +msgctxt "shortcut window" +msgid "Actions" +msgstr "アクション" + +#: ui/gtk/help-overlay.ui:99 +msgctxt "shortcut window" +msgid "Compose a new message" +msgstr "新規メッセージを作成する" + +#: ui/gtk/help-overlay.ui:106 +msgctxt "shortcut window" +msgid "Reply to sender " +msgstr "送信者に返信する " + +#: ui/gtk/help-overlay.ui:113 +msgctxt "shortcut window" +msgid "Reply to all" +msgstr "全員に返信する" + +#: ui/gtk/help-overlay.ui:120 +msgctxt "shortcut window" +msgid "Forward" +msgstr "転送する" -#: ../../ui/account_spinner.glade:41 -msgid "Please wait while Geary validates your account." -msgstr "アカウントを検証しています..." +#: ui/gtk/help-overlay.ui:127 +msgctxt "shortcut window" +msgid "Archive" +msgstr "アーカイブに移動する" -#: ../../ui/login.glade:421 -msgid "Por_t:" -msgstr "ポート(_T):" +#: ui/gtk/help-overlay.ui:134 +msgctxt "shortcut window" +msgid "Move to trash" +msgstr "ごみ箱に移動する" + +#: ui/gtk/help-overlay.ui:141 +msgctxt "shortcut window" +msgid "Toggle spam" +msgstr "迷惑メールに指定/解除する" + +#: ui/gtk/help-overlay.ui:148 +msgctxt "shortcut window" +msgid "Move the conversation" +msgstr "スレッドを移動する" + +#: ui/gtk/help-overlay.ui:155 +msgctxt "shortcut window" +msgid "Label the conversation" +msgstr "スレッドにラベルを付ける" -#: ../../ui/password-dialog.glade:367 ../../ui/password-dialog.glade:470 -msgid "Port:" -msgstr "ポート:" +#: ui/gtk/help-overlay.ui:163 +msgctxt "shortcut window" +msgid "Mark read" +msgstr "既読としてマークする" + +#: ui/gtk/help-overlay.ui:170 +msgctxt "shortcut window" +msgid "Mark unread" +msgstr "未読としてマークする" + +#: ui/gtk/help-overlay.ui:179 +msgctxt "shortcut window" +msgid "View" +msgstr "表示" + +#: ui/gtk/help-overlay.ui:183 +msgctxt "shortcut window" +msgid "Zoom in" +msgstr "拡大する" + +#: ui/gtk/help-overlay.ui:190 +msgctxt "shortcut window" +msgid "Zoom out" +msgstr "縮小する" + +#: ui/gtk/help-overlay.ui:197 +msgctxt "shortcut window" +msgid "Reset zoom" +msgstr "拡大/縮小をリセットする" + +#: ui/gtk/help-overlay.ui:206 +msgctxt "shortcut window" +msgid "Additional Shortcuts" +msgstr "その他のショートカット" + +#: ui/gtk/help-overlay.ui:210 +msgctxt "shortcut window" +msgid "Star" +msgstr "星を付ける" + +#: ui/gtk/help-overlay.ui:217 +msgctxt "shortcut window" +msgid "Unstar" +msgstr "星を外す" + +#: ui/gtk/help-overlay.ui:224 +msgctxt "shortcut window" +msgid "Delete" +msgstr "削除する" + +#: ui/gtk/help-overlay.ui:231 +msgctxt "shortcut window" +msgid "Jump to next (older) conversation" +msgstr "古いスレッドに移動する" + +#: ui/gtk/help-overlay.ui:238 +msgctxt "shortcut window" +msgid "Jump to previous (newer) conversation" +msgstr "新しいスレッドに移動する" + +#: ui/gtk/help-overlay.ui:250 +msgid "Composer Shortcuts" +msgstr "作成画面のショートカット" + +#: ui/gtk/help-overlay.ui:258 +msgctxt "shortcut window" +msgid "Quote text" +msgstr "テキストを引用する" + +#: ui/gtk/help-overlay.ui:265 +msgctxt "shortcut window" +msgid "Unquote text" +msgstr "テキストの引用を解除する" + +#: ui/gtk/help-overlay.ui:278 +msgctxt "shortcut window" +msgid "Send" +msgstr "送信する" + +#: ui/gtk/help-overlay.ui:285 +msgctxt "shortcut window" +msgid "Add attachment" +msgstr "添付ファイルを追加する" + +#: ui/gtk/help-overlay.ui:294 +msgctxt "shortcut window" +msgid "Rich text mode" +msgstr "リッチテキストモード" + +#: ui/gtk/help-overlay.ui:298 +msgctxt "shortcut window" +msgid "Bold text" +msgstr "テキストを太字にする" + +#: ui/gtk/help-overlay.ui:305 +msgctxt "shortcut window" +msgid "Italicize text" +msgstr "テキストを斜体にする" + +#: ui/gtk/help-overlay.ui:312 +msgctxt "shortcut window" +msgid "Underline text" +msgstr "テキストに下線を引く" + +#: ui/gtk/help-overlay.ui:319 +msgctxt "shortcut window" +msgid "Strike text" +msgstr "テキストに取り消し線を引く" + +#: ui/gtk/help-overlay.ui:326 +msgctxt "shortcut window" +msgid "Insert a link" +msgstr "リンクを挿入する" + +#: ui/gtk/help-overlay.ui:333 +msgctxt "shortcut window" +msgid "Remove formatting" +msgstr "書式を削除する" -#: ../../ui/composer.glade:81 -msgid "Quote text (Ctrl+])" -msgstr "" +#: ui/main-toolbar.ui:23 +msgctxt "tooltip" +msgid "Compose Message" +msgstr "メッセージを作成" -#: ../../src/client/geary-controller.vala:322 -msgid "R_eply All" -msgstr "" +#: ui/main-toolbar.ui:52 +msgid "Toggle search bar" +msgstr "アカウント内を検索" + +#: ui/main-toolbar.ui:112 +msgid "Reply" +msgstr "返信" + +#: ui/main-toolbar.ui:135 +msgid "Reply All" +msgstr "全員に返信" + +#: ui/main-toolbar.ui:158 +msgid "Forward" +msgstr "転送" + +#: ui/main-toolbar.ui:264 +msgid "Toggle find bar" +msgstr "スレッド内を検索" -#: ../../ui/preferences.glade:55 -msgid "Reading" -msgstr "表示画面" +#: ui/main-toolbar.ui:306 +msgid "_Archive" +msgstr "アーカイブ(_A)" -#: ../../ui/password-dialog.glade:274 -msgid "Real name:" -msgstr "本名:" +#: ui/main-toolbar-menus.ui:21 +msgid "Mark as S_pam" +msgstr "迷惑メールとしてマーク(_P)" -#: ../../src/client/accounts/add-edit-page.vala:590 -msgid "Remem_ber password" -msgstr "" +#: ui/main-toolbar-menus.ui:25 +msgid "Mark as not S_pam" +msgstr "迷惑メールマークを外す(_P)" -#: ../../src/client/accounts/add-edit-page.vala:583 -msgid "Remem_ber passwords" -msgstr "" +#: ui/main-toolbar-menus.ui:32 +msgid "Empty _Spam…" +msgstr "迷惑メールを空にする(_S)…" + +#: ui/main-toolbar-menus.ui:36 +msgid "Empty _Trash…" +msgstr "ごみ箱を空にする(_T)…" + +#: ui/main-toolbar-menus.ui:42 +msgid "_Accounts" +msgstr "アカウント(_A)" + +#: ui/main-toolbar-menus.ui:50 +msgid "_Keyboard Shortcuts" +msgstr "キーボードショートカット(_K)" + +#: ui/main-toolbar-menus.ui:61 +msgid "_About Geary" +msgstr "Geary について(_A)" + +#. Infobar title when one or more accounts are offline +#: ui/main-window.ui:183 +msgid "Working offline" +msgstr "オフラインで作業中です" -#: ../../ui/account_list.glade:97 -msgid "Remove account" +#. Label and tooltip for offline infobar +#: ui/main-window.ui:197 +msgid "" +"Your computer does not appear to be connected to the Internet.\n" +"You will not be able to send or receive email until it is re-connected." msgstr "" +"コンピューターがインターネットに接続していません。\n" +"再接続するまでメールの送受信はできません。" + +#. Label and tooltip for offline infobar +#: ui/main-window.ui:200 +msgid "You will not be able to send or receive email until re-connected." +msgstr "再接続するまでメールの送受信はできません。" + +#. Button label for displaying technical details about an account problem +#. Dialog title for displaying technical details of a problem. Same as the button that invokes it. +#: ui/main-window.ui:247 ui/problem-details-dialog.ui:13 +msgid "Details" +msgstr "詳細" + +#. Button label for retrying an account problem +#: ui/main-window.ui:261 +msgid "Retry" +msgstr "再試行" + +#. Infobar title when one or more accounts have encounted an error +#: ui/main-window.ui:294 +msgid "Account problem" +msgstr "アカウントの問題" -#: ../../ui/composer.glade:95 -msgid "Remove formatting (Ctrl+Space)" -msgstr "書式をリセット(Ctrl+Space)" +#. Label and tooltip for account service problem infobar +#: ui/main-window.ui:308 +msgid "" +"Geary encountered a problem connecting to an account.\n" +"Please check your Internet connection, the server configuration and try " +"again." +msgstr "" +"アカウントへの接続時に問題が発生しました。\n" +"インターネット接続やサーバー設定を確認して再試行してください。" + +#. Label and tooltip for account service problem infobar +#: ui/main-window.ui:311 +msgid "Geary encountered a problem connecting to an account." +msgstr "アカウントへの接続時に問題が発生しました。" + +#. Button label for retrying TLS cert validation +#: ui/main-window.ui:358 +msgid "Check" +msgstr "確認" + +#. Button tooltip for retrying TLS cert validation +#: ui/main-window.ui:362 +msgid "Check the security details for the connection" +msgstr "接続のセキュリティに関する詳細を確認" + +#. Infobar title when one or more accounts have a TLS cert validation error +#: ui/main-window.ui:391 +msgid "Security problem" +msgstr "セキュリティの問題" -#: ../../src/client/geary-controller.vala:318 -msgid "Reply (Ctrl+R, R)" +#. Label and tooltip for TLS cert validation error infobar +#: ui/main-window.ui:405 +msgid "" +"An account has reported an untrusted server.\n" +"Please check the server configuration and try again." msgstr "" +"信頼できないサーバーです。\n" +"サーバー設定を確認して再試行してください。" -#: ../../src/client/geary-controller.vala:323 -msgid "Reply all (Ctrl+Shift+R, Shift+R)" +#. Label and tooltip for TLS cert validation error infobar +#: ui/main-window.ui:408 +msgid "An account has reported an untrusted server." +msgstr "信頼できないサーバーです。" + +#. Button tooltip for retrying when a login error has occurred +#: ui/main-window.ui:459 +msgid "Retry login, you will be prompted for your password" +msgstr "ログインの再試行にはパスワードが必要です" + +#. Infobar title when one or more accounts have a login error +#: ui/main-window.ui:488 +msgid "Login problem" +msgstr "ログインの問題" + +#. Label and tooltip for authentication problem infobar +#: ui/main-window.ui:502 +msgid "" +"An account has reported an incorrect login or password.\n" +"Please check your login name and try again." msgstr "" +"ログイン情報かパスワードが間違っています。\n" +"ログイン名を確認して再試行してください。" -#: ../../src/client/views/conversation-viewer.vala:1452 -msgid "Reply to _All" -msgstr "全員に返信(_A)" +#. Label and tooltip for authentication problem infobar +#: ui/main-window.ui:505 +msgid "An account has reported an incorrect login or password." +msgstr "ログイン情報かパスワードが間違っています。" -#: ../../ui/password-dialog.glade:163 +#: ui/password-dialog.glade:74 msgid "SMTP Credentials" msgstr "SMTP 証明書" -#: ../../ui/login.glade:507 -msgid "SMTP password" -msgstr "" +#: ui/password-dialog.glade:91 +msgid "Username" +msgstr "ユーザー名" + +#: ui/password-dialog.glade:152 +msgid "_Remember password" +msgstr "パスワードを記憶する(_R)" + +#: ui/password-dialog.glade:210 +msgid "_Authenticate" +msgstr "認証(_A)" -#: ../../ui/login.glade:439 ../../ui/password-dialog.glade:437 -msgid "SMTP settings" -msgstr "SMTP 設定" +#: ui/preferences-dialog.ui:38 +msgid "Reading" +msgstr "表示画面" -#: ../../ui/login.glade:491 -msgid "SMTP username" -msgstr "" +#: ui/preferences-dialog.ui:51 +msgid "_Automatically select next message" +msgstr "自動的に次のメッセージを選択する(_A)" -#: ../../src/client/dialogs/password-dialog.vala:124 -msgid "SSL" -msgstr "SSL" +#: ui/preferences-dialog.ui:70 +msgid "_Display conversation preview" +msgstr "スレッドプレビューを表示する(_D)" -#: ../../ui/password-dialog.glade:382 ../../ui/password-dialog.glade:485 -msgid "SSL/TLS encryption:" -msgstr "SSL/TLS 暗号化:" +#: ui/preferences-dialog.ui:89 +msgid "Use _three pane view" +msgstr "3ペインビューを使う(_T)" -#: ../../src/client/dialogs/password-dialog.vala:126 -msgid "STARTTLS" -msgstr "STARTTLS" +#: ui/preferences-dialog.ui:113 +msgid "Notifications" +msgstr "通知" -#: ../../ui/composer.glade:169 -msgid "S_ans Serif" -msgstr "" +#: ui/preferences-dialog.ui:126 +msgid "_Play notification sounds" +msgstr "通知音を鳴らす(_P)" -#: ../../ui/composer.glade:175 -msgid "S_erif" -msgstr "" +#: ui/preferences-dialog.ui:145 +msgid "Show _notifications for new mail" +msgstr "新着メールの通知を表示する(_N)" -#: ../../ui/login.glade:160 -msgid "S_ervice:" -msgstr "" +#: ui/preferences-dialog.ui:164 +msgid "_Watch for new mail when closed" +msgstr "閉じても新着メールを監視する(_W)" + +#: ui/preferences-dialog.ui:168 +msgid "Geary will keep running after all windows are closed" +msgstr "すべてのウィンドウを閉じても Geary の実行を続けます" + +#: ui/preferences-dialog.ui:195 +msgid "Preferences" +msgstr "設定" + +#. Button label for copying technical information to the clipboard +#: ui/problem-details-dialog.ui:17 +msgid "Copy to Clipboard" +msgstr "クリップボードにコピー" -#: ../../ui/composer.glade:170 -msgid "Sans Serif" -msgstr "ゴシック体" +#. Button tooltip for copying technical information to the clipboard +#: ui/problem-details-dialog.ui:21 +msgid "" +"Copy technical details to clipboard for pasting into an email or bug report" +msgstr "バグ報告用の技術的な詳細情報をクリップボードにコピー" -#: ../../src/client/views/conversation-viewer.vala:1437 -msgid "Save A_ttachment..." -msgid_plural "Save All A_ttachments..." -msgstr[0] "添付ファイルを保存(_T)" +#: ui/problem-details-dialog.ui:73 +msgid "" +"If the problem is serious or persists, please copy and send these details to " +"the mailing list " +"or file a new " +"bug report." +msgstr "" +"問題が深刻な場合や継続して発生している場合は、詳細をコピーしてメーリングリストに投稿する" +"か、バグとして報" +"告してください。" -#: ../../src/client/views/conversation-viewer.vala:1400 -msgid "Save All A_ttachments..." -msgstr "全ての添付ファイルを保存(_T)" +#: ui/problem-details-dialog.ui:89 +msgid "Details:" +msgstr "詳細:" -#: ../../src/client/composer/composer-window.vala:41 -msgid "Saved" -msgstr "" +#: ui/upgrade_dialog.glade:60 +msgid "Geary update in progress…" +msgstr "Geary を更新しています…" -#: ../../src/client/composer/composer-window.vala:42 -msgid "Saving" -msgstr "" +#~ msgid "A_ccounts" +#~ msgstr "アカウント(_C)" -#: ../../ui/login.glade:304 -msgid "Se_rver:" -msgstr "サーバー(_R):" - -#: ../../src/client/folder-list/folder-list-search-branch.vala:38 -#: ../../src/client/ui/main-toolbar.vala:10 -#: ../../src/engine/api/geary-special-folder-type.vala:51 -msgid "Search" -msgstr "" +#~ msgid "Empty Spam or Trash folders" +#~ msgstr "迷惑メールかごみ箱を空にする" -#: ../../src/client/folder-list/folder-list-search-branch.vala:39 -#: ../../src/client/ui/main-toolbar.vala:170 -#, c-format -msgid "Search %s account" -msgstr "" +#~ msgid "Delete conversations (Shift+Delete)" +#~ msgstr "スレッドを削除(Shift+Delete)" -#: ../../src/client/ui/main-toolbar.vala:76 -msgid "Search all mail in account for keywords (Ctrl+S)" -msgstr "" +#~ msgid "Move conversations to Trash (Delete, Backspace)" +#~ msgstr "スレッドをごみ箱に移動(Delete, Backspace)" -#: ../../src/client/composer/composer-window.vala:1243 -msgid "Select Color" -msgstr "色を選択" +#~ msgid "Archive conversations (A)" +#~ msgstr "スレッドをアーカイブに移動(A)" -#: ../../src/client/ui/stock.vala:32 -#: ../../src/client/views/conversation-viewer.vala:916 -msgid "Select _All" -msgstr "すべて選択(_A)" +#~ msgid "Mark conversations" +#~ msgstr "スレッドをマーク" -#: ../../src/client/views/conversation-viewer.vala:910 -msgid "Select _Message" -msgstr "" +#~ msgid "Add label to conversations" +#~ msgstr "スレッドにラベル付け" -#: ../../src/client/geary-application.vala:27 -msgid "Send and receive email" -msgstr "送受信" +#~ msgid "Move conversations" +#~ msgstr "スレッドを移動" -#: ../../src/client/composer/composer-window.vala:747 -msgid "Send message with an empty body?" -msgstr "" +#~ msgid "Retry connecting now" +#~ msgstr "今すぐ接続を再試行" -#: ../../src/client/composer/composer-window.vala:743 -msgid "Send message with an empty subject and body?" -msgstr "" +#~ msgid "Try reconnecting now" +#~ msgstr "今すぐ再接続を試行" -#: ../../src/client/composer/composer-window.vala:745 -msgid "Send message with an empty subject?" -msgstr "" +#~ msgid "Problem with connection to incoming server for %s" +#~ msgstr "%s の受信サーバーとの接続に問題があります" -#: ../../src/client/composer/composer-window.vala:749 -msgid "Send message without an attachment?" -msgstr "" +#~ msgid "Problem with connection to outgoing server for %s" +#~ msgstr "%s の送信サーバーとの接続に問題があります" -#. / Displayed in the space-limited status bar while a message is in the process of being sent. -#: ../../src/client/ui/status-bar.vala:25 -msgid "Sending..." -msgstr "" +#~ msgid "To: " +#~ msgstr "宛先: " -#: ../../src/engine/api/geary-special-folder-type.vala:30 -msgid "Sent Mail" -msgstr "送信済みメール" +#~ msgid "Cc: " +#~ msgstr "Cc: " -#: ../../ui/login.glade:404 -msgid "Ser_ver:" -msgstr "サーバー(_V):" - -#: ../../ui/composer.glade:176 -msgid "Serif" -msgstr "明朝体" - -#: ../../ui/password-dialog.glade:352 ../../ui/password-dialog.glade:455 -msgid "Server:" -msgstr "サーバー:" - -#: ../../ui/password-dialog.glade:259 -msgid "Service:" -msgstr "メールサービス:" +#~ msgid "Bcc: " +#~ msgstr "Bcc: " -#: ../../src/client/views/conversation-viewer.vala:508 -msgid "Show Images" -msgstr "" +#~ msgid "From: %s\n" +#~ msgstr "差出人: %s\n" -#: ../../ui/preferences.glade:193 -msgid "Show _notifications for new mail" -msgstr "新着メールに通知を表示(_N)" +#~ msgid "Subject: %s\n" +#~ msgstr "件名: %s\n" -#: ../../ui/composer.glade:164 -msgid "Small" -msgstr "小" +#~ msgid "Date: %s\n" +#~ msgstr "日時: %s\n" -#: ../../src/engine/api/geary-special-folder-type.vala:42 -msgid "Spam" -msgstr "迷惑メール" +#~ msgid "To: %s\n" +#~ msgstr "宛先: %s\n" -#: ../../src/engine/api/geary-special-folder-type.vala:33 -msgid "Starred" -msgstr "スター" +#~ msgid "Cc: %s\n" +#~ msgstr "Cc: %s\n" -#: ../../ui/login.glade:761 -msgid "Storage" -msgstr "ストレージ" +#~ msgid "Geary Email" +#~ msgstr "Geary メール" -#: ../../ui/composer.glade:134 -msgid "Strikethrough (Ctrl+K)" -msgstr "取り消し線(Ctrl+K)" +#~ msgid "Geary Mail" +#~ msgstr "Geary メール" -#: ../../src/client/views/conversation-viewer.vala:617 -msgid "Subject:" -msgstr "件名:" +#~ msgid "Additional addresses for %s" +#~ msgstr "%s の追加アドレス" -#: ../../src/engine/rfc822/rfc822-utils.vala:216 -#, c-format -msgid "Subject: %s\n" -msgstr "件名: %s\n" +#~ msgid "First Last" +#~ msgstr "氏名" -#: ../../src/client/util/util-files.vala:19 -msgctxt "Abbreviation for terabyte" -msgid "TB" -msgstr "TB" +#~ msgid "Enter your account information to get started." +#~ msgstr "アカウント情報を入力してください" -#: ../../src/client/geary-controller.vala:1493 -#, c-format -msgid "" -"The file already exists in \"%s\". Replacing it will overwrite its contents." -msgstr "\"%s\" は既に存在します. 上書きしますか?" +#~ msgid "Edit" +#~ msgstr "編集" -#: ../../src/client/geary-controller.vala:747 -msgid "" -"The version number of the local mail database is formatted for a newer " -"version of Geary. Unfortunately, the database cannot be \"rolled back\" to " -"work with this version of Geary.\n" -"\n" -"Please install the latest version of Geary and try again." -msgstr "" +#~ msgid "Preview" +#~ msgstr "プレビュー" -#: ../../src/client/geary-controller.vala:758 -msgid "" -"There was an error opening the local account. This is probably due to " -"connectivity issues.\n" -"\n" -"Please check your network connection and restart Geary." -msgstr "" +#~ msgid "Remem_ber passwords" +#~ msgstr "パスワードを記憶する(_B)" -#: ../../src/client/geary-controller.vala:737 -#, c-format -msgid "" -"There was an error opening the local mail database for this account. This is " -"possibly due to a file permissions problem.\n" -"\n" -"Please check that you have read/write permissions for all files in this " -"directory:\n" -"\n" -"%s" -msgstr "" +#~ msgid "Remem_ber password" +#~ msgstr "パスワードを記憶する(_B)" -#: ../../src/client/geary-controller.vala:702 -#, c-format -msgid "" -"There was an error opening the local mail database for this account. This is " -"possibly due to corruption of the database file in this directory:\n" -"\n" -"%s\n" -"\n" -"Geary can rebuild the database and re-synchronize with the server or exit.\n" -"\n" -"Rebuilding the database will destroy all local email and its attachments. " -"The mail on the your server will not be affected." -msgstr "" +#~ msgid "Unable to validate:\n" +#~ msgstr "検証できません:\n" -#: ../../src/client/views/conversation-viewer.vala:1147 -msgid "This link appears to go to" -msgstr "" +#~ msgid " • Invalid account nickname.\n" +#~ msgstr " • アカウントのニックネームが不正です。\n" -#: ../../src/client/views/conversation-viewer.vala:508 -msgid "This message contains remote images." -msgstr "" +#~ msgid " • Email address already added to Geary.\n" +#~ msgstr " • メールアドレスが Geary に追加済みです。\n" -#: ../../ui/composer.glade:435 -msgid "To add them as attachments" -msgstr "添付ファイルとして追加" +#~ msgid " • IMAP connection error.\n" +#~ msgstr " • IMAP の接続エラーです。\n" -#: ../../src/client/views/conversation-viewer.vala:608 -msgid "To:" -msgstr "宛先:" +#~ msgid " • IMAP username or password incorrect.\n" +#~ msgstr " • IMAP のユーザー名またはパスワードが不正です。\n" -#: ../../src/engine/rfc822/rfc822-utils.vala:220 -#, c-format -msgid "To: %s\n" -msgstr "宛先: %s\n" +#~ msgid " • SMTP connection error.\n" +#~ msgstr " • SMTP の接続エラーです。\n" -#: ../../src/engine/api/geary-special-folder-type.vala:45 -msgid "Trash" -msgstr "ごみ箱" +#~ msgid " • SMTP username or password incorrect.\n" +#~ msgstr " • SMTP のユーザー名またはパスワードが不正です。\n" -#: ../../src/client/geary-controller.vala:294 -msgid "U_nstar" -msgstr "スターをはずす(_N)" +#~ msgid " • Connection error.\n" +#~ msgstr " • 接続エラーです。\n" -#: ../../src/client/dialogs/password-dialog.vala:17 -msgid "Unable to login to email server" -msgstr "ログインに失敗しました" +#~ msgid " • Username or password incorrect.\n" +#~ msgstr " • ユーザー名またはパスワードが不正です。\n" -#: ../../src/client/geary-controller.vala:736 -#: ../../src/client/geary-controller.vala:746 -#: ../../src/client/geary-controller.vala:757 -#, c-format -msgid "Unable to open local mailbox for %s" -msgstr "" +#, fuzzy +#~| msgid "_Mark as..." +#~ msgid "_Mark as…" +#~ msgstr "名前を付けてマーク(_M)" -#: ../../src/client/geary-controller.vala:701 -#, c-format -msgid "Unable to open the database for %s" -msgstr "" +#~ msgid "_Label" +#~ msgstr "ラベル(_L)" -#: ../../src/client/geary-controller.vala:713 -#, c-format -msgid "Unable to rebuild database for \"%s\"" -msgstr "" +#~ msgid "_Move" +#~ msgstr "移動(_M)" -#: ../../src/client/accounts/add-edit-page.vala:619 -msgid "Unable to validate:\n" -msgstr "検証できません: \n" +#~ msgid "Compose new message (Ctrl+N, N)" +#~ msgstr "新規メッセージを作成(Ctrl+N, N)" -#: ../../ui/composer.glade:127 -msgid "Underline (Ctrl+U)" -msgstr "下線(Ctrl+U)" +#~ msgid "Reply (Ctrl+R, R)" +#~ msgstr "返信(Ctrl+R, R)" -#: ../../ui/composer.glade:88 -msgid "Unquote text (Ctrl+[)" -msgstr "" +#~ msgid "Reply all (Ctrl+Shift+R, Shift+R)" +#~ msgstr "全員に返信(Ctrl+Shift+R, Shift+R)" -#: ../../src/client/geary-args.vala:65 -#, c-format -msgid "Unrecognized command line option \"%s\"\n" -msgstr "不明なオプション \"%s\"\n" +#~ msgid "Forward (Ctrl+L, F)" +#~ msgstr "転送(Ctrl+L, F)" -#: ../../ui/login.glade:457 -msgid "User_name:" -msgstr "ユーザー名(_N):" +#~ msgid "Unable to store server trust exception" +#~ msgstr "サーバーの信頼に関する例外を保存できません" -#: ../../ui/password-dialog.glade:70 ../../ui/password-dialog.glade:181 -msgid "Username:" -msgstr "ユーザー名:" +#~ msgid "Your settings are insecure" +#~ msgstr "設定がセキュアではありません" -#: ../../src/client/geary-application.vala:19 -msgid "Visit the Yorba web site" -msgstr "Yorba のウェブサイト" +#~ msgid "" +#~ "Your IMAP and/or SMTP settings do not specify SSL or TLS. This means " +#~ "your username and password could be read by another person on the " +#~ "network. Are you sure you want to do this?" +#~ msgstr "" +#~ "IMAP/SMTP の通信に SSL/TLS が指定されていません。ユーザー名やパスワードが" +#~ "ネットワーク上の他人に読み取られる可能性があります。よろしいですか?" -#: ../../src/client/accounts/add-edit-page.vala:190 -#, c-format -msgid "Welcome to Geary." -msgstr "Geary へようこそ" +#~ msgid "Co_ntinue" +#~ msgstr "継続(_N)" -#: ../../ui/login.glade:252 -msgid "Work, Home, etc." -msgstr "" +#~ msgid "" +#~ "Geary encountered an error sending an email. If the problem persists, " +#~ "please manually delete the email from your Outbox folder." +#~ msgstr "" +#~ "メール送信時にエラーが発生しました。問題を解決できない場合は、メールを手動" +#~ "で送信済みトレイから削除してください。" -#: ../../src/engine/api/geary-service-provider.vala:55 -msgid "Yahoo! Mail" -msgstr "Yahoo! Mail" +#~ msgid "" +#~ "Geary encountered an error saving a sent message to Sent Mail. The " +#~ "message will stay in your Outbox folder until you delete it." +#~ msgstr "" +#~ "メールを送信済みトレイに保存するときにエラーが発生しました。削除するまでこ" +#~ "のメッセージを送信済みトレイに表示します。" -#: ../../src/client/util/util-date.vala:177 -msgid "Yesterday" -msgstr "" +#~ msgid "Unable to open local mailbox for %s" +#~ msgstr "%s のローカルメールボックスを開けません" -#: ../../src/client/geary-controller.vala:552 -msgid "" -"Your IMAP and/or SMTP settings do not specify SSL or TLS. This means your " -"username and password could be read by another person on the network. Are " -"you sure you want to do this?" -msgstr "" -"IMAP/SMTP の通信に SSL/TLS が指定されていません. ユーザー名やパスワードはネッ" -"トワーク上の他人に読み取られる可能性があります. よろしいですか?" +#~ msgid "" +#~ "There was an error opening the local mail database for this account. This " +#~ "is possibly due to a file permissions problem.\n" +#~ "\n" +#~ "Please check that you have read/write permissions for all files in this " +#~ "directory:\n" +#~ "\n" +#~ "%s" +#~ msgstr "" +#~ "このアカウントのローカルメールデータベースを開くときにエラーが発生しまし" +#~ "た。おそらくファイルのアクセス権に問題があります。\n" +#~ "\n" +#~ "このディレクトリのすべてのファイルの読み取り/書き込み権限を確認してくださ" +#~ "い:\n" +#~ "\n" +#~ "%s" -#: ../../src/client/geary-controller.vala:551 -msgid "Your settings are insecure" -msgstr "設定がセキュアでありません" +#~ msgid "" +#~ "The version number of the local mail database is formatted for a newer " +#~ "version of Geary. Unfortunately, the database cannot be “rolled back” to " +#~ "work with this version of Geary.\n" +#~ "\n" +#~ "Please install the latest version of Geary and try again." +#~ msgstr "" +#~ "ローカルメールデータベースは新しいバージョンの Geary 用にフォーマットされ" +#~ "ています。残念ですが、このバージョンの Geary で動作するようデータベース" +#~ "を“ロールバック”することはできません。\n" +#~ "\n" +#~ "Geary の最新バージョンをインストールして再試行してください。" -#: ../../src/client/geary-controller.vala:258 -#: ../../src/client/ui/stock.vala:21 -msgid "_About" -msgstr "このアプリケーションについて(_A)" +#~ msgid "" +#~ "There was an error opening the local account. This is probably due to " +#~ "connectivity issues.\n" +#~ "\n" +#~ "Please check your network connection and restart Geary." +#~ msgstr "" +#~ "ローカルアカウントを開くときにエラーが発生しました。おそらく接続の問題によ" +#~ "るものです。\n" +#~ "\n" +#~ "ネットワーク接続を確認し、Geary を再起動してください。" -#: ../../src/client/accounts/account-dialog-add-edit-pane.vala:48 -#: ../../src/client/ui/stock.vala:22 -msgid "_Add" -msgstr "追加(_A)" +#~ msgid "Geary will exit if you have no other open email accounts." +#~ msgstr "他に開いているメールアカウントがなければ終了します。" -#: ../../src/client/geary-controller.vala:47 -msgid "_Archive" -msgstr "アーカイブ(_A)" +#~ msgid "Other" +#~ msgstr "その他" -#: ../../src/client/dialogs/attachment-dialog.vala:23 -msgid "_Attach" -msgstr "添付(_A)" +#~ msgid "IMAP" +#~ msgstr "IMAP" -#: ../../ui/composer.glade:525 -msgid "_Attach File" -msgstr "ファイルを添付(_A)" +#~ msgid "SMTP" +#~ msgstr "SMTP" -#: ../../ui/preferences.glade:69 -msgid "_Automatically select next message" -msgstr "自動的に次のメッセージを選択(_A)" +#~ msgid "Cannot remove account " +#~ msgstr "" +#~ "アカウントを削除できません " -#: ../../src/client/ui/stock.vala:19 -msgid "_Cancel" -msgstr "キャンセル(_C)" +#~ msgid "" +#~ "A composer window associated with this account is currently open. Send or " +#~ "discard the message and try again." +#~ msgstr "" +#~ "アカウントと関連づけられた作成画面がすでに開いています. メッセージを送信す" +#~ "るか破棄してからもう一度試してください" -#: ../../ui/composer.glade:52 -msgid "_Center" -msgstr "中央揃え(_C)" +#~ msgid "Please wait while Geary validates your account." +#~ msgstr "アカウントを検証しています..." -#: ../../src/client/ui/stock.vala:23 -msgid "_Close" -msgstr "" +#~ msgid "" +#~ "Some email services require additional addresses be configured on the " +#~ "server. Contact your email provider for more information." +#~ msgstr "" +#~ "一部のメールサービスでは、サーバーの設定に追加のアドレスが必要になります。" +#~ "詳細についてはメールプロバイダーに問い合わせてください。" -#: ../../src/client/views/conversation-viewer.vala:889 -msgid "_Copy" -msgstr "コピー(_C)" +#~ msgid "_Update" +#~ msgstr "更新(_U)" -#: ../../src/client/geary-controller.vala:42 -msgid "_Delete" -msgstr "削除(_D)" +#~ msgid "E_mail address" +#~ msgstr "メールアドレス(_M)" -#: ../../ui/password-dialog.glade:548 -msgid "_Details" -msgstr "詳細(_D)" +#~ msgid "_Password" +#~ msgstr "パスワード(_P)" -#: ../../src/client/ui/stock.vala:24 -msgid "_Discard" -msgstr "" +#~ msgid "S_ervice" +#~ msgstr "メールサービス(_E)" -#: ../../ui/preferences.glade:90 -msgid "_Display conversation preview" -msgstr "" +#~ msgid "N_ame" +#~ msgstr "名前(_A)" -#: ../../src/client/geary-controller.vala:262 -msgid "_Donate" -msgstr "" +#~ msgid "N_ickname" +#~ msgstr "ニックネーム(_I)" -#: ../../ui/login.glade:782 -msgid "_Download mail:" -msgstr "メールをダウンロード(_D):" +#~ msgid "Work, Home, etc." +#~ msgstr "仕事用、自宅用、など。" -#: ../../ui/composer.glade:181 -msgid "_Fixed Width" -msgstr "" +#~ msgid "Addi_tional email addresses…" +#~ msgstr "追加のメールアドレス(_T)…" -#: ../../src/client/geary-controller.vala:328 -#: ../../src/client/views/conversation-viewer.vala:1457 -msgid "_Forward" -msgstr "転送(_F)" +#~ msgid "IMAP settings" +#~ msgstr "IMAP 設定" -#: ../../src/client/geary-controller.vala:254 -#: ../../src/client/ui/stock.vala:25 -msgid "_Help" -msgstr "ヘルプ(_H)" +#~ msgid "Se_rver" +#~ msgstr "サーバー(_R)" -#: ../../ui/composer.glade:542 -msgid "_Include Original Attachments" -msgstr "" +#~ msgid "P_ort" +#~ msgstr "ポート(_O)" -#: ../../src/client/views/conversation-viewer.vala:922 -msgid "_Inspect" -msgstr "検証(_I)" +#~ msgid "Ser_ver" +#~ msgstr "サーバー(_V)" -#: ../../ui/composer.glade:57 -msgid "_Justify" -msgstr "両端揃え(_J)" +#~ msgid "Por_t" +#~ msgstr "ポート(_T)" -#: ../../src/client/ui/stock.vala:33 -msgid "_Keep" -msgstr "" +#~ msgid "User_name" +#~ msgstr "ユーザー名(_N)" -#: ../../src/client/geary-controller.vala:305 -msgid "_Label" -msgstr "ラベル(_L)" - -#: ../../ui/composer.glade:42 -msgid "_Left" -msgstr "左揃え(_L)" +#~ msgid "Pass_word" +#~ msgstr "パスワード(_W)" -#: ../../src/client/views/conversation-viewer.vala:1469 -msgid "_Mark as Read" -msgstr "既読としてマーク(_M)" +#~ msgid "SMTP password" +#~ msgstr "SMTP のパスワード" -#: ../../src/client/views/conversation-viewer.vala:1473 -msgid "_Mark as Unread" -msgstr "未読としてマーク(_M)" +#~ msgid "_Username" +#~ msgstr "ユーザー名(_U)" -#: ../../src/client/geary-controller.vala:271 -msgid "_Mark as..." -msgstr "名前を付けてマーク(_M)" +#~ msgid "IMAP password" +#~ msgstr "IMAP のパスワード" -#: ../../ui/composer.glade:157 -msgid "_Medium" -msgstr "" +#~ msgid "Encr_yption" +#~ msgstr "暗号化(_Y)" -#: ../../src/client/geary-controller.vala:309 -msgid "_Move" -msgstr "移動(_M)" +#~ msgid "Encrypt_ion" +#~ msgstr "暗号化(_I)" -#: ../../src/client/ui/stock.vala:18 -msgid "_OK" -msgstr "" +#~ msgid "STARTTLS" +#~ msgstr "STARTTLS" -#: ../../src/client/ui/stock.vala:26 -msgid "_Open" -msgstr "" +#~ msgid "No authentication re_quired" +#~ msgstr "認証の必要はない(_Q)" -#: ../../ui/login.glade:131 ../../ui/login.glade:539 -#: ../../ui/password-dialog.glade:85 -msgid "_Password:" -msgstr "パスワード(_P):" +#~ msgid "Use IMAP cre_dentials" +#~ msgstr "IMAP の情報を使う(_D)" -#: ../../ui/composer.glade:35 -msgid "_Paste" -msgstr "" +#~ msgid "Composer" +#~ msgstr "作成画面" -#: ../../ui/preferences.glade:172 -msgid "_Play notification sounds" -msgstr "通知音を再生(_P)" +#~ msgid "Si_gn emails (HTML allowed):" +#~ msgstr "メールに署名する (HTML 使用可)(_G):" -#: ../../src/client/geary-controller.vala:250 -#: ../../src/client/ui/stock.vala:27 -msgid "_Preferences" -msgstr "設定(_P)" +#~ msgid "Storage" +#~ msgstr "ストレージ" -#: ../../src/client/ui/stock.vala:28 -msgid "_Print..." -msgstr "" +#~ msgid "Geary will run in the background and notify of new mail" +#~ msgstr "Geary をバックグラウンドで実行して新着メールを通知します" -#: ../../src/client/geary-controller.vala:266 -#: ../../src/client/ui/stock.vala:29 -msgid "_Quit" -msgstr "終了(_Q)" +#~ msgid "" +#~ "Are you sure you want to remove " +#~ "this account? " +#~ msgstr "" +#~ "このアカウントを削除してもよろしいで" +#~ "すか? " -#: ../../src/client/geary-controller.vala:704 -msgid "_Rebuild" -msgstr "" +#~ msgid "" +#~ "All email associated with this account will be removed from your " +#~ "computer. This will not affect email on the server." +#~ msgstr "" +#~ "このアカウントに関連したすべてのメールを、コンピューターから削除します。" +#~ "サーバー上のメールには影響しません。" -#: ../../ui/composer.glade:14 -msgid "_Redo" -msgstr "" +#~ msgid "Nickname:" +#~ msgstr "ニックネーム:" -#: ../../ui/password-dialog.glade:561 -msgid "_Remember passwords" -msgstr "パスワードを保存(_R)" +#~ msgid "Archive conversation (Delete, Backspace, A)" +#~ msgstr "スレッドをアーカイブ (Delete, Backspace, A)" -#: ../../src/client/ui/stock.vala:30 -msgid "_Remove" -msgstr "削除(_R)" +#~ msgid "Copyright 2011-2013 Yorba Foundation" +#~ msgstr "Copyright 2011-2013 Yorba Foundation" -#: ../../src/client/geary-controller.vala:1496 -msgid "_Replace" -msgstr "置換(_R)" +#~ msgid "Large" +#~ msgstr "大" -#: ../../src/client/geary-controller.vala:317 -#: ../../src/client/views/conversation-viewer.vala:1447 -msgid "_Reply" -msgstr "返信(_R)" +#~ msgid "Link (Ctrl+L)" +#~ msgstr "リンク(Ctrl+L)" -#: ../../ui/composer.glade:141 -msgid "_Rich Text" -msgstr "" +#~ msgid "Mail Client" +#~ msgstr "メールクライアント" -#: ../../ui/composer.glade:47 -msgid "_Right" -msgstr "右揃え(_R)" +#~ msgid "Medium" +#~ msgstr "中" -#: ../../src/client/accounts/account-dialog-add-edit-pane.vala:48 -#: ../../src/client/ui/stock.vala:31 -msgid "_Save" -msgstr "保存(_S)" +#~ msgid "Password:" +#~ msgstr "パスワード:" -#: ../../src/client/views/conversation-viewer.vala:1395 -msgid "_Save As..." -msgstr "名前を付けて保存(_S)..." +#~ msgid "Port:" +#~ msgstr "ポート:" -#: ../../src/client/views/conversation-viewer.vala:1414 -msgid "_Save Image As..." -msgstr "" +#~ msgid "Real name:" +#~ msgstr "本名:" -#: ../../ui/composer.glade:594 -msgid "_Send" -msgstr "送信(_S)" +#~ msgid "SSL" +#~ msgstr "SSL" -#: ../../ui/composer.glade:163 -msgid "_Small" -msgstr "" +#~ msgid "SSL/TLS encryption:" +#~ msgstr "SSL/TLS 暗号化:" -#: ../../src/client/geary-controller.vala:289 -msgid "_Star" -msgstr "スター(_S)" +#~ msgid "Sans Serif" +#~ msgstr "ゴシック体" -#: ../../ui/composer.glade:7 -msgid "_Undo" -msgstr "" +#~ msgid "Serif" +#~ msgstr "明朝体" -#: ../../ui/login.glade:522 -msgid "_Username:" -msgstr "ユーザー名(_U):" +#~ msgid "Server:" +#~ msgstr "サーバー:" -#: ../../src/client/views/conversation-viewer.vala:1494 -msgid "_View Source" -msgstr "ソースを表示(_V)" +#~ msgid "Small" +#~ msgstr "小" -#. / A list of keywords, separated by pipe ("|") characters, that suggest an attachment -#: ../../src/client/composer/composer-window.vala:90 -msgid "attach|enclosed|enclosing|cover letter" -msgstr "" +#~ msgid "Unable to login to email server" +#~ msgstr "ログインに失敗しました" -#: ../../src/client/views/conversation-viewer.vala:1148 -msgid "but actually goes to" -msgstr "" +#~ msgid "_Center" +#~ msgstr "中央揃え(_C)" -#: ../../src/client/util/util-files.vala:16 -msgid "bytes" -msgstr "バイト" +#~ msgid "_Justify" +#~ msgstr "両端揃え(_J)" -#. / Placeholder filename for attachments with no filename. -#: ../../src/client/views/conversation-viewer.vala:1799 -#: ../../src/engine/rfc822/rfc822-utils.vala:327 -msgid "none" -msgstr "無題" +#~ msgid "_Left" +#~ msgstr "左揃え(_L)" -#: ../../src/client/views/conversation-find-bar.vala:226 -msgid "not found" -msgstr "" +#~ msgid "_Right" +#~ msgstr "右揃え(_R)" -#. / Translators: add your name and email address to receive credit in the About dialog -#. / For example: Yamada Taro -#: ../../src/client/geary-controller.vala:1230 -msgid "translator-credits" -msgstr "Minato Kanzaki " +#~ msgid "_Save As..." +#~ msgstr "名前を付けて保存(_S)..." + +#~ msgid "none" +#~ msgstr "無題" diff -Nru geary-0.12.4/po/kk.po geary-3.32.0/po/kk.po --- geary-0.12.4/po/kk.po 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/po/kk.po 2019-03-17 13:39:29.000000000 +0000 @@ -6,10 +6,9 @@ msgid "" msgstr "" "Project-Id-Version: geary master\n" -"Report-Msgid-Bugs-To: https://bugzilla.gnome.org/enter_bug.cgi?" -"product=geary&keywords=I18N+L10N&component=internationalization\n" -"POT-Creation-Date: 2017-09-26 12:56+0000\n" -"PO-Revision-Date: 2017-09-26 20:17+0500\n" +"Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/geary/issues\n" +"POT-Creation-Date: 2018-08-23 12:11+0000\n" +"PO-Revision-Date: 2018-08-26 08:32+0500\n" "Last-Translator: Baurzhan Muftakhidinov \n" "Language-Team: Kazakh \n" "Language: kk\n" @@ -17,28 +16,52 @@ "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Poedit 2.0.3\n" +"X-Generator: Poedit 2.1.1\n" + +#: desktop/geary-attach.contract.desktop.in:3 +msgid "Send by email" +msgstr "Эл. поштамен жіберу" + +#. Translators: Do NOT translate or transliterate this text (this is an icon file name)! +#: desktop/geary-attach.contract.desktop.in:5 +msgid "mail-send" +msgstr "mail-send" #. Translators: The application name -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:2 -#: ../desktop/org.gnome.Geary.desktop.in.h:1 -#: ../desktop/geary-autostart.desktop.in.h:1 +#: desktop/geary-autostart.desktop.in:3 +#: desktop/org.gnome.Geary.appdata.xml.in:11 +#: desktop/org.gnome.Geary.desktop.in:3 msgid "Geary" msgstr "Geary" -#. Translators: The development team's name -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:4 -msgid "Geary Development Team" -msgstr "Geary әзірлеушілер тобы" +#: desktop/geary-autostart.desktop.in:4 desktop/org.gnome.Geary.desktop.in:4 +msgid "Email" +msgstr "Эл. пошта" #. Translators: The application's summary / tagline -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:6 -#: ../desktop/org.gnome.Geary.desktop.in.h:4 -#: ../desktop/geary-autostart.desktop.in.h:4 +#: desktop/geary-autostart.desktop.in:5 +#: desktop/org.gnome.Geary.appdata.xml.in:15 +#: desktop/org.gnome.Geary.desktop.in:5 +#: src/client/application/geary-application.vala:21 msgid "Send and receive email" msgstr "Эл. поштаны жіберу және қабылдау" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:7 +#. Translators: These are desktop search terms. Do not translate semicolons, end line with a semicolon. +#: desktop/geary-autostart.desktop.in:7 +msgid "Email;E-mail;Mail;" +msgstr "Email;E-mail;Mail;Эл. пошта;электрондық пошта;е-пошта;" + +#. Translators: Do NOT translate or transliterate this text (this is an icon file name)! +#: desktop/geary-autostart.desktop.in:9 desktop/org.gnome.Geary.desktop.in:9 +msgid "org.gnome.Geary" +msgstr "org.gnome.Geary" + +#. Translators: The development team's name +#: desktop/org.gnome.Geary.appdata.xml.in:13 +msgid "Geary Development Team" +msgstr "Geary әзірлеушілер тобы" + +#: desktop/org.gnome.Geary.appdata.xml.in:17 msgid "" "Geary is an email application built around conversations, for the GNOME 3 " "desktop. It allows you to read, find and send email with a straightforward, " @@ -48,7 +71,7 @@ "жұмыс үстелі үшін арналған. Оның көмегімен тура, замануи интерфейсін " "қолданып, эл. поштаны оқу, табу және жіберуге болады." -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:8 +#: desktop/org.gnome.Geary.appdata.xml.in:22 msgid "" "Conversations allow you to read a complete discussion without having to find " "and click from message to message." @@ -56,103 +79,261 @@ "Сөйлесулер көмегімен сіз талқылауды толығымен оқи аласыз, бір хаттан екінші " "хатқа іздеп, шертуді орындамай-ақ." -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:9 +#: desktop/org.gnome.Geary.appdata.xml.in:26 msgid "Geary’s features include:" msgstr "Geary мүмкіндіктерінің ішінде:" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:10 +#: desktop/org.gnome.Geary.appdata.xml.in:28 msgid "Quick email account setup" msgstr "Эл. пошта тіркелгісін жылдам баптау" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:11 +#: desktop/org.gnome.Geary.appdata.xml.in:29 msgid "Shows related messages together in conversations" msgstr "Өзара байланысқан хабарламаларды сөйлесуде бірге көрсетеді" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:12 +#: desktop/org.gnome.Geary.appdata.xml.in:30 msgid "Fast, full text and keyword search" msgstr "Жылдам, толық мәтіндік және кілт сөздерін іздеу" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:13 +#: desktop/org.gnome.Geary.appdata.xml.in:31 msgid "Full-featured HTML and plain text message composer" msgstr "Кең мүмкіндікті HTML және ашық мәтін хаттарын құрастыру түзеткіші" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:14 +#: desktop/org.gnome.Geary.appdata.xml.in:32 msgid "Desktop notification of new mail" msgstr "Жаңа хаттар үшін жұмыс үстел хабарламаларын көрсету" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:15 +#: desktop/org.gnome.Geary.appdata.xml.in:33 msgid "Compatible with GMail, Yahoo! Mail, Outlook.com and other IMAP servers" msgstr "" "GMail, Yahoo! Mail, Outlook.com және басқа да IMAP серверлерімен үйлесімді" #. Translators: A screenshot description. -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:17 -#| msgid "_Display conversation preview" +#: desktop/org.gnome.Geary.appdata.xml.in:47 msgid "Geary displaying a conversation" msgstr "Geary сөйлесуді көрсетуде" #. Translators: A screenshot description. -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:19 +#: desktop/org.gnome.Geary.appdata.xml.in:52 msgid "Geary showing the rich text composer" msgstr "Geary мәтінді мүмкіндіктері кең түзеткішін көрсетуде" -#: ../desktop/org.gnome.Geary.desktop.in.h:2 -msgid "Email" -msgstr "Эл. пошта" - -#: ../desktop/org.gnome.Geary.desktop.in.h:3 -msgid "Geary Email" -msgstr "Geary поштасы" - #. Translators: These are desktop search terms. Do not translate semicolons, end line with a semicolon. -#: ../desktop/org.gnome.Geary.desktop.in.h:6 +#: desktop/org.gnome.Geary.desktop.in:7 msgid "Mail;E-mail;IMAP;GMail;Yahoo;Hotmail;Outlook;" msgstr "Mail;E-mail;IMAP;GMail;Yahoo;Hotmail;Outlook;Пошта;Эл.пошта;" -#: ../desktop/org.gnome.Geary.desktop.in.h:7 ../ui/gtk/menus.ui.h:1 +#: desktop/org.gnome.Geary.desktop.in:21 ui/gtk/menus.ui:7 msgid "Compose Message" msgstr "Хатты жазу" -#: ../desktop/geary-autostart.desktop.in.h:2 -#: ../src/client/application/geary-application.vala:21 -msgid "Mail Client" -msgstr "Пошта клиенті" - -#: ../desktop/geary-autostart.desktop.in.h:3 -msgid "Geary Mail" -msgstr "Geary поштасы" +#: desktop/org.gnome.Geary.gschema.xml:8 +msgid "Default attachments directory" +msgstr "Салынымдардың негізгі бумасы" -#: ../desktop/geary-autostart.desktop.in.h:5 -msgid "Email;E-mail;Mail;" -msgstr "Email;E-mail;Mail;Эл. пошта;электрондық пошта;е-пошта;" +#: desktop/org.gnome.Geary.gschema.xml:9 +msgid "Location used when opening and saving attachments." +msgstr "Салынымдарды ашу және сақтаудың орны." -#: ../desktop/geary-attach.contract.in.h:1 -msgid "Send by email" -msgstr "Эл. поштамен жіберу" +#: desktop/org.gnome.Geary.gschema.xml:14 +msgid "Default print output directory" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:15 +msgid "Location used when printing to a file." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:20 +msgid "Maximize window" +msgstr "Терезені жазық қылу" + +#: desktop/org.gnome.Geary.gschema.xml:21 +msgid "True if the application window is maximized, false otherwise." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:26 +msgid "Width of window" +msgstr "Терезе ені" + +#: desktop/org.gnome.Geary.gschema.xml:27 +msgid "The last recorded width of the application window." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:32 +msgid "Height of window" +msgstr "Терезе биіктігі" + +#: desktop/org.gnome.Geary.gschema.xml:33 +msgid "The last recorded height of the application window." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:38 +msgid "Position of folder list pane" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:39 +msgid "Position of the folder list Paned grabber." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:44 +msgid "Position of folder list pane when horizontal" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:45 +msgid "" +"Position of the folder list Paned grabber in the horizontal orientation." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:50 +msgid "Position of folder list pane when vertical" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:51 +msgid "Position of the folder list Paned grabber in the vertical orientation." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:56 +msgid "Orientation of the folder list pane" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:57 +msgid "True if the folder list Paned is in the horizontal orientation." +msgstr "" -#: ../desktop/geary-attach.contract.in.h:2 -msgid "Send files using Geary" -msgstr "Файлдарды Geary көмегімен жіберу" - -#: ../src/client/accounts/account-dialog-add-edit-pane.vala:51 -#: ../src/client/components/stock.vala:31 -#: ../ui/conversation-email-menus.ui.h:10 +#: desktop/org.gnome.Geary.gschema.xml:62 +msgid "Position of message list pane" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:63 +msgid "Position of the message list Paned grabber." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:68 +msgid "Autoselect next message" +msgstr "Келесі хатты автотаңдау" + +#: desktop/org.gnome.Geary.gschema.xml:69 +msgid "True if we should autoselect the next available conversation." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:74 +msgid "Display message previews" +msgstr "Хабарламаның алдын-ала көрінісін көрсету" + +#: desktop/org.gnome.Geary.gschema.xml:75 +msgid "True if we should display a short preview of each message." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:80 +msgid "Languages that shall be used in the spell checker" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:81 +msgid "List of the languages to use in the spell checker." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:86 +msgid "Languages that are displayed in the spell checker popover" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:87 +msgid "" +"List of languages that are always displayed in the popover of the spell " +"checker." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:92 +msgid "Enable notification sounds" +msgstr "Хабарлау дыбыстарын ойнату" + +#: desktop/org.gnome.Geary.gschema.xml:93 +msgid "True to play sounds for notifications and sending." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:98 +msgid "Show notifications for new mail" +msgstr "Жаңа хаттар үшін ескертуді көрсету" + +#: desktop/org.gnome.Geary.gschema.xml:99 +msgid "True to show notification bubbles." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:104 +msgid "Notify of new mail at startup" +msgstr "Іске қосылғанда жаңа хаттар туралы хабарлау" + +#: desktop/org.gnome.Geary.gschema.xml:105 +msgid "True to notify of new mail at startup." +msgstr "Іске қосылғанда жаңа хаттар туралы хабарлау үшін True." + +#: desktop/org.gnome.Geary.gschema.xml:110 +msgid "Ask when opening an attachment" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:111 +msgid "True to ask when opening an attachment." +msgstr "Салынымдарды ашу кезінде сұрау үшін True." + +#: desktop/org.gnome.Geary.gschema.xml:116 +msgid "Whether to compose emails in HTML" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:117 +msgid "True to compose emails in HTML; false for plain text." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:122 +msgid "Advisory strategy for full-text searching" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:123 +msgid "" +"Acceptable values are “exact”, “conservative”, “aggressive”, and “horizon”." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:128 +msgid "Zoom of conversation viewer" +msgstr "Сөйлесулер аймағының масштабы" + +#: desktop/org.gnome.Geary.gschema.xml:129 +msgid "The zoom to apply on the conservation view." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:134 +msgid "Size of detached composer window" +msgstr "Бөлінген жаңа хатты жазу терезесінің өлшемі" + +#: desktop/org.gnome.Geary.gschema.xml:135 +msgid "The last recorded size of the detached composer window." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:140 +msgid "Whether we migrated the old settings" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:141 +msgid "" +"False to check for the old “org.yorba.geary”-schema and copy its values." +msgstr "" + +#: src/client/accounts/account-dialog-add-edit-pane.vala:54 +#: src/client/components/stock.vala:31 ui/conversation-email-menus.ui:83 msgid "_Save" msgstr "_Сақтау" -#: ../src/client/accounts/account-dialog-add-edit-pane.vala:51 -#: ../src/client/components/stock.vala:22 +#: src/client/accounts/account-dialog-add-edit-pane.vala:54 +#: src/client/components/stock.vala:22 msgid "_Add" msgstr "Қо_су" #. reset/clear widgets -#: ../src/client/accounts/account-dialog-edit-alternate-emails-pane.vala:120 +#: src/client/accounts/account-dialog-edit-alternate-emails-pane.vala:120 #, c-format msgid "Additional addresses for %s" msgstr "%s үшін қосымша адрестер" #. Sets min size. -#: ../src/client/accounts/account-dialog.vala:21 +#: src/client/accounts/account-dialog.vala:28 msgid "Accounts" msgstr "Тіркелгілер" @@ -163,113 +344,118 @@ #. #. Page for adding or editing an account. #. / Placeholder text indicating that the user should type their first name and last name -#: ../src/client/accounts/add-edit-page.vala:10 +#: src/client/accounts/add-edit-page.vala:10 msgid "First Last" msgstr "Аты Фамилиясы" -#: ../src/client/accounts/add-edit-page.vala:235 +#: src/client/accounts/add-edit-page.vala:240 msgid "Welcome to Geary." msgstr "Geary қолданбасына қош келдіңіз." -#: ../src/client/accounts/add-edit-page.vala:235 +#: src/client/accounts/add-edit-page.vala:240 msgid "Enter your account information to get started." msgstr "Бастау үшін тіркелгі ақпаратыңызды енгізіңіз." -#: ../src/client/accounts/add-edit-page.vala:255 +#: src/client/accounts/add-edit-page.vala:260 msgid "2 weeks back" msgstr "2 апта бұрын" #. IDs are # of days -#: ../src/client/accounts/add-edit-page.vala:256 +#: src/client/accounts/add-edit-page.vala:261 msgid "1 month back" msgstr "1 ай бұрын" -#: ../src/client/accounts/add-edit-page.vala:257 +#: src/client/accounts/add-edit-page.vala:262 msgid "3 months back" msgstr "3 ай бұрын" -#: ../src/client/accounts/add-edit-page.vala:258 +#: src/client/accounts/add-edit-page.vala:263 msgid "6 months back" msgstr "6 ай бұрын" -#: ../src/client/accounts/add-edit-page.vala:259 +#: src/client/accounts/add-edit-page.vala:264 msgid "1 year back" msgstr "1 жыл бұрын" -#: ../src/client/accounts/add-edit-page.vala:260 +#: src/client/accounts/add-edit-page.vala:265 msgid "2 years back" msgstr "2 жыл бұрын" -#: ../src/client/accounts/add-edit-page.vala:261 +#: src/client/accounts/add-edit-page.vala:266 msgid "4 years back" msgstr "4 жыл бұрын" #. Separator -#: ../src/client/accounts/add-edit-page.vala:263 +#: src/client/accounts/add-edit-page.vala:268 msgid "Everything" msgstr "Барлығы" -#: ../src/client/accounts/add-edit-page.vala:283 +#: src/client/accounts/add-edit-page.vala:288 msgid "Edit" msgstr "Түзету" -#: ../src/client/accounts/add-edit-page.vala:285 +#: src/client/accounts/add-edit-page.vala:290 msgid "Preview" msgstr "Алдын-ала қарау" -#: ../src/client/accounts/add-edit-page.vala:751 +#: src/client/accounts/add-edit-page.vala:778 msgid "Remem_ber passwords" msgstr "Парольдерді есте сақ_тау" -#: ../src/client/accounts/add-edit-page.vala:758 ../ui/login.glade.h:7 +#: src/client/accounts/add-edit-page.vala:785 ui/login.glade:233 msgid "Remem_ber password" msgstr "Парольді есте сақ_тау" -#: ../src/client/accounts/add-edit-page.vala:792 +#: src/client/accounts/add-edit-page.vala:819 msgid "Unable to validate:\n" msgstr "Тексеру сәтсіз аяқталды:\n" -#: ../src/client/accounts/add-edit-page.vala:794 +#: src/client/accounts/add-edit-page.vala:821 msgid " • Invalid account nickname.\n" msgstr " • Тіркелгінің ник аты қате.\n" -#: ../src/client/accounts/add-edit-page.vala:797 +#: src/client/accounts/add-edit-page.vala:824 msgid " • Email address already added to Geary.\n" msgstr " • Эл. пошта адресі Geary-ге бұрын қосылған.\n" -#: ../src/client/accounts/add-edit-page.vala:801 +#: src/client/accounts/add-edit-page.vala:828 msgid " • IMAP connection error.\n" msgstr " • IMAP байланысу қатесі.\n" -#: ../src/client/accounts/add-edit-page.vala:804 +#: src/client/accounts/add-edit-page.vala:831 msgid " • IMAP username or password incorrect.\n" msgstr " • IMAP пайдаланушы аты не паролі қате.\n" -#: ../src/client/accounts/add-edit-page.vala:807 +#: src/client/accounts/add-edit-page.vala:834 msgid " • SMTP connection error.\n" msgstr " • SMTP байланысу қатесі.\n" -#: ../src/client/accounts/add-edit-page.vala:810 +#: src/client/accounts/add-edit-page.vala:837 msgid " • SMTP username or password incorrect.\n" msgstr " • SMTP пайдаланушы аты не паролі қате.\n" -#: ../src/client/accounts/add-edit-page.vala:814 +#: src/client/accounts/add-edit-page.vala:841 msgid " • Connection error.\n" msgstr " • Байланыс қатесі.\n" -#: ../src/client/accounts/add-edit-page.vala:818 +#: src/client/accounts/add-edit-page.vala:845 msgid " • Username or password incorrect.\n" msgstr " • Пайдаланушы аты не паролі қате.\n" -#: ../src/client/application/geary-application.vala:22 +#: src/client/application/geary-application.vala:22 msgid "Copyright 2016 Software Freedom Conservancy Inc." msgstr "Copyright 2016 Software Freedom Conservancy Inc." -#: ../src/client/application/geary-application.vala:24 +#: src/client/application/geary-application.vala:23 +#| msgid "Geary Development Team" +msgid "Copyright 2016-2017 Geary Development Team." +msgstr "Copyright 2016-2017 Geary әзірлеушілер тобы." + +#: src/client/application/geary-application.vala:25 msgid "Visit the Geary web site" msgstr "Geary веб сайтын шолу" -#: ../src/client/application/geary-application.vala:464 +#: src/client/application/geary-application.vala:416 #, c-format msgid "About %s" msgstr "%s туралы" @@ -277,249 +463,101 @@ #. Translators: add your name and email address to receive #. credit in the About dialog For example: Yamada Taro #. -#: ../src/client/application/geary-application.vala:468 +#: src/client/application/geary-application.vala:420 msgid "translator-credits" msgstr "Baurzhan Muftakhidinov " -#: ../src/client/application/geary-args.vala:10 +#: src/client/application/geary-args.vala:10 msgid "Start Geary with hidden main window" msgstr "Geary қолданбасын жасырылған басты тереземен іске қосу" -#: ../src/client/application/geary-args.vala:11 +#: src/client/application/geary-args.vala:11 msgid "Output debugging information" msgstr "Жөндеу ақпаратын көрсету" -#: ../src/client/application/geary-args.vala:12 +#: src/client/application/geary-args.vala:12 msgid "Log conversation monitoring" msgstr "Сөйлесулерді бақылауды журналдау" -#: ../src/client/application/geary-args.vala:13 +#: src/client/application/geary-args.vala:13 msgid "Log network deserialization" msgstr "Желілік десериализациясын журналдау" -#: ../src/client/application/geary-args.vala:14 +#: src/client/application/geary-args.vala:14 msgid "Log network activity" msgstr "Желілік белсенділікті журналдау" #. / The IMAP replay queue is how changes on the server are replicated on the client. #. / It could also be called the IMAP events queue. -#: ../src/client/application/geary-args.vala:17 +#: src/client/application/geary-args.vala:17 msgid "Log IMAP replay queue" msgstr "IMAP қайта ойнату тізбегін журналдау" #. / Serialization is how commands and responses are converted into a stream of bytes for #. / network transmission -#: ../src/client/application/geary-args.vala:20 +#: src/client/application/geary-args.vala:20 msgid "Log network serialization" msgstr "Желілік сериализациясын журналдау" -#: ../src/client/application/geary-args.vala:21 +#: src/client/application/geary-args.vala:21 msgid "Log periodic activity" msgstr "Мезгіл-мезгіл белсенділікті журналдау" -#: ../src/client/application/geary-args.vala:22 +#: src/client/application/geary-args.vala:22 msgid "Log database queries (generates lots of messages)" msgstr "Дерекқор сұранымдарын журналдау (көптеген хабарламаларды туғызады)" #. / "Normalization" can also be called "synchronization" -#: ../src/client/application/geary-args.vala:24 +#: src/client/application/geary-args.vala:24 msgid "Log folder normalization" msgstr "Бумалар синхронизациясын журналдау" -#: ../src/client/application/geary-args.vala:25 +#: src/client/application/geary-args.vala:25 msgid "Allow inspection of WebView" msgstr "WebView бақылауды рұқсат ету" -#: ../src/client/application/geary-args.vala:26 +#: src/client/application/geary-args.vala:26 msgid "Revoke all server certificates with TLS warnings" msgstr "TLS ескертулері бар барлық сервер сертификаттарын қайта шақыру" -#: ../src/client/application/geary-args.vala:27 +#: src/client/application/geary-args.vala:27 msgid "Perform a graceful quit" msgstr "Қалыпты шығуды орындау" -#: ../src/client/application/geary-args.vala:28 +#: src/client/application/geary-args.vala:28 msgid "Display program version" msgstr "Бағдарлама нұсқасын көрсету" #. This gives a command-line hint on how to open new composer windows with mailto: -#: ../src/client/application/geary-args.vala:53 +#: src/client/application/geary-args.vala:53 #, c-format msgid "Use %s to open a new composer window" msgstr "Жаңа хатты жазу терезесін ашу үшін %s қолданыңыз" -#: ../src/client/application/geary-args.vala:54 +#: src/client/application/geary-args.vala:56 msgid "Please report comments, suggestions and bugs to:" msgstr "Пікірлер, ұсыныстар және ақаулықтарды хабарлау жері:" #. i18n: Command line arguments are invalid -#: ../src/client/application/geary-args.vala:61 +#: src/client/application/geary-args.vala:63 #, c-format msgid "Failed to parse command line options: %s\n" msgstr "Командалық жол опцияларын талдау сәтсіз аяқталды: %s\n" -#: ../src/client/application/geary-args.vala:72 +#: src/client/application/geary-args.vala:74 #, c-format msgid "Unrecognized command line option “%s”\n" msgstr "Белгісіз командалық жол опциясы \"%s\"\n" -#: ../src/client/application/geary-controller.vala:57 -msgid "Delete conversation" -msgstr "Сөйлесуді өшіру" - -#: ../src/client/application/geary-controller.vala:58 -msgid "Delete conversation (Shift+Delete)" -msgstr "Сөйлесуді өшіру (Shift+Delete)" - -#: ../src/client/application/geary-controller.vala:59 -msgid "Delete conversations (Shift+Delete)" -msgstr "Сөйлесулерді өшіру (Shift+Delete)" - -#. This refers to the action ("move email to the trash"), not the Trash folder itself -#: ../src/client/application/geary-controller.vala:63 -msgid "Move conversation to Trash (Delete, Backspace)" -msgstr "Сөйлесуді қоқыс шелегіне тастау (Delete, Backspace)" - -#: ../src/client/application/geary-controller.vala:64 -msgid "Move conversations to Trash (Delete, Backspace)" -msgstr "Сөйлесудерді қоқыс шелегіне тастау (Delete, Backspace)" - -#. This refers to the action ("archive an email"), not the Archive folder itself -#: ../src/client/application/geary-controller.vala:68 -msgid "_Archive" -msgstr "_Архив" - -#: ../src/client/application/geary-controller.vala:69 -msgid "Archive conversation (A)" -msgstr "Сөйлесуді архивтеу (A)" - -#: ../src/client/application/geary-controller.vala:70 -msgid "Archive conversations (A)" -msgstr "Сөйлесулерді архивтеу (A)" - -#: ../src/client/application/geary-controller.vala:73 -msgid "Mark as S_pam" -msgstr "С_пам етіп белгілеу" - -#: ../src/client/application/geary-controller.vala:74 -msgid "Mark as not S_pam" -msgstr "С_пам емес етіп белгілеу" - -#: ../src/client/application/geary-controller.vala:76 -#: ../src/client/application/geary-controller.vala:448 -msgid "Mark conversation" -msgstr "Сөйлесуді белгілеу" - -#: ../src/client/application/geary-controller.vala:77 -msgid "Mark conversations" -msgstr "Сөйлесулерді белгілеу" - -#: ../src/client/application/geary-controller.vala:78 -msgid "Add label to conversation" -msgstr "Сөйлесуге белгіні қосу" - -#: ../src/client/application/geary-controller.vala:79 -msgid "Add label to conversations" -msgstr "Сөйлесулерге белгіні қосу" - -#: ../src/client/application/geary-controller.vala:80 -#: ../src/client/application/geary-controller.vala:487 -msgid "Move conversation" -msgstr "Сөйлесуді жылжыту" - -#: ../src/client/application/geary-controller.vala:81 -msgid "Move conversations" -msgstr "Сөйлесулерді жылжыту" - -#: ../src/client/application/geary-controller.vala:450 -msgid "_Mark as…" -msgstr "Қалайша _белгілеу…" - -#: ../src/client/application/geary-controller.vala:456 -msgid "Mark as _Read" -msgstr "Оқ_ылған етіп белгілеу" - -#: ../src/client/application/geary-controller.vala:462 -msgid "Mark as _Unread" -msgstr "Оқыл_маған етіп белгілеу" - -#: ../src/client/application/geary-controller.vala:468 -msgid "_Star" -msgstr "_Жұлдызшаны орнату" - -#: ../src/client/application/geary-controller.vala:473 -msgid "U_nstar" -msgstr "Жұлдызшаны алы_п тастау" - -#: ../src/client/application/geary-controller.vala:483 -msgid "Add label" -msgstr "Белгіні қосу" - -#: ../src/client/application/geary-controller.vala:484 -msgid "_Label" -msgstr "Ж_азу" - -#: ../src/client/application/geary-controller.vala:488 -msgid "_Move" -msgstr "Жы_лжыту" - -#: ../src/client/application/geary-controller.vala:492 -msgid "Compose new message (Ctrl+N, N)" -msgstr "Жаңа хатты жазу (Ctrl+N, N)" - -#: ../src/client/application/geary-controller.vala:496 -#: ../ui/conversation-email-menus.ui.h:1 -msgid "_Reply" -msgstr "Жауап бе_ру" - -#: ../src/client/application/geary-controller.vala:497 -msgid "Reply (Ctrl+R, R)" -msgstr "Жауап беру (Ctrl+R, R)" - -#: ../src/client/application/geary-controller.vala:501 -msgid "R_eply All" -msgstr "Барлығына жауап б_еру" - -#: ../src/client/application/geary-controller.vala:502 -msgid "Reply all (Ctrl+Shift+R, Shift+R)" -msgstr "Барлығына жауап беру (Ctrl+Shift+R, Shift+R)" - -#: ../src/client/application/geary-controller.vala:507 -#: ../ui/conversation-email-menus.ui.h:3 -msgid "_Forward" -msgstr "Қа_йта бағдарлау" - -#: ../src/client/application/geary-controller.vala:508 -msgid "Forward (Ctrl+L, F)" -msgstr "Қайта бағдарлау (Ctrl+L, F)" - -#: ../src/client/application/geary-controller.vala:538 -msgid "Empty _Spam…" -msgstr "_Спам бумасын босату…" - -#: ../src/client/application/geary-controller.vala:542 -msgid "Empty _Trash…" -msgstr "Қ_оқыс шелегін босату…" - -#. No callback is connected, since we bind the toggle button to the search bar visibility -#: ../src/client/application/geary-controller.vala:574 -msgid "Toggle search bar" -msgstr "Іздеу панелін іске қосу/сөндіру" - -#. No callback is connected, since we bind the toggle button to the find bar visibility -#: ../src/client/application/geary-controller.vala:579 -msgid "Toggle find bar" -msgstr "Іздеу панелін іске қосу/сөндіру" - -#: ../src/client/application/geary-controller.vala:757 +#: src/client/application/geary-controller.vala:719 msgid "Unable to store server trust exception" msgstr "Серверге сену ережесін сақтау қатесі" -#: ../src/client/application/geary-controller.vala:992 +#: src/client/application/geary-controller.vala:970 msgid "Your settings are insecure" msgstr "Сіздің баптауларыңыз қауіпсіз емес" -#: ../src/client/application/geary-controller.vala:993 +#: src/client/application/geary-controller.vala:971 msgid "" "Your IMAP and/or SMTP settings do not specify SSL or TLS. This means your " "username and password could be read by another person on the network. Are " @@ -529,29 +567,17 @@ "Бұл дегеніміз, сіздің пайдаланушы аты мен пароліңіз желідегі басқа адам біле " "алады. Осыны жасауды шынымен қалайсыз ба?" -#: ../src/client/application/geary-controller.vala:994 +#: src/client/application/geary-controller.vala:972 msgid "Co_ntinue" msgstr "Жалғ_астыру" -#: ../src/client/application/geary-controller.vala:1041 -msgid "Error connecting to the server" -msgstr "Серверге байланысу қатесі" - -#: ../src/client/application/geary-controller.vala:1042 -msgid "" -"Geary encountered an error while connecting to the server. Please try again " -"in a few moments." -msgstr "" -"Geary серверге байланысу кезінде қате орын алды. Біраз уақыттан кейін " -"қайталап көріңіз." - #. / Displayed in the space-limited status bar when a message fails to be sent due to error. -#: ../src/client/application/geary-controller.vala:1079 -#: ../src/client/components/status-bar.vala:29 +#: src/client/application/geary-controller.vala:1076 +#: src/client/components/status-bar.vala:29 msgid "Error sending email" msgstr "Хатты жіберу сәтсіз" -#: ../src/client/application/geary-controller.vala:1080 +#: src/client/application/geary-controller.vala:1077 msgid "" "Geary encountered an error sending an email. If the problem persists, " "please manually delete the email from your Outbox folder." @@ -561,12 +587,12 @@ #. Displayed in the space-limited status bar when a message fails to be uploaded #. to Sent Mail after being sent. -#: ../src/client/application/geary-controller.vala:1084 -#: ../src/client/components/status-bar.vala:33 +#: src/client/application/geary-controller.vala:1081 +#: src/client/components/status-bar.vala:33 msgid "Error saving sent mail" msgstr "Жіберліген хаттын сақтау сәтсіз" -#: ../src/client/application/geary-controller.vala:1085 +#: src/client/application/geary-controller.vala:1082 msgid "" "Geary encountered an error saving a sent message to Sent Mail. The message " "will stay in your Outbox folder until you delete it." @@ -574,19 +600,19 @@ "Geary жіберілген хатты Жіберілген хаттар бумасына сақтау кезінде қате орын " "алды. Бұл хат сіз оны қолмен өшірілгенге дейін Шығыс бумасында қала береді." -#: ../src/client/application/geary-controller.vala:1154 +#: src/client/application/geary-controller.vala:1149 msgid "Labels" msgstr "Белгілер" #. give the user two options: reset the Account local store, or exit Geary. A third #. could be done to leave the Account in an unopened state, but we don't currently #. have provisions for that. -#: ../src/client/application/geary-controller.vala:1166 +#: src/client/application/geary-controller.vala:1161 #, c-format msgid "Unable to open the database for %s" msgstr "%s үшін дерекқорды ашу мүмкін емес" -#: ../src/client/application/geary-controller.vala:1167 +#: src/client/application/geary-controller.vala:1162 #, c-format msgid "" "There was an error opening the local mail database for this account. This is " @@ -610,20 +636,20 @@ "Дерекқорды қайта құрастыру барлық жергілікті пошта хаттар және салынымдарын " "жояды. Сервердегі поштаға әсері болмайды." -#: ../src/client/application/geary-controller.vala:1169 +#: src/client/application/geary-controller.vala:1164 msgid "_Rebuild" msgstr "Қа_йта құрастыру" -#: ../src/client/application/geary-controller.vala:1169 +#: src/client/application/geary-controller.vala:1164 msgid "E_xit" msgstr "_Шығу" -#: ../src/client/application/geary-controller.vala:1178 +#: src/client/application/geary-controller.vala:1173 #, c-format msgid "Unable to rebuild database for “%s”" msgstr "\"%s\" үшін дерекқорды қайта құрастыру мүмкін емес" -#: ../src/client/application/geary-controller.vala:1179 +#: src/client/application/geary-controller.vala:1174 #, c-format msgid "" "Error during rebuild:\n" @@ -636,14 +662,14 @@ #. some other problem opening the account ... as with other flow path, can't run #. Geary today with an account in unopened state, so have to exit -#: ../src/client/application/geary-controller.vala:1201 -#: ../src/client/application/geary-controller.vala:1211 -#: ../src/client/application/geary-controller.vala:1222 +#: src/client/application/geary-controller.vala:1196 +#: src/client/application/geary-controller.vala:1206 +#: src/client/application/geary-controller.vala:1217 #, c-format msgid "Unable to open local mailbox for %s" msgstr "%s үшін жергілікті эл. пошта жәшігін ашу мүмкін емес" -#: ../src/client/application/geary-controller.vala:1202 +#: src/client/application/geary-controller.vala:1197 #, c-format msgid "" "There was an error opening the local mail database for this account. This is " @@ -662,7 +688,7 @@ "\n" "%s" -#: ../src/client/application/geary-controller.vala:1212 +#: src/client/application/geary-controller.vala:1207 msgid "" "The version number of the local mail database is formatted for a newer " "version of Geary. Unfortunately, the database cannot be “rolled back” to " @@ -677,7 +703,7 @@ "\n" "Geary қолданбасының соңғы нұсқасын орнатып, қайталап көріңіз." -#: ../src/client/application/geary-controller.vala:1223 +#: src/client/application/geary-controller.vala:1218 msgid "" "There was an error opening the local account. This is probably due to " "connectivity issues.\n" @@ -689,15 +715,15 @@ "\n" "Желілік байланысыңызды тексеріп, Geary қолданбасын қайта іске қосыңыз." -#: ../src/client/application/geary-controller.vala:1999 +#: src/client/application/geary-controller.vala:2038 msgid "Undo move (Ctrl+Z)" msgstr "Жылжытуды болдырмау (Ctrl+Z)" -#: ../src/client/application/geary-controller.vala:2009 +#: src/client/application/geary-controller.vala:2048 msgid "Are you sure you want to open these attachments?" msgstr "Бұл салынымдарды ашуды шынымен қалайсыз ба?" -#: ../src/client/application/geary-controller.vala:2010 +#: src/client/application/geary-controller.vala:2049 msgid "" "Attachments may cause damage to your system if opened. Only open files from " "trusted sources." @@ -705,200 +731,430 @@ "Салынымдар ашылса, жүйеңіздге зақым келтіруі мүмкін. Тек сенімді " "жіберушілерден салынымдарды ашыңыз." -#: ../src/client/application/geary-controller.vala:2011 +#: src/client/application/geary-controller.vala:2050 msgid "Don’t _ask me again" msgstr "М_ені қайта сұрамау" -#: ../src/client/application/geary-controller.vala:2121 +#: src/client/application/geary-controller.vala:2146 #, c-format msgid "A file named “%s” already exists. Do you want to replace it?" msgstr "\"%s\" деп аталатын файл бар болып тұр. Оны алмастыруды қалайсыз ба?" -#: ../src/client/application/geary-controller.vala:2123 +#: src/client/application/geary-controller.vala:2148 #, c-format msgid "" "The file already exists in “%s”. Replacing it will overwrite its contents." msgstr "" "Файл \"%s\" ішінде бар болып тұр. Оны ауыстырсаңыз, құрамы үстінен жазылады." -#: ../src/client/application/geary-controller.vala:2126 +#: src/client/application/geary-controller.vala:2151 msgid "_Replace" msgstr "А_лмастыру" -#. Find out what to do with the inline composers. -#. TODO: Remove this in favor of automatically saving drafts -#: ../src/client/application/geary-controller.vala:2374 -msgid "Close open draft messages?" -msgstr "Ашық шимай хаттарды жабу керек пе?" +#: src/client/application/geary-controller.vala:2391 +msgid "Close the draft message?" +msgid_plural "Close all draft messages?" +msgstr[0] "Ашық шимай хаттарды жабу керек пе?" -#: ../src/client/application/geary-controller.vala:2496 +#: src/client/application/geary-controller.vala:2517 #, c-format msgid "Empty all email from your %s folder?" msgstr "Барлық хаттарды сіздің %s бумаңыздан босату керек пе?" -#: ../src/client/application/geary-controller.vala:2497 +#: src/client/application/geary-controller.vala:2518 msgid "This removes the email from Geary and your email server." msgstr "" "Бұл әрекет бұл эл. пошта хатын Geary ішінен және эл. пошта серверінен де " "өшіреді." -#: ../src/client/application/geary-controller.vala:2498 +#: src/client/application/geary-controller.vala:2519 msgid "This cannot be undone." msgstr "Бұны болдырмау мүмкін емес болады." -#: ../src/client/application/geary-controller.vala:2499 +#: src/client/application/geary-controller.vala:2520 #, c-format msgid "Empty %s" msgstr "%s босату" -#: ../src/client/application/geary-controller.vala:2516 +#: src/client/application/geary-controller.vala:2537 #, c-format msgid "Error emptying %s" msgstr "%s босату сәтсіз аяқталды" -#: ../src/client/application/geary-controller.vala:2546 +#: src/client/application/geary-controller.vala:2569 msgid "Do you want to permanently delete this message?" msgid_plural "Do you want to permanently delete these messages?" msgstr[0] "Бұл хат(тар)ды толығымен өшіруді қалайсыз ба?" -#: ../src/client/application/geary-controller.vala:2548 +#: src/client/application/geary-controller.vala:2571 msgid "Delete" msgstr "Өшіру" -#: ../src/client/application/geary-controller.vala:2580 -msgid "Undo archive (Ctrl+Z)" -msgstr "Архивтеуді болдырмау (Ctrl+Z)" - -#: ../src/client/application/geary-controller.vala:2595 +#: src/client/application/geary-controller.vala:2585 msgid "Undo trash (Ctrl+Z)" msgstr "Қоқыс шелегіне тастауды болдырмау (Ctrl+Z)" -#: ../src/client/application/geary-controller.vala:2649 +#: src/client/application/geary-controller.vala:2635 +msgid "Undo archive (Ctrl+Z)" +msgstr "Архивтеуді болдырмау (Ctrl+Z)" + +#: src/client/application/geary-controller.vala:2680 msgid "Undo (Ctrl+Z)" msgstr "Болдырмау (Ctrl+Z)" -#: ../src/client/application/geary-controller.vala:2780 +#. Translators: The label for an in-app notification. The +#. string substitution is a list of recipients of the email. +#: src/client/application/geary-controller.vala:2757 +#, c-format +msgid "Successfully sent mail to %s." +msgstr "" + +#: src/client/application/geary-controller.vala:2838 msgid "Failed to open default text editor." msgstr "Үнсіз келісім бойынша мәтіндік түзеткішін ашу сәтсіз аяқталды." -#: ../src/client/components/main-window.vala:389 +#. Tooltips +#: src/client/components/main-toolbar.vala:69 +msgid "Delete conversation (Shift+Delete)" +msgstr "Сөйлесуді өшіру (Shift+Delete)" + +#: src/client/components/main-toolbar.vala:70 +msgid "Delete conversations (Shift+Delete)" +msgstr "Сөйлесулерді өшіру (Shift+Delete)" + +#: src/client/components/main-toolbar.vala:71 +msgid "Move conversation to Trash (Delete, Backspace)" +msgstr "Сөйлесуді қоқыс шелегіне тастау (Delete, Backspace)" + +#: src/client/components/main-toolbar.vala:72 +msgid "Move conversations to Trash (Delete, Backspace)" +msgstr "Сөйлесудерді қоқыс шелегіне тастау (Delete, Backspace)" + +#: src/client/components/main-toolbar.vala:73 +msgid "Archive conversation (A)" +msgstr "Сөйлесуді архивтеу (A)" + +#: src/client/components/main-toolbar.vala:74 +msgid "Archive conversations (A)" +msgstr "Сөйлесулерді архивтеу (A)" + +#: src/client/components/main-toolbar.vala:75 +msgid "Mark conversation" +msgstr "Сөйлесуді белгілеу" + +#: src/client/components/main-toolbar.vala:76 +msgid "Mark conversations" +msgstr "Сөйлесулерді белгілеу" + +#: src/client/components/main-toolbar.vala:77 +msgid "Add label to conversation" +msgstr "Сөйлесуге белгіні қосу" + +#: src/client/components/main-toolbar.vala:78 +msgid "Add label to conversations" +msgstr "Сөйлесулерге белгіні қосу" + +#: src/client/components/main-toolbar.vala:79 +msgid "Move conversation" +msgstr "Сөйлесуді жылжыту" + +#: src/client/components/main-toolbar.vala:80 +msgid "Move conversations" +msgstr "Сөйлесулерді жылжыту" + +#: src/client/components/main-window.vala:437 #, c-format msgid "%s (%d)" msgstr "%s (%d)" -#: ../src/client/components/search-bar.vala:8 -#: ../src/client/folder-list/folder-list-search-branch.vala:38 -#: ../src/engine/api/geary-special-folder-type.vala:51 +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:54 +#, c-format +msgid "Problem connecting to incoming server for %s" +msgstr "%s үшін кіріс серверіне байланысу қатесі" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:56 +#: src/client/components/main-window-info-bar.vala:64 +#, c-format +msgid "" +"Could not connect to %s, check your Internet access and the server name and " +"try again" +msgstr "" + +#: src/client/components/main-window-info-bar.vala:57 +#: src/client/components/main-window-info-bar.vala:66 +msgid "Retry connecting now" +msgstr "" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:62 +#, c-format +msgid "Problem connecting to outgoing server for %s" +msgstr "%s үшін шығыс серверіне байланысу қатесі" + +#: src/client/components/main-window-info-bar.vala:65 +msgid "Try reconnecting now" +msgstr "" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:71 +#, c-format +msgid "Problem with connection to incoming server for %s" +msgstr "" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:73 +#: src/client/components/main-window-info-bar.vala:81 +#, c-format +msgid "Network error talking to %s, check your Internet access and try again" +msgstr "" + +#: src/client/components/main-window-info-bar.vala:74 +#: src/client/components/main-window-info-bar.vala:82 +#: src/client/components/main-window-info-bar.vala:90 +#: src/client/components/main-window-info-bar.vala:98 +#: src/client/components/main-window-info-bar.vala:119 +msgid "Try reconnecting" +msgstr "" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:79 +#, c-format +msgid "Problem with connection to outgoing server for %s" +msgstr "%s үшін шығыс серверіне байланысу қатесі" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:87 +#, c-format +msgid "Problem communicating with incoming server for %s" +msgstr "" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:89 +#, c-format +msgid "" +"Geary did not understand a message from %s or vice versa, please file a bug " +"report" +msgstr "" + +#: src/client/components/main-window-info-bar.vala:94 +msgid "Problem communicating with outgoing mail server" +msgstr "" + +#. Translators: First string substitution is the server +#. name, second is the account name +#: src/client/components/main-window-info-bar.vala:97 +#, c-format +msgid "" +"Could not communicate with %s for %s, check the server name and try again in " +"a moment" +msgstr "" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:103 +#, c-format +msgid "Incoming mail server password required for %s" +msgstr "" + +#: src/client/components/main-window-info-bar.vala:104 +msgid "Messages cannot be received without the correct password." +msgstr "" + +#: src/client/components/main-window-info-bar.vala:105 +msgid "Retry receiving email, you will be prompted for a password" +msgstr "" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:110 +#, c-format +msgid "Outgoing mail server password required for %s" +msgstr "" + +#: src/client/components/main-window-info-bar.vala:111 +msgid "Messages cannot be sent without the correct password." +msgstr "" + +#: src/client/components/main-window-info-bar.vala:112 +msgid "Retry sending queued messages, you will be prompted for a password" +msgstr "" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:117 +#, c-format +msgid "A problem occurred checking mail for %s" +msgstr "" + +#: src/client/components/main-window-info-bar.vala:118 +#: src/client/components/main-window-info-bar.vala:125 +msgid "Something went wrong, please file a bug report if the problem persists" +msgstr "" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:124 +#, c-format +msgid "A problem occurred sending mail for %s" +msgstr "" + +#: src/client/components/main-window-info-bar.vala:126 +msgid "Retry sending queued messages" +msgstr "" + +#: src/client/components/main-window-info-bar.vala:137 +msgid "A database problem has occurred" +msgstr "" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:139 +#, c-format +msgid "Messages for %s must be downloaded again." +msgstr "" + +#: src/client/components/main-window-info-bar.vala:152 +msgid "Geary has encountered a problem" +msgstr "Geary мәселеге тап болды" + +#: src/client/components/main-window-info-bar.vala:153 +msgid "" +"Please check the technical details and report the problem if it persists." +msgstr "" + +#: src/client/components/main-window-info-bar.vala:161 +msgid "_Details" +msgstr "_Ақпараты" + +#: src/client/components/main-window-info-bar.vala:162 +msgid "View technical details about the error" +msgstr "Қате туралы техникалық ақпаратты қарау" + +#: src/client/components/main-window-info-bar.vala:166 +msgid "_Retry" +msgstr "Қа_йталау" + +#: src/client/components/main-window-info-bar.vala:253 +msgid "Details" +msgstr "Ақпараты" + +#: src/client/components/main-window-info-bar.vala:266 +#: src/client/components/stock.vala:23 +msgid "_Close" +msgstr "_Жабу" + +#: src/client/components/main-window-info-bar.vala:270 +msgid "Copy to Clipboard" +msgstr "Алмасу буферіне көшіріп алу" + +#: src/client/components/main-window-info-bar.vala:273 +msgid "" +"Copy technical details to clipboard for pasting into an email or bug report" +msgstr "" + +#: src/client/components/search-bar.vala:8 +#: src/client/folder-list/folder-list-search-branch.vala:38 +#: src/engine/api/geary-special-folder-type.vala:51 msgid "Search" msgstr "Іздеу" #. Search entry. -#: ../src/client/components/search-bar.vala:23 +#: src/client/components/search-bar.vala:23 msgid "Search all mail in account for keywords (Ctrl+S)" msgstr "Тіркелгінің барлық хаттарынан кілтсөзді іздеу (Ctrl+S)" -#: ../src/client/components/search-bar.vala:100 +#: src/client/components/search-bar.vala:100 #, c-format msgid "Indexing %s account" msgstr "%s тіркелгісін индекстеу" -#: ../src/client/components/search-bar.vala:111 -#: ../src/client/folder-list/folder-list-search-branch.vala:39 +#: src/client/components/search-bar.vala:111 +#: src/client/folder-list/folder-list-search-branch.vala:39 #, c-format msgid "Search %s account" msgstr "%s тіркелгісінен іздеу" #. / Displayed in the space-limited status bar while a message is in the process of being sent. -#: ../src/client/components/status-bar.vala:26 +#: src/client/components/status-bar.vala:26 msgid "Sending…" msgstr "Жіберуде…" -#: ../src/client/components/stock.vala:18 ../ui/account_cannot_remove.glade.h:3 +#: src/client/components/stock.vala:18 ui/account_cannot_remove.glade:74 msgid "_OK" msgstr "О_К" -#: ../src/client/components/stock.vala:19 ../ui/edit_alternate_emails.glade.h:3 -#: ../ui/password-dialog.glade.h:5 ../ui/remove_confirm.glade.h:5 +#: src/client/components/stock.vala:19 ui/edit_alternate_emails.glade:160 +#: ui/password-dialog.glade:196 ui/remove_confirm.glade:155 msgid "_Cancel" msgstr "Ба_с тарту" -#: ../src/client/components/stock.vala:21 ../ui/gtk/menus.ui.h:6 +#: src/client/components/stock.vala:21 ui/gtk/menus.ui:34 msgid "_About" msgstr "Осы тур_алы" -#: ../src/client/components/stock.vala:23 -msgid "_Close" -msgstr "_Жабу" - -#: ../src/client/components/stock.vala:24 +#: src/client/components/stock.vala:24 msgid "_Discard" msgstr "_Елемеу" -#: ../src/client/components/stock.vala:25 ../ui/gtk/menus.ui.h:5 +#: src/client/components/stock.vala:25 ui/gtk/menus.ui:29 msgid "_Help" msgstr "_Көмек" -#: ../src/client/components/stock.vala:26 ../ui/conversation-email-menus.ui.h:9 +#: src/client/components/stock.vala:26 ui/conversation-email-menus.ui:77 msgid "_Open" msgstr "_Ашу" -#: ../src/client/components/stock.vala:27 ../ui/gtk/menus.ui.h:3 +#: src/client/components/stock.vala:27 ui/gtk/menus.ui:17 msgid "_Preferences" msgstr "Ба_птаулар" -#: ../src/client/components/stock.vala:28 ../ui/conversation-email-menus.ui.h:7 +#. Translators: Menu item to print a single, specific message +#: src/client/components/stock.vala:28 ui/conversation-email-menus.ui:64 msgid "_Print…" msgstr "Бас_паға шығару…" -#: ../src/client/components/stock.vala:29 ../ui/gtk/menus.ui.h:7 +#: src/client/components/stock.vala:29 ui/gtk/menus.ui:38 msgid "_Quit" msgstr "_Шығу" -#: ../src/client/components/stock.vala:30 ../ui/remove_confirm.glade.h:6 +#: src/client/components/stock.vala:30 ui/remove_confirm.glade:170 msgid "_Remove" msgstr "Ө_шіру" -#: ../src/client/components/stock.vala:32 +#: src/client/components/stock.vala:32 msgid "_Keep" msgstr "Ұс_тау" -#: ../src/client/composer/composer-link-popover.vala:150 +#: src/client/composer/composer-link-popover.vala:149 msgid "Link URL is not correctly formatted, e.g. http://example.com" msgstr "Сілтеме URL-ы дұрыс пішімделмеген, мыс. http://example.com" -#: ../src/client/composer/composer-link-popover.vala:157 +#: src/client/composer/composer-link-popover.vala:156 msgid "Invalid link URL" msgstr "Бұл сілтеменің URL-ы қате" -#: ../src/client/composer/composer-link-popover.vala:157 +#: src/client/composer/composer-link-popover.vala:156 msgid "Invalid email address" msgstr "Эл. пошта адресі қате" -#: ../src/client/composer/composer-widget.vala:154 +#: src/client/composer/composer-widget.vala:158 msgid "Saved" msgstr "Сақталды" -#: ../src/client/composer/composer-widget.vala:155 +#: src/client/composer/composer-widget.vala:159 msgid "Saving" msgstr "Сақталуда" -#: ../src/client/composer/composer-widget.vala:156 +#: src/client/composer/composer-widget.vala:160 msgid "Error saving" msgstr "Сақтау қатесі" -#: ../src/client/composer/composer-widget.vala:157 +#: src/client/composer/composer-widget.vala:161 msgid "Press Backspace to delete quote" msgstr "Дәйексөзді өшіру үшін Backspace басыңыз" -#: ../src/client/composer/composer-widget.vala:158 -msgid "New Message" -msgstr "Жаңа хат" - #. Translators: This is list of keywords, separated by pipe ("|") #. characters, that suggest an attachment; since this is full-word #. checking, include all variants of each word. No spaces are #. allowed. -#: ../src/client/composer/composer-widget.vala:167 +#: src/client/composer/composer-widget.vala:170 msgid "" "attach|attaching|attaches|attachment|attachments|attached|enclose|enclosed|" "enclosing|encloses|enclosure|enclosures" @@ -907,28 +1163,37 @@ "attachment|attachments|attached|enclose|enclosed|enclosing|encloses|" "enclosure|enclosures" -#: ../src/client/composer/composer-widget.vala:1122 -#: ../src/client/composer/composer-widget.vala:1141 -msgid "Do you want to discard this message?" -msgstr "Бұл хатты елемеуді қалайсыз ба?" +#. Translators: This dialog text is displayed to the +#. user when closing a composer where the options are +#. Keep, Discard or Cancel. +#: src/client/composer/composer-widget.vala:1102 +msgid "Do you want to keep or discard this draft message?" +msgstr "Бұл хатты сақтауды немесе елемеуді қалайсыз ба?" + +#. Translators: This dialog text is displayed to the +#. user when closing a composer where the options are +#. only Discard or Cancel. +#: src/client/composer/composer-widget.vala:1130 +msgid "Do you want to discard this draft message?" +msgstr "Бұл шимай хатты елемеуді қалайсыз ба?" -#: ../src/client/composer/composer-widget.vala:1245 +#: src/client/composer/composer-widget.vala:1247 msgid "Send message with an empty subject and body?" msgstr "Хатты бос тақырыппен және бос денесімен жіберу керек пе?" -#: ../src/client/composer/composer-widget.vala:1247 +#: src/client/composer/composer-widget.vala:1249 msgid "Send message with an empty subject?" msgstr "Хатты бос тақырыппен жіберу керек пе?" -#: ../src/client/composer/composer-widget.vala:1249 +#: src/client/composer/composer-widget.vala:1251 msgid "Send message with an empty body?" msgstr "Хатты бос денесімен жіберу керек пе?" -#: ../src/client/composer/composer-widget.vala:1253 +#: src/client/composer/composer-widget.vala:1255 msgid "Send message without an attachment?" msgstr "Хатты салынымсыз жіберу керек пе?" -#: ../src/client/composer/composer-widget.vala:1515 +#: src/client/composer/composer-widget.vala:1560 #, c-format msgid "“%s” already attached for delivery." msgstr "\"%s\" салыным ретінде белгіленіп тұр." @@ -938,168 +1203,214 @@ #. description of the document type, the second will #. be a human-friendly size string. For example: #. Document (100.9MB) -#: ../src/client/composer/composer-widget.vala:1523 -#: ../src/client/conversation-viewer/conversation-email.vala:138 +#: src/client/composer/composer-widget.vala:1568 +#: src/client/conversation-viewer/conversation-email.vala:136 #, c-format msgid "%s (%s)" msgstr "%s (%s)" -#: ../src/client/composer/composer-widget.vala:1560 +#: src/client/composer/composer-widget.vala:1605 #, c-format msgid "“%s” could not be found." msgstr "\"%s\" табу мүмкін емес." -#: ../src/client/composer/composer-widget.vala:1566 +#: src/client/composer/composer-widget.vala:1611 #, c-format msgid "“%s” is a folder." msgstr "\"%s\" бума болып тұр." -#: ../src/client/composer/composer-widget.vala:1572 +#: src/client/composer/composer-widget.vala:1617 #, c-format msgid "“%s” is an empty file." msgstr "\"%s\" бос файл болып тұр." -#: ../src/client/composer/composer-widget.vala:1585 +#: src/client/composer/composer-widget.vala:1630 #, c-format msgid "“%s” could not be opened for reading." msgstr "\"%s\" оқу үшін ашу мүмкін емес." -#: ../src/client/composer/composer-widget.vala:1593 +#: src/client/composer/composer-widget.vala:1638 msgid "Cannot add attachment" msgstr "Салынымды қосу мүмкін емес" -#: ../src/client/composer/composer-widget.vala:1645 +#: src/client/composer/composer-widget.vala:1687 msgid "To: " -msgstr "Кімге:" +msgstr "Кімге: " -#: ../src/client/composer/composer-widget.vala:1648 +#: src/client/composer/composer-widget.vala:1690 msgid "Cc: " -msgstr "Көшірмесі:" +msgstr "Көшірмесі: " -#: ../src/client/composer/composer-widget.vala:1651 +#: src/client/composer/composer-widget.vala:1693 msgid "Bcc: " -msgstr "Жасырын көшірме:" +msgstr "Жасырын көшірме: " -#: ../src/client/composer/composer-widget.vala:1654 +#: src/client/composer/composer-widget.vala:1696 msgid "Reply-To: " -msgstr "Жауап беру:" +msgstr "Жауап беру: " -#: ../src/client/composer/composer-widget.vala:1786 +#: src/client/composer/composer-widget.vala:1834 msgid "Select Color" msgstr "Түсті таңдаңыз" #. Displayed in the From dropdown to indicate an "alternate email address" #. for an account. The first printf argument will be the alternate email #. address, and the second will be the account's primary email address. -#: ../src/client/composer/composer-widget.vala:1986 +#: src/client/composer/composer-widget.vala:2024 #, c-format msgid "%1$s via %2$s" msgstr "%1$s, %2$s арқылы" #. Composer label (with mnemonic underscore) for the account selector #. when choosing what address to send a message from. -#: ../src/client/composer/composer-widget.vala:2028 +#: src/client/composer/composer-widget.vala:2082 msgid "_From:" msgstr "_Кімнен:" #. Translators: This is the name of the file chooser filter #. when inserting an image in the composer. -#: ../src/client/composer/composer-widget.vala:2252 +#: src/client/composer/composer-widget.vala:2307 msgid "Images" msgstr "Суреттер" -#: ../src/client/composer/spell-check-popover.vala:117 +#: src/client/composer/composer-window.vala:14 +msgid "New Message" +msgstr "Жаңа хат" + +#: src/client/composer/spell-check-popover.vala:117 msgid "Remove this language from the preferred list" msgstr "Бұл тілді таңдамалы тілдер тізімінен өшіру" -#: ../src/client/composer/spell-check-popover.vala:121 +#: src/client/composer/spell-check-popover.vala:121 msgid "Add this language to the preferred list" msgstr "Бұл тілді таңдамалы тілдер тізіміне қосу" -#: ../src/client/composer/spell-check-popover.vala:217 +#: src/client/composer/spell-check-popover.vala:217 msgid "Search for more languages" msgstr "Көбірек тілдерді іздеу" -#: ../src/client/conversation-list/formatted-conversation-data.vala:11 +#: src/client/conversation-list/conversation-list-view.vala:283 +msgid "Delete conversation" +msgstr "Сөйлесуді өшіру" + +#: src/client/conversation-list/conversation-list-view.vala:286 +#: ui/main-toolbar-menus.ui:16 +msgid "Mark as _Read" +msgstr "Оқ_ылған етіп белгілеу" + +#: src/client/conversation-list/conversation-list-view.vala:289 +#: ui/main-toolbar-menus.ui:20 +msgid "Mark as _Unread" +msgstr "Оқыл_маған етіп белгілеу" + +#: src/client/conversation-list/conversation-list-view.vala:292 +#: ui/main-toolbar-menus.ui:28 +msgid "U_nstar" +msgstr "Жұлдызшаны алы_п тастау" + +#: src/client/conversation-list/conversation-list-view.vala:294 +#: ui/main-toolbar-menus.ui:24 +msgid "_Star" +msgstr "_Жұлдызшаны орнату" + +#. Translators: Menu item to reply to a specific message. +#: src/client/conversation-list/conversation-list-view.vala:297 +#: ui/conversation-email-menus.ui:9 +msgid "_Reply" +msgstr "Жауап бе_ру" + +#: src/client/conversation-list/conversation-list-view.vala:298 +msgid "R_eply All" +msgstr "Барлығына жауап б_еру" + +#. Translators: Menu item to forward a specific message. +#: src/client/conversation-list/conversation-list-view.vala:299 +#: ui/conversation-email-menus.ui:21 +msgid "_Forward" +msgstr "Қа_йта бағдарлау" + +#: src/client/conversation-list/formatted-conversation-data.vala:11 msgid "Me" msgstr "Мен" #. Translators: This is the file type displayed for #. attachments with unknown file types. -#: ../src/client/conversation-viewer/conversation-email.vala:124 +#: src/client/conversation-viewer/conversation-email.vala:122 msgid "Unknown" msgstr "Белгісіз" +#: src/client/conversation-viewer/conversation-message.vala:60 +msgid "This email address may have been forged" +msgstr "" + #. Preview headers #. Translators: This is displayed in place of the from address #. when the message has no from address. -#: ../src/client/conversation-viewer/conversation-message.vala:331 +#: src/client/conversation-viewer/conversation-message.vala:330 msgid "No sender" msgstr "Жіберуші жоқ" #. Translators: This separates multiple 'from' #. addresses in the header preview for a message. -#: ../src/client/conversation-viewer/conversation-message.vala:586 +#: src/client/conversation-viewer/conversation-message.vala:589 msgid ", " msgstr ", " #. Translators: This string is used as the HTML IMG ALT #. attribute value when displaying an inline image in an email #. that did not specify a file name. E.g. ImageCannot remove account " msgstr "" "Тіркелгіні өшіру мүмкін емес " -#: ../ui/account_cannot_remove.glade.h:2 +#: ui/account_cannot_remove.glade:56 msgid "" "A composer window associated with this account is currently open. Send or " "discard the message and try again." @@ -1711,434 +2040,456 @@ "Бұл тіркелгімен сәйкестелген жаңа хатты жазу терезесі ашық болып тұр. Хатты " "жіберіңіз не елемеңіз, одан кейін қайталап көріңіз." -#: ../ui/account_list.glade.h:1 +#: ui/account_list.glade:69 msgid "Add account" msgstr "Тіркелгіні қосу" -#: ../ui/account_list.glade.h:2 +#: ui/account_list.glade:82 msgid "Edit account" msgstr "Тіркелгіні түзету" -#: ../ui/account_list.glade.h:3 +#: ui/account_list.glade:95 msgid "Remove account" msgstr "Тіркелгіні өшіру" -#: ../ui/account_spinner.glade.h:1 +#: ui/account_spinner.glade:41 msgid "Please wait while Geary validates your account." msgstr "Geary тіркелгіңізді тексеріп болғанша дейін күте тұрыңыз." -#: ../ui/certificate_warning_dialog.glade.h:1 +#: ui/certificate_warning_dialog.glade:7 msgid "Untrusted Connection" msgstr "Сенімсіз байланыс" -#: ../ui/certificate_warning_dialog.glade.h:2 +#: ui/certificate_warning_dialog.glade:29 msgid "_Always Trust This Server" msgstr "Бұл серверге әрқ_ашан сену" -#: ../ui/certificate_warning_dialog.glade.h:3 +#: ui/certificate_warning_dialog.glade:43 msgid "_Trust This Server" msgstr "Бұл серверге _сену" -#: ../ui/certificate_warning_dialog.glade.h:4 +#: ui/certificate_warning_dialog.glade:57 msgid "_Don’t Trust This Server" msgstr "_Бұл серверге сенбеу" -#: ../ui/composer-headerbar.ui.h:1 +#: ui/composer-headerbar.ui:17 ui/composer-headerbar.ui:174 msgid "Detach (Ctrl+D)" msgstr "Бөліп жіберу (Ctrl+D)" -#: ../ui/composer-headerbar.ui.h:2 +#: ui/composer-headerbar.ui:57 ui/composer-headerbar.ui:83 msgid "Attach File (Ctrl+T)" msgstr "Файлды қосып жіберу" -#: ../ui/composer-headerbar.ui.h:3 +#: ui/composer-headerbar.ui:107 msgid "Include Original Attachments" msgstr "Бастапқы салынымдарды қосып жіберу" -#: ../ui/composer-headerbar.ui.h:4 -msgid "Send (Ctrl+Enter)" -msgstr "Жіберу (Ctrl+Enter)" - -#: ../ui/composer-headerbar.ui.h:5 +#: ui/composer-headerbar.ui:202 msgid "_Send" msgstr "_Жіберу" -#: ../ui/composer-headerbar.ui.h:6 +#: ui/composer-headerbar.ui:207 +msgid "Send (Ctrl+Enter)" +msgstr "Жіберу (Ctrl+Enter)" + +#: ui/composer-headerbar.ui:230 msgid "Discard and Close" msgstr "Елемеу және жабу" -#: ../ui/composer-headerbar.ui.h:7 +#: ui/composer-headerbar.ui:254 msgid "Save and Close" msgstr "Сақтау және жабу" #. Note that this button and the Update button will never be shown at the same time to the user. -#: ../ui/composer-link-popover.ui.h:2 +#: ui/composer-link-popover.ui:41 msgid "Insert the new link with this URL" msgstr "Бұл URL-мен жаңа сілтемені кірістіру" -#: ../ui/composer-link-popover.ui.h:3 +#: ui/composer-link-popover.ui:52 msgid "Link URL" msgstr "Сілтеме URL-ы" #. Note that this button and the Insert button will never be shown at the same time to the user. -#: ../ui/composer-link-popover.ui.h:5 +#: ui/composer-link-popover.ui:66 msgid "Update this link’s URL" msgstr "Бұл сілтеменің URL-ын жаңарту" -#: ../ui/composer-link-popover.ui.h:6 +#: ui/composer-link-popover.ui:86 msgid "Delete this link" msgstr "Бұл сілтемені өшіру" -#: ../ui/composer-link-popover.ui.h:7 +#: ui/composer-link-popover.ui:106 msgid "Open this link" msgstr "Бұл сілтемені ашу" -#: ../ui/composer-menus.ui.h:1 +#: ui/composer-menus.ui:7 msgid "S_ans Serif" msgstr "S_ans Serif" -#: ../ui/composer-menus.ui.h:2 +#: ui/composer-menus.ui:12 msgid "S_erif" msgstr "S_erif" -#: ../ui/composer-menus.ui.h:3 +#: ui/composer-menus.ui:17 msgid "_Fixed Width" msgstr "Бе_кітілген ені" -#: ../ui/composer-menus.ui.h:4 +#: ui/composer-menus.ui:24 msgid "_Small" msgstr "_Кішкентай" -#: ../ui/composer-menus.ui.h:5 +#: ui/composer-menus.ui:29 msgid "_Medium" msgstr "О_рташа" -#: ../ui/composer-menus.ui.h:6 +#: ui/composer-menus.ui:34 msgid "Lar_ge" msgstr "Үл_кен" -#: ../ui/composer-menus.ui.h:7 +#: ui/composer-menus.ui:41 msgid "C_olor" msgstr "_Түсі" -#: ../ui/composer-menus.ui.h:8 +#: ui/composer-menus.ui:47 ui/composer-menus.ui:62 msgid "_Rich Text" msgstr "_Кеңейтілген пішімдеу" -#: ../ui/composer-menus.ui.h:9 +#: ui/composer-menus.ui:53 ui/composer-menus.ui:68 msgid "Show Extended Fields" msgstr "Кеңейтілген өрістерді көрсету" -#: ../ui/composer-menus.ui.h:10 +#: ui/composer-menus.ui:78 msgid "_Undo" msgstr "Бол_дырмау" -#: ../ui/composer-menus.ui.h:11 +#: ui/composer-menus.ui:82 msgid "_Redo" msgstr "Қай_талау" -#: ../ui/composer-menus.ui.h:12 +#: ui/composer-menus.ui:88 ui/composer-menus.ui:106 msgid "Cu_t" msgstr "Қ_иып алу" -#: ../ui/composer-menus.ui.h:13 ../ui/conversation-message-menus.ui.h:7 +#: ui/composer-menus.ui:92 ui/composer-menus.ui:110 +#: ui/conversation-message-menus.ui:37 msgid "_Copy" msgstr "_Көшіру" -#: ../ui/composer-menus.ui.h:14 +#: ui/composer-menus.ui:96 ui/composer-menus.ui:114 msgid "_Paste" msgstr "Кірі_стіру" -#: ../ui/composer-menus.ui.h:15 -msgctxt "Clipboard paste with rich text" -msgid "Paste _With Formatting" -msgstr "Пішімдеу_мен кірістіру" +#: ui/composer-menus.ui:100 +msgctxt "Clipboard paste as plain text" +msgid "Paste _Without Formatting" +msgstr "Пішімдеу_сіз кірістіру" -#: ../ui/composer-menus.ui.h:16 +#: ui/composer-menus.ui:120 msgid "Select _All" msgstr "Барлығын _таңдау" -#: ../ui/composer-menus.ui.h:17 ../ui/conversation-message-menus.ui.h:9 +#: ui/composer-menus.ui:127 ui/conversation-message-menus.ui:49 msgid "_Inspect…" msgstr "_Бақылау…" #. Address(es) e-mail is to be sent to -#: ../ui/composer-widget.ui.h:2 +#: ui/composer-widget.ui:56 msgid "_To" msgstr "Кі_мге" -#: ../ui/composer-widget.ui.h:3 +#: ui/composer-widget.ui:75 msgid "_Cc" msgstr "Көшірме_сі" -#: ../ui/composer-widget.ui.h:4 +#: ui/composer-widget.ui:130 msgid "_Subject" msgstr "_Тақырып" -#: ../ui/composer-widget.ui.h:5 +#: ui/composer-widget.ui:149 msgid "_Bcc" msgstr "_Жасырын көшірме" -#: ../ui/composer-widget.ui.h:6 +#: ui/composer-widget.ui:179 msgid "_Reply-To" msgstr "Жауап бе_ру" #. Geary account mail will be sent from -#: ../ui/composer-widget.ui.h:8 +#: ui/composer-widget.ui:208 msgid "From" msgstr "Кімнен" -#: ../ui/composer-widget.ui.h:9 +#: ui/composer-widget.ui:293 msgid "Drop files here" msgstr "Файлдарды осында тастау" -#: ../ui/composer-widget.ui.h:10 +#: ui/composer-widget.ui:309 msgid "To add them as attachments" msgstr "Оларды салынымдар ретінде қосу үшін" -#: ../ui/composer-widget.ui.h:11 +#: ui/composer-widget.ui:348 msgid "Undo last edit (Ctrl+Z)" msgstr "Соңғы түзетуді болдырмау (Ctrl+Z)" -#: ../ui/composer-widget.ui.h:12 +#: ui/composer-widget.ui:372 msgid "Redo last edit (Ctrl+Shift+Z)" msgstr "Соңғы түзетуді қайталау (Ctrl+Z)" -#: ../ui/composer-widget.ui.h:13 +#: ui/composer-widget.ui:410 msgid "Bold (Ctrl+B)" msgstr "Жуан (Ctrl+B)" -#: ../ui/composer-widget.ui.h:14 +#: ui/composer-widget.ui:434 msgid "Italic (Ctrl+I)" msgstr "Көлбеу (Ctrl+I)" -#: ../ui/composer-widget.ui.h:15 +#: ui/composer-widget.ui:458 msgid "Underline (Ctrl+U)" msgstr "Асты сызылған (Ctrl+U)" -#: ../ui/composer-widget.ui.h:16 +#: ui/composer-widget.ui:482 msgid "Strikethrough (Ctrl+K)" msgstr "Сызып тастау (Ctrl+K)" -#: ../ui/composer-widget.ui.h:17 +#: ui/composer-widget.ui:520 +msgid "Insert unordered list" +msgstr "" + +#: ui/composer-widget.ui:544 +msgid "Insert ordered list" +msgstr "Реттелген тізімді кірістіру" + +#: ui/composer-widget.ui:582 msgid "Quote text (Ctrl+])" msgstr "Мәтінді дәйексөз қылу (Ctrl+])" -#: ../ui/composer-widget.ui.h:18 +#: ui/composer-widget.ui:606 msgid "Unquote text (Ctrl+[)" msgstr "Мәтінді дәйексөз емес қылу (Ctrl+[)" -#: ../ui/composer-widget.ui.h:19 +#: ui/composer-widget.ui:644 msgid "Insert or update selection link (Ctrl+L)" msgstr "Таңдалғанның сілтемесін кірістіру немесе жаңарту (Ctrl+L)" -#: ../ui/composer-widget.ui.h:20 +#: ui/composer-widget.ui:668 msgid "Insert an image (Ctrl+G)" msgstr "Суретті кірістіру (Ctrl+G)" -#: ../ui/composer-widget.ui.h:21 +#: ui/composer-widget.ui:702 msgid "Remove selection formatting (Ctrl+Space)" msgstr "Таңдалғанның пішімдеуін өшіру (Ctrl+Space)" -#: ../ui/composer-widget.ui.h:22 +#: ui/composer-widget.ui:726 msgid "Select spell checking languages" msgstr "Емлені тексеру тілдерін таңдау" -#: ../ui/conversation-email.ui.h:1 +#: ui/conversation-email.ui:27 msgid "Save all attachments" msgstr "Барлық салынымдарды сақтау" #. Note: The application will never show this button at the same time as unstar_button, one will always be hidden. -#: ../ui/conversation-email.ui.h:3 +#: ui/conversation-email.ui:50 msgid "Mark this message as starred" msgstr "Бұл хабарламаны жұлдызшамен белгілеу" #. Note: The application will never show this button at the same time as star_button, one will always be hidden. -#: ../ui/conversation-email.ui.h:5 +#: ui/conversation-email.ui:72 msgid "Mark this message as not starred" msgstr "Бұл хабарламадан жұлдызшаны алып тастау" -#: ../ui/conversation-email.ui.h:6 +#: ui/conversation-email.ui:95 msgid "Display the message menu" msgstr "Хабарлама мәзірін көрсету" -#: ../ui/conversation-email.ui.h:7 +#: ui/conversation-email.ui:161 msgid "Open selected attachments" msgstr "Таңдалған салынымдарды ашу" -#: ../ui/conversation-email.ui.h:8 +#: ui/conversation-email.ui:178 msgid "Save selected attachments" msgstr "Таңдалған салынымдарды сақтау" -#: ../ui/conversation-email.ui.h:9 +#: ui/conversation-email.ui:195 msgid "Select all attachments" msgstr "Барлық салынымдарды таңдау" -#: ../ui/conversation-email.ui.h:10 +#: ui/conversation-email.ui:240 msgid "Edit Draft" msgstr "Шимайды түзету" -#: ../ui/conversation-email.ui.h:11 +#: ui/conversation-email.ui:267 msgid "Draft message" msgstr "Шимай қағаз хабарламасы" -#: ../ui/conversation-email.ui.h:12 +#: ui/conversation-email.ui:283 msgid "This message has not yet been sent." msgstr "Бұл хат әлі жіберілмеген." -#: ../ui/conversation-email.ui.h:13 -msgid "Try Again" -msgstr "Қайтадан көру" - -#: ../ui/conversation-email.ui.h:14 +#: ui/conversation-email.ui:329 msgid "Message not saved" msgstr "Хат сақталмады" -#: ../ui/conversation-email.ui.h:15 +#: ui/conversation-email.ui:345 msgid "This message was sent, but has not been saved to your account." msgstr "Бұл хат сәтті жіберілді, бірақ сіздің тіркегіңізге сақталмады." -#: ../ui/conversation-email-menus.ui.h:2 +#. Translators: Menu item to reply to a specific message. +#: ui/conversation-email-menus.ui:15 msgid "Reply to _All" msgstr "Б_арлығына жауап беру" -#: ../ui/conversation-email-menus.ui.h:4 +#. Translators: Menu item to mark a specific message as +#. read. +#: ui/conversation-email-menus.ui:30 msgid "_Mark Read" msgstr "Оқылған етіп бел_гілеу" -#: ../ui/conversation-email-menus.ui.h:5 +#: ui/conversation-email-menus.ui:36 msgid "_Mark Unread" msgstr "Оқылмаған етіп бел_гілеу" -#: ../ui/conversation-email-menus.ui.h:6 +#. Translators: Menu item to mark all messages in a +#. conversation from this one as unread. +#: ui/conversation-email-menus.ui:42 msgid "Mark Unread From _Here" msgstr "Ос_ыдан бастап оқылмаған етіп белгілеу" -#: ../ui/conversation-email-menus.ui.h:8 +#. Translators: Menu item to move a single, specific message +#. to the trash +#: ui/conversation-email-menus.ui:50 +msgid "_Trash" +msgstr "Қ_оқыс шелегі" + +#. Translators: Menu item to delete a single, specific message +#: ui/conversation-email-menus.ui:57 +msgid "_Delete…" +msgstr "Ө_шіру…" + +#. Translators: Menu item to view the source for a message +#: ui/conversation-email-menus.ui:69 msgid "_View Source" msgstr "Бастапқы кодын қ_арау" -#: ../ui/conversation-email-menus.ui.h:11 +#: ui/conversation-email-menus.ui:87 msgid "_Save All" msgstr "Барл_ығын сақтау" -#: ../ui/conversation-message-menus.ui.h:1 +#: ui/conversation-message-menus.ui:7 msgid "_Open Link" msgstr "Сі_лтемені ашу" -#: ../ui/conversation-message-menus.ui.h:2 +#: ui/conversation-message-menus.ui:11 msgid "Copy Link _Address" msgstr "Сілтеме адр_есін көшіру" -#: ../ui/conversation-message-menus.ui.h:3 +#: ui/conversation-message-menus.ui:17 msgid "Send New _Message…" msgstr "Жаңа _хатты жіберу…" -#: ../ui/conversation-message-menus.ui.h:4 +#: ui/conversation-message-menus.ui:21 msgid "Copy Email _Address" msgstr "Эл. пошта _адресін көшіру" -#: ../ui/conversation-message-menus.ui.h:5 +#: ui/conversation-message-menus.ui:27 msgid "Save _Image As…" msgstr "Суретті қалайша _сақтау…" -#: ../ui/conversation-message-menus.ui.h:6 +#: ui/conversation-message-menus.ui:33 msgid "_Select All" msgstr "Бар_лығын таңдау" -#: ../ui/conversation-message-menus.ui.h:8 +#: ui/conversation-message-menus.ui:43 msgid "Search for messages from" msgstr "Келесіден хабарламаларды іздеу:" -#: ../ui/conversation-message.ui.h:1 +#: ui/conversation-message.ui:64 msgid "From " msgstr " жіберген" -#: ../ui/conversation-message.ui.h:2 +#: ui/conversation-message.ui:80 ui/conversation-message.ui:179 msgid "1/1/1970\t" msgstr "1/1/1970\t" -#: ../ui/conversation-message.ui.h:3 +#: ui/conversation-message.ui:103 msgid "Preview body text." msgstr "Дене мәтінін алдын-ала қарау." -#: ../ui/conversation-message.ui.h:4 +#: ui/conversation-message.ui:203 msgid "Sent by:" msgstr "Кім жіберген:" -#: ../ui/conversation-message.ui.h:5 +#: ui/conversation-message.ui:248 msgid "Reply to:" msgstr "Кімге жауап беру:" -#: ../ui/conversation-message.ui.h:6 +#: ui/conversation-message.ui:292 msgid "Subject" msgstr "Тақырыбы" -#: ../ui/conversation-message.ui.h:7 +#: ui/conversation-message.ui:313 msgid "To:" msgstr "Кімге:" -#: ../ui/conversation-message.ui.h:8 +#: ui/conversation-message.ui:358 msgid "Cc:" msgstr "Көшірмесі:" -#: ../ui/conversation-message.ui.h:9 +#: ui/conversation-message.ui:403 msgid "Bcc:" msgstr "Жасырын көшірме:" -#: ../ui/conversation-message.ui.h:10 +#: ui/conversation-message.ui:502 msgid "Show Images" msgstr "Суреттерді көрсету" -#: ../ui/conversation-message.ui.h:11 +#: ui/conversation-message.ui:515 msgid "Always Show From Sender" msgstr "Жіберушіден әрқашан көрсету" -#: ../ui/conversation-message.ui.h:12 +#: ui/conversation-message.ui:543 msgid "Remote images not shown" msgstr "Қашықтағы суреттер көрсетілмейді" -#: ../ui/conversation-message.ui.h:13 +#: ui/conversation-message.ui:560 msgid "Only show remote images from senders you trust." msgstr "Тек өзіңіз сенетін жіберушілерден суреттерді көрсету." -#: ../ui/conversation-message.ui.h:14 +#: ui/conversation-message.ui:692 msgid "But actually goes to:" msgstr "Бірақ, шын мәнінде мына жерге барады:" -#: ../ui/conversation-message.ui.h:15 +#: ui/conversation-message.ui:723 msgid "The link appears to go to:" msgstr "Бұл сілтеме осы жерге көрсетіп тұрған сияқты:" -#: ../ui/conversation-message.ui.h:16 +#: ui/conversation-message.ui:735 msgid "Deceptive link found" msgstr "Зиянкес сілтеме табылды" -#: ../ui/conversation-message.ui.h:17 +#: ui/conversation-message.ui:750 msgid "The email sender may be leading you to the wrong web site." msgstr "Эл. поштаны жіберуші сізді қате сайтқа бағдарлауы мүмкін." -#: ../ui/conversation-message.ui.h:18 +#: ui/conversation-message.ui:763 msgid "If unsure, contact the sender and ask before continuing." msgstr "Сенімсіз болсаңыз, жалғастыру алдында жіберушіге хабарласыңыз." -#: ../ui/conversation-viewer.ui.h:1 +#: ui/conversation-viewer.ui:60 msgid "Find in conversation" msgstr "Сөйлесуден табу" -#: ../ui/conversation-viewer.ui.h:2 +#: ui/conversation-viewer.ui:74 msgid "Find the previous occurrence of the search string." msgstr "Ізделетін мәтіннің алдыңғы кездесуін табу." -#: ../ui/conversation-viewer.ui.h:3 +#: ui/conversation-viewer.ui:95 msgid "Find the next occurrence of the search string." msgstr "Ізделетін мәтіннің келесі кездесуін табу." -#: ../ui/edit_alternate_emails.glade.h:1 +#: ui/edit_alternate_emails.glade:112 msgid "Remove email address" msgstr "Эл. пошта адресін өшіру" -#: ../ui/edit_alternate_emails.glade.h:2 +#: ui/edit_alternate_emails.glade:136 msgid "" "Some email services require additional addresses be configured on the " "server. Contact your email provider for more information." @@ -2146,476 +2497,528 @@ "Кейбір эл. пошта қызметтері серверде қосымша адрестердің бапталуын талап " "етеді. Көбірек білу үшін өз эл. пошта ұсынушысыңызға хабарласыңыз." -#: ../ui/edit_alternate_emails.glade.h:4 +#: ui/edit_alternate_emails.glade:175 msgid "_Update" msgstr "_Жаңарту" -#: ../ui/find_bar.glade.h:1 +#: ui/find_bar.glade:66 msgid "Find:" msgstr "Іздеу:" -#: ../ui/find_bar.glade.h:2 +#: ui/find_bar.glade:89 msgid "_Previous" msgstr "А_лдыңғы" -#: ../ui/find_bar.glade.h:3 +#: ui/find_bar.glade:107 msgid "_Next" msgstr "_Келесі" -#: ../ui/find_bar.glade.h:4 +#: ui/find_bar.glade:125 msgid "_Case sensitive" msgstr "Регистрді _ескеру" -#: ../ui/find_bar.glade.h:5 +#: ui/find_bar.glade:145 msgid "label" msgstr "белгі" -#: ../ui/gtk/help-overlay.ui.h:1 +#: ui/gtk/help-overlay.ui:9 msgid "Conversation Shortcuts" msgstr "Сөйлесулер жарлықтары" -#: ../ui/gtk/help-overlay.ui.h:2 +#: ui/gtk/help-overlay.ui:13 ui/gtk/help-overlay.ui:254 msgctxt "shortcut window" msgid "General" msgstr "Жалпы" -#: ../ui/gtk/help-overlay.ui.h:3 +#: ui/gtk/help-overlay.ui:17 msgctxt "shortcut window" msgid "Move focus to the next/previous pane" msgstr "Фокусты келесі/алдыңғы панельге жылжыту" -#: ../ui/gtk/help-overlay.ui.h:4 +#: ui/gtk/help-overlay.ui:24 msgctxt "shortcut window" msgid "Move focus to conversation list" msgstr "Фокусты сөйлесулер тізіміне жылжыту" -#: ../ui/gtk/help-overlay.ui.h:5 +#: ui/gtk/help-overlay.ui:31 msgctxt "shortcut window" msgid "Detach composer window" msgstr "Жаңа хатты жазу терезесін бөліп жіберу" -#: ../ui/gtk/help-overlay.ui.h:6 +#: ui/gtk/help-overlay.ui:38 msgctxt "shortcut window" msgid "Close composer window" msgstr "Жаңа хатты жазу терезесін жабу" -#: ../ui/gtk/help-overlay.ui.h:7 +#: ui/gtk/help-overlay.ui:45 msgctxt "shortcut window" msgid "Show keyboard shortcuts" msgstr "Пернетақта жарлықтарын көрсету" -#: ../ui/gtk/help-overlay.ui.h:8 +#: ui/gtk/help-overlay.ui:52 msgctxt "shortcut window" msgid "Show help" msgstr "Көмекті көрсету" -#: ../ui/gtk/help-overlay.ui.h:9 +#: ui/gtk/help-overlay.ui:59 msgctxt "shortcut window" msgid "Quit the application" msgstr "Қолданба жұмысын аяқтау" -#: ../ui/gtk/help-overlay.ui.h:10 +#: ui/gtk/help-overlay.ui:68 msgctxt "shortcut window" msgid "Search" msgstr "Іздеу" -#: ../ui/gtk/help-overlay.ui.h:11 +#: ui/gtk/help-overlay.ui:72 msgctxt "shortcut window" msgid "Jump to search box" msgstr "Іздеу жолағына өту" -#: ../ui/gtk/help-overlay.ui.h:12 +#: ui/gtk/help-overlay.ui:79 msgctxt "shortcut window" msgid "Find in current conversation" msgstr "Ағымдағы сөйлесуден табу" -#: ../ui/gtk/help-overlay.ui.h:13 +#: ui/gtk/help-overlay.ui:86 msgctxt "shortcut window" msgid "Find next/previous in current conversation" msgstr "Ағымдағы сөйлесуден алдыңғы/келесі сәйкестікті табу" -#: ../ui/gtk/help-overlay.ui.h:14 +#: ui/gtk/help-overlay.ui:95 ui/gtk/help-overlay.ui:274 msgctxt "shortcut window" msgid "Actions" msgstr "Әрекеттер" -#: ../ui/gtk/help-overlay.ui.h:15 +#: ui/gtk/help-overlay.ui:99 msgctxt "shortcut window" msgid "Compose a new message" msgstr "Жаңа хатты жазу" -#: ../ui/gtk/help-overlay.ui.h:16 +#: ui/gtk/help-overlay.ui:106 msgctxt "shortcut window" msgid "Reply to sender " msgstr "Жіберушіге жауап беру " -#: ../ui/gtk/help-overlay.ui.h:17 +#: ui/gtk/help-overlay.ui:113 msgctxt "shortcut window" msgid "Reply to all" msgstr "Барлығына жауап беру" -#: ../ui/gtk/help-overlay.ui.h:18 +#: ui/gtk/help-overlay.ui:120 msgctxt "shortcut window" msgid "Forward" msgstr "Қайта бағдарлау" -#: ../ui/gtk/help-overlay.ui.h:19 +#: ui/gtk/help-overlay.ui:127 msgctxt "shortcut window" msgid "Archive" msgstr "Архивтек" -#: ../ui/gtk/help-overlay.ui.h:20 +#: ui/gtk/help-overlay.ui:134 msgctxt "shortcut window" msgid "Move to trash" msgstr "Қоқыс шелегіне тастау" -#: ../ui/gtk/help-overlay.ui.h:21 +#: ui/gtk/help-overlay.ui:141 msgctxt "shortcut window" msgid "Toggle spam" msgstr "Спам күйін ауыстыру" -#: ../ui/gtk/help-overlay.ui.h:22 +#: ui/gtk/help-overlay.ui:148 msgctxt "shortcut window" msgid "Move the conversation" msgstr "Сөйлесуді жылжыту" -#: ../ui/gtk/help-overlay.ui.h:23 +#: ui/gtk/help-overlay.ui:155 msgctxt "shortcut window" msgid "Label the conversation" msgstr "Сөйлесуге белгіні орнату" -#: ../ui/gtk/help-overlay.ui.h:24 +#: ui/gtk/help-overlay.ui:163 msgctxt "shortcut window" msgid "Mark read" msgstr "Оқылған етіп белгілеу" -#: ../ui/gtk/help-overlay.ui.h:25 +#: ui/gtk/help-overlay.ui:170 msgctxt "shortcut window" msgid "Mark unread" msgstr "Оқылмаған етіп белгілеу" -#: ../ui/gtk/help-overlay.ui.h:26 +#: ui/gtk/help-overlay.ui:179 msgctxt "shortcut window" msgid "View" msgstr "Қарау" -#: ../ui/gtk/help-overlay.ui.h:27 +#: ui/gtk/help-overlay.ui:183 msgctxt "shortcut window" msgid "Zoom in" msgstr "Үлкейту" -#: ../ui/gtk/help-overlay.ui.h:28 +#: ui/gtk/help-overlay.ui:190 msgctxt "shortcut window" msgid "Zoom out" msgstr "Кішірейту" -#: ../ui/gtk/help-overlay.ui.h:29 +#: ui/gtk/help-overlay.ui:197 msgctxt "shortcut window" msgid "Reset zoom" msgstr "Масштабты тастау" -#: ../ui/gtk/help-overlay.ui.h:30 +#: ui/gtk/help-overlay.ui:206 msgctxt "shortcut window" msgid "Additional Shortcuts" msgstr "Қосымша жарлықтар" -#: ../ui/gtk/help-overlay.ui.h:31 +#: ui/gtk/help-overlay.ui:210 msgctxt "shortcut window" msgid "Star" msgstr "Жұлдызшаны орнату" -#: ../ui/gtk/help-overlay.ui.h:32 +#: ui/gtk/help-overlay.ui:217 msgctxt "shortcut window" msgid "Unstar" msgstr "Жұлдызшаны алып тастау" -#: ../ui/gtk/help-overlay.ui.h:33 +#: ui/gtk/help-overlay.ui:224 msgctxt "shortcut window" msgid "Delete" msgstr "Өшіру" -#: ../ui/gtk/help-overlay.ui.h:34 +#: ui/gtk/help-overlay.ui:231 msgctxt "shortcut window" msgid "Jump to next (older) conversation" msgstr "Келесі (ескілеу) сөйлесуге өту" -#: ../ui/gtk/help-overlay.ui.h:35 +#: ui/gtk/help-overlay.ui:238 msgctxt "shortcut window" msgid "Jump to previous (newer) conversation" msgstr "Алдыңғы (жаңалау) сөйлесуге өту" -#: ../ui/gtk/help-overlay.ui.h:36 +#: ui/gtk/help-overlay.ui:250 msgid "Composer Shortcuts" msgstr "Хатты жазу терезесінің жарлықтары" -#: ../ui/gtk/help-overlay.ui.h:37 +#: ui/gtk/help-overlay.ui:258 msgctxt "shortcut window" msgid "Quote text" msgstr "Мәтінді дәйексөз қылу" -#: ../ui/gtk/help-overlay.ui.h:38 +#: ui/gtk/help-overlay.ui:265 msgctxt "shortcut window" msgid "Unquote text" msgstr "Мәтінді дәйексөз емес қылу" -#: ../ui/gtk/help-overlay.ui.h:39 +#: ui/gtk/help-overlay.ui:278 msgctxt "shortcut window" msgid "Send" msgstr "Жіберу" -#: ../ui/gtk/help-overlay.ui.h:40 +#: ui/gtk/help-overlay.ui:285 msgctxt "shortcut window" msgid "Add attachment" msgstr "Салынымды қосу" -#: ../ui/gtk/help-overlay.ui.h:41 +#: ui/gtk/help-overlay.ui:294 msgctxt "shortcut window" msgid "Rich text mode" msgstr "Кеңейтілген пішімдеу" -#: ../ui/gtk/help-overlay.ui.h:42 +#: ui/gtk/help-overlay.ui:298 msgctxt "shortcut window" msgid "Bold text" msgstr "Жуан қаріп" -#: ../ui/gtk/help-overlay.ui.h:43 +#: ui/gtk/help-overlay.ui:305 msgctxt "shortcut window" msgid "Italicize text" msgstr "Мәтінді көлбеу қылу" -#: ../ui/gtk/help-overlay.ui.h:44 +#: ui/gtk/help-overlay.ui:312 msgctxt "shortcut window" msgid "Underline text" msgstr "Асты сызылған" -#: ../ui/gtk/help-overlay.ui.h:45 +#: ui/gtk/help-overlay.ui:319 msgctxt "shortcut window" msgid "Strike text" msgstr "Мәтінді сызылған қылу" -#: ../ui/gtk/help-overlay.ui.h:46 +#: ui/gtk/help-overlay.ui:326 msgctxt "shortcut window" msgid "Insert a link" msgstr "Сілтемені кірістіру" -#: ../ui/gtk/help-overlay.ui.h:47 +#: ui/gtk/help-overlay.ui:333 msgctxt "shortcut window" msgid "Remove formatting" msgstr "Пішімдеуді өшіру" -#: ../ui/gtk/menus.ui.h:2 +#: ui/gtk/menus.ui:13 msgid "A_ccounts" msgstr "_Тіркелгілер" -#: ../ui/gtk/menus.ui.h:4 +#: ui/gtk/menus.ui:23 msgid "_Keyboard Shortcuts" msgstr "Пернетақта жарлықтары" -#: ../ui/login.glade.h:1 +#: ui/login.glade:88 msgid "email@example.com" msgstr "email@example.com" -#: ../ui/login.glade.h:2 ../ui/password-dialog.glade.h:3 +#: ui/login.glade:107 ui/password-dialog.glade:108 msgid "Password" msgstr "Пароль" -#: ../ui/login.glade.h:3 +#: ui/login.glade:123 msgid "E_mail address" msgstr "Эл. _пошта адресі" -#: ../ui/login.glade.h:4 +#: ui/login.glade:144 ui/login.glade:635 msgid "_Password" msgstr "_Пароль" -#: ../ui/login.glade.h:5 +#: ui/login.glade:178 msgid "S_ervice" msgstr "Қ_ызмет" -#: ../ui/login.glade.h:6 +#: ui/login.glade:199 msgid "N_ame" msgstr "А_ты" -#: ../ui/login.glade.h:8 +#: ui/login.glade:256 msgid "N_ickname" msgstr "Н_ик аты" -#: ../ui/login.glade.h:9 +#: ui/login.glade:280 msgid "Work, Home, etc." msgstr "Үй, Жұмыс, т.с.с." -#: ../ui/login.glade.h:10 +#: ui/login.glade:291 msgid "_Save sent mail" msgstr "Жіберілген поштаны _сақтау" -#: ../ui/login.glade.h:11 +#: ui/login.glade:309 msgid "Addi_tional email addresses…" msgstr "Қосы_мша эл. пошта адрестері…" -#: ../ui/login.glade.h:12 +#: ui/login.glade:353 msgid "IMAP settings" msgstr "IMAP баптаулары" -#: ../ui/login.glade.h:13 +#: ui/login.glade:372 msgid "Se_rver" msgstr "Се_рвер" -#: ../ui/login.glade.h:14 +#: ui/login.glade:393 msgid "imap.example.com" msgstr "imap.example.com" -#: ../ui/login.glade.h:15 +#: ui/login.glade:409 msgid "P_ort" msgstr "_Порт" -#: ../ui/login.glade.h:16 +#: ui/login.glade:448 msgid "smtp.example.com" msgstr "smtp.example.com" -#: ../ui/login.glade.h:17 +#: ui/login.glade:480 msgid "Ser_ver" msgstr "Сер_вер" -#: ../ui/login.glade.h:18 +#: ui/login.glade:501 msgid "Por_t" msgstr "Пор_т" -#: ../ui/login.glade.h:19 +#: ui/login.glade:522 msgid "SMTP settings" msgstr "SMTP баптаулары" -#: ../ui/login.glade.h:20 +#: ui/login.glade:541 msgid "User_name" msgstr "Па_йдаланушы аты" -#: ../ui/login.glade.h:21 +#: ui/login.glade:562 msgid "Pass_word" msgstr "Пар_оль" -#: ../ui/login.glade.h:22 +#: ui/login.glade:582 msgid "SMTP username" msgstr "SMTP пайдаланушы аты" -#: ../ui/login.glade.h:23 +#: ui/login.glade:598 msgid "SMTP password" msgstr "SMTP паролі" -#: ../ui/login.glade.h:24 +#: ui/login.glade:614 msgid "_Username" -msgstr "_Пайдаланушы аты:" +msgstr "_Пайдаланушы аты" -#: ../ui/login.glade.h:25 +#: ui/login.glade:655 msgid "IMAP username" msgstr "IMAP пайдаланушы аты" -#: ../ui/login.glade.h:26 +#: ui/login.glade:671 msgid "IMAP password" msgstr "IMAP паролі" -#: ../ui/login.glade.h:27 +#: ui/login.glade:688 msgid "Encr_yption" msgstr "_Шифрлеу" -#: ../ui/login.glade.h:28 +#: ui/login.glade:711 msgid "Encrypt_ion" msgstr "Ш_ифрлеу" -#: ../ui/login.glade.h:30 +#: ui/login.glade:733 ui/login.glade:751 msgid "SSL/TLS" msgstr "SSL/TLS" -#: ../ui/login.glade.h:31 +#: ui/login.glade:734 ui/login.glade:752 msgid "STARTTLS" msgstr "STARTTLS" -#: ../ui/login.glade.h:32 +#: ui/login.glade:764 msgid "No authentication re_quired" msgstr "Аутентификация керек е_мес" -#: ../ui/login.glade.h:33 +#: ui/login.glade:781 msgid "Use IMAP cre_dentials" msgstr "IMAP тіркелгі _деректерін қолдану" -#: ../ui/login.glade.h:34 +#: ui/login.glade:888 msgid "Composer" msgstr "Хатты жазу терезесі" -#: ../ui/login.glade.h:35 +#: ui/login.glade:901 msgid "Save dra_fts on server" msgstr "Ши_май хаттарды серверде сақату" -#: ../ui/login.glade.h:36 +#: ui/login.glade:918 msgid "Si_gn emails (HTML allowed):" msgstr "Эл. пошта хаттарына қол_таңбаны қою (HTML рұқсат етілген):" -#: ../ui/login.glade.h:37 +#: ui/login.glade:976 msgid "Storage" msgstr "Сақтау құрылғылары" -#: ../ui/login.glade.h:38 +#: ui/login.glade:998 msgid "_Download mail" msgstr "_Поштаны жүктеп алу" -#: ../ui/main-toolbar.ui.h:1 +#: ui/main-toolbar.ui:51 +msgid "Toggle search bar" +msgstr "Іздеу панелін іске қосу/сөндіру" + +#: ui/main-toolbar.ui:72 msgid "Empty Spam or Trash folders" msgstr "Спам немесе Қоқыс шелегі бумаларын босату" -#: ../ui/password-dialog.glade.h:1 +#: ui/main-toolbar.ui:112 +msgid "Reply" +msgstr "Жауап беру" + +#: ui/main-toolbar.ui:135 +msgid "Reply All" +msgstr "Барлығына жауап беру" + +#: ui/main-toolbar.ui:158 +msgid "Forward" +msgstr "Қайта бағдарлау" + +#: ui/main-toolbar.ui:264 +msgid "Toggle find bar" +msgstr "Іздеу панелін іске қосу/сөндіру" + +#: ui/main-toolbar.ui:306 +msgid "_Archive" +msgstr "_Архивтеу" + +#: ui/main-toolbar-menus.ui:5 +msgid "Empty _Spam…" +msgstr "_Спам бумасын босату…" + +#: ui/main-toolbar-menus.ui:9 +msgid "Empty _Trash…" +msgstr "Қ_оқыс шелегін босату…" + +#: ui/main-toolbar-menus.ui:32 +msgid "Mark as S_pam" +msgstr "С_пам етіп белгілеу" + +#: ui/main-toolbar-menus.ui:36 +msgid "Mark as not S_pam" +msgstr "С_пам емес етіп белгілеу" + +#: ui/main-window-info-bar.ui:97 +msgid "" +"If the problem is serious or persists, please copy and send these details to " +"the mailing list " +"or file a new " +"bug report." +msgstr "" + +#: ui/main-window-info-bar.ui:113 +msgid "Details:" +msgstr "Ақпараты:" + +#: ui/password-dialog.glade:74 msgid "SMTP Credentials" msgstr "SMTP тіркелгі ақпараты" -#: ../ui/password-dialog.glade.h:2 +#: ui/password-dialog.glade:91 msgid "Username" msgstr "Пайдаланушы аты" -#: ../ui/password-dialog.glade.h:4 +#: ui/password-dialog.glade:152 msgid "_Remember password" msgstr "Парольді есте сақ_тау" -#: ../ui/password-dialog.glade.h:6 +#: ui/password-dialog.glade:210 msgid "_Authenticate" msgstr "_Аутентификация" -#: ../ui/preferences-dialog.ui.h:1 +#: ui/preferences-dialog.ui:38 msgid "Reading" msgstr "Оқу" -#: ../ui/preferences-dialog.ui.h:2 +#: ui/preferences-dialog.ui:51 msgid "_Automatically select next message" msgstr "Келесі хатты _автотаңдау" -#: ../ui/preferences-dialog.ui.h:3 +#: ui/preferences-dialog.ui:70 msgid "_Display conversation preview" msgstr "Сөйлесудің алдын-ала көрінісін көр_сету" -#: ../ui/preferences-dialog.ui.h:4 +#: ui/preferences-dialog.ui:89 msgid "Use _three pane view" msgstr "_Ағаш тектес панель көрінісін қолдану" -#: ../ui/preferences-dialog.ui.h:5 +#: ui/preferences-dialog.ui:113 msgid "Notifications" msgstr "Хабарламалар" -#: ../ui/preferences-dialog.ui.h:6 +#: ui/preferences-dialog.ui:126 msgid "_Play notification sounds" msgstr "Хабарлау дыбы_старын ойнату" -#: ../ui/preferences-dialog.ui.h:7 +#: ui/preferences-dialog.ui:145 msgid "Show _notifications for new mail" msgstr "Жаңа хаттар үшін _хабарламаларды көрсету" -#: ../ui/preferences-dialog.ui.h:8 -msgid "Always _watch for new mail" -msgstr "Жаңа хаттар келгенін _бақылау" - -#: ../ui/preferences-dialog.ui.h:9 -msgid "Geary will run in the background and notify of new mail" -msgstr "Geary фонда орындалып, жаңа түскен хаттар жөнінде хабарлайды" +#: ui/preferences-dialog.ui:164 +msgid "_Watch for new mail when closed" +msgstr "Жабылған кезінде жаңа хаттар келгенін _бақылау" + +#: ui/preferences-dialog.ui:168 +msgid "Geary will keep running after all windows are closed" +msgstr "" -#: ../ui/preferences-dialog.ui.h:10 +#: ui/preferences-dialog.ui:195 msgid "Preferences" msgstr "Баптаулар" -#: ../ui/remove_confirm.glade.h:1 +#: ui/remove_confirm.glade:43 msgid "" "Are you sure you want to remove this " "account? " @@ -2623,7 +3026,7 @@ "Бұл тіркелгіні өшіруді шынымен " "қалайсыз ба? " -#: ../ui/remove_confirm.glade.h:2 +#: ui/remove_confirm.glade:58 msgid "" "All email associated with this account will be removed from your computer. " "This will not affect email on the server." @@ -2631,18 +3034,67 @@ "Бұл тіркелгімен байланысқан барлық эл. пошта хаттары компьютеріңізден " "өшірілетін болады. Бірақ, сервердегі хаттарға әсері болмайды." -#: ../ui/remove_confirm.glade.h:3 +#: ui/remove_confirm.glade:80 msgid "Nickname:" msgstr "Ник:" -#: ../ui/remove_confirm.glade.h:4 +#: ui/remove_confirm.glade:94 msgid "Email address:" msgstr "Эл. пошта адресі:" -#: ../ui/upgrade_dialog.glade.h:1 +#: ui/upgrade_dialog.glade:60 msgid "Geary update in progress…" msgstr "Geary жаңартуы жүруде…" +#~ msgid "Geary Email" +#~ msgstr "Geary поштасы" + +#~ msgid "Mail Client" +#~ msgstr "Пошта клиенті" + +#~ msgid "Geary Mail" +#~ msgstr "Geary поштасы" + +#~ msgid "Send files using Geary" +#~ msgstr "Файлдарды Geary көмегімен жіберу" + +#~ msgid "_Mark as…" +#~ msgstr "Қалайша _белгілеу…" + +#~ msgid "Add label" +#~ msgstr "Белгіні қосу" + +#~ msgid "_Label" +#~ msgstr "Ж_азу" + +#~ msgid "_Move" +#~ msgstr "Жы_лжыту" + +#~ msgid "Compose new message (Ctrl+N, N)" +#~ msgstr "Жаңа хатты жазу (Ctrl+N, N)" + +#~ msgid "Reply (Ctrl+R, R)" +#~ msgstr "Жауап беру (Ctrl+R, R)" + +#~ msgid "Reply all (Ctrl+Shift+R, Shift+R)" +#~ msgstr "Барлығына жауап беру (Ctrl+Shift+R, Shift+R)" + +#~ msgid "Forward (Ctrl+L, F)" +#~ msgstr "Қайта бағдарлау (Ctrl+L, F)" + +#~ msgid "" +#~ "Geary encountered an error while connecting to the server. Please try " +#~ "again in a few moments." +#~ msgstr "" +#~ "Geary серверге байланысу кезінде қате орын алды. Біраз уақыттан кейін " +#~ "қайталап көріңіз." + +#~ msgid "Try Again" +#~ msgstr "Қайтадан көру" + +#~ msgid "Geary will run in the background and notify of new mail" +#~ msgstr "Geary фонда орындалып, жаңа түскен хаттар жөнінде хабарлайды" + #~ msgid "Empty" #~ msgstr "Босату" @@ -2745,9 +3197,6 @@ #~ msgid "Fixed Width" #~ msgstr "Бекітілген ені" -#~ msgid "Detach" -#~ msgstr "Бөліп жіберу" - #~ msgid "_Attach File" #~ msgstr "Ф_айлды қосып жіберу" @@ -2769,14 +3218,5 @@ #~ msgid "_Donate" #~ msgstr "Ақ_шалай көмектесу" -#~ msgid "_Delete" -#~ msgstr "Ө_шіру" - -#~ msgid "_Trash" -#~ msgstr "Қ_оқыс шелегі" - #~ msgid "Do you want to discard the unsaved message?" #~ msgstr "Сақталмаған хатты елемеуді қалайсыз ба?" - -#~ msgid "Notify of new mail at start_up" -#~ msgstr "І_ске қосылғанда жаңа хаттар туралы хабарлау" diff -Nru geary-0.12.4/po/LINGUAS geary-3.32.0/po/LINGUAS --- geary-0.12.4/po/LINGUAS 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/po/LINGUAS 2019-03-17 13:39:29.000000000 +0000 @@ -1,4 +1,5 @@ ar +be bs ca cs @@ -14,6 +15,7 @@ fa fi fr +fur gl he hi diff -Nru geary-0.12.4/po/meson.build geary-3.32.0/po/meson.build --- geary-0.12.4/po/meson.build 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/po/meson.build 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,4 @@ +i18n.gettext(meson.project_name(), + preset: 'glib', + args: '--keyword=Description' +) diff -Nru geary-0.12.4/po/nl.po geary-3.32.0/po/nl.po --- geary-0.12.4/po/nl.po 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/po/nl.po 2019-03-17 13:39:29.000000000 +0000 @@ -12,55 +12,82 @@ # Martijn Braam , 2013. # renearts , 2013. # Hannie Dumoleyn , 2014, 2017. -# Nathan Follens , 2015-2016. -# Justin van Steijn , 2016. +# Nathan Follens , 2015-2019. +# Justin van Steijn , 2016, 2018. msgid "" msgstr "" "Project-Id-Version: geary-0.4.1\n" -"Report-Msgid-Bugs-To: https://bugzilla.gnome.org/enter_bug.cgi?product=geary" -"&keywords=I18N+L10N&component=internationalization\n" -"POT-Creation-Date: 2017-09-26 12:56+0000\n" -"PO-Revision-Date: 2017-09-26 17:15+0100\n" -"Last-Translator: Hannie Dumoleyn \n" +"Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/geary/issues\n" +"POT-Creation-Date: 2019-02-25 22:32+0000\n" +"PO-Revision-Date: 2019-02-26 00:19+0100\n" +"Last-Translator: Nathan Follens \n" "Language-Team: Dutch \n" "Language: nl_NL\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Lokalize 2.0\n" +"X-Generator: Poedit 2.2.1\n" + +#: desktop/geary-attach.contract.desktop.in:3 +msgid "Send by email" +msgstr "Verzenden via e-mail" + +#. Translators: Do NOT translate or transliterate this text (this is an icon file name)! +#: desktop/geary-attach.contract.desktop.in:5 +msgid "mail-send" +msgstr "mail-send" + +#: desktop/geary-attach.contract.desktop.in:6 +msgid "Send files using Geary" +msgstr "Bestanden verzenden met Geary" #. Translators: The application name -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:2 -#: ../desktop/org.gnome.Geary.desktop.in.h:1 -#: ../desktop/geary-autostart.desktop.in.h:1 +#: desktop/geary-autostart.desktop.in:3 +#: desktop/org.gnome.Geary.appdata.xml.in:11 +#: desktop/org.gnome.Geary.desktop.in:3 +#: src/client/accounts/accounts-editor-servers-pane.vala:555 msgid "Geary" msgstr "Geary" -#. Translators: The development team's name -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:4 -msgid "Geary Development Team" -msgstr "Geary-ontwikkelingsteam" +#: desktop/geary-autostart.desktop.in:4 desktop/org.gnome.Geary.desktop.in:4 +msgid "Email" +msgstr "E-mail" #. Translators: The application's summary / tagline -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:6 -#: ../desktop/org.gnome.Geary.desktop.in.h:4 -#: ../desktop/geary-autostart.desktop.in.h:4 +#: desktop/geary-autostart.desktop.in:5 +#: desktop/org.gnome.Geary.appdata.xml.in:15 +#: desktop/org.gnome.Geary.desktop.in:5 +#: src/client/application/geary-application.vala:23 msgid "Send and receive email" msgstr "E-mail verzenden en ontvangen" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:7 +#. Translators: These are desktop search terms. Do not translate semicolons, end line with a semicolon. +#: desktop/geary-autostart.desktop.in:7 +msgid "Email;E-mail;Mail;" +msgstr "Email;E-mail;Mail;" + +#. Translators: Do NOT translate or transliterate this text (this is an icon file name)! +#: desktop/geary-autostart.desktop.in:9 desktop/org.gnome.Geary.desktop.in:9 +msgid "org.gnome.Geary" +msgstr "org.gnome.Geary" + +#. Translators: The development team's name +#: desktop/org.gnome.Geary.appdata.xml.in:13 +msgid "Geary Development Team" +msgstr "Geary-ontwikkelingsteam" + +#: desktop/org.gnome.Geary.appdata.xml.in:17 msgid "" "Geary is an email application built around conversations, for the GNOME 3 " "desktop. It allows you to read, find and send email with a straightforward, " "modern interface." msgstr "" -"Geary is een e-mailtoepassing die draait om gesprekken, voor de Gnome" -" 3-desktop. Via de duidelijke, moderne interface kunt u e-mails lezen, zoeken" -" en verzenden." -"." +"Geary is een e-mailtoepassing die draait om gesprekken, voor de Gnome 3-" +"desktop. Via de duidelijke, moderne interface kunt u e-mails lezen, zoeken " +"en verzenden." -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:8 +#: desktop/org.gnome.Geary.appdata.xml.in:22 msgid "" "Conversations allow you to read a complete discussion without having to find " "and click from message to message." @@ -68,219 +95,704 @@ "Met gesprekken kunt u een volledige discussie lezen zonder te hoeven zoeken " "of van bericht naar bericht te hoeven klikken." -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:9 +#: desktop/org.gnome.Geary.appdata.xml.in:26 msgid "Geary’s features include:" msgstr "Functies van Geary zijn:" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:10 +#: desktop/org.gnome.Geary.appdata.xml.in:28 msgid "Quick email account setup" msgstr "Snel instellen van e-mailaccount" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:11 +#: desktop/org.gnome.Geary.appdata.xml.in:29 msgid "Shows related messages together in conversations" msgstr "Groepeert gerelateerde berichten in gesprekken" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:12 +#: desktop/org.gnome.Geary.appdata.xml.in:30 msgid "Fast, full text and keyword search" msgstr "Snel zoeken in volledige tekst met zoekwoorden" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:13 +#: desktop/org.gnome.Geary.appdata.xml.in:31 msgid "Full-featured HTML and plain text message composer" msgstr "Berichten opstellen in HTML en platte tekst met veel functies" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:14 +#: desktop/org.gnome.Geary.appdata.xml.in:32 msgid "Desktop notification of new mail" msgstr "Bureaubladmelding voor nieuwe e-mail" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:15 +#: desktop/org.gnome.Geary.appdata.xml.in:33 msgid "Compatible with GMail, Yahoo! Mail, Outlook.com and other IMAP servers" msgstr "Werkt met Gmail, Yahoo! Mail, Outlook.com en andere IMAP-servers" #. Translators: A screenshot description. -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:17 -#| msgid "_Display conversation preview" +#: desktop/org.gnome.Geary.appdata.xml.in:47 msgid "Geary displaying a conversation" msgstr "Weergave van een gesprek in Geary" #. Translators: A screenshot description. -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:19 +#: desktop/org.gnome.Geary.appdata.xml.in:52 msgid "Geary showing the rich text composer" msgstr "Tekstverwerker met opmaakmogelijkheden in Geary" -#: ../desktop/org.gnome.Geary.desktop.in.h:2 -msgid "Email" -msgstr "E-mail" - -#: ../desktop/org.gnome.Geary.desktop.in.h:3 -msgid "Geary Email" -msgstr "Geary e-mail" - #. Translators: These are desktop search terms. Do not translate semicolons, end line with a semicolon. -#: ../desktop/org.gnome.Geary.desktop.in.h:6 +#: desktop/org.gnome.Geary.desktop.in:7 msgid "Mail;E-mail;IMAP;GMail;Yahoo;Hotmail;Outlook;" msgstr "Mail;E-mail;IMAP;GMail;Yahoo;Hotmail;Outlook;" -#: ../desktop/org.gnome.Geary.desktop.in.h:7 ../ui/gtk/menus.ui.h:1 +#: desktop/org.gnome.Geary.desktop.in:21 msgid "Compose Message" msgstr "Bericht opstellen" -#: ../desktop/geary-autostart.desktop.in.h:2 -#: ../src/client/application/geary-application.vala:21 -msgid "Mail Client" -msgstr "E-mailprogramma" - -#: ../desktop/geary-autostart.desktop.in.h:3 -msgid "Geary Mail" -msgstr "Geary e-mail" - -#: ../desktop/geary-autostart.desktop.in.h:5 -msgid "Email;E-mail;Mail;" -msgstr "Email;E-mail;Mail;" +#: desktop/org.gnome.Geary.gschema.xml:8 +msgid "Maximize window" +msgstr "Venster maximaliseren" + +# Naar mijn idee is dit een gegeven, niet iets dat is te wijzigen. -Justin +#: desktop/org.gnome.Geary.gschema.xml:9 +msgid "True if the application window is maximized, false otherwise." +msgstr "" +"Ingeschakeld als het toepassingsvenster gemaximaliseerd is, anders uit." + +#: desktop/org.gnome.Geary.gschema.xml:14 +msgid "Width of window" +msgstr "Breedte van het venster" + +#: desktop/org.gnome.Geary.gschema.xml:15 +msgid "The last recorded width of the application window." +msgstr "De laatst geregistreerde breedte van het toepassingsvenster." + +#: desktop/org.gnome.Geary.gschema.xml:20 +msgid "Height of window" +msgstr "Hoogte van het venster" + +#: desktop/org.gnome.Geary.gschema.xml:21 +msgid "The last recorded height of the application window." +msgstr "De laatst geregistreerde hoogte van het toepassingsvenster." + +#: desktop/org.gnome.Geary.gschema.xml:26 +msgid "Position of folder list pane" +msgstr "Positie van maplijstpaneel" + +#: desktop/org.gnome.Geary.gschema.xml:27 +msgid "Position of the folder list Paned grabber." +msgstr "Positie van de maplijst-Paneel-grabber." + +#: desktop/org.gnome.Geary.gschema.xml:32 +msgid "Position of folder list pane when horizontal" +msgstr "Positie van horizontaal maplijstpaneel" + +#: desktop/org.gnome.Geary.gschema.xml:33 +msgid "" +"Position of the folder list Paned grabber in the horizontal orientation." +msgstr "Positie van de maplijst-Paneel-grabber in de horizontale oriëntatie." + +#: desktop/org.gnome.Geary.gschema.xml:38 +msgid "Position of folder list pane when vertical" +msgstr "Positie van verticaal maplijstpaneel" + +#: desktop/org.gnome.Geary.gschema.xml:39 +msgid "Position of the folder list Paned grabber in the vertical orientation." +msgstr "Positie van de maplijst-Paneel-grabber in de verticale oriëntatie." + +#: desktop/org.gnome.Geary.gschema.xml:44 +msgid "Orientation of the folder list pane" +msgstr "Oriëntatie van het maplijstpaneel" + +#: desktop/org.gnome.Geary.gschema.xml:45 +msgid "True if the folder list Paned is in the horizontal orientation." +msgstr "" +"Schakel dit in als het maplijst-Paneel zich in de horizontale oriëntatie " +"bevindt." + +#: desktop/org.gnome.Geary.gschema.xml:50 +msgid "Position of message list pane" +msgstr "Positie van berichtlijstpaneel" + +#: desktop/org.gnome.Geary.gschema.xml:51 +msgid "Position of the message list Paned grabber." +msgstr "Positie van de berichtlijst-Paneel-grabber." + +#: desktop/org.gnome.Geary.gschema.xml:56 +msgid "Autoselect next message" +msgstr "Volgend bericht automatisch selecteren" + +#: desktop/org.gnome.Geary.gschema.xml:57 +msgid "True if we should autoselect the next available conversation." +msgstr "" +"Schakel dit in als het volgend beschikbare gesprek automatisch geselecteerd " +"moet worden." + +#: desktop/org.gnome.Geary.gschema.xml:62 +msgid "Display message previews" +msgstr "Voorbeeldweergave voor berichten inschakelen" + +#: desktop/org.gnome.Geary.gschema.xml:63 +msgid "True if we should display a short preview of each message." +msgstr "" +"Schakel dit in als een korte voorbeeldweergave van elk bericht getoond moet " +"worden." + +#: desktop/org.gnome.Geary.gschema.xml:68 +msgid "Languages that shall be used in the spell checker" +msgstr "Talen die gebruikt worden in de spellingscontrole" + +#: desktop/org.gnome.Geary.gschema.xml:69 +msgid "List of the languages to use in the spell checker." +msgstr "Lijst van de te gebruiken talen in de spellingscontrole." + +#: desktop/org.gnome.Geary.gschema.xml:74 +msgid "Languages that are displayed in the spell checker popover" +msgstr "Talen getoond in de spellingscontrole-pop-over" + +#: desktop/org.gnome.Geary.gschema.xml:75 +msgid "" +"List of languages that are always displayed in the popover of the spell " +"checker." +msgstr "" +"Lijst van talen die altijd worden getoond in de pop-over van de " +"spellingscontrole." + +#: desktop/org.gnome.Geary.gschema.xml:80 +msgid "Enable notification sounds" +msgstr "Geluid afspelen bij melding" + +#: desktop/org.gnome.Geary.gschema.xml:81 +msgid "True to play sounds for notifications and sending." +msgstr "Schakel dit in om geluiden af te spelen voor meldingen en verzenden." + +#: desktop/org.gnome.Geary.gschema.xml:86 +msgid "Show notifications for new mail" +msgstr "Melding weergeven voor nieuwe e-mail" + +#: desktop/org.gnome.Geary.gschema.xml:87 +msgid "True to show notification bubbles." +msgstr "Schakel dit in om meldingsbubbels te tonen." + +#: desktop/org.gnome.Geary.gschema.xml:92 +msgid "Notify of new mail at startup" +msgstr "Melding van nieuwe e-mails bij opstarten" + +#: desktop/org.gnome.Geary.gschema.xml:93 +msgid "True to notify of new mail at startup." +msgstr "" +"Schakel dit in om een melding te krijgen van nieuwe e-mails bij opstarten." + +#: desktop/org.gnome.Geary.gschema.xml:98 +msgid "Ask when opening an attachment" +msgstr "Vragen bij openen van bijlage" + +#: desktop/org.gnome.Geary.gschema.xml:99 +msgid "True to ask when opening an attachment." +msgstr "Schakel dit in om bevestiging te vragen bij openen van een bijlage." + +#: desktop/org.gnome.Geary.gschema.xml:104 +msgid "Whether to compose emails in HTML" +msgstr "Of e-mails in HTML opgesteld moeten worden" + +#: desktop/org.gnome.Geary.gschema.xml:105 +msgid "True to compose emails in HTML; false for plain text." +msgstr "" +"Schakel dit in om e-mails op te stellen in HTML; schakel uit voor platte " +"tekst." + +#: desktop/org.gnome.Geary.gschema.xml:110 +msgid "Advisory strategy for full-text searching" +msgstr "Adviesstrategie voor doorzoeken van volledige tekst" + +#: desktop/org.gnome.Geary.gschema.xml:111 +msgid "" +"Acceptable values are “exact”, “conservative”, “aggressive”, and “horizon”." +msgstr "" +"Aanvaardbare waarden zijn ‘exact’, ‘conservative’, ‘agressive’ en ‘horizon’." + +#: desktop/org.gnome.Geary.gschema.xml:116 +msgid "Zoom of conversation viewer" +msgstr "Zoomniveau van gespreksweergave" + +#: desktop/org.gnome.Geary.gschema.xml:117 +msgid "The zoom to apply on the conservation view." +msgstr "Het toe te passen zoomniveau op de gespreksweergave." + +#: desktop/org.gnome.Geary.gschema.xml:122 +msgid "Size of detached composer window" +msgstr "Grootte van losgemaakt opstelvenster" + +#: desktop/org.gnome.Geary.gschema.xml:123 +msgid "The last recorded size of the detached composer window." +msgstr "De laatst geregistreerde grootte van het losgemaakte opstelvenster." + +#: desktop/org.gnome.Geary.gschema.xml:128 +msgid "Base URL to look up contact avatars" +msgstr "Basis-URL voor opzoeken van contactavatars" + +#: desktop/org.gnome.Geary.gschema.xml:129 +msgid "" +"A Gravatar or Libravatar compatible URL, set to the empty string to disable." +msgstr "" +"Een URL compatibel met Gravatar of Libravatar, stel in als leeg om uit te " +"schakelen." + +#: desktop/org.gnome.Geary.gschema.xml:134 +msgid "Whether we migrated the old settings" +msgstr "Of we de oude instellingen overgezet hebben" + +#: desktop/org.gnome.Geary.gschema.xml:135 +msgid "" +"False to check for the old “org.yorba.geary”-schema and copy its values." +msgstr "" +"Schakel dit uit om te zoeken naar het oude ‘org.yorba.geary’-schema en de " +"waarden ervan over te nemen." + +#. Translators: In-app notification label, when +#. the app had a problem pinning an otherwise +#. untrusted TLS certificate +#: src/client/accounts/accounts-editor.vala:203 +msgid "Failed to store certificate" +msgstr "Opslaan van certificaat mislukt" + +#. Translators: Label for adding an email account +#. account for a generic IMAP service provider. +#: src/client/accounts/accounts-editor-add-pane.vala:109 +msgid "All others" +msgstr "Alle andere" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:196 +#: src/client/accounts/accounts-editor-servers-pane.vala:316 +msgid "Check your receiving login and password" +msgstr "Controleer uw login en wachtwoord voor ontvangen" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:211 +#: src/client/accounts/accounts-editor-servers-pane.vala:329 +msgid "Check your receiving server details" +msgstr "Controleer uw servergegevens voor ontvangen" + +#. Translators: In-app notification label +#. There was an SMTP auth error, but IMAP already +#. succeeded, so the user probably needs to +#. specify custom creds here +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:233 +#: src/client/accounts/accounts-editor-servers-pane.vala:350 +msgid "Check your sending login and password" +msgstr "Controleer uw login en wachtwoord voor verzenden" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:247 +#: src/client/accounts/accounts-editor-servers-pane.vala:363 +msgid "Check your sending server details" +msgstr "Controleer uw serverdetails voor verzenden" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:262 +msgid "Check your email address and password" +msgstr "Controleer uw e-mailadres en wachtwoord" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:273 +msgid "Could not connect, check your network" +msgstr "Kon geen verbinding maken, controleer uw netwerkverbinding" + +#. Translators: In-app notification label for a +#. generic error creating an account +#: src/client/accounts/accounts-editor-add-pane.vala:286 +msgid "An unexpected problem occurred" +msgstr "Er is een onverwacht probleem opgetreden" + +#. Translators: In-app notification label, the +#. string substitution is a more detailed reason. +#: src/client/accounts/accounts-editor-add-pane.vala:304 +#, c-format +msgid "Account not created: %s" +msgstr "Account niet aangemaakt: %s" + +#. Translators: Label for the person's actual name when adding +#. an account +#: src/client/accounts/accounts-editor-add-pane.vala:551 +msgid "Your name" +msgstr "Uw naam" + +#. Translators: Label used for the address part of an +#. email address when editing a user's sender address +#. preferences for an account. +#: src/client/accounts/accounts-editor-add-pane.vala:568 +#: src/client/accounts/accounts-editor-edit-pane.vala:501 +msgid "Email address" +msgstr "E-mailadres" + +#. Translators: Placeholder for the default sender address +#. when adding an account +#. Translators: This is used as a placeholder for the +#. address part of an email address when editing a user's +#. sender address preferences for an account. +#: src/client/accounts/accounts-editor-add-pane.vala:571 +#: src/client/accounts/accounts-editor-edit-pane.vala:469 +msgid "person@example.com" +msgstr "persoon@voorbeeld.be" + +#. Translators: Label for an IMAP/SMTP service login/user name +#. when adding an account +#. Translators: Label for the user's login name for an +#. IMAP, SMTP, etc service +#: src/client/accounts/accounts-editor-add-pane.vala:585 +#: src/client/accounts/accounts-editor-servers-pane.vala:880 +msgid "Login name" +msgstr "Loginnaam" + +#. Translators: Label for the user's password for an IMAP, +#. SMTP, etc service +#: src/client/accounts/accounts-editor-add-pane.vala:599 +#: src/client/accounts/accounts-editor-servers-pane.vala:999 +#: ui/password-dialog.glade:108 +msgid "Password" +msgstr "Wachtwoord" -#: ../desktop/geary-attach.contract.in.h:1 -msgid "Send by email" -msgstr "Verzenden via e-mail" +#. Translators: Label for the IMAP server hostname when +#. adding an account. +#. Translators: This label describes the host name or IP +#. address and port used by an account's IMAP service. +#: src/client/accounts/accounts-editor-add-pane.vala:621 +#: src/client/accounts/accounts-editor-servers-pane.vala:727 +msgid "IMAP server" +msgstr "IMAP-server" + +#. Translators: Placeholder for the IMAP server hostname +#. when adding an account. +#: src/client/accounts/accounts-editor-add-pane.vala:624 +msgid "imap.example.com" +msgstr "imap.voorbeeld.be" -#: ../desktop/geary-attach.contract.in.h:2 -msgid "Send files using Geary" -msgstr "Bestanden verzenden met Geary" +#. Translators: Label for the SMTP server hostname when +#. adding an account. +#. Translators: This label describes the host name or IP +#. address and port used by an account's SMTP service. +#: src/client/accounts/accounts-editor-add-pane.vala:630 +#: src/client/accounts/accounts-editor-servers-pane.vala:733 +msgid "SMTP server" +msgstr "SMTP-server" + +#. Translators: Placeholder for the SMTP server hostname +#. when adding an account. +#: src/client/accounts/accounts-editor-add-pane.vala:633 +msgid "smtp.example.com" +msgstr "smtp.voorbeeld.be" -#: ../src/client/accounts/account-dialog-add-edit-pane.vala:51 -#: ../src/client/components/stock.vala:31 -#: ../ui/conversation-email-menus.ui.h:10 -msgid "_Save" -msgstr "Op_slaan" +#. Translators: Label in the account editor for the user's +#. custom name for an account. +#: src/client/accounts/accounts-editor-edit-pane.vala:278 +#: ui/accounts_editor_remove_pane.ui:123 +msgid "Account name" +msgstr "Accountnaam" + +#. Translators: Tooltip used to undo changing +#. the name of an account. The string +#. substitution is the old name of the +#. account. +#: src/client/accounts/accounts-editor-edit-pane.vala:312 +#, c-format +msgid "Change account name back to “%s”" +msgstr "Accountnaam opnieuw instellen op ‘%s’" + +#. Translators: Tooltip for adding a new email sender/from +#. address's address to an account +#: src/client/accounts/accounts-editor-edit-pane.vala:336 +msgid "Add a new sender email address" +msgstr "Voeg nieuw e-mailadres toe voor afzender" + +#. Translators: Label used to indicate the user has +#. provided no display name for one of their sender +#. email addresses in their account settings. +#: src/client/accounts/accounts-editor-edit-pane.vala:417 +msgid "Name not set" +msgstr "Naam niet ingesteld" + +#. Translators: This is used as a placeholder for the +#. display name for an email address when editing a user's +#. sender address preferences for an account. +#: src/client/accounts/accounts-editor-edit-pane.vala:456 +msgid "Sender Name" +msgstr "Naam van afzender" -#: ../src/client/accounts/account-dialog-add-edit-pane.vala:51 -#: ../src/client/components/stock.vala:22 -msgid "_Add" -msgstr "_Toevoegen" +#: src/client/accounts/accounts-editor-edit-pane.vala:479 +msgid "Remove" +msgstr "Verwijderen" -#. reset/clear widgets -#: ../src/client/accounts/account-dialog-edit-alternate-emails-pane.vala:120 +#. Translators: Label used for the display name part of an +#. email address when editing a user's sender address +#. preferences for an account. +#: src/client/accounts/accounts-editor-edit-pane.vala:494 +msgid "Sender name" +msgstr "Naam van afzender" + +#. Translators: Label used as the undo tooltip after adding an +#. new sender email address to an account. The string +#. substitution is the email address added. +#: src/client/accounts/accounts-editor-edit-pane.vala:561 +#, c-format +msgid "Remove “%s”" +msgstr "‘%s’ verwijderen" + +#. Translators: Label used as the undo tooltip after editing a +#. sender address for an account. The string substitution is +#. the email address edited. +#: src/client/accounts/accounts-editor-edit-pane.vala:601 +#, c-format +msgid "Undo changes to “%s”" +msgstr "Wijzigingen aan ‘%s’ ongedaan maken" + +#. Translators: Label used as the undo tooltip after removing +#. a sender address from an account. The string substitution +#. is the email address edited. +#: src/client/accounts/accounts-editor-edit-pane.vala:688 +#, c-format +msgid "Add “%s” back" +msgstr "‘%s’ weer toevoegen" + +#. Translators: Label used as the undo tooltip after removing +#. a sender address from an account. The string substitution +#. is the email address edited. +#: src/client/accounts/accounts-editor-edit-pane.vala:730 +msgid "Undo signature changes" +msgstr "Wijzigingen aan ondertekening ongedaan maken" + +#. Translators: This label describes the account +#. preference for the length of time (weeks, months or +#. years) that past email should be downloaded. +#: src/client/accounts/accounts-editor-edit-pane.vala:778 +msgid "Download mail" +msgstr "Berichten downloaden" + +#. Translators: Tooltip for undoing a change +#. to the length of time that past email +#. should be downloaded for an account. The +#. string substitution is the duration, +#. e.g. "1 month back". +#: src/client/accounts/accounts-editor-edit-pane.vala:810 #, c-format -msgid "Additional addresses for %s" -msgstr "Bijkomstige adressen voor %s" - -#. Sets min size. -#: ../src/client/accounts/account-dialog.vala:21 -msgid "Accounts" -msgstr "Accounts" +msgid "Change download period back to: %s" +msgstr "Downloadperiode opnieuw instellen op: %s" -#. Copyright 2016 Software Freedom Conservancy Inc. -#. * -#. * This software is licensed under the GNU Lesser General Public License -#. * (version 2.1 or later). See the COPYING file in this distribution. -#. -#. Page for adding or editing an account. -#. / Placeholder text indicating that the user should type their first name and last name -#: ../src/client/accounts/add-edit-page.vala:10 -msgid "First Last" -msgstr "Voornaam Achternaam" - -#: ../src/client/accounts/add-edit-page.vala:235 -msgid "Welcome to Geary." -msgstr "Welkom bij Geary." - -#: ../src/client/accounts/add-edit-page.vala:235 -msgid "Enter your account information to get started." -msgstr "Vul uw accountgegevens in om te beginnen." +#: src/client/accounts/accounts-editor-edit-pane.vala:831 +msgid "Everything" +msgstr "Alles" -#: ../src/client/accounts/add-edit-page.vala:255 +#: src/client/accounts/accounts-editor-edit-pane.vala:835 msgid "2 weeks back" msgstr "Tot 2 weken geleden" -#. IDs are # of days -#: ../src/client/accounts/add-edit-page.vala:256 +#: src/client/accounts/accounts-editor-edit-pane.vala:839 msgid "1 month back" msgstr "Tot 1 maand geleden" -#: ../src/client/accounts/add-edit-page.vala:257 +#: src/client/accounts/accounts-editor-edit-pane.vala:843 msgid "3 months back" msgstr "Tot 3 maanden geleden" -#: ../src/client/accounts/add-edit-page.vala:258 +#: src/client/accounts/accounts-editor-edit-pane.vala:847 msgid "6 months back" msgstr "Tot 6 maanden geleden" -#: ../src/client/accounts/add-edit-page.vala:259 +#: src/client/accounts/accounts-editor-edit-pane.vala:851 msgid "1 year back" msgstr "Tot 1 jaar geleden" -#: ../src/client/accounts/add-edit-page.vala:260 +#: src/client/accounts/accounts-editor-edit-pane.vala:855 msgid "2 years back" msgstr "Tot 2 jaar geleden" -#: ../src/client/accounts/add-edit-page.vala:261 +#: src/client/accounts/accounts-editor-edit-pane.vala:859 msgid "4 years back" msgstr "Tot 4 jaar geleden" -#. Separator -#: ../src/client/accounts/add-edit-page.vala:263 -msgid "Everything" -msgstr "Alles" +#: src/client/accounts/accounts-editor-edit-pane.vala:865 +#, c-format +msgid "%d day back" +msgid_plural "%d days back" +msgstr[0] "Tot %d dag geleden" +msgstr[1] "Tot %d dagen geleden" + +#: src/client/accounts/accounts-editor-list-pane.vala:243 +msgid "Undo" +msgstr "Ongedaan maken" + +#: src/client/accounts/accounts-editor-list-pane.vala:251 +msgid "Redo" +msgstr "Opnieuw" + +#: src/client/accounts/accounts-editor-list-pane.vala:345 +#: src/client/accounts/accounts-editor-list-pane.vala:433 +#: src/client/accounts/accounts-editor-row.vala:278 +msgid "Gmail" +msgstr "Gmail" -#: ../src/client/accounts/add-edit-page.vala:283 -msgid "Edit" -msgstr "Bewerken" - -#: ../src/client/accounts/add-edit-page.vala:285 -msgid "Preview" -msgstr "Voorbeeld" - -#: ../src/client/accounts/add-edit-page.vala:751 -msgid "Remem_ber passwords" -msgstr "_Wachtwoorden onthouden" - -#: ../src/client/accounts/add-edit-page.vala:758 ../ui/login.glade.h:7 -msgid "Remem_ber password" -msgstr "Wachtwoord _onthouden" - -#: ../src/client/accounts/add-edit-page.vala:792 -msgid "Unable to validate:\n" -msgstr "Verifiëren mislukt:\n" - -#: ../src/client/accounts/add-edit-page.vala:794 -msgid " • Invalid account nickname.\n" -msgstr " • Ongeldige accountbijnaam.\n" - -#: ../src/client/accounts/add-edit-page.vala:797 -msgid " • Email address already added to Geary.\n" -msgstr " • E-mailadres is al aan Geary toegevoegd.\n" - -#: ../src/client/accounts/add-edit-page.vala:801 -msgid " • IMAP connection error.\n" -msgstr " • IMAP-verbindingsfout.\n" - -#: ../src/client/accounts/add-edit-page.vala:804 -msgid " • IMAP username or password incorrect.\n" -msgstr " • IMAP-gebruikersnaam of -wachtwoord onjuist.\n" - -#: ../src/client/accounts/add-edit-page.vala:807 -msgid " • SMTP connection error.\n" -msgstr " • SMTP-verbindingsfout.\n" - -#: ../src/client/accounts/add-edit-page.vala:810 -msgid " • SMTP username or password incorrect.\n" -msgstr " • SMTP-gebruikersnaam of -wachtwoord onjuist.\n" - -#: ../src/client/accounts/add-edit-page.vala:814 -msgid " • Connection error.\n" -msgstr " • Verbindingsfout.\n" - -#: ../src/client/accounts/add-edit-page.vala:818 -msgid " • Username or password incorrect.\n" -msgstr " • Gebruikersnaam of wachtwoord onjuist.\n" +#: src/client/accounts/accounts-editor-list-pane.vala:349 +#: src/client/accounts/accounts-editor-list-pane.vala:437 +#: src/client/accounts/accounts-editor-row.vala:282 +msgid "Outlook.com" +msgstr "Outlook.com" -#: ../src/client/application/geary-application.vala:22 +#: src/client/accounts/accounts-editor-list-pane.vala:353 +#: src/client/accounts/accounts-editor-list-pane.vala:441 +#: src/client/accounts/accounts-editor-row.vala:286 +msgid "Yahoo" +msgstr "Yahoo" + +#. Translators: Tooltip for accounts that have been +#. loaded but disabled by the user. +#: src/client/accounts/accounts-editor-list-pane.vala:371 +msgid "This account has been disabled" +msgstr "Deze account is uitgeschakeld" + +#. Translators: Tooltip for accounts that have been +#. loaded but because of some error are not able to be +#. used. +#: src/client/accounts/accounts-editor-list-pane.vala:380 +msgid "This account has encountered a problem and is unavailable" +msgstr "Deze account heeft een probleem ondervonden en is niet beschikbaar" + +#. Translators: Label for adding a generic email account +#: src/client/accounts/accounts-editor-list-pane.vala:430 +msgid "Other email providers" +msgstr "Andere e-mailproviders" + +#. Translators: Notification shown after removing an +#. account. The string substitution is the name of the +#. account. +#: src/client/accounts/accounts-editor-list-pane.vala:547 +#, c-format +msgid "Account “%s” removed" +msgstr "Account ‘%s’ verwijderd" + +#. Translators: Notification shown after removing an account +#. is undone. The string substitution is the name of the +#. account. +#: src/client/accounts/accounts-editor-list-pane.vala:554 +#, c-format +msgid "Account “%s” restored" +msgstr "Account ‘%s’ hersteld" + +#. Translators: Tooltip for dragging list items +#: src/client/accounts/accounts-editor-row.vala:49 +msgid "Drag to move this item" +msgstr "Sleep om dit item te verplaatsen" + +#. Translators: Label describes the service provider +#. hosting the email account, e.g. Gmail, Yahoo, or some +#. other generic IMAP service. +#: src/client/accounts/accounts-editor-row.vala:294 +msgid "Service provider" +msgstr "Dienstprovider" + +#. Translators: This label describes what form of transport +#. security (TLS, StartTLS, etc) used by an account's IMAP or SMTP +#. service. +#: src/client/accounts/accounts-editor-row.vala:467 +msgid "Connection security" +msgstr "Verbindingsbeveiliging" + +#. Translators: Label used when no auth scheme is used +#. by an account's IMAP or SMTP service. +#: src/client/accounts/accounts-editor-row.vala:478 +#: src/client/accounts/accounts-editor-servers-pane.vala:752 +#: src/client/accounts/accounts-editor-servers-pane.vala:964 +#: src/engine/api/geary-special-folder-type.vala:58 +msgid "None" +msgstr "Geen" + +#: src/client/accounts/accounts-editor-row.vala:485 +msgid "StartTLS" +msgstr "StartTLS" + +#: src/client/accounts/accounts-editor-row.vala:492 +msgid "TLS" +msgstr "TLS" + +#. Translators: Label for source of SMTP authentication +#. credentials (none, use IMAP, custom) when adding a new +#. account +#. Button label for retrying when a login error has occurred +#: src/client/accounts/accounts-editor-row.vala:533 ui/main-window.ui:455 +msgid "Login" +msgstr "Aanmelden" + +#. Translators: ComboBox value for source of SMTP +#. authentication credentials (none) when adding a new +#. account +#: src/client/accounts/accounts-editor-row.vala:540 +msgid "No login needed" +msgstr "Geen aanmelding vereist" + +#. Translators: ComboBox value for source of SMTP +#. authentication credentials (use IMAP) when adding a new +#. account +#: src/client/accounts/accounts-editor-row.vala:548 +msgid "Use same login as receiving" +msgstr "Zelfde login gebruiken als voor ontvangen" + +#. Translators: ComboBox value for source of SMTP +#. authentication credentials (custom) when adding a new +#. account +#: src/client/accounts/accounts-editor-row.vala:556 +msgid "Use a different login" +msgstr "Verschillende login gebruiken" + +#. Translators: In-app notification label, the +#. string substitution is a more detailed reason. +#: src/client/accounts/accounts-editor-servers-pane.vala:377 +#, c-format +msgid "Account not updated: %s" +msgstr "Account niet bijgewerkt: %s" + +#. Translators: This label describes the program that +#. created the account, e.g. an SSO service like GOA, or +#. locally by Geary. +#: src/client/accounts/accounts-editor-servers-pane.vala:540 +msgid "Account source" +msgstr "Accountbron" + +#: src/client/accounts/accounts-editor-servers-pane.vala:552 +msgid "GNOME Online Accounts" +msgstr "Gnome Online-accounts" + +#. Translators: This label describes an account +#. preference. +#: src/client/accounts/accounts-editor-servers-pane.vala:611 +msgid "Save drafts on server" +msgstr "Concepten opslaan op server" + +#. Translators: This label describes an account +#. preference. +#: src/client/accounts/accounts-editor-servers-pane.vala:666 +msgid "Save sent email on server" +msgstr "Verzonden berichten op server opslaan" + +#. Add a suffix for OAuth2 auth so people know they +#. shouldn't expect to be prompted for a password +#. Translators: Label used when an account's IMAP or +#. SMTP service uses OAuth2. The string replacement is +#. the service's login name. +#: src/client/accounts/accounts-editor-servers-pane.vala:950 +#, c-format +msgid "%s using OAuth2" +msgstr "%s via OAuth2" + +#: src/client/accounts/accounts-editor-servers-pane.vala:960 +msgid "Use receiving server login" +msgstr "Serverlogin voor ontvangen gebruiken" + +#: src/client/application/geary-application.vala:24 msgid "Copyright 2016 Software Freedom Conservancy Inc." msgstr "Copyright 2016 Software Freedom Conservancy Inc." -#: ../src/client/application/geary-application.vala:24 +#: src/client/application/geary-application.vala:25 +msgid "Copyright 2016-2019 Geary Development Team." +msgstr "Copyright 2016-2019 Geary-ontwikkelingsteam." + +#: src/client/application/geary-application.vala:27 msgid "Visit the Geary web site" msgstr "Bezoek de Geary-website" -#: ../src/client/application/geary-application.vala:464 +#: src/client/application/geary-application.vala:454 #, c-format msgid "About %s" msgstr "Over %s" @@ -288,7 +800,7 @@ #. Translators: add your name and email address to receive #. credit in the About dialog For example: Yamada Taro #. -#: ../src/client/application/geary-application.vala:468 +#: src/client/application/geary-application.vala:458 msgid "translator-credits" msgstr "" "Bas Duineveld \n" @@ -297,312 +809,107 @@ "\n" "Meer info over Gnome-NL http://nl.gnome.org" -#: ../src/client/application/geary-args.vala:10 +#: src/client/application/geary-args.vala:10 msgid "Start Geary with hidden main window" msgstr "Geary starten met verborgen hoofdvenster" -#: ../src/client/application/geary-args.vala:11 +#: src/client/application/geary-args.vala:11 msgid "Output debugging information" msgstr "Debuginformatie weergeven" -#: ../src/client/application/geary-args.vala:12 +#: src/client/application/geary-args.vala:12 msgid "Log conversation monitoring" msgstr "Gesprekscontrole loggen" -#: ../src/client/application/geary-args.vala:13 +#: src/client/application/geary-args.vala:13 msgid "Log network deserialization" msgstr "Netwerkdeserialisatie loggen" -#: ../src/client/application/geary-args.vala:14 +#: src/client/application/geary-args.vala:14 msgid "Log network activity" msgstr "Netwerkactiviteit loggen" #. / The IMAP replay queue is how changes on the server are replicated on the client. #. / It could also be called the IMAP events queue. -#: ../src/client/application/geary-args.vala:17 +#: src/client/application/geary-args.vala:17 msgid "Log IMAP replay queue" msgstr "IMAP-replaywachtrij loggen" #. / Serialization is how commands and responses are converted into a stream of bytes for #. / network transmission -#: ../src/client/application/geary-args.vala:20 +#: src/client/application/geary-args.vala:20 msgid "Log network serialization" msgstr "Netwerkserialisatie loggen" -#: ../src/client/application/geary-args.vala:21 +#: src/client/application/geary-args.vala:21 msgid "Log periodic activity" msgstr "Periodieke activiteit loggen" -#: ../src/client/application/geary-args.vala:22 +#: src/client/application/geary-args.vala:22 msgid "Log database queries (generates lots of messages)" -msgstr "Databasequery's loggen (genereert veel berichten)" +msgstr "Databasequery’s loggen (genereert veel berichten)" #. / "Normalization" can also be called "synchronization" -#: ../src/client/application/geary-args.vala:24 +#: src/client/application/geary-args.vala:24 msgid "Log folder normalization" msgstr "Mapnormalisatie loggen" -#: ../src/client/application/geary-args.vala:25 +#: src/client/application/geary-args.vala:25 msgid "Allow inspection of WebView" msgstr "Inspectie van WebView toestaan" -#: ../src/client/application/geary-args.vala:26 +#: src/client/application/geary-args.vala:26 msgid "Revoke all server certificates with TLS warnings" msgstr "Alle servercertificaten met TLS-waarschuwingen intrekken" -#: ../src/client/application/geary-args.vala:27 +#: src/client/application/geary-args.vala:27 msgid "Perform a graceful quit" msgstr "Sierlijk afsluiten" -#: ../src/client/application/geary-args.vala:28 +#: src/client/application/geary-args.vala:28 msgid "Display program version" msgstr "Programmaversie weergeven" #. This gives a command-line hint on how to open new composer windows with mailto: -#: ../src/client/application/geary-args.vala:53 +#: src/client/application/geary-args.vala:53 #, c-format msgid "Use %s to open a new composer window" msgstr "Gebruik %s om een nieuw opstelvenster te openen" -#: ../src/client/application/geary-args.vala:54 +#: src/client/application/geary-args.vala:56 msgid "Please report comments, suggestions and bugs to:" msgstr "Stuur opmerkingen, suggesties en bugs naar:" #. i18n: Command line arguments are invalid -#: ../src/client/application/geary-args.vala:61 +#: src/client/application/geary-args.vala:63 #, c-format msgid "Failed to parse command line options: %s\n" msgstr "Interpreteren van opdrachtregelopties mislukt: %s\n" -#: ../src/client/application/geary-args.vala:72 +#: src/client/application/geary-args.vala:74 #, c-format msgid "Unrecognized command line option “%s”\n" msgstr "Onbekende opdrachtregeloptie ‘%s’\n" -#: ../src/client/application/geary-controller.vala:57 -msgid "Delete conversation" -msgstr "Gesprek verwijderen" - -#: ../src/client/application/geary-controller.vala:58 -msgid "Delete conversation (Shift+Delete)" -msgstr "Gesprek verwijderen (Shift+Delete)" - -#: ../src/client/application/geary-controller.vala:59 -msgid "Delete conversations (Shift+Delete)" -msgstr "Gesprekken verwijderen (Shift+Delete)" - -#. This refers to the action ("move email to the trash"), not the Trash folder itself -#: ../src/client/application/geary-controller.vala:63 -msgid "Move conversation to Trash (Delete, Backspace)" -msgstr "Gesprek naar prullenbak verplaatsen (Delete, Backspace)" - -#: ../src/client/application/geary-controller.vala:64 -msgid "Move conversations to Trash (Delete, Backspace)" -msgstr "Gesprekken naar prullenbak verplaatsen (Delete, Backspace)" - -#. This refers to the action ("archive an email"), not the Archive folder itself -#: ../src/client/application/geary-controller.vala:68 -msgid "_Archive" -msgstr "_Archiveren" - -#: ../src/client/application/geary-controller.vala:69 -msgid "Archive conversation (A)" -msgstr "Gesprek archiveren (A)" - -#: ../src/client/application/geary-controller.vala:70 -msgid "Archive conversations (A)" -msgstr "Gesprekken archiveren (A)" - -#: ../src/client/application/geary-controller.vala:73 -msgid "Mark as S_pam" -msgstr "Markeren als s_pam" - -#: ../src/client/application/geary-controller.vala:74 -msgid "Mark as not S_pam" -msgstr "S_pammarkering opheffen" - -#: ../src/client/application/geary-controller.vala:76 -#: ../src/client/application/geary-controller.vala:448 -msgid "Mark conversation" -msgstr "Gesprek markeren" - -#: ../src/client/application/geary-controller.vala:77 -msgid "Mark conversations" -msgstr "Gesprekken markeren" - -#: ../src/client/application/geary-controller.vala:78 -msgid "Add label to conversation" -msgstr "Label aan gesprek toevoegen" - -#: ../src/client/application/geary-controller.vala:79 -msgid "Add label to conversations" -msgstr "Label aan gesprekken toevoegen" - -#: ../src/client/application/geary-controller.vala:80 -#: ../src/client/application/geary-controller.vala:487 -msgid "Move conversation" -msgstr "Gesprek verplaatsen" - -#: ../src/client/application/geary-controller.vala:81 -msgid "Move conversations" -msgstr "Gesprekken verplaatsen" - -#: ../src/client/application/geary-controller.vala:450 -msgid "_Mark as…" -msgstr "_Markeren als…" - -#: ../src/client/application/geary-controller.vala:456 -msgid "Mark as _Read" -msgstr "Markeren als _gelezen" - -#: ../src/client/application/geary-controller.vala:462 -msgid "Mark as _Unread" -msgstr "Markeren als _ongelezen" - -#: ../src/client/application/geary-controller.vala:468 -msgid "_Star" -msgstr "_Ster toevoegen" - -#: ../src/client/application/geary-controller.vala:473 -msgid "U_nstar" -msgstr "Ster _verwijderen" - -#: ../src/client/application/geary-controller.vala:483 -msgid "Add label" -msgstr "Label toevoegen" - -#: ../src/client/application/geary-controller.vala:484 -msgid "_Label" -msgstr "_Labelen" - -#: ../src/client/application/geary-controller.vala:488 -msgid "_Move" -msgstr "_Verplaatsen" - -#: ../src/client/application/geary-controller.vala:492 -msgid "Compose new message (Ctrl+N, N)" -msgstr "Nieuw bericht opstellen (Ctrl+N, N)" - -#: ../src/client/application/geary-controller.vala:496 -#: ../ui/conversation-email-menus.ui.h:1 -msgid "_Reply" -msgstr "_Beantwoorden" - -#: ../src/client/application/geary-controller.vala:497 -msgid "Reply (Ctrl+R, R)" -msgstr "Beantwoorden (Ctrl+R, R)" - -#: ../src/client/application/geary-controller.vala:501 -msgid "R_eply All" -msgstr "_Allen beantwoorden" - -#: ../src/client/application/geary-controller.vala:502 -msgid "Reply all (Ctrl+Shift+R, Shift+R)" -msgstr "Allen beantwoorden (Ctrl+Shift+R, Shift+R)" - -#: ../src/client/application/geary-controller.vala:507 -#: ../ui/conversation-email-menus.ui.h:3 -msgid "_Forward" -msgstr "_Doorsturen" - -#: ../src/client/application/geary-controller.vala:508 -msgid "Forward (Ctrl+L, F)" -msgstr "Doorsturen (Ctrl+L, F)" - -#: ../src/client/application/geary-controller.vala:538 -msgid "Empty _Spam…" -msgstr "_Spam legen…" - -#: ../src/client/application/geary-controller.vala:542 -msgid "Empty _Trash…" -msgstr "Prullen_bak legen…" - -#. No callback is connected, since we bind the toggle button to the search bar visibility -#: ../src/client/application/geary-controller.vala:574 -msgid "Toggle search bar" -msgstr "Zoekbalk in-/uitschakelen" - -#. No callback is connected, since we bind the toggle button to the find bar visibility -#: ../src/client/application/geary-controller.vala:579 -msgid "Toggle find bar" -msgstr "Zoekbalk in-/uitschakelen" - -#: ../src/client/application/geary-controller.vala:757 -msgid "Unable to store server trust exception" -msgstr "Kan serververtrouwenuitzondering niet opslaan" - -#: ../src/client/application/geary-controller.vala:992 -msgid "Your settings are insecure" -msgstr "Uw instellingen zijn onveilig" - -#: ../src/client/application/geary-controller.vala:993 -msgid "" -"Your IMAP and/or SMTP settings do not specify SSL or TLS. This means your " -"username and password could be read by another person on the network. Are " -"you sure you want to do this?" -msgstr "" -"Uw IMAP- en/of SMTP-instellingen zijn niet ingesteld op SSL of TLS. Dit " -"betekent dat uw gebruikersnaam en wachtwoord zichtbaar zijn voor anderen op " -"uw netwerk. Weet u zeker dat u dit wilt doen?" - -#: ../src/client/application/geary-controller.vala:994 -msgid "Co_ntinue" -msgstr "Doorgaa_n" - -#: ../src/client/application/geary-controller.vala:1041 -msgid "Error connecting to the server" -msgstr "Fout bij verbinden met de server" - -#: ../src/client/application/geary-controller.vala:1042 -msgid "" -"Geary encountered an error while connecting to the server. Please try again " -"in a few moments." -msgstr "" -"Er trad een fout op bij het verbinden met de server. Probeer het opnieuw " -"binnen enkele ogenblikken." - -#. / Displayed in the space-limited status bar when a message fails to be sent due to error. -#: ../src/client/application/geary-controller.vala:1079 -#: ../src/client/components/status-bar.vala:29 -msgid "Error sending email" -msgstr "E-mail verzenden mislukt" - -#: ../src/client/application/geary-controller.vala:1080 -msgid "" -"Geary encountered an error sending an email. If the problem persists, " -"please manually delete the email from your Outbox folder." -msgstr "" -"Er is een fout opgetreden bij het verzenden van een e-mail. Als het probleem " -"blijft bestaan, verwijder dan handmatig de e-mail in het Postvak Uit." - -#. Displayed in the space-limited status bar when a message fails to be uploaded -#. to Sent Mail after being sent. -#: ../src/client/application/geary-controller.vala:1084 -#: ../src/client/components/status-bar.vala:33 -msgid "Error saving sent mail" -msgstr "Verzonden e-mail opslaan mislukt" - -#: ../src/client/application/geary-controller.vala:1085 -msgid "" -"Geary encountered an error saving a sent message to Sent Mail. The message " -"will stay in your Outbox folder until you delete it." -msgstr "" -"Er is een fout opgetreden bij het opslaan van een verzonden e-mailbericht. " -"Het bericht zal in het Postvak Uit blijven staan totdat u het verwijdert." +#. Translators: File name used in save chooser when saving +#. attachments that do not otherwise have a name. +#: src/client/application/geary-controller.vala:58 +msgid "Untitled" +msgstr "Naamloos" -#: ../src/client/application/geary-controller.vala:1154 +#: src/client/application/geary-controller.vala:919 msgid "Labels" msgstr "Labels" #. give the user two options: reset the Account local store, or exit Geary. A third #. could be done to leave the Account in an unopened state, but we don't currently #. have provisions for that. -#: ../src/client/application/geary-controller.vala:1166 +#: src/client/application/geary-controller.vala:932 #, c-format msgid "Unable to open the database for %s" msgstr "De lokale database van %s kan niet geopend worden" -#: ../src/client/application/geary-controller.vala:1167 +#: src/client/application/geary-controller.vala:933 #, c-format msgid "" "There was an error opening the local mail database for this account. This is " @@ -627,20 +934,20 @@ "Het opnieuw bouwen van de database verwijdert alle lokale e-mailberichten en " "bijlagen. Mail op de server zal niet worden aangetast." -#: ../src/client/application/geary-controller.vala:1169 +#: src/client/application/geary-controller.vala:935 msgid "_Rebuild" msgstr "_Opnieuw bouwen" -#: ../src/client/application/geary-controller.vala:1169 +#: src/client/application/geary-controller.vala:935 msgid "E_xit" msgstr "_Afsluiten" -#: ../src/client/application/geary-controller.vala:1178 +#: src/client/application/geary-controller.vala:944 #, c-format msgid "Unable to rebuild database for “%s”" msgstr "De lokale database van ‘%s’ kan niet opnieuw gebouwd worden" -#: ../src/client/application/geary-controller.vala:1179 +#: src/client/application/geary-controller.vala:945 #, c-format msgid "" "Error during rebuild:\n" @@ -651,70 +958,15 @@ "\n" "%s" -#. some other problem opening the account ... as with other flow path, can't run -#. Geary today with an account in unopened state, so have to exit -#: ../src/client/application/geary-controller.vala:1201 -#: ../src/client/application/geary-controller.vala:1211 -#: ../src/client/application/geary-controller.vala:1222 -#, c-format -msgid "Unable to open local mailbox for %s" -msgstr "De lokale postbus van %s kan niet geopend worden" - -#: ../src/client/application/geary-controller.vala:1202 -#, c-format -msgid "" -"There was an error opening the local mail database for this account. This is " -"possibly due to a file permissions problem.\n" -"\n" -"Please check that you have read/write permissions for all files in this " -"directory:\n" -"\n" -"%s" -msgstr "" -"Er is een fout opgetreden bij het openen van de lokale e-maildatabase van " -"deze account. Dit wordt waarschijnlijk veroorzaakt door een probleem met de " -"bestandsrechten. ⏎\n" -"⏎\n" -"Controleer of u voldoende lees/schrijfrechten hebt voor alle bestanden in " -"deze map:⏎\n" -"⏎\n" -"%s" - -#: ../src/client/application/geary-controller.vala:1212 -msgid "" -"The version number of the local mail database is formatted for a newer " -"version of Geary. Unfortunately, the database cannot be “rolled back” to " -"work with this version of Geary.\n" -"\n" -"Please install the latest version of Geary and try again." -msgstr "" -"Het versienummer van de lokale e-maildatabase is opgemaakt voor een nieuwere " -"versie van Geary. Het is helaas niet mogelijk de database 'terug te zetten' " -"zodat hij met deze versie van Geary werkt.\n" -"\n" -"Installeer de nieuwste versie van Geary en probeer het opnieuw." - -#: ../src/client/application/geary-controller.vala:1223 -msgid "" -"There was an error opening the local account. This is probably due to " -"connectivity issues.\n" -"\n" -"Please check your network connection and restart Geary." -msgstr "" -"Er is een fout opgetreden bij het openen van de lokale account. Dit wordt " -"waarschijnlijk veroorzaakt door verbindingsproblemen.\n" -"\n" -"Controleer uw netwerkverbinding en herstart Geary." - -#: ../src/client/application/geary-controller.vala:1999 +#: src/client/application/geary-controller.vala:1800 msgid "Undo move (Ctrl+Z)" msgstr "Verplaatsen ongedaan maken (Ctrl+Z)" -#: ../src/client/application/geary-controller.vala:2009 +#: src/client/application/geary-controller.vala:1810 msgid "Are you sure you want to open these attachments?" msgstr "Weet u zeker dat u deze bijlagen wilt openen?" -#: ../src/client/application/geary-controller.vala:2010 +#: src/client/application/geary-controller.vala:1811 msgid "" "Attachments may cause damage to your system if opened. Only open files from " "trusted sources." @@ -722,199 +974,487 @@ "Bijlagen kunnen uw systeem schade toebrengen wanneer deze worden geopend. " "Open alleen bestanden van vertrouwde bronnen." -#: ../src/client/application/geary-controller.vala:2011 +#: src/client/application/geary-controller.vala:1812 msgid "Don’t _ask me again" msgstr "_Niet opnieuw vragen" -#: ../src/client/application/geary-controller.vala:2121 +#. Translators: Dialog primary label when prompting to +#. overwrite a file. The string substitution is the file'sx +#. name. +#: src/client/application/geary-controller.vala:1941 #, c-format msgid "A file named “%s” already exists. Do you want to replace it?" msgstr "Het bestand ‘%s’ bestaat al. Wilt u het vervangen?" -#: ../src/client/application/geary-controller.vala:2123 +#. Translators: Dialog secondary label when prompting to +#. overwrite a file. The string substitution is the parent +#. folder's name. +#: src/client/application/geary-controller.vala:1948 #, c-format msgid "" "The file already exists in “%s”. Replacing it will overwrite its contents." msgstr "Het bestand bestaat al in ‘%s’. Vervangen overschrijft de inhoud." -#: ../src/client/application/geary-controller.vala:2126 +#: src/client/application/geary-controller.vala:1952 msgid "_Replace" msgstr "_Vervangen" -#. Find out what to do with the inline composers. -#. TODO: Remove this in favor of automatically saving drafts -#: ../src/client/application/geary-controller.vala:2374 -msgid "Close open draft messages?" -msgstr "Geopende conceptberichten sluiten?" +#: src/client/application/geary-controller.vala:2228 +msgid "Close the draft message?" +msgid_plural "Close all draft messages?" +msgstr[0] "Conceptbericht sluiten?" +msgstr[1] "Alle conceptberichten sluiten?" -#: ../src/client/application/geary-controller.vala:2496 +# ‘van uw ...-map legen’ is niet duidelijk, voor verwijderen gekozen omdat het hierom gaat. -Justin +#: src/client/application/geary-controller.vala:2354 #, c-format msgid "Empty all email from your %s folder?" -msgstr "Alle e-mails van uw %s-map legen?" +msgstr "Alle e-mails uit uw %s-map verwijderen?" -#: ../src/client/application/geary-controller.vala:2497 +#: src/client/application/geary-controller.vala:2355 msgid "This removes the email from Geary and your email server." msgstr "Dit verwijdert de e-mail van Geary en uw e-mailserver." -#: ../src/client/application/geary-controller.vala:2498 +#: src/client/application/geary-controller.vala:2356 msgid "This cannot be undone." msgstr "Dit kan niet ongedaan gemaakt worden." -#: ../src/client/application/geary-controller.vala:2499 +#: src/client/application/geary-controller.vala:2357 #, c-format msgid "Empty %s" msgstr "Lege %s" -#: ../src/client/application/geary-controller.vala:2516 +#: src/client/application/geary-controller.vala:2374 #, c-format msgid "Error emptying %s" msgstr "Fout bij legen van %s" -#: ../src/client/application/geary-controller.vala:2546 +#: src/client/application/geary-controller.vala:2406 msgid "Do you want to permanently delete this message?" msgid_plural "Do you want to permanently delete these messages?" msgstr[0] "Wilt u dit bericht definitief verwijderen?" msgstr[1] "Wilt u deze berichten definitief verwijderen?" -#: ../src/client/application/geary-controller.vala:2548 +#: src/client/application/geary-controller.vala:2408 msgid "Delete" msgstr "Verwijderen" -#: ../src/client/application/geary-controller.vala:2580 -msgid "Undo archive (Ctrl+Z)" -msgstr "Archivering ongedaan maken (Ctrl+Z)" - -#: ../src/client/application/geary-controller.vala:2595 +#: src/client/application/geary-controller.vala:2422 msgid "Undo trash (Ctrl+Z)" msgstr "Verwijderen ongedaan maken (Ctrl+Z)" -#: ../src/client/application/geary-controller.vala:2649 +#: src/client/application/geary-controller.vala:2472 +msgid "Undo archive (Ctrl+Z)" +msgstr "Archivering ongedaan maken (Ctrl+Z)" + +#: src/client/application/geary-controller.vala:2517 msgid "Undo (Ctrl+Z)" msgstr "Ongedaan maken (Ctrl+Z)" -#: ../src/client/application/geary-controller.vala:2780 +#. Translators: The label for an in-app notification. The +#. string substitution is a list of recipients of the email. +#: src/client/application/geary-controller.vala:2598 +#, c-format +msgid "Successfully sent mail to %s." +msgstr "Bericht verzonden naar %s." + +#: src/client/application/geary-controller.vala:2680 msgid "Failed to open default text editor." msgstr "Openen van standaard tekstverwerker mislukt." -#: ../src/client/components/main-window.vala:389 +#. Translators: Tooltip used when an entry requires a valid +#. email address to be entered, but one is not provided. +#: src/client/components/components-validator.vala:341 +msgid "An email address is required" +msgstr "Een e-mailadres is vereist" + +#. Translators: Tooltip used when an entry requires a valid +#. email address to be entered, but the address is invalid. +#: src/client/components/components-validator.vala:345 +msgid "Not a valid email address" +msgstr "Geen geldig e-mailadres" + +#. Translators: Tooltip used when an entry requires a valid, +#. resolvable server name to be entered, but one is not +#. provided. +#: src/client/components/components-validator.vala:391 +msgid "A server name is required" +msgstr "Servernaam vereist" + +#. Translators: Tooltip used when an entry requires a valid +#. server name to be entered, but it was unable to be +#. looked-up in the DNS. +#: src/client/components/components-validator.vala:396 +msgid "Could not look up server name" +msgstr "Kon servernaam niet opzoeken" + +#: src/client/components/main-toolbar.vala:151 +msgid "Mark conversation" +msgid_plural "Mark conversations" +msgstr[0] "Gesprek markeren" +msgstr[1] "Gesprekken markeren" + +#: src/client/components/main-toolbar.vala:156 +msgid "Add label to conversation" +msgid_plural "Add label to conversations" +msgstr[0] "Label aan gesprek toevoegen" +msgstr[1] "Label aan gesprekken toevoegen" + +#: src/client/components/main-toolbar.vala:161 +msgid "Move conversation" +msgid_plural "Move conversations" +msgstr[0] "Gesprek verplaatsen" +msgstr[1] "Gesprekken verplaatsen" + +#: src/client/components/main-toolbar.vala:166 +msgid "Archive conversation (A)" +msgid_plural "Archive conversations (A)" +msgstr[0] "Gesprek archiveren (A)" +msgstr[1] "Gesprekken archiveren (A)" + +#: src/client/components/main-toolbar.vala:175 +msgid "Move conversation to Trash (Delete, Backspace)" +msgid_plural "Move conversations to Trash (Delete, Backspace)" +msgstr[0] "Gesprek naar prullenbak verplaatsen (Delete, Backspace)" +msgstr[1] "Gesprekken naar prullenbak verplaatsen (Delete, Backspace)" + +#: src/client/components/main-toolbar.vala:183 +msgid "Delete conversation (Shift+Delete)" +msgid_plural "Delete conversations (Shift+Delete)" +msgstr[0] "Gesprek verwijderen (Shift+Delete)" +msgstr[1] "Gesprekken verwijderen (Shift+Delete)" + +#: src/client/components/main-window.vala:503 #, c-format msgid "%s (%d)" msgstr "%s (%d)" -#: ../src/client/components/search-bar.vala:8 -#: ../src/client/folder-list/folder-list-search-branch.vala:38 -#: ../src/engine/api/geary-special-folder-type.vala:51 +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:47 +#, c-format +msgid "Problem connecting to incoming server for %s" +msgstr "Probleem bij verbinden met inkomende server voor %s" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:49 +#: src/client/components/main-window-info-bar.vala:58 +#, c-format +msgid "" +"Could not connect to %s, check your Internet access and the server name and " +"try again" +msgstr "" +"Kon niet verbinden met %s, controleer uw internettoegang en de servernaam en " +"probeer het opnieuw" + +#. Translators: Tooltip label for Retry button +#. Button tooltip for retrying an account problem +#: src/client/components/main-window-info-bar.vala:51 +#: src/client/components/main-window-info-bar.vala:60 +#: src/client/components/main-window-info-bar.vala:69 +#: src/client/components/main-window-info-bar.vala:78 +#: src/client/components/main-window-info-bar.vala:87 +#: src/client/components/main-window-info-bar.vala:96 +#: src/client/components/main-window-info-bar.vala:136 ui/main-window.ui:265 +msgid "Try reconnecting" +msgstr "Opnieuw verbinden" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:56 +#, c-format +msgid "Problem connecting to outgoing server for %s" +msgstr "Probleem bij verbinden met uitgaande server voor %s" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:65 +#: src/client/components/main-window-info-bar.vala:83 +#, c-format +msgid "Problem communicating with incoming server for %s" +msgstr "Probleem bij communicatie met inkomende server voor %s" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:67 +#: src/client/components/main-window-info-bar.vala:76 +#, c-format +msgid "Network error talking to %s, check your Internet access and try again" +msgstr "" +"Netwerkfout bij communicatie met %s, controleer uw internettoegang en " +"probeer het opnieuw" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:74 +#: src/client/components/main-window-info-bar.vala:91 +msgid "Problem communicating with outgoing mail server" +msgstr "Probleem bij communicatie met uitgaande mailserver" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:85 +#, c-format +msgid "" +"Geary did not understand a message from %s or vice versa, please file a bug " +"report" +msgstr "" +"Geary begreep een bericht van %s niet of vice versa, dien hiervoor een " +"foutmelding in" + +#. Translators: First string substitution is the server +#. name, second is the account name +#: src/client/components/main-window-info-bar.vala:94 +#, c-format +msgid "" +"Could not communicate with %s for %s, check the server name and try again in " +"a moment" +msgstr "" +"Kon niet communiceren met %s voor %s, controleer de servernaam en probeer " +"het zo dadelijk opnieuw" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:101 +#, c-format +msgid "Incoming mail server password required for %s" +msgstr "Wachtwoord vereist voor inkomende mailserver voor %s" + +#: src/client/components/main-window-info-bar.vala:102 +msgid "Messages cannot be received without the correct password." +msgstr "Berichten kunnen niet opgehaald worden zonder het juiste wachtwoord." + +#. Translators: Tooltip label for Retry button +#: src/client/components/main-window-info-bar.vala:104 +msgid "Retry receiving email, you will be prompted for a password" +msgstr "" +"Probeer e-mails opnieuw te ontvangen, het wachtwoord zal gevraagd worden" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:109 +#, c-format +msgid "Outgoing mail server password required for %s" +msgstr "Wachtwoord vereist voor uitgaande mailserver voor %s" + +#: src/client/components/main-window-info-bar.vala:110 +msgid "Messages cannot be sent without the correct password." +msgstr "Berichten kunnen niet verzonden worden zonder het juiste wachtwoord." + +#. Translators: Tooltip label for Retry button +#: src/client/components/main-window-info-bar.vala:112 +msgid "Retry sending queued messages, you will be prompted for a password" +msgstr "" +"Probeer berichten in wachtrij opnieuw te verzenden, het wachtwoord zal " +"gevraagd worden" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:117 +#, c-format +msgid "Incoming mail server security is not trusted for %s" +msgstr "Beveiliging van inkomende mailserver wordt niet vertrouwd voor %s" + +# ?????? +#: src/client/components/main-window-info-bar.vala:118 +msgid "Messages will not be received until checked." +msgstr "Berichten kunnen niet opgehaald worden tot controle." + +#. Translators: Tooltip label for Retry button +#: src/client/components/main-window-info-bar.vala:120 +#: src/client/components/main-window-info-bar.vala:128 +msgid "Check security details" +msgstr "Controleer de beveiligingsgegevens" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:125 +#, c-format +msgid "Outgoing mail server security is not trusted for %s" +msgstr "Beveiliging van uitgaande mailserver wordt niet vertrouwd voor %s" + +# ??????? +#: src/client/components/main-window-info-bar.vala:126 +msgid "Messages cannot be sent until checked." +msgstr "Berichten kunnen niet verzonden worden tot controle." + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:133 +#, c-format +msgid "A problem occurred checking mail for %s" +msgstr "Er trad een fout op bij het controleren van mail voor %s" + +#: src/client/components/main-window-info-bar.vala:134 +#: src/client/components/main-window-info-bar.vala:142 +msgid "Something went wrong, please file a bug report if the problem persists" +msgstr "" +"Er ging iets mis, dien een foutmelding in als het probleem zich blijft " +"voordoen" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:141 +#, c-format +msgid "A problem occurred sending mail for %s" +msgstr "Er trad een fout op bij het verzenden van e-mail voor %s" + +#. Translators: Tooltip label for Retry button +#: src/client/components/main-window-info-bar.vala:144 +msgid "Retry sending queued messages" +msgstr "Probeer berichten in wachtrij opnieuw te verzenden" + +#: src/client/components/main-window-info-bar.vala:155 +msgid "A database problem has occurred" +msgstr "Er is een databaseprobleem opgetreden" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:157 +#, c-format +msgid "Messages for %s must be downloaded again." +msgstr "Berichten voor %s moeten opnieuw gedownload worden." + +#: src/client/components/main-window-info-bar.vala:170 +msgid "Geary has encountered a problem" +msgstr "Geary heeft een probleem ondervonden" + +#: src/client/components/main-window-info-bar.vala:171 +msgid "" +"Please check the technical details and report the problem if it persists." +msgstr "" +"Controleer de technische gegevens, en meld het probleem als het zich blijft " +"voordoen." + +#: src/client/components/main-window-info-bar.vala:179 +msgid "_Details" +msgstr "_Details" + +#. Button tooltip for displaying technical details about an account problem +#: src/client/components/main-window-info-bar.vala:180 ui/main-window.ui:250 +msgid "View technical details about the error" +msgstr "Bekijk technische details over de fout" + +#: src/client/components/main-window-info-bar.vala:184 +msgid "_Retry" +msgstr "Opnieu_w" + +#: src/client/components/search-bar.vala:8 +#: src/client/folder-list/folder-list-search-branch.vala:38 +#: src/engine/api/geary-special-folder-type.vala:51 msgid "Search" msgstr "Zoeken" #. Search entry. -#: ../src/client/components/search-bar.vala:23 +#: src/client/components/search-bar.vala:23 msgid "Search all mail in account for keywords (Ctrl+S)" msgstr "Trefwoorden zoeken in alle berichten van account (Ctrl+S)" -#: ../src/client/components/search-bar.vala:100 +#: src/client/components/search-bar.vala:101 #, c-format msgid "Indexing %s account" msgstr "Bezig met indexeren van %s-account" -#: ../src/client/components/search-bar.vala:111 -#: ../src/client/folder-list/folder-list-search-branch.vala:39 +#: src/client/components/search-bar.vala:112 +#: src/client/folder-list/folder-list-search-branch.vala:39 #, c-format msgid "Search %s account" msgstr "%s-account doorzoeken" #. / Displayed in the space-limited status bar while a message is in the process of being sent. -#: ../src/client/components/status-bar.vala:26 +#: src/client/components/status-bar.vala:26 msgid "Sending…" msgstr "Verzenden…" -#: ../src/client/components/stock.vala:18 ../ui/account_cannot_remove.glade.h:3 +#. / Displayed in the space-limited status bar when a message fails to be sent due to error. +#: src/client/components/status-bar.vala:29 +msgid "Error sending email" +msgstr "E-mail verzenden mislukt" + +#. Displayed in the space-limited status bar when a message fails to be uploaded +#. to Sent Mail after being sent. +#: src/client/components/status-bar.vala:33 +msgid "Error saving sent mail" +msgstr "Verzonden e-mail opslaan mislukt" + +#: src/client/components/stock.vala:18 msgid "_OK" msgstr "_Oké" -#: ../src/client/components/stock.vala:19 ../ui/edit_alternate_emails.glade.h:3 -#: ../ui/password-dialog.glade.h:5 ../ui/remove_confirm.glade.h:5 +#: src/client/components/stock.vala:19 ui/password-dialog.glade:196 msgid "_Cancel" msgstr "_Annuleren" -#: ../src/client/components/stock.vala:21 ../ui/gtk/menus.ui.h:6 +#: src/client/components/stock.vala:21 msgid "_About" msgstr "_Info" -#: ../src/client/components/stock.vala:23 +#: src/client/components/stock.vala:22 +msgid "_Add" +msgstr "_Toevoegen" + +#: src/client/components/stock.vala:23 msgid "_Close" msgstr "_Sluiten" -#: ../src/client/components/stock.vala:24 +#: src/client/components/stock.vala:24 msgid "_Discard" msgstr "_Verwerpen" -#: ../src/client/components/stock.vala:25 ../ui/gtk/menus.ui.h:5 +#: src/client/components/stock.vala:25 ui/main-toolbar-menus.ui:56 msgid "_Help" msgstr "_Hulp" -#: ../src/client/components/stock.vala:26 ../ui/conversation-email-menus.ui.h:9 +#: src/client/components/stock.vala:26 ui/conversation-email-menus.ui:77 msgid "_Open" msgstr "_Openen" -#: ../src/client/components/stock.vala:27 ../ui/gtk/menus.ui.h:3 +#: src/client/components/stock.vala:27 ui/main-toolbar-menus.ui:46 msgid "_Preferences" msgstr "_Voorkeuren" -#: ../src/client/components/stock.vala:28 ../ui/conversation-email-menus.ui.h:7 +#. Translators: Menu item to print a single, specific message +#: src/client/components/stock.vala:28 ui/conversation-email-menus.ui:64 msgid "_Print…" msgstr "_Afdrukken…" -#: ../src/client/components/stock.vala:29 ../ui/gtk/menus.ui.h:7 +#: src/client/components/stock.vala:29 msgid "_Quit" msgstr "_Afsluiten" -#: ../src/client/components/stock.vala:30 ../ui/remove_confirm.glade.h:6 +#: src/client/components/stock.vala:30 msgid "_Remove" msgstr "_Verwijderen" -#: ../src/client/components/stock.vala:32 +#: src/client/components/stock.vala:31 ui/conversation-email-menus.ui:83 +msgid "_Save" +msgstr "Op_slaan" + +#: src/client/components/stock.vala:32 msgid "_Keep" msgstr "_Behouden" -#: ../src/client/composer/composer-link-popover.vala:150 +#: src/client/composer/composer-link-popover.vala:149 msgid "Link URL is not correctly formatted, e.g. http://example.com" msgstr "" "Verwijzings-URL is niet correct geformatteerd, bv. https://voorbeeld.be" -#: ../src/client/composer/composer-link-popover.vala:157 +#: src/client/composer/composer-link-popover.vala:156 msgid "Invalid link URL" msgstr "Ongeldige verwijzings-URL" -#: ../src/client/composer/composer-link-popover.vala:157 +#: src/client/composer/composer-link-popover.vala:156 msgid "Invalid email address" msgstr "Ongeldig e-mailadres" -#: ../src/client/composer/composer-widget.vala:154 +#: src/client/composer/composer-widget.vala:150 msgid "Saved" msgstr "Opgeslagen" -#: ../src/client/composer/composer-widget.vala:155 +#: src/client/composer/composer-widget.vala:151 msgid "Saving" msgstr "Bezig met opslaan" -#: ../src/client/composer/composer-widget.vala:156 +#: src/client/composer/composer-widget.vala:152 msgid "Error saving" msgstr "Opslaan mislukt" -#: ../src/client/composer/composer-widget.vala:157 +#: src/client/composer/composer-widget.vala:153 msgid "Press Backspace to delete quote" msgstr "Druk op Backspace om citaat te verwijderen" -#: ../src/client/composer/composer-widget.vala:158 -msgid "New Message" -msgstr "Nieuw bericht" - #. Translators: This is list of keywords, separated by pipe ("|") #. characters, that suggest an attachment; since this is full-word #. checking, include all variants of each word. No spaces are #. allowed. -#: ../src/client/composer/composer-widget.vala:167 +#: src/client/composer/composer-widget.vala:162 msgid "" "attach|attaching|attaches|attachment|attachments|attached|enclose|enclosed|" "enclosing|encloses|enclosure|enclosures" @@ -923,28 +1463,37 @@ "enclosing|encloses|enclosure|enclosures|bijlage|bijlagen|bijlages|bijvoegen|" "bijgevoegd|bijgevoegde" -#: ../src/client/composer/composer-widget.vala:1122 -#: ../src/client/composer/composer-widget.vala:1141 -msgid "Do you want to discard this message?" -msgstr "Wilt u dit bericht verwerpen?" +#. Translators: This dialog text is displayed to the +#. user when closing a composer where the options are +#. Keep, Discard or Cancel. +#: src/client/composer/composer-widget.vala:1123 +msgid "Do you want to keep or discard this draft message?" +msgstr "Wilt u dit conceptbericht bewaren of verwerpen?" + +#. Translators: This dialog text is displayed to the +#. user when closing a composer where the options are +#. only Discard or Cancel. +#: src/client/composer/composer-widget.vala:1151 +msgid "Do you want to discard this draft message?" +msgstr "Wilt u dit conceptbericht verwerpen?" -#: ../src/client/composer/composer-widget.vala:1245 +#: src/client/composer/composer-widget.vala:1268 msgid "Send message with an empty subject and body?" msgstr "Bericht zonder onderwerp en inhoud verzenden?" -#: ../src/client/composer/composer-widget.vala:1247 +#: src/client/composer/composer-widget.vala:1270 msgid "Send message with an empty subject?" msgstr "Bericht zonder onderwerp verzenden?" -#: ../src/client/composer/composer-widget.vala:1249 +#: src/client/composer/composer-widget.vala:1272 msgid "Send message with an empty body?" msgstr "Bericht zonder inhoud verzenden?" -#: ../src/client/composer/composer-widget.vala:1253 +#: src/client/composer/composer-widget.vala:1276 msgid "Send message without an attachment?" msgstr "Bericht zonder bijlage verzenden?" -#: ../src/client/composer/composer-widget.vala:1515 +#: src/client/composer/composer-widget.vala:1581 #, c-format msgid "“%s” already attached for delivery." msgstr "‘%s’ is al toegevoegd voor verzending." @@ -954,170 +1503,262 @@ #. description of the document type, the second will #. be a human-friendly size string. For example: #. Document (100.9MB) -#: ../src/client/composer/composer-widget.vala:1523 -#: ../src/client/conversation-viewer/conversation-email.vala:138 +#: src/client/composer/composer-widget.vala:1589 +#: src/client/conversation-viewer/conversation-email.vala:173 #, c-format msgid "%s (%s)" msgstr "%s (%s)" -#: ../src/client/composer/composer-widget.vala:1560 +#: src/client/composer/composer-widget.vala:1626 #, c-format msgid "“%s” could not be found." msgstr "‘%s’ kon niet worden gevonden." -#: ../src/client/composer/composer-widget.vala:1566 +#: src/client/composer/composer-widget.vala:1632 #, c-format msgid "“%s” is a folder." msgstr "‘%s’ is een map." -#: ../src/client/composer/composer-widget.vala:1572 +#: src/client/composer/composer-widget.vala:1638 #, c-format msgid "“%s” is an empty file." msgstr "‘%s’ is een leeg bestand." -#: ../src/client/composer/composer-widget.vala:1585 +#: src/client/composer/composer-widget.vala:1651 #, c-format msgid "“%s” could not be opened for reading." msgstr "Leesfout bij openen van ‘%s’." -#: ../src/client/composer/composer-widget.vala:1593 +#: src/client/composer/composer-widget.vala:1659 msgid "Cannot add attachment" msgstr "Toevoegen van bijlage mislukt" -#: ../src/client/composer/composer-widget.vala:1645 -msgid "To: " -msgstr "Aan: " - -#: ../src/client/composer/composer-widget.vala:1648 -msgid "Cc: " -msgstr "Cc: " - -#: ../src/client/composer/composer-widget.vala:1651 -msgid "Bcc: " -msgstr "Bcc: " +#. Translators: Human-readable version of the RFC 822 To header +#: src/client/composer/composer-widget.vala:1709 +#: src/client/conversation-viewer/conversation-email.vala:975 +#: src/engine/rfc822/rfc822-utils.vala:298 ui/conversation-message.ui:313 +msgid "To:" +msgstr "Aan:" + +#. Translators: Human-readable version of the RFC 822 CC header +#: src/client/composer/composer-widget.vala:1715 +#: src/client/conversation-viewer/conversation-email.vala:980 +#: src/engine/rfc822/rfc822-utils.vala:303 ui/conversation-message.ui:358 +msgid "Cc:" +msgstr "Cc:" + +#. Translators: Human-readable version of the RFC 822 BCC header +#: src/client/composer/composer-widget.vala:1721 +#: src/client/conversation-viewer/conversation-email.vala:985 +#: ui/conversation-message.ui:403 +msgid "Bcc:" +msgstr "Bcc:" -#: ../src/client/composer/composer-widget.vala:1654 +#. Translators: Human-readable version of the RFC 822 Reply-To header +#: src/client/composer/composer-widget.vala:1727 msgid "Reply-To: " msgstr "Beantwoord: " -#: ../src/client/composer/composer-widget.vala:1786 +#: src/client/composer/composer-widget.vala:1867 msgid "Select Color" msgstr "Kleur selecteren" -#. Displayed in the From dropdown to indicate an "alternate email address" -#. for an account. The first printf argument will be the alternate email -#. address, and the second will be the account's primary email address. -#: ../src/client/composer/composer-widget.vala:1986 +#. Displayed in the From dropdown to indicate an +#. "alternate email address" for an account. The first +#. printf argument will be the alternate email address, +#. and the second will be the account's primary email +#. address. +#: src/client/composer/composer-widget.vala:2057 #, c-format msgid "%1$s via %2$s" msgstr "%1$s via %2$s" #. Composer label (with mnemonic underscore) for the account selector #. when choosing what address to send a message from. -#: ../src/client/composer/composer-widget.vala:2028 +#: src/client/composer/composer-widget.vala:2118 msgid "_From:" msgstr "_Van:" #. Translators: This is the name of the file chooser filter #. when inserting an image in the composer. -#: ../src/client/composer/composer-widget.vala:2252 +#: src/client/composer/composer-widget.vala:2344 msgid "Images" msgstr "Afbeeldingen" -#: ../src/client/composer/spell-check-popover.vala:117 +#: src/client/composer/composer-window.vala:14 +msgid "New Message" +msgstr "Nieuw bericht" + +#: src/client/composer/spell-check-popover.vala:117 msgid "Remove this language from the preferred list" msgstr "Deze taal verwijderen uit de voorkeurslijst" -#: ../src/client/composer/spell-check-popover.vala:121 +#: src/client/composer/spell-check-popover.vala:121 msgid "Add this language to the preferred list" msgstr "Deze taal toevoegen aan de voorkeurslijst" -#: ../src/client/composer/spell-check-popover.vala:217 +#: src/client/composer/spell-check-popover.vala:217 msgid "Search for more languages" msgstr "Zoeken naar meer talen" -#: ../src/client/conversation-list/formatted-conversation-data.vala:11 +#: src/client/conversation-list/conversation-list-view.vala:283 +msgid "Delete conversation" +msgstr "Gesprek verwijderen" + +#: src/client/conversation-list/conversation-list-view.vala:286 +#: ui/main-toolbar-menus.ui:5 +msgid "Mark as _Read" +msgstr "Markeren als _gelezen" + +#: src/client/conversation-list/conversation-list-view.vala:289 +#: ui/main-toolbar-menus.ui:9 +msgid "Mark as _Unread" +msgstr "Markeren als _ongelezen" + +#: src/client/conversation-list/conversation-list-view.vala:292 +#: ui/main-toolbar-menus.ui:17 +msgid "U_nstar" +msgstr "Ster _verwijderen" + +#: src/client/conversation-list/conversation-list-view.vala:294 +#: ui/main-toolbar-menus.ui:13 +msgid "_Star" +msgstr "_Ster toevoegen" + +#. Translators: Menu item to reply to a specific message. +#: src/client/conversation-list/conversation-list-view.vala:297 +#: ui/conversation-email-menus.ui:9 +msgid "_Reply" +msgstr "_Beantwoorden" + +#: src/client/conversation-list/conversation-list-view.vala:298 +msgid "R_eply All" +msgstr "_Allen beantwoorden" + +#. Translators: Menu item to forward a specific message. +#: src/client/conversation-list/conversation-list-view.vala:299 +#: ui/conversation-email-menus.ui:21 +msgid "_Forward" +msgstr "_Doorsturen" + +#: src/client/conversation-list/formatted-conversation-data.vala:11 msgid "Me" msgstr "Ik" #. Translators: This is the file type displayed for #. attachments with unknown file types. -#: ../src/client/conversation-viewer/conversation-email.vala:124 +#: src/client/conversation-viewer/conversation-email.vala:159 msgid "Unknown" msgstr "Onbekend" -#. Preview headers +#. Translators: Human-readable version of the RFC 822 From header +#: src/client/conversation-viewer/conversation-email.vala:970 +#: src/engine/rfc822/rfc822-utils.vala:289 +msgid "From:" +msgstr "Van:" + +#. Translators: Human-readable version of the RFC 822 Date header +#: src/client/conversation-viewer/conversation-email.vala:990 +#: src/engine/rfc822/rfc822-utils.vala:294 +msgid "Date:" +msgstr "Datum:" + +#. Translators: Human-readable version of the RFC 822 Subject header +#: src/client/conversation-viewer/conversation-email.vala:995 +#: src/engine/rfc822/rfc822-utils.vala:292 +msgid "Subject:" +msgstr "Onderwerp:" + +#: src/client/conversation-viewer/conversation-message.vala:65 +msgid "This email address may have been forged" +msgstr "Dit e-mailadres is mogelijk nep" + +#. Compact headers #. Translators: This is displayed in place of the from address #. when the message has no from address. -#: ../src/client/conversation-viewer/conversation-message.vala:331 +#: src/client/conversation-viewer/conversation-message.vala:394 msgid "No sender" msgstr "Geen afzender" #. Translators: This separates multiple 'from' -#. addresses in the header preview for a message. -#: ../src/client/conversation-viewer/conversation-message.vala:586 +#. addresses in the compact header for a message. +#: src/client/conversation-viewer/conversation-message.vala:767 msgid ", " msgstr ", " #. Translators: This string is used as the HTML IMG ALT #. attribute value when displaying an inline image in an email #. that did not specify a file name. E.g. ImageCannot remove account " -msgstr "" -"Account verwijderen mislukt " +#: ui/accounts_editor_list_pane.ui:8 +msgid "Accounts" +msgstr "Accounts" -#: ../ui/account_cannot_remove.glade.h:2 -msgid "" -"A composer window associated with this account is currently open. Send or " -"discard the message and try again." -msgstr "" -"Vanuit deze account wordt momenteel een bericht opgesteld. Verzend of " -"verwerp dit bericht en probeer het opnieuw." +#: ui/accounts_editor_list_pane.ui:63 +msgid "To get started, select an email provider below." +msgstr "Kies om te beginnen hieronder een e-mailprovider." -#: ../ui/account_list.glade.h:1 -msgid "Add account" -msgstr "Account toevoegen" +#: ui/accounts_editor_list_pane.ui:76 +msgid "Welcome to Geary" +msgstr "Welkom bij Geary" -#: ../ui/account_list.glade.h:2 -msgid "Edit account" -msgstr "Account bewerken" +#. This title is shown to users when confirming if they want to remove an account. The string substitution is replaced with the account's name. +#: ui/accounts_editor_remove_pane.ui:73 +#, c-format +msgid "Confirm removing: %s" +msgstr "Bevestig verwijderen: %s" + +#: ui/accounts_editor_remove_pane.ui:91 +msgid "" +"Removing an account will remove it from Geary and delete locally cached " +"email data from your computer, but not from your service provider." +msgstr "" +"Een account verwijderen zal deze, samen met alle e-mailgegevens opgeslagen " +"in het lokale cachegeheugen, verwijderen uit Geary en van uw computer, maar " +"niet bij uw dienstprovider." -#: ../ui/account_list.glade.h:3 +#: ui/accounts_editor_remove_pane.ui:122 msgid "Remove account" msgstr "Account verwijderen" -#: ../ui/account_spinner.glade.h:1 -msgid "Please wait while Geary validates your account." -msgstr "Even geduld, Geary verifieert uw account." +#: ui/accounts_editor_servers_pane.ui:17 +msgid "Cancel" +msgstr "Annuleren" + +#: ui/accounts_editor_servers_pane.ui:47 +msgid "Apply" +msgstr "Toepassen" -#: ../ui/certificate_warning_dialog.glade.h:1 +#: ui/certificate_warning_dialog.glade:7 msgid "Untrusted Connection" msgstr "Onbetrouwbare verbinding" -#: ../ui/certificate_warning_dialog.glade.h:2 +#: ui/certificate_warning_dialog.glade:29 msgid "_Always Trust This Server" msgstr "Deze server _altijd vertrouwen" -#: ../ui/certificate_warning_dialog.glade.h:3 +#: ui/certificate_warning_dialog.glade:43 msgid "_Trust This Server" msgstr "Deze server ver_trouwen" -#: ../ui/certificate_warning_dialog.glade.h:4 +#: ui/certificate_warning_dialog.glade:57 msgid "_Don’t Trust This Server" msgstr "_Deze server niet vertrouwen" -#: ../ui/composer-headerbar.ui.h:1 +#: ui/composer-headerbar.ui:17 ui/composer-headerbar.ui:174 msgid "Detach (Ctrl+D)" msgstr "Loskoppelen (Ctrl+D)" -#: ../ui/composer-headerbar.ui.h:2 +#: ui/composer-headerbar.ui:57 ui/composer-headerbar.ui:83 msgid "Attach File (Ctrl+T)" msgstr "Bestand toevoegen (Ctrl+T)" -#: ../ui/composer-headerbar.ui.h:3 +#: ui/composer-headerbar.ui:107 msgid "Include Original Attachments" msgstr "Originele bijlagen toevoegen" -#: ../ui/composer-headerbar.ui.h:4 -msgid "Send (Ctrl+Enter)" -msgstr "Verzenden (Ctrl+Enter)" - -#: ../ui/composer-headerbar.ui.h:5 +#: ui/composer-headerbar.ui:202 msgid "_Send" msgstr "_Verzenden" -#: ../ui/composer-headerbar.ui.h:6 +#: ui/composer-headerbar.ui:207 +msgid "Send (Ctrl+Enter)" +msgstr "Verzenden (Ctrl+Enter)" + +#: ui/composer-headerbar.ui:230 msgid "Discard and Close" msgstr "Verwerpen en sluiten" -#: ../ui/composer-headerbar.ui.h:7 +#: ui/composer-headerbar.ui:254 msgid "Save and Close" msgstr "Opslaan en sluiten" #. Note that this button and the Update button will never be shown at the same time to the user. -#: ../ui/composer-link-popover.ui.h:2 +#: ui/composer-link-popover.ui:41 msgid "Insert the new link with this URL" msgstr "Nieuwe verwijzing met deze URL invoeren" -#: ../ui/composer-link-popover.ui.h:3 +#: ui/composer-link-popover.ui:52 msgid "Link URL" msgstr "Verwijzings-URL" #. Note that this button and the Insert button will never be shown at the same time to the user. -#: ../ui/composer-link-popover.ui.h:5 +#: ui/composer-link-popover.ui:66 msgid "Update this link’s URL" msgstr "URL van deze verwijzing bijwerken" -#: ../ui/composer-link-popover.ui.h:6 +#: ui/composer-link-popover.ui:86 msgid "Delete this link" msgstr "Deze verwijzing verwijderen" -#: ../ui/composer-link-popover.ui.h:7 +#: ui/composer-link-popover.ui:106 msgid "Open this link" msgstr "Deze verwijzing openen" -#: ../ui/composer-menus.ui.h:1 +#: ui/composer-menus.ui:7 msgid "S_ans Serif" msgstr "_Schreefloos" -#: ../ui/composer-menus.ui.h:2 +#: ui/composer-menus.ui:12 msgid "S_erif" msgstr "_Met schreef" -#: ../ui/composer-menus.ui.h:3 +#: ui/composer-menus.ui:17 msgid "_Fixed Width" msgstr "_Vaste breedte" -#: ../ui/composer-menus.ui.h:4 +#: ui/composer-menus.ui:24 msgid "_Small" msgstr "_Klein" -#: ../ui/composer-menus.ui.h:5 +#: ui/composer-menus.ui:29 msgid "_Medium" msgstr "_Middel" -#: ../ui/composer-menus.ui.h:6 +#: ui/composer-menus.ui:34 msgid "Lar_ge" msgstr "_Groot" -#: ../ui/composer-menus.ui.h:7 +#: ui/composer-menus.ui:41 msgid "C_olor" msgstr "K_leur" -#: ../ui/composer-menus.ui.h:8 +#: ui/composer-menus.ui:47 ui/composer-menus.ui:62 msgid "_Rich Text" msgstr "Opge_maakte tekst" -#: ../ui/composer-menus.ui.h:9 +#: ui/composer-menus.ui:53 ui/composer-menus.ui:68 msgid "Show Extended Fields" msgstr "Uitgebreide velden weergeven" -#: ../ui/composer-menus.ui.h:10 +#: ui/composer-menus.ui:78 msgid "_Undo" msgstr "O_ngedaan maken" -#: ../ui/composer-menus.ui.h:11 +#: ui/composer-menus.ui:82 msgid "_Redo" msgstr "_Opnieuw" -#: ../ui/composer-menus.ui.h:12 +#: ui/composer-menus.ui:88 ui/composer-menus.ui:106 msgid "Cu_t" msgstr "_Knippen" -#: ../ui/composer-menus.ui.h:13 ../ui/conversation-message-menus.ui.h:7 +#: ui/composer-menus.ui:92 ui/composer-menus.ui:110 +#: ui/conversation-message-menus.ui:37 msgid "_Copy" msgstr "_Kopiëren" -#: ../ui/composer-menus.ui.h:14 +#: ui/composer-menus.ui:96 ui/composer-menus.ui:114 msgid "_Paste" msgstr "_Plakken" -#: ../ui/composer-menus.ui.h:15 -msgctxt "Clipboard paste with rich text" -msgid "Paste _With Formatting" -msgstr "Plakken met _opmaak" +#: ui/composer-menus.ui:100 +msgctxt "Clipboard paste as plain text" +msgid "Paste _Without Formatting" +msgstr "Plakken _zonder opmaak" -#: ../ui/composer-menus.ui.h:16 +#: ui/composer-menus.ui:120 msgid "Select _All" msgstr "_Alles selecteren" -#: ../ui/composer-menus.ui.h:17 ../ui/conversation-message-menus.ui.h:9 +#: ui/composer-menus.ui:127 ui/conversation-message-menus.ui:49 msgid "_Inspect…" msgstr "_Inspecteren…" #. Address(es) e-mail is to be sent to -#: ../ui/composer-widget.ui.h:2 +#: ui/composer-widget.ui:56 msgid "_To" msgstr "_Aan" -#: ../ui/composer-widget.ui.h:3 +#: ui/composer-widget.ui:75 msgid "_Cc" msgstr "_Cc" -#: ../ui/composer-widget.ui.h:4 +#: ui/composer-widget.ui:130 msgid "_Subject" msgstr "_Onderwerp" -#: ../ui/composer-widget.ui.h:5 +#: ui/composer-widget.ui:149 msgid "_Bcc" msgstr "_Bcc" -#: ../ui/composer-widget.ui.h:6 +#: ui/composer-widget.ui:179 msgid "_Reply-To" msgstr "Antwoo_rden aan" #. Geary account mail will be sent from -#: ../ui/composer-widget.ui.h:8 +#: ui/composer-widget.ui:208 msgid "From" msgstr "Van" -#: ../ui/composer-widget.ui.h:9 +#: ui/composer-widget.ui:293 msgid "Drop files here" msgstr "Bestanden hier neerzetten" -#: ../ui/composer-widget.ui.h:10 +#: ui/composer-widget.ui:309 msgid "To add them as attachments" msgstr "Om ze als bijlage toe te voegen" -#: ../ui/composer-widget.ui.h:11 +#: ui/composer-widget.ui:353 msgid "Undo last edit (Ctrl+Z)" msgstr "Laatste wijziging ongedaan maken (Ctrl+Z)" -#: ../ui/composer-widget.ui.h:12 +#: ui/composer-widget.ui:377 msgid "Redo last edit (Ctrl+Shift+Z)" msgstr "Laatste wijziging opnieuw uitvoeren (Ctrl+Shift+Z)" -#: ../ui/composer-widget.ui.h:13 +#: ui/composer-widget.ui:415 msgid "Bold (Ctrl+B)" msgstr "Vet (Ctrl+B)" -#: ../ui/composer-widget.ui.h:14 +#: ui/composer-widget.ui:439 msgid "Italic (Ctrl+I)" msgstr "Cursief (Ctrl+I)" -#: ../ui/composer-widget.ui.h:15 +#: ui/composer-widget.ui:463 msgid "Underline (Ctrl+U)" msgstr "Onderstrepen (Ctrl+U)" -#: ../ui/composer-widget.ui.h:16 +#: ui/composer-widget.ui:487 msgid "Strikethrough (Ctrl+K)" msgstr "Doorstrepen (Ctrl+K)" -#: ../ui/composer-widget.ui.h:17 +#: ui/composer-widget.ui:525 +msgid "Insert unordered list" +msgstr "Niet-geordende lijst invoegen" + +#: ui/composer-widget.ui:549 +msgid "Insert ordered list" +msgstr "Geordende lijst invoegen" + +#: ui/composer-widget.ui:587 msgid "Quote text (Ctrl+])" msgstr "Citaat (Ctrl+])" -#: ../ui/composer-widget.ui.h:18 +#: ui/composer-widget.ui:611 msgid "Unquote text (Ctrl+[)" msgstr "Einde citaat (Ctrl+[)" -#: ../ui/composer-widget.ui.h:19 +#: ui/composer-widget.ui:649 msgid "Insert or update selection link (Ctrl+L)" msgstr "Selectieverwijzing invoegen of bijwerken (Ctrl+L)" -#: ../ui/composer-widget.ui.h:20 +#: ui/composer-widget.ui:673 msgid "Insert an image (Ctrl+G)" msgstr "Afbeelding invoegen (Ctrl+G)" -#: ../ui/composer-widget.ui.h:21 +#: ui/composer-widget.ui:707 msgid "Remove selection formatting (Ctrl+Space)" msgstr "Opmaak van selectie verwijderen (Ctrl+Space)" -#: ../ui/composer-widget.ui.h:22 +#: ui/composer-widget.ui:731 msgid "Select spell checking languages" msgstr "Talen voor spellingscontrole selecteren" -#: ../ui/conversation-email.ui.h:1 +#: ui/conversation-email.ui:27 msgid "Save all attachments" msgstr "Alle bijlagen opslaan" #. Note: The application will never show this button at the same time as unstar_button, one will always be hidden. -#: ../ui/conversation-email.ui.h:3 +#: ui/conversation-email.ui:50 msgid "Mark this message as starred" msgstr "Markeer dit bericht met ster" #. Note: The application will never show this button at the same time as star_button, one will always be hidden. -#: ../ui/conversation-email.ui.h:5 +#: ui/conversation-email.ui:72 msgid "Mark this message as not starred" msgstr "Markeer dit bericht zonder ster" -#: ../ui/conversation-email.ui.h:6 +#: ui/conversation-email.ui:95 msgid "Display the message menu" msgstr "Het berichtmenu weergeven" -#: ../ui/conversation-email.ui.h:7 +#: ui/conversation-email.ui:161 msgid "Open selected attachments" msgstr "Geselecteerde bijlagen openen" -#: ../ui/conversation-email.ui.h:8 +#: ui/conversation-email.ui:178 msgid "Save selected attachments" msgstr "Geselecteerde bijlagen opslaan" -#: ../ui/conversation-email.ui.h:9 +#: ui/conversation-email.ui:195 msgid "Select all attachments" msgstr "Alle bijlagen selecteren" -#: ../ui/conversation-email.ui.h:10 +#: ui/conversation-email.ui:240 msgid "Edit Draft" msgstr "Concept bewerken" -#: ../ui/conversation-email.ui.h:11 +#: ui/conversation-email.ui:267 msgid "Draft message" msgstr "Conceptbericht" -#: ../ui/conversation-email.ui.h:12 +#: ui/conversation-email.ui:283 msgid "This message has not yet been sent." msgstr "Dit bericht is nog niet verzonden." -#: ../ui/conversation-email.ui.h:13 -msgid "Try Again" -msgstr "Probeer het opnieuw" - -#: ../ui/conversation-email.ui.h:14 +#: ui/conversation-email.ui:329 msgid "Message not saved" msgstr "Bericht niet opgeslagen" -#: ../ui/conversation-email.ui.h:15 +#: ui/conversation-email.ui:345 msgid "This message was sent, but has not been saved to your account." msgstr "Dit bericht is verzonden, maar is niet opgeslagen in uw account." -#: ../ui/conversation-email-menus.ui.h:2 +#. Translators: Menu item to reply to a specific message. +#: ui/conversation-email-menus.ui:15 msgid "Reply to _All" msgstr "_Allen beantwoorden" -#: ../ui/conversation-email-menus.ui.h:4 +#. Translators: Menu item to mark a specific message as +#. read. +#: ui/conversation-email-menus.ui:30 msgid "_Mark Read" msgstr "_Markeren als gelezen" -#: ../ui/conversation-email-menus.ui.h:5 +#: ui/conversation-email-menus.ui:36 msgid "_Mark Unread" msgstr "_Markeren als ongelezen" -#: ../ui/conversation-email-menus.ui.h:6 +#. Translators: Menu item to mark all messages in a +#. conversation from this one as unread. +#: ui/conversation-email-menus.ui:42 msgid "Mark Unread From _Here" msgstr "Vanaf _hier markeren als ongelezen" -#: ../ui/conversation-email-menus.ui.h:8 +#. Translators: Menu item to move a single, specific message +#. to the trash +#: ui/conversation-email-menus.ui:50 +msgid "_Trash" +msgstr "_Prullenbak" + +#. Translators: Menu item to delete a single, specific message +#: ui/conversation-email-menus.ui:57 +msgid "_Delete…" +msgstr "Verwij_deren…" + +#. Translators: Menu item to view the source for a message +#: ui/conversation-email-menus.ui:69 msgid "_View Source" msgstr "_Bron weergeven" -#: ../ui/conversation-email-menus.ui.h:11 +#: ui/conversation-email-menus.ui:87 msgid "_Save All" msgstr "Alles op_slaan" -#: ../ui/conversation-message-menus.ui.h:1 +#: ui/conversation-message-menus.ui:7 msgid "_Open Link" msgstr "Verwijzing _openen" -#: ../ui/conversation-message-menus.ui.h:2 +#: ui/conversation-message-menus.ui:11 msgid "Copy Link _Address" msgstr "Verwijzings_adres kopiëren" -#: ../ui/conversation-message-menus.ui.h:3 +#: ui/conversation-message-menus.ui:17 msgid "Send New _Message…" msgstr "_Nieuw bericht verzenden…" -#: ../ui/conversation-message-menus.ui.h:4 +#: ui/conversation-message-menus.ui:21 msgid "Copy Email _Address" msgstr "E-mail_adres kopiëren" -#: ../ui/conversation-message-menus.ui.h:5 +#: ui/conversation-message-menus.ui:27 msgid "Save _Image As…" msgstr "_Afbeelding opslaan als…" -#: ../ui/conversation-message-menus.ui.h:6 +#: ui/conversation-message-menus.ui:33 msgid "_Select All" msgstr "Alles _selecteren" -#: ../ui/conversation-message-menus.ui.h:8 +#: ui/conversation-message-menus.ui:43 msgid "Search for messages from" msgstr "Zoeken naar berichten van" -#: ../ui/conversation-message.ui.h:1 +#: ui/conversation-message.ui:64 msgid "From " msgstr "Van " -#: ../ui/conversation-message.ui.h:2 +#: ui/conversation-message.ui:80 ui/conversation-message.ui:179 msgid "1/1/1970\t" msgstr "1/1/1970\t" -#: ../ui/conversation-message.ui.h:3 +#: ui/conversation-message.ui:103 msgid "Preview body text." msgstr "Hoofdtekst voor berichtvoorbeeld." -#: ../ui/conversation-message.ui.h:4 +#: ui/conversation-message.ui:203 msgid "Sent by:" msgstr "Verzonden door:" -#: ../ui/conversation-message.ui.h:5 +#: ui/conversation-message.ui:248 msgid "Reply to:" msgstr "Antwoorden aan:" -#: ../ui/conversation-message.ui.h:6 +#: ui/conversation-message.ui:292 msgid "Subject" msgstr "Onderwerp" -#: ../ui/conversation-message.ui.h:7 -msgid "To:" -msgstr "Aan:" - -#: ../ui/conversation-message.ui.h:8 -msgid "Cc:" -msgstr "Cc:" - -#: ../ui/conversation-message.ui.h:9 -msgid "Bcc:" -msgstr "Bcc:" - -#: ../ui/conversation-message.ui.h:10 +#: ui/conversation-message.ui:502 msgid "Show Images" msgstr "Afbeeldingen tonen" -#: ../ui/conversation-message.ui.h:11 +#: ui/conversation-message.ui:515 msgid "Always Show From Sender" msgstr "Afbeeldingen van deze afzender altijd tonen" -#: ../ui/conversation-message.ui.h:12 +#: ui/conversation-message.ui:543 msgid "Remote images not shown" msgstr "Externe afbeeldingen worden niet getoond" -#: ../ui/conversation-message.ui.h:13 +#: ui/conversation-message.ui:560 msgid "Only show remote images from senders you trust." msgstr "Toon enkel externe afbeeldingen van afzenders die u vertrouwt." -#: ../ui/conversation-message.ui.h:14 +#: ui/conversation-message.ui:693 msgid "But actually goes to:" msgstr "Maar gaat eigenlijk naar:" -#: ../ui/conversation-message.ui.h:15 +#: ui/conversation-message.ui:724 msgid "The link appears to go to:" msgstr "De verwijzing lijkt te leiden naar:" -#: ../ui/conversation-message.ui.h:16 +#: ui/conversation-message.ui:736 msgid "Deceptive link found" msgstr "Misleidende verwijzing gevonden" -#: ../ui/conversation-message.ui.h:17 +#: ui/conversation-message.ui:751 msgid "The email sender may be leading you to the wrong web site." msgstr "" "De afzender van deze e-mail leidt u misschien naar de verkeerde website." -#: ../ui/conversation-message.ui.h:18 +#: ui/conversation-message.ui:764 msgid "If unsure, contact the sender and ask before continuing." msgstr "" "Neem bij twijfel eerst contact op met de afzender vooraleer verder te gaan." -#: ../ui/conversation-viewer.ui.h:1 +#: ui/conversation-viewer.ui:60 msgid "Find in conversation" msgstr "Zoeken in gesprek" -#: ../ui/conversation-viewer.ui.h:2 +#: ui/conversation-viewer.ui:74 msgid "Find the previous occurrence of the search string." msgstr "Zoek het vorige voorkomen van de zoekopdracht." -#: ../ui/conversation-viewer.ui.h:3 +#: ui/conversation-viewer.ui:95 msgid "Find the next occurrence of the search string." msgstr "Zoek het volgende voorkomen van de zoekopdracht." -#: ../ui/edit_alternate_emails.glade.h:1 -msgid "Remove email address" -msgstr "E-mailadres verwijderen" - -#: ../ui/edit_alternate_emails.glade.h:2 -msgid "" -"Some email services require additional addresses be configured on the " -"server. Contact your email provider for more information." -msgstr "" -"Sommige e-maildiensten vereisen dat bijkomstige adressen geconfigureerd " -"worden op de server. Neem contact op met uw e-mailprovider voor meer " -"informatie." - -#: ../ui/edit_alternate_emails.glade.h:4 -msgid "_Update" -msgstr "B_ijwerken" - -#: ../ui/find_bar.glade.h:1 +#: ui/find_bar.glade:66 msgid "Find:" msgstr "Zoeken:" -#: ../ui/find_bar.glade.h:2 +#: ui/find_bar.glade:89 msgid "_Previous" msgstr "_Vorige" -#: ../ui/find_bar.glade.h:3 +#: ui/find_bar.glade:107 msgid "_Next" msgstr "_Volgende" -#: ../ui/find_bar.glade.h:4 +#: ui/find_bar.glade:125 msgid "_Case sensitive" msgstr "_Hoofdlettergevoelig" -#: ../ui/find_bar.glade.h:5 +#: ui/find_bar.glade:145 msgid "label" -msgstr "Label" +msgstr "label" -#: ../ui/gtk/help-overlay.ui.h:1 +#: ui/gtk/help-overlay.ui:9 msgid "Conversation Shortcuts" msgstr "Gesprekssneltoetsen" -#: ../ui/gtk/help-overlay.ui.h:2 +#: ui/gtk/help-overlay.ui:13 ui/gtk/help-overlay.ui:254 msgctxt "shortcut window" msgid "General" msgstr "Algemeen" -#: ../ui/gtk/help-overlay.ui.h:3 +#: ui/gtk/help-overlay.ui:17 msgctxt "shortcut window" msgid "Move focus to the next/previous pane" msgstr "Verplaats focus naar volgend/vorig paneel" -#: ../ui/gtk/help-overlay.ui.h:4 +#: ui/gtk/help-overlay.ui:24 msgctxt "shortcut window" msgid "Move focus to conversation list" msgstr "Verplaats focus naar gesprekslijst" -#: ../ui/gtk/help-overlay.ui.h:5 +#: ui/gtk/help-overlay.ui:31 msgctxt "shortcut window" msgid "Detach composer window" msgstr "Opstelvenster losmaken" -#: ../ui/gtk/help-overlay.ui.h:6 +#: ui/gtk/help-overlay.ui:38 msgctxt "shortcut window" msgid "Close composer window" msgstr "Opstelvenster sluiten" -#: ../ui/gtk/help-overlay.ui.h:7 +#: ui/gtk/help-overlay.ui:45 msgctxt "shortcut window" msgid "Show keyboard shortcuts" msgstr "Sneltoetsen tonen" -#: ../ui/gtk/help-overlay.ui.h:8 +#: ui/gtk/help-overlay.ui:52 msgctxt "shortcut window" msgid "Show help" msgstr "Hulp tonen" -#: ../ui/gtk/help-overlay.ui.h:9 +#: ui/gtk/help-overlay.ui:59 msgctxt "shortcut window" msgid "Quit the application" msgstr "De toepassing afsluiten" -#: ../ui/gtk/help-overlay.ui.h:10 +#: ui/gtk/help-overlay.ui:68 msgctxt "shortcut window" msgid "Search" msgstr "Zoeken" -#: ../ui/gtk/help-overlay.ui.h:11 +#: ui/gtk/help-overlay.ui:72 msgctxt "shortcut window" msgid "Jump to search box" msgstr "Spring naar zoekbalk" -#: ../ui/gtk/help-overlay.ui.h:12 +#: ui/gtk/help-overlay.ui:79 msgctxt "shortcut window" msgid "Find in current conversation" msgstr "Zoeken in huidig gesprek" -#: ../ui/gtk/help-overlay.ui.h:13 +#: ui/gtk/help-overlay.ui:86 msgctxt "shortcut window" msgid "Find next/previous in current conversation" msgstr "Zoek volgende/vorige in huidig gesprek" -#: ../ui/gtk/help-overlay.ui.h:14 +#: ui/gtk/help-overlay.ui:95 ui/gtk/help-overlay.ui:274 msgctxt "shortcut window" msgid "Actions" msgstr "Acties" -#: ../ui/gtk/help-overlay.ui.h:15 +#: ui/gtk/help-overlay.ui:99 msgctxt "shortcut window" msgid "Compose a new message" msgstr "Een nieuw bericht opstellen" -#: ../ui/gtk/help-overlay.ui.h:16 +#: ui/gtk/help-overlay.ui:106 msgctxt "shortcut window" msgid "Reply to sender " msgstr "Afzender beantwoorden " -#: ../ui/gtk/help-overlay.ui.h:17 +#: ui/gtk/help-overlay.ui:113 msgctxt "shortcut window" msgid "Reply to all" msgstr "Allen beantwoorden" -#: ../ui/gtk/help-overlay.ui.h:18 +#: ui/gtk/help-overlay.ui:120 msgctxt "shortcut window" msgid "Forward" msgstr "Doorsturen" -#: ../ui/gtk/help-overlay.ui.h:19 +#: ui/gtk/help-overlay.ui:127 msgctxt "shortcut window" msgid "Archive" msgstr "Archiveren" -#: ../ui/gtk/help-overlay.ui.h:20 +#: ui/gtk/help-overlay.ui:134 msgctxt "shortcut window" msgid "Move to trash" msgstr "Verplaatsen naar prullenbak" -#: ../ui/gtk/help-overlay.ui.h:21 +#: ui/gtk/help-overlay.ui:141 msgctxt "shortcut window" msgid "Toggle spam" msgstr "Spam in-/uitschakelen" -#: ../ui/gtk/help-overlay.ui.h:22 +#: ui/gtk/help-overlay.ui:148 msgctxt "shortcut window" msgid "Move the conversation" msgstr "Het gesprek verplaatsen" -#: ../ui/gtk/help-overlay.ui.h:23 +#: ui/gtk/help-overlay.ui:155 msgctxt "shortcut window" msgid "Label the conversation" msgstr "Label aan gesprek toevoegen" -#: ../ui/gtk/help-overlay.ui.h:24 +#: ui/gtk/help-overlay.ui:163 msgctxt "shortcut window" msgid "Mark read" msgstr "Markeren als gelezen" -#: ../ui/gtk/help-overlay.ui.h:25 +#: ui/gtk/help-overlay.ui:170 msgctxt "shortcut window" msgid "Mark unread" msgstr "Markeren als ongelezen" -#: ../ui/gtk/help-overlay.ui.h:26 +#: ui/gtk/help-overlay.ui:179 msgctxt "shortcut window" msgid "View" msgstr "Weergeven" -#: ../ui/gtk/help-overlay.ui.h:27 +#: ui/gtk/help-overlay.ui:183 msgctxt "shortcut window" msgid "Zoom in" msgstr "Inzoomen" -#: ../ui/gtk/help-overlay.ui.h:28 +#: ui/gtk/help-overlay.ui:190 msgctxt "shortcut window" msgid "Zoom out" msgstr "Uitzoomen" -#: ../ui/gtk/help-overlay.ui.h:29 +#: ui/gtk/help-overlay.ui:197 msgctxt "shortcut window" msgid "Reset zoom" msgstr "Zoomniveau terugzetten" -#: ../ui/gtk/help-overlay.ui.h:30 +#: ui/gtk/help-overlay.ui:206 msgctxt "shortcut window" msgid "Additional Shortcuts" msgstr "Extra sneltoetsen" -#: ../ui/gtk/help-overlay.ui.h:31 +#: ui/gtk/help-overlay.ui:210 msgctxt "shortcut window" msgid "Star" msgstr "Ster toevoegen" -#: ../ui/gtk/help-overlay.ui.h:32 +#: ui/gtk/help-overlay.ui:217 msgctxt "shortcut window" msgid "Unstar" msgstr "Ster verwijderen" -#: ../ui/gtk/help-overlay.ui.h:33 +#: ui/gtk/help-overlay.ui:224 msgctxt "shortcut window" msgid "Delete" msgstr "Verwijderen" -#: ../ui/gtk/help-overlay.ui.h:34 +#: ui/gtk/help-overlay.ui:231 msgctxt "shortcut window" msgid "Jump to next (older) conversation" msgstr "Spring naar volgend (ouder) gesprek" -#: ../ui/gtk/help-overlay.ui.h:35 +#: ui/gtk/help-overlay.ui:238 msgctxt "shortcut window" msgid "Jump to previous (newer) conversation" msgstr "Spring naar vorig (recenter) gesprek" -#: ../ui/gtk/help-overlay.ui.h:36 +#: ui/gtk/help-overlay.ui:250 msgid "Composer Shortcuts" msgstr "Opstelvenstersneltoetsen" -#: ../ui/gtk/help-overlay.ui.h:37 +#: ui/gtk/help-overlay.ui:258 msgctxt "shortcut window" msgid "Quote text" msgstr "Citaat" -#: ../ui/gtk/help-overlay.ui.h:38 +#: ui/gtk/help-overlay.ui:265 msgctxt "shortcut window" msgid "Unquote text" msgstr "Einde citaat" -#: ../ui/gtk/help-overlay.ui.h:39 +#: ui/gtk/help-overlay.ui:278 msgctxt "shortcut window" msgid "Send" msgstr "Verzenden" -#: ../ui/gtk/help-overlay.ui.h:40 +#: ui/gtk/help-overlay.ui:285 msgctxt "shortcut window" msgid "Add attachment" msgstr "Bijlage toevoegen" -#: ../ui/gtk/help-overlay.ui.h:41 +#: ui/gtk/help-overlay.ui:294 msgctxt "shortcut window" msgid "Rich text mode" msgstr "Opgemaaktetekstmodus" -#: ../ui/gtk/help-overlay.ui.h:42 +#: ui/gtk/help-overlay.ui:298 msgctxt "shortcut window" msgid "Bold text" msgstr "Vette tekst" -#: ../ui/gtk/help-overlay.ui.h:43 +#: ui/gtk/help-overlay.ui:305 msgctxt "shortcut window" msgid "Italicize text" msgstr "Cursieve tekst" -#: ../ui/gtk/help-overlay.ui.h:44 +#: ui/gtk/help-overlay.ui:312 msgctxt "shortcut window" msgid "Underline text" msgstr "Tekst onderstrepen" -#: ../ui/gtk/help-overlay.ui.h:45 +#: ui/gtk/help-overlay.ui:319 msgctxt "shortcut window" msgid "Strike text" msgstr "Tekst doorstrepen" -#: ../ui/gtk/help-overlay.ui.h:46 +#: ui/gtk/help-overlay.ui:326 msgctxt "shortcut window" msgid "Insert a link" msgstr "Een verwijzing invoegen" -#: ../ui/gtk/help-overlay.ui.h:47 +#: ui/gtk/help-overlay.ui:333 msgctxt "shortcut window" msgid "Remove formatting" msgstr "Opmaak verwijderen" -#: ../ui/gtk/menus.ui.h:2 -msgid "A_ccounts" -msgstr "A_ccounts" +#: ui/main-toolbar.ui:23 +msgctxt "tooltip" +msgid "Compose Message" +msgstr "Bericht opstellen" -#: ../ui/gtk/menus.ui.h:4 -msgid "_Keyboard Shortcuts" -msgstr "_Sneltoetsen" +#: ui/main-toolbar.ui:52 +msgid "Toggle search bar" +msgstr "Zoekbalk in-/uitschakelen" -#: ../ui/login.glade.h:1 -msgid "email@example.com" -msgstr "email@voorbeeld.be" +#: ui/main-toolbar.ui:112 +msgid "Reply" +msgstr "Beantwoorden" -#: ../ui/login.glade.h:2 ../ui/password-dialog.glade.h:3 -msgid "Password" -msgstr "Wachtwoord" +#: ui/main-toolbar.ui:135 +msgid "Reply All" +msgstr "Allen beantwoorden" -#: ../ui/login.glade.h:3 -msgid "E_mail address" -msgstr "E-_mailadres" - -#: ../ui/login.glade.h:4 -msgid "_Password" -msgstr "_Wachtwoord" - -#: ../ui/login.glade.h:5 -msgid "S_ervice" -msgstr "Di_enst" - -#: ../ui/login.glade.h:6 -msgid "N_ame" -msgstr "N_aam" - -#: ../ui/login.glade.h:8 -msgid "N_ickname" -msgstr "B_ijnaam" - -#: ../ui/login.glade.h:9 -msgid "Work, Home, etc." -msgstr "Werk, Thuis, etc." - -#: ../ui/login.glade.h:10 -msgid "_Save sent mail" -msgstr "Verzonden berichten op_slaan" - -#: ../ui/login.glade.h:11 -msgid "Addi_tional email addresses…" -msgstr "Bijkoms_tige e-mailadressen…" - -#: ../ui/login.glade.h:12 -msgid "IMAP settings" -msgstr "IMAP-instellingen" - -#: ../ui/login.glade.h:13 -msgid "Se_rver" -msgstr "Se_rver" +#: ui/main-toolbar.ui:158 +msgid "Forward" +msgstr "Doorsturen" -#: ../ui/login.glade.h:14 -msgid "imap.example.com" -msgstr "imap.voorbeeld.be" +#: ui/main-toolbar.ui:264 +msgid "Toggle find bar" +msgstr "Zoekbalk in-/uitschakelen" -#: ../ui/login.glade.h:15 -msgid "P_ort" -msgstr "P_oort" +#: ui/main-toolbar.ui:306 +msgid "_Archive" +msgstr "_Archiveren" -#: ../ui/login.glade.h:16 -msgid "smtp.example.com" -msgstr "smtp.voorbeeld.be" +#: ui/main-toolbar-menus.ui:21 +msgid "Mark as S_pam" +msgstr "Markeren als s_pam" + +#: ui/main-toolbar-menus.ui:25 +msgid "Mark as not S_pam" +msgstr "S_pammarkering opheffen" + +#: ui/main-toolbar-menus.ui:32 +msgid "Empty _Spam…" +msgstr "_Spam legen…" + +#: ui/main-toolbar-menus.ui:36 +msgid "Empty _Trash…" +msgstr "Prullen_bak legen…" -#: ../ui/login.glade.h:17 -msgid "Ser_ver" -msgstr "Ser_ver" - -#: ../ui/login.glade.h:18 -msgid "Por_t" -msgstr "Poor_t" - -#: ../ui/login.glade.h:19 -msgid "SMTP settings" -msgstr "SMTP-instellingen" - -#: ../ui/login.glade.h:20 -msgid "User_name" -msgstr "Gebruikers_naam" - -#: ../ui/login.glade.h:21 -msgid "Pass_word" -msgstr "_Wachtwoord" - -#: ../ui/login.glade.h:22 -msgid "SMTP username" -msgstr "SMTP-gebruikersnaam" - -#: ../ui/login.glade.h:23 -msgid "SMTP password" -msgstr "SMTP-wachtwoord" - -#: ../ui/login.glade.h:24 -msgid "_Username" -msgstr "Gebr_uikersnaam" - -#: ../ui/login.glade.h:25 -msgid "IMAP username" -msgstr "IMAP-gebruikersnaam" - -#: ../ui/login.glade.h:26 -msgid "IMAP password" -msgstr "IMAP-wachtwoord" - -#: ../ui/login.glade.h:27 -msgid "Encr_yption" -msgstr "_Versleuteling" - -#: ../ui/login.glade.h:28 -msgid "Encrypt_ion" -msgstr "Versleutel_ing" - -#: ../ui/login.glade.h:30 -msgid "SSL/TLS" -msgstr "SSL/TLS" - -#: ../ui/login.glade.h:31 -msgid "STARTTLS" -msgstr "STARTTLS" - -#: ../ui/login.glade.h:32 -msgid "No authentication re_quired" -msgstr "Geen authenticatie ve_reist" - -#: ../ui/login.glade.h:33 -msgid "Use IMAP cre_dentials" -msgstr "IMAP-inlog_gegevens gebruiken" - -#: ../ui/login.glade.h:34 -msgid "Composer" -msgstr "Opstelvenster" - -#: ../ui/login.glade.h:35 -msgid "Save dra_fts on server" -msgstr "Concep_ten opslaan op server" - -#: ../ui/login.glade.h:36 -msgid "Si_gn emails (HTML allowed):" -msgstr "Berichten ondertekenen (HTML toe_gestaan):" - -#: ../ui/login.glade.h:37 -msgid "Storage" -msgstr "Opslag" - -#: ../ui/login.glade.h:38 -msgid "_Download mail" -msgstr "Berichten _downloaden" - -#: ../ui/main-toolbar.ui.h:1 -msgid "Empty Spam or Trash folders" -msgstr "Spam- of prullenbakmappen legen" +#: ui/main-toolbar-menus.ui:42 +msgid "_Accounts" +msgstr "_Accounts" -#: ../ui/password-dialog.glade.h:1 +#: ui/main-toolbar-menus.ui:50 +msgid "_Keyboard Shortcuts" +msgstr "_Sneltoetsen" + +#: ui/main-toolbar-menus.ui:61 +msgid "_About Geary" +msgstr "_Over Geary" + +#. Infobar title when one or more accounts are offline +#: ui/main-window.ui:183 +msgid "Working offline" +msgstr "Offlinemodus" + +#. Label and tooltip for offline infobar +#: ui/main-window.ui:197 +msgid "" +"Your computer does not appear to be connected to the Internet.\n" +"You will not be able to send or receive email until it is re-connected." +msgstr "" +"Uw computer lijkt niet verbonden te zijn met het internet.\n" +"U zult geen berichten kunnen verzenden of ontvangen totdat de verbinding " +"hersteld is." + +#. Label and tooltip for offline infobar +#: ui/main-window.ui:200 +msgid "You will not be able to send or receive email until re-connected." +msgstr "" +"U zult geen e-mails kunnen verzenden of ontvangen totdat de verbinding " +"hersteld is." + +#. Button label for displaying technical details about an account problem +#. Dialog title for displaying technical details of a problem. Same as the button that invokes it. +#: ui/main-window.ui:247 ui/problem-details-dialog.ui:13 +msgid "Details" +msgstr "Details" + +#. Button label for retrying an account problem +#: ui/main-window.ui:261 +msgid "Retry" +msgstr "Opnieuw" + +#. Infobar title when one or more accounts have encounted an error +#: ui/main-window.ui:294 +msgid "Account problem" +msgstr "Probleem met account" + +#. Label and tooltip for account service problem infobar +#: ui/main-window.ui:308 +msgid "" +"Geary encountered a problem connecting to an account.\n" +"Please check your Internet connection, the server configuration and try " +"again." +msgstr "" +"Geary heeft een probleem ondervonden bij het verbinden met een account.\n" +"Controleer uw internettoegang en de serverconfiguratie en probeer het " +"opnieuw." + +#. Label and tooltip for account service problem infobar +#: ui/main-window.ui:311 +msgid "Geary encountered a problem connecting to an account." +msgstr "" +"Geary heeft een probleem ondervonden bij het verbinden met een account." + +#. Button label for retrying TLS cert validation +#: ui/main-window.ui:358 +msgid "Check" +msgstr "Controleren" + +#. Button tooltip for retrying TLS cert validation +#: ui/main-window.ui:362 +msgid "Check the security details for the connection" +msgstr "Controleer de beveiligingsgegevens voor de verbinding" + +#. Infobar title when one or more accounts have a TLS cert validation error +#: ui/main-window.ui:391 +msgid "Security problem" +msgstr "Beveiligingsprobleem" + +#. Label and tooltip for TLS cert validation error infobar +#: ui/main-window.ui:405 +msgid "" +"An account has reported an untrusted server.\n" +"Please check the server configuration and try again." +msgstr "" +"Een account heeft een onvertrouwde server gemeld.\n" +"Controleer de serverconfiguratie en probeer het opnieuw." + +#. Label and tooltip for TLS cert validation error infobar +#: ui/main-window.ui:408 +msgid "An account has reported an untrusted server." +msgstr "Een account heeft een onvertrouwde server gemeld." + +#. Button tooltip for retrying when a login error has occurred +#: ui/main-window.ui:459 +msgid "Retry login, you will be prompted for your password" +msgstr "Probeer opnieuw in te loggen, het wachtwoord zal gevraagd worden" + +#. Infobar title when one or more accounts have a login error +#: ui/main-window.ui:488 +msgid "Login problem" +msgstr "Probleem met inloggen" + +#. Label and tooltip for authentication problem infobar +#: ui/main-window.ui:502 +msgid "" +"An account has reported an incorrect login or password.\n" +"Please check your login name and try again." +msgstr "" +"Een account heeft een onjuiste login of wachtwoord gemeld.\n" +"Controleer uw loginnaam en probeer het opnieuw." + +#. Label and tooltip for authentication problem infobar +#: ui/main-window.ui:505 +msgid "An account has reported an incorrect login or password." +msgstr "Een account heeft een onjuiste login of wachtwoord gemeld." + +#: ui/password-dialog.glade:74 msgid "SMTP Credentials" msgstr "SMTP-inloggegevens" -#: ../ui/password-dialog.glade.h:2 +#: ui/password-dialog.glade:91 msgid "Username" msgstr "Gebruikersnaam" -#: ../ui/password-dialog.glade.h:4 +#: ui/password-dialog.glade:152 msgid "_Remember password" msgstr "_Wachtwoord onthouden" -#: ../ui/password-dialog.glade.h:6 +#: ui/password-dialog.glade:210 msgid "_Authenticate" msgstr "_Authenticeren" -#: ../ui/preferences-dialog.ui.h:1 +#: ui/preferences-dialog.ui:38 msgid "Reading" msgstr "Leesvenster" -#: ../ui/preferences-dialog.ui.h:2 +#: ui/preferences-dialog.ui:51 msgid "_Automatically select next message" msgstr "Volgend bericht _automatisch selecteren" -#: ../ui/preferences-dialog.ui.h:3 +#: ui/preferences-dialog.ui:70 msgid "_Display conversation preview" msgstr "_Berichtvoorbeeld weergeven" -#: ../ui/preferences-dialog.ui.h:4 +#: ui/preferences-dialog.ui:89 msgid "Use _three pane view" msgstr "Overzich_t met drie panelen gebruiken" -#: ../ui/preferences-dialog.ui.h:5 +#: ui/preferences-dialog.ui:113 msgid "Notifications" msgstr "Meldingen" -#: ../ui/preferences-dialog.ui.h:6 +#: ui/preferences-dialog.ui:126 msgid "_Play notification sounds" msgstr "_Geluid afspelen bij melding" -#: ../ui/preferences-dialog.ui.h:7 +#: ui/preferences-dialog.ui:145 msgid "Show _notifications for new mail" msgstr "_Melding weergeven voor nieuwe e-mail" -#: ../ui/preferences-dialog.ui.h:8 -msgid "Always _watch for new mail" -msgstr "Altijd controleren op nieu_we mail" +#: ui/preferences-dialog.ui:164 +msgid "_Watch for new mail when closed" +msgstr "Controleren op nieu_we e-mail indien gesloten" + +#: ui/preferences-dialog.ui:168 +msgid "Geary will keep running after all windows are closed" +msgstr "Geary zal blijven draaien wanneer alle vensters gesloten zijn" -#: ../ui/preferences-dialog.ui.h:9 -msgid "Geary will run in the background and notify of new mail" -msgstr "" -"Geary zal in de achtergrond draaien en meldingen geven over nieuwe mail" - -#: ../ui/preferences-dialog.ui.h:10 +#: ui/preferences-dialog.ui:195 msgid "Preferences" msgstr "Voorkeuren" -#: ../ui/remove_confirm.glade.h:1 +#. Button label for copying technical information to the clipboard +#: ui/problem-details-dialog.ui:17 +msgid "Copy to Clipboard" +msgstr "Kopiëren naar klembord" + +#. Button tooltip for copying technical information to the clipboard +#: ui/problem-details-dialog.ui:21 msgid "" -"Are you sure you want to remove this " -"account? " +"Copy technical details to clipboard for pasting into an email or bug report" msgstr "" -"Weet u zeker dat u deze account wilt " -"verwijderen? " +"Kopieer technische gegevens naar het klembord om ze in een e-mail of " +"foutmelding te plakken" -#: ../ui/remove_confirm.glade.h:2 +#: ui/problem-details-dialog.ui:73 msgid "" -"All email associated with this account will be removed from your computer. " -"This will not affect email on the server." +"If the problem is serious or persists, please copy and send these details to " +"the mailing list " +"or file a new " +"bug report." msgstr "" -"Deze account wordt met alle bijbehorende berichten van uw computer " -"verwijderd. Dit heeft geen invloed op berichten op de server." - -#: ../ui/remove_confirm.glade.h:3 -msgid "Nickname:" -msgstr "Bijnaam:" +"Als het probleem ernstig is of zich blijft voordoen, kopieer en verzend deze " +"gegevens dan naar de mailinglijst of dien een nieuwe foutmelding in." -#: ../ui/remove_confirm.glade.h:4 -msgid "Email address:" -msgstr "E-mailadres:" +#: ui/problem-details-dialog.ui:89 +msgid "Details:" +msgstr "Details:" -#: ../ui/upgrade_dialog.glade.h:1 +#: ui/upgrade_dialog.glade:60 msgid "Geary update in progress…" msgstr "Geary wordt bijgewerkt…" +#~ msgid "Email address:" +#~ msgstr "E-mailadres:" + +#~ msgid "Delete conversations (Shift+Delete)" +#~ msgstr "Gesprekken verwijderen (Shift+Delete)" + +#~ msgid "Move conversations to Trash (Delete, Backspace)" +#~ msgstr "Gesprekken naar prullenbak verplaatsen (Delete, Backspace)" + +#~ msgid "Archive conversations (A)" +#~ msgstr "Gesprekken archiveren (A)" + +#~ msgid "Mark conversations" +#~ msgstr "Gesprekken markeren" + +#~ msgid "Add label to conversations" +#~ msgstr "Label aan gesprekken toevoegen" + +#~ msgid "Move conversations" +#~ msgstr "Gesprekken verplaatsen" + +#~ msgid "Retry connecting now" +#~ msgstr "Nu opnieuw verbinden" + +#~ msgid "Try reconnecting now" +#~ msgstr "Nu opnieuw verbinden" + +#~ msgid "Problem with connection to incoming server for %s" +#~ msgstr "Probleem bij verbinding met inkomende server voor %s" + +#~ msgid "Problem with connection to outgoing server for %s" +#~ msgstr "Probleem bij verbinding met uitgaande server voor %s" + +#~ msgid "To: " +#~ msgstr "Aan: " + +#~ msgid "Cc: " +#~ msgstr "Cc: " + +#~ msgid "Bcc: " +#~ msgstr "Bcc: " + +#~ msgid "From: %s\n" +#~ msgstr "Van: %s\n" + +#~ msgid "Subject: %s\n" +#~ msgstr "Onderwerp: %s\n" + +#~ msgid "Date: %s\n" +#~ msgstr "Datum: %s\n" + +#~ msgid "To: %s\n" +#~ msgstr "Aan: %s\n" + +#~ msgid "Cc: %s\n" +#~ msgstr "Cc: %s\n" + +#~ msgid "A_ccounts" +#~ msgstr "A_ccounts" + +#~ msgid "Empty Spam or Trash folders" +#~ msgstr "Spam- of prullenbakmappen legen" + +#~ msgid "Default attachments directory" +#~ msgstr "Standaardmap voor bijlagen" + +#~ msgid "Location used when opening and saving attachments." +#~ msgstr "Te gebruiken locatie voor openen en opslaan van bijlagen." + +#~ msgid "Default print output directory" +#~ msgstr "Standaardmap voor printuitvoer" + +#~ msgid "Location used when printing to a file." +#~ msgstr "Te gebruiken locatie voor printen naar bestand." + +#~ msgid "Additional addresses for %s" +#~ msgstr "Bijkomstige adressen voor %s" + +#~ msgid "First Last" +#~ msgstr "Voornaam Achternaam" + +#~ msgid "Enter your account information to get started." +#~ msgstr "Vul uw accountgegevens in om te beginnen." + +#~ msgid "Edit" +#~ msgstr "Bewerken" + +#~ msgid "Preview" +#~ msgstr "Voorbeeld" + +#~ msgid "Remem_ber passwords" +#~ msgstr "_Wachtwoorden onthouden" + +#~ msgid "Remem_ber password" +#~ msgstr "Wachtwoord _onthouden" + +#~ msgid "Unable to validate:\n" +#~ msgstr "Verifiëren mislukt:\n" + +#~ msgid " • Invalid account nickname.\n" +#~ msgstr " • Ongeldige accountbijnaam.\n" + +#~ msgid " • Email address already added to Geary.\n" +#~ msgstr " • E-mailadres is al aan Geary toegevoegd.\n" + +#~ msgid " • IMAP connection error.\n" +#~ msgstr " • IMAP-verbindingsfout.\n" + +#~ msgid " • IMAP username or password incorrect.\n" +#~ msgstr " • IMAP-gebruikersnaam of -wachtwoord onjuist.\n" + +#~ msgid " • SMTP connection error.\n" +#~ msgstr " • SMTP-verbindingsfout.\n" + +#~ msgid " • SMTP username or password incorrect.\n" +#~ msgstr " • SMTP-gebruikersnaam of -wachtwoord onjuist.\n" + +#~ msgid " • Connection error.\n" +#~ msgstr " • Verbindingsfout.\n" + +#~ msgid " • Username or password incorrect.\n" +#~ msgstr " • Gebruikersnaam of wachtwoord onjuist.\n" + +#~ msgid "Unable to store server trust exception" +#~ msgstr "Kan serververtrouwenuitzondering niet opslaan" + +#~ msgid "Your settings are insecure" +#~ msgstr "Uw instellingen zijn onveilig" + +#~ msgid "" +#~ "Your IMAP and/or SMTP settings do not specify SSL or TLS. This means " +#~ "your username and password could be read by another person on the " +#~ "network. Are you sure you want to do this?" +#~ msgstr "" +#~ "Uw IMAP- en/of SMTP-instellingen zijn niet ingesteld op SSL of TLS. Dit " +#~ "betekent dat uw gebruikersnaam en wachtwoord zichtbaar zijn voor anderen " +#~ "op uw netwerk. Weet u zeker dat u dit wilt doen?" + +#~ msgid "Co_ntinue" +#~ msgstr "Doorgaa_n" + +#~ msgid "" +#~ "Geary encountered an error sending an email. If the problem persists, " +#~ "please manually delete the email from your Outbox folder." +#~ msgstr "" +#~ "Er is een fout opgetreden bij het verzenden van een e-mail. Als het " +#~ "probleem blijft bestaan, verwijder dan handmatig de e-mail in het Postvak " +#~ "Uit." + +#~ msgid "" +#~ "Geary encountered an error saving a sent message to Sent Mail. The " +#~ "message will stay in your Outbox folder until you delete it." +#~ msgstr "" +#~ "Er is een fout opgetreden bij het opslaan van een verzonden e-" +#~ "mailbericht. Het bericht zal in het Postvak Uit blijven staan totdat u " +#~ "het verwijdert." + +#~ msgid "Unable to open local mailbox for %s" +#~ msgstr "De lokale postbus van %s kan niet geopend worden" + +#~ msgid "" +#~ "There was an error opening the local mail database for this account. This " +#~ "is possibly due to a file permissions problem.\n" +#~ "\n" +#~ "Please check that you have read/write permissions for all files in this " +#~ "directory:\n" +#~ "\n" +#~ "%s" +#~ msgstr "" +#~ "Er is een fout opgetreden bij het openen van de lokale e-maildatabase van " +#~ "deze account. Dit wordt waarschijnlijk veroorzaakt door een probleem met " +#~ "de bestandsrechten. ⏎\n" +#~ "⏎\n" +#~ "Controleer of u voldoende lees/schrijfrechten hebt voor alle bestanden in " +#~ "deze map:⏎\n" +#~ "⏎\n" +#~ "%s" + +#~ msgid "" +#~ "The version number of the local mail database is formatted for a newer " +#~ "version of Geary. Unfortunately, the database cannot be “rolled back” to " +#~ "work with this version of Geary.\n" +#~ "\n" +#~ "Please install the latest version of Geary and try again." +#~ msgstr "" +#~ "Het versienummer van de lokale e-maildatabase is opgemaakt voor een " +#~ "nieuwere versie van Geary. Het is helaas niet mogelijk de database ‘terug " +#~ "te zetten’ zodat hij met deze versie van Geary werkt.\n" +#~ "\n" +#~ "Installeer de nieuwste versie van Geary en probeer het opnieuw." + +#~ msgid "" +#~ "There was an error opening the local account. This is probably due to " +#~ "connectivity issues.\n" +#~ "\n" +#~ "Please check your network connection and restart Geary." +#~ msgstr "" +#~ "Er is een fout opgetreden bij het openen van de lokale account. Dit wordt " +#~ "waarschijnlijk veroorzaakt door verbindingsproblemen.\n" +#~ "\n" +#~ "Controleer uw netwerkverbinding en herstart Geary." + +#~ msgid "Geary will exit if you have no other open email accounts." +#~ msgstr "Geary zal afsluiten als u geen andere open e-mailaccounts hebt." + +#~ msgid "Other" +#~ msgstr "Overige" + +#~ msgid "IMAP" +#~ msgstr "IMAP" + +#~ msgid "SMTP" +#~ msgstr "SMTP" + +#~ msgid "Cannot remove account " +#~ msgstr "" +#~ "Account verwijderen mislukt " + +#~ msgid "" +#~ "A composer window associated with this account is currently open. Send or " +#~ "discard the message and try again." +#~ msgstr "" +#~ "Vanuit deze account wordt momenteel een bericht opgesteld. Verzend of " +#~ "verwerp dit bericht en probeer het opnieuw." + +#~ msgid "Please wait while Geary validates your account." +#~ msgstr "Even geduld, Geary verifieert uw account." + +#~ msgid "" +#~ "Some email services require additional addresses be configured on the " +#~ "server. Contact your email provider for more information." +#~ msgstr "" +#~ "Sommige e-maildiensten vereisen dat bijkomstige adressen geconfigureerd " +#~ "worden op de server. Neem contact op met uw e-mailprovider voor meer " +#~ "informatie." + +#~ msgid "_Update" +#~ msgstr "B_ijwerken" + +#~ msgid "E_mail address" +#~ msgstr "E-_mailadres" + +#~ msgid "_Password" +#~ msgstr "_Wachtwoord" + +#~ msgid "S_ervice" +#~ msgstr "Di_enst" + +#~ msgid "N_ame" +#~ msgstr "N_aam" + +#~ msgid "N_ickname" +#~ msgstr "B_ijnaam" + +#~ msgid "Work, Home, etc." +#~ msgstr "Werk, Thuis, etc." + +#~ msgid "Addi_tional email addresses…" +#~ msgstr "Aan_vullende e-mailadressen…" + +#~ msgid "IMAP settings" +#~ msgstr "IMAP-instellingen" + +#~ msgid "Se_rver" +#~ msgstr "Se_rver" + +#~ msgid "P_ort" +#~ msgstr "P_oort" + +#~ msgid "Ser_ver" +#~ msgstr "Ser_ver" + +#~ msgid "Por_t" +#~ msgstr "Poor_t" + +#~ msgid "User_name" +#~ msgstr "Gebruikers_naam" + +#~ msgid "Pass_word" +#~ msgstr "_Wachtwoord" + +#~ msgid "SMTP password" +#~ msgstr "SMTP-wachtwoord" + +#~ msgid "_Username" +#~ msgstr "Gebr_uikersnaam" + +#~ msgid "IMAP password" +#~ msgstr "IMAP-wachtwoord" + +#~ msgid "Encr_yption" +#~ msgstr "_Versleuteling" + +#~ msgid "Encrypt_ion" +#~ msgstr "Versleutel_ing" + +#~ msgid "STARTTLS" +#~ msgstr "STARTTLS" + +#~ msgid "No authentication re_quired" +#~ msgstr "Geen authenticatie ve_reist" + +#~ msgid "Use IMAP cre_dentials" +#~ msgstr "IMAP-inlog_gegevens gebruiken" + +#~ msgid "Composer" +#~ msgstr "Opstelvenster" + +#~ msgid "Si_gn emails (HTML allowed):" +#~ msgstr "Berichten ondertekenen (HTML toe_gestaan):" + +#~ msgid "Storage" +#~ msgstr "Opslag" + +#~ msgid "" +#~ "Are you sure you want to remove " +#~ "this account? " +#~ msgstr "" +#~ "Weet u zeker dat u deze account " +#~ "wilt verwijderen? " + +#~ msgid "" +#~ "All email associated with this account will be removed from your " +#~ "computer. This will not affect email on the server." +#~ msgstr "" +#~ "Deze account wordt met alle bijbehorende berichten van uw computer " +#~ "verwijderd. Dit heeft geen invloed op berichten op de server." + +#~ msgid "Nickname:" +#~ msgstr "Bijnaam:" + +#~ msgid "Geary will run in the background and notify of new mail" +#~ msgstr "" +#~ "Geary zal in de achtergrond draaien en meldingen geven over nieuwe mail" + +#~ msgid "Geary Email" +#~ msgstr "Geary e-mail" + +#~ msgid "Mail Client" +#~ msgstr "E-mailprogramma" + +#~ msgid "Geary Mail" +#~ msgstr "Geary e-mail" + +#~ msgid "_Mark as…" +#~ msgstr "_Markeren als…" + +#~ msgid "Add label" +#~ msgstr "Label toevoegen" + +#~ msgid "_Label" +#~ msgstr "_Labelen" + +#~ msgid "_Move" +#~ msgstr "_Verplaatsen" + +#~ msgid "Compose new message (Ctrl+N, N)" +#~ msgstr "Nieuw bericht opstellen (Ctrl+N, N)" + +#~ msgid "Reply (Ctrl+R, R)" +#~ msgstr "Beantwoorden (Ctrl+R, R)" + +#~ msgid "Reply all (Ctrl+Shift+R, Shift+R)" +#~ msgstr "Allen beantwoorden (Ctrl+Shift+R, Shift+R)" + +#~ msgid "Forward (Ctrl+L, F)" +#~ msgstr "Doorsturen (Ctrl+L, F)" + +#~ msgid "" +#~ "Geary encountered an error while connecting to the server. Please try " +#~ "again in a few moments." +#~ msgstr "" +#~ "Er trad een fout op bij het verbinden met de server. Probeer het opnieuw " +#~ "binnen enkele ogenblikken." + +#~ msgid "Try Again" +#~ msgstr "Probeer het opnieuw" + #~ msgid "_Inspect" #~ msgstr "_Inspecteren" diff -Nru geary-0.12.4/po/pl.po geary-3.32.0/po/pl.po --- geary-0.12.4/po/pl.po 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/po/pl.po 2019-03-17 13:39:29.000000000 +0000 @@ -1,5 +1,5 @@ # Polish translation for geary. -# Copyright © 2012-2017 the geary authors. +# Copyright © 2012-2019 the geary authors. # This file is distributed under the same license as the geary package. # scrx , 2012. # Piotrek290 , 2012-2013. @@ -9,15 +9,15 @@ # wmq , 2012. # yorbajim , 2013. # zacol , 2012. -# Piotr Drąg , 2014-2017. -# Aviary.pl , 2014-2017. +# Piotr Drąg , 2014-2019. +# Aviary.pl , 2014-2019. # msgid "" msgstr "" "Project-Id-Version: geary\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-10-02 16:39+0200\n" -"PO-Revision-Date: 2017-10-02 16:40+0200\n" +"Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/geary/issues\n" +"POT-Creation-Date: 2019-02-24 16:03+0000\n" +"PO-Revision-Date: 2019-02-24 17:28+0100\n" "Last-Translator: Piotr Drąg \n" "Language-Team: Polish \n" "Language: pl\n" @@ -27,27 +27,55 @@ "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " "|| n%100>=20) ? 1 : 2);\n" +#: desktop/geary-attach.contract.desktop.in:3 +msgid "Send by email" +msgstr "Wyślij jako e-mail" + +#. Translators: Do NOT translate or transliterate this text (this is an icon file name)! +#: desktop/geary-attach.contract.desktop.in:5 +msgid "mail-send" +msgstr "mail-send" + +#: desktop/geary-attach.contract.desktop.in:6 +msgid "Send files using Geary" +msgstr "Wysyłanie plików za pomocą programu Geary" + #. Translators: The application name -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:2 -#: ../desktop/org.gnome.Geary.desktop.in.h:1 -#: ../desktop/geary-autostart.desktop.in.h:1 +#: desktop/geary-autostart.desktop.in:3 +#: desktop/org.gnome.Geary.appdata.xml.in:11 +#: desktop/org.gnome.Geary.desktop.in:3 +#: src/client/accounts/accounts-editor-servers-pane.vala:555 msgid "Geary" msgstr "Geary" -#. Translators: The development team's name -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:4 -msgid "Geary Development Team" -msgstr "Zespół programistów Geary" +#: desktop/geary-autostart.desktop.in:4 desktop/org.gnome.Geary.desktop.in:4 +msgid "Email" +msgstr "Poczta" #. Translators: The application's summary / tagline -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:6 -#: ../desktop/org.gnome.Geary.desktop.in.h:4 -#: ../desktop/geary-autostart.desktop.in.h:4 -#: ../src/client/application/geary-application.vala:21 +#: desktop/geary-autostart.desktop.in:5 +#: desktop/org.gnome.Geary.appdata.xml.in:15 +#: desktop/org.gnome.Geary.desktop.in:5 +#: src/client/application/geary-application.vala:23 msgid "Send and receive email" msgstr "Wysyłanie i odbieranie poczty" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:7 +#. Translators: These are desktop search terms. Do not translate semicolons, end line with a semicolon. +#: desktop/geary-autostart.desktop.in:7 +msgid "Email;E-mail;Mail;" +msgstr "Email;E-mail;Mail;Poczta;Skrzynka;Wiadomość;Wiadomości;mejl;majl;" + +#. Translators: Do NOT translate or transliterate this text (this is an icon file name)! +#: desktop/geary-autostart.desktop.in:9 desktop/org.gnome.Geary.desktop.in:9 +msgid "org.gnome.Geary" +msgstr "org.gnome.Geary" + +#. Translators: The development team's name +#: desktop/org.gnome.Geary.appdata.xml.in:13 +msgid "Geary Development Team" +msgstr "Zespół programistów Geary" + +#: desktop/org.gnome.Geary.appdata.xml.in:17 msgid "" "Geary is an email application built around conversations, for the GNOME 3 " "desktop. It allows you to read, find and send email with a straightforward, " @@ -57,7 +85,7 @@ "wątków. Umożliwia czytanie, wyszukiwanie i wysyłanie wiadomości za pomocą " "prostego, nowoczesnego interfejsu." -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:8 +#: desktop/org.gnome.Geary.appdata.xml.in:22 msgid "" "Conversations allow you to read a complete discussion without having to find " "and click from message to message." @@ -65,221 +93,698 @@ "Wątki umożliwiają czytanie pełnych dyskusji bez potrzeby wyszukiwania " "i klikania poszczególnych wiadomości." -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:9 +#: desktop/org.gnome.Geary.appdata.xml.in:26 msgid "Geary’s features include:" msgstr "Funkcje programu Geary:" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:10 +#: desktop/org.gnome.Geary.appdata.xml.in:28 msgid "Quick email account setup" msgstr "Szybkie ustawianie konta poczty" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:11 +#: desktop/org.gnome.Geary.appdata.xml.in:29 msgid "Shows related messages together in conversations" msgstr "Wyświetlanie powiązanych wiadomości w jednym wątku" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:12 +#: desktop/org.gnome.Geary.appdata.xml.in:30 msgid "Fast, full text and keyword search" msgstr "Szybkie wyszukiwanie w wiadomościach i słowach kluczowych" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:13 +#: desktop/org.gnome.Geary.appdata.xml.in:31 msgid "Full-featured HTML and plain text message composer" msgstr "Wiele funkcji tworzenia wiadomości HTML i w zwykłym tekście" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:14 +#: desktop/org.gnome.Geary.appdata.xml.in:32 msgid "Desktop notification of new mail" msgstr "Powiadomienia o nowych wiadomościach na pulpicie" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:15 +#: desktop/org.gnome.Geary.appdata.xml.in:33 msgid "Compatible with GMail, Yahoo! Mail, Outlook.com and other IMAP servers" msgstr "" "Zgodność z serwisami Gmail, Yahoo! Mail, Outlook.com i innymi serwerami IMAP" #. Translators: A screenshot description. -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:17 +#: desktop/org.gnome.Geary.appdata.xml.in:47 msgid "Geary displaying a conversation" msgstr "Widok wątku" #. Translators: A screenshot description. -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:19 +#: desktop/org.gnome.Geary.appdata.xml.in:52 msgid "Geary showing the rich text composer" msgstr "Okno tworzenia wiadomości z formatowaniem" -#: ../desktop/org.gnome.Geary.desktop.in.h:2 -#: ../desktop/geary-autostart.desktop.in.h:2 -msgid "Email" -msgstr "Poczta" - -#: ../desktop/org.gnome.Geary.desktop.in.h:3 -msgid "Geary Email" -msgstr "Klient poczty Geary" - #. Translators: These are desktop search terms. Do not translate semicolons, end line with a semicolon. -#: ../desktop/org.gnome.Geary.desktop.in.h:6 +#: desktop/org.gnome.Geary.desktop.in:7 msgid "Mail;E-mail;IMAP;GMail;Yahoo;Hotmail;Outlook;" msgstr "" "Poczta;Mail;E-mail;Email;Emajl;Emejl;Imail;Imejl;Imajl;Mejl;Majl;Wiadomości;" "IMAP;Gmail;Dżimejl;Dżimajl;Yahoo;Jahu;Hotmail;Hotmejl;Outlook;Ałtluk;" -#: ../desktop/org.gnome.Geary.desktop.in.h:7 ../ui/gtk/menus.ui.h:1 +#: desktop/org.gnome.Geary.desktop.in:21 msgid "Compose Message" msgstr "Utwórz wiadomość" -#: ../desktop/geary-autostart.desktop.in.h:3 -msgid "Geary Mail" -msgstr "Klient poczty Geary" +#: desktop/org.gnome.Geary.gschema.xml:8 +msgid "Maximize window" +msgstr "Maksymalizacja okna" + +#: desktop/org.gnome.Geary.gschema.xml:9 +msgid "True if the application window is maximized, false otherwise." +msgstr "Okno programu jest zmaksymalizowane." + +#: desktop/org.gnome.Geary.gschema.xml:14 +msgid "Width of window" +msgstr "Szerokość okna" + +#: desktop/org.gnome.Geary.gschema.xml:15 +msgid "The last recorded width of the application window." +msgstr "Ostatnio zapisana szerokość okna programu." + +#: desktop/org.gnome.Geary.gschema.xml:20 +msgid "Height of window" +msgstr "Wysokość okna" + +#: desktop/org.gnome.Geary.gschema.xml:21 +msgid "The last recorded height of the application window." +msgstr "Ostatnio zapisana wysokość okna programu." + +#: desktop/org.gnome.Geary.gschema.xml:26 +msgid "Position of folder list pane" +msgstr "Położenie panelu listy katalogów" + +#: desktop/org.gnome.Geary.gschema.xml:27 +msgid "Position of the folder list Paned grabber." +msgstr "Położenie uchwytu panelu listy katalogów." + +#: desktop/org.gnome.Geary.gschema.xml:32 +msgid "Position of folder list pane when horizontal" +msgstr "Położenie panelu listy katalogów w trybie poziomym" + +#: desktop/org.gnome.Geary.gschema.xml:33 +msgid "" +"Position of the folder list Paned grabber in the horizontal orientation." +msgstr "Położenie uchwytu panelu listy katalogów w trybie poziomym." + +#: desktop/org.gnome.Geary.gschema.xml:38 +msgid "Position of folder list pane when vertical" +msgstr "Położenie panelu listy katalogów w trybie pionowym" + +#: desktop/org.gnome.Geary.gschema.xml:39 +msgid "Position of the folder list Paned grabber in the vertical orientation." +msgstr "Położenie uchwytu panelu listy katalogów w trybie pionowym." + +#: desktop/org.gnome.Geary.gschema.xml:44 +msgid "Orientation of the folder list pane" +msgstr "Tryb orientacji panelu listy katalogów" + +#: desktop/org.gnome.Geary.gschema.xml:45 +msgid "True if the folder list Paned is in the horizontal orientation." +msgstr "Panel listy katalogów jest w trybie poziomym." + +#: desktop/org.gnome.Geary.gschema.xml:50 +msgid "Position of message list pane" +msgstr "Położenie panelu listy wiadomości" + +#: desktop/org.gnome.Geary.gschema.xml:51 +msgid "Position of the message list Paned grabber." +msgstr "Położenie uchwytu panelu listy wiadomości." + +#: desktop/org.gnome.Geary.gschema.xml:56 +msgid "Autoselect next message" +msgstr "Automatyczne wybieranie następnej wiadomości" + +#: desktop/org.gnome.Geary.gschema.xml:57 +msgid "True if we should autoselect the next available conversation." +msgstr "Następny dostępny wątek ma być automatycznie wybierany." + +#: desktop/org.gnome.Geary.gschema.xml:62 +msgid "Display message previews" +msgstr "Podgląd wiadomości" + +#: desktop/org.gnome.Geary.gschema.xml:63 +msgid "True if we should display a short preview of each message." +msgstr "Wyświetlanie krótkiego podglądu każdej wiadomości." + +#: desktop/org.gnome.Geary.gschema.xml:68 +msgid "Languages that shall be used in the spell checker" +msgstr "Języki używane do sprawdzania pisowni" + +#: desktop/org.gnome.Geary.gschema.xml:69 +msgid "List of the languages to use in the spell checker." +msgstr "Lista języków używanych do sprawdzania pisowni." + +#: desktop/org.gnome.Geary.gschema.xml:74 +msgid "Languages that are displayed in the spell checker popover" +msgstr "Języki wyświetlane w oknie sprawdzania pisowni" + +#: desktop/org.gnome.Geary.gschema.xml:75 +msgid "" +"List of languages that are always displayed in the popover of the spell " +"checker." +msgstr "Lista języków zawsze wyświetlanych w oknie sprawdzania pisowni." + +#: desktop/org.gnome.Geary.gschema.xml:80 +msgid "Enable notification sounds" +msgstr "Dźwięki powiadomień" + +#: desktop/org.gnome.Geary.gschema.xml:81 +msgid "True to play sounds for notifications and sending." +msgstr "Odtwarzanie dźwięków powiadomień i wysyłania." + +#: desktop/org.gnome.Geary.gschema.xml:86 +msgid "Show notifications for new mail" +msgstr "Powiadomienia o nowych wiadomościach" + +#: desktop/org.gnome.Geary.gschema.xml:87 +msgid "True to show notification bubbles." +msgstr "Wyświetlanie powiadomień." + +#: desktop/org.gnome.Geary.gschema.xml:92 +msgid "Notify of new mail at startup" +msgstr "Powiadamianie o nowych wiadomościach po uruchomieniu" + +#: desktop/org.gnome.Geary.gschema.xml:93 +msgid "True to notify of new mail at startup." +msgstr "Powiadamianie o nowych wiadomościach po uruchomieniu." + +#: desktop/org.gnome.Geary.gschema.xml:98 +msgid "Ask when opening an attachment" +msgstr "Pytanie podczas otwierania załącznika" + +#: desktop/org.gnome.Geary.gschema.xml:99 +msgid "True to ask when opening an attachment." +msgstr "Pytanie podczas otwierania załącznika." + +#: desktop/org.gnome.Geary.gschema.xml:104 +msgid "Whether to compose emails in HTML" +msgstr "Tworzenie wiadomości w formacie HTML" + +#: desktop/org.gnome.Geary.gschema.xml:105 +msgid "True to compose emails in HTML; false for plain text." +msgstr "" +"Tworzenie wiadomości w formacie HTML. Wyłączenie powoduje tworzenie " +"wiadomości w zwykłym tekście." + +#: desktop/org.gnome.Geary.gschema.xml:110 +msgid "Advisory strategy for full-text searching" +msgstr "Strategia wyszukiwania w treści" + +#: desktop/org.gnome.Geary.gschema.xml:111 +msgid "" +"Acceptable values are “exact”, “conservative”, “aggressive”, and “horizon”." +msgstr "" +"Przyjmowane wartości: „exact” (dokładna), „conservative” (konserwatywna), " +"„aggressive” (agresywna) i „horizon” (horyzontalna)." + +#: desktop/org.gnome.Geary.gschema.xml:116 +msgid "Zoom of conversation viewer" +msgstr "Powiększenie widoku wątku" + +#: desktop/org.gnome.Geary.gschema.xml:117 +msgid "The zoom to apply on the conservation view." +msgstr "Powiększenie widoku wątku." + +#: desktop/org.gnome.Geary.gschema.xml:122 +msgid "Size of detached composer window" +msgstr "Rozmiar odłączonego okna tworzenia wiadomości" + +#: desktop/org.gnome.Geary.gschema.xml:123 +msgid "The last recorded size of the detached composer window." +msgstr "Ostatnio zapisany rozmiar odłączonego okna tworzenia wiadomości." + +#: desktop/org.gnome.Geary.gschema.xml:128 +msgid "Base URL to look up contact avatars" +msgstr "Podstawowy adres URL do wyszukiwania awatarów kontaktów" + +#: desktop/org.gnome.Geary.gschema.xml:129 +msgid "" +"A Gravatar or Libravatar compatible URL, set to the empty string to disable." +msgstr "" +"Adres URL zgodny z serwisem Gravatar lub Libravatar, ustawienie na pusty " +"ciąg wyłączy." + +#: desktop/org.gnome.Geary.gschema.xml:134 +msgid "Whether we migrated the old settings" +msgstr "Poprzednie ustawienia zostały migrowane" + +#: desktop/org.gnome.Geary.gschema.xml:135 +msgid "" +"False to check for the old “org.yorba.geary”-schema and copy its values." +msgstr "" +"Wyłączenie powoduje wyszukanie poprzednich schematów „org.yorba.geary” " +"i skopiowanie ich wartości." + +#. Translators: In-app notification label, when +#. the app had a problem pinning an otherwise +#. untrusted TLS certificate +#: src/client/accounts/accounts-editor.vala:203 +msgid "Failed to store certificate" +msgstr "Zachowanie certyfikatu się nie powiodło" + +#. Translators: Label for adding an email account +#. account for a generic IMAP service provider. +#: src/client/accounts/accounts-editor-add-pane.vala:109 +msgid "All others" +msgstr "Wszystkie pozostałe" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:196 +#: src/client/accounts/accounts-editor-servers-pane.vala:316 +msgid "Check your receiving login and password" +msgstr "Proszę sprawdzić poprawność loginu i hasła odbierania" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:211 +#: src/client/accounts/accounts-editor-servers-pane.vala:329 +msgid "Check your receiving server details" +msgstr "Proszę sprawdzić poprawność informacji o serwerze odbierania" + +#. Translators: In-app notification label +#. There was an SMTP auth error, but IMAP already +#. succeeded, so the user probably needs to +#. specify custom creds here +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:233 +#: src/client/accounts/accounts-editor-servers-pane.vala:350 +msgid "Check your sending login and password" +msgstr "Proszę sprawdzić poprawność loginu i hasła wysyłania" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:247 +#: src/client/accounts/accounts-editor-servers-pane.vala:363 +msgid "Check your sending server details" +msgstr "Proszę sprawdzić poprawność informacji o serwerze wysyłania" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:262 +msgid "Check your email address and password" +msgstr "Proszę sprawdzić poprawność adresu e-mail i hasła" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:273 +msgid "Could not connect, check your network" +msgstr "Nie można się połączyć, proszę sprawdzić sieć" + +#. Translators: In-app notification label for a +#. generic error creating an account +#: src/client/accounts/accounts-editor-add-pane.vala:286 +msgid "An unexpected problem occurred" +msgstr "Wystąpił nieoczekiwany problem" + +#. Translators: In-app notification label, the +#. string substitution is a more detailed reason. +#: src/client/accounts/accounts-editor-add-pane.vala:304 +#, c-format +msgid "Account not created: %s" +msgstr "Nie utworzono konta: %s" + +#. Translators: Label for the person's actual name when adding +#. an account +#: src/client/accounts/accounts-editor-add-pane.vala:551 +msgid "Your name" +msgstr "Imię i nazwisko" + +#. Translators: Label used for the address part of an +#. email address when editing a user's sender address +#. preferences for an account. +#: src/client/accounts/accounts-editor-add-pane.vala:568 +#: src/client/accounts/accounts-editor-edit-pane.vala:501 +msgid "Email address" +msgstr "Adres e-mail" + +#. Translators: Placeholder for the default sender address +#. when adding an account +#. Translators: This is used as a placeholder for the +#. address part of an email address when editing a user's +#. sender address preferences for an account. +#: src/client/accounts/accounts-editor-add-pane.vala:571 +#: src/client/accounts/accounts-editor-edit-pane.vala:469 +msgid "person@example.com" +msgstr "e-mail@example.com" -#: ../desktop/geary-autostart.desktop.in.h:5 -msgid "Email;E-mail;Mail;" -msgstr "Email;E-mail;Mail;Poczta;Skrzynka;Wiadomość;Wiadomości;mejl;majl;" +#. Translators: Label for an IMAP/SMTP service login/user name +#. when adding an account +#. Translators: Label for the user's login name for an +#. IMAP, SMTP, etc service +#: src/client/accounts/accounts-editor-add-pane.vala:585 +#: src/client/accounts/accounts-editor-servers-pane.vala:880 +msgid "Login name" +msgstr "Login" + +#. Translators: Label for the user's password for an IMAP, +#. SMTP, etc service +#: src/client/accounts/accounts-editor-add-pane.vala:599 +#: src/client/accounts/accounts-editor-servers-pane.vala:999 +#: ui/password-dialog.glade:108 +msgid "Password" +msgstr "Hasło" -#: ../desktop/geary-attach.contract.in.h:1 -msgid "Send by email" -msgstr "Wyślij jako e-mail" +#. Translators: Label for the IMAP server hostname when +#. adding an account. +#. Translators: This label describes the host name or IP +#. address and port used by an account's IMAP service. +#: src/client/accounts/accounts-editor-add-pane.vala:621 +#: src/client/accounts/accounts-editor-servers-pane.vala:727 +msgid "IMAP server" +msgstr "Serwer IMAP" + +#. Translators: Placeholder for the IMAP server hostname +#. when adding an account. +#: src/client/accounts/accounts-editor-add-pane.vala:624 +msgid "imap.example.com" +msgstr "imap.example.com" -#: ../desktop/geary-attach.contract.in.h:2 -msgid "Send files using Geary" -msgstr "Wysyłanie plików za pomocą programu Geary" +#. Translators: Label for the SMTP server hostname when +#. adding an account. +#. Translators: This label describes the host name or IP +#. address and port used by an account's SMTP service. +#: src/client/accounts/accounts-editor-add-pane.vala:630 +#: src/client/accounts/accounts-editor-servers-pane.vala:733 +msgid "SMTP server" +msgstr "Serwer SMTP" + +#. Translators: Placeholder for the SMTP server hostname +#. when adding an account. +#: src/client/accounts/accounts-editor-add-pane.vala:633 +msgid "smtp.example.com" +msgstr "smtp.example.com" -#: ../src/client/accounts/account-dialog-add-edit-pane.vala:51 -#: ../src/client/components/stock.vala:31 -#: ../ui/conversation-email-menus.ui.h:10 -msgid "_Save" -msgstr "_Zapisz" +#. Translators: Label in the account editor for the user's +#. custom name for an account. +#: src/client/accounts/accounts-editor-edit-pane.vala:278 +#: ui/accounts_editor_remove_pane.ui:123 +msgid "Account name" +msgstr "Nazwa konta" + +#. Translators: Tooltip used to undo changing +#. the name of an account. The string +#. substitution is the old name of the +#. account. +#: src/client/accounts/accounts-editor-edit-pane.vala:312 +#, c-format +msgid "Change account name back to “%s”" +msgstr "Zmienia nazwę konta z powrotem na „%s”" + +#. Translators: Tooltip for adding a new email sender/from +#. address's address to an account +#: src/client/accounts/accounts-editor-edit-pane.vala:336 +msgid "Add a new sender email address" +msgstr "Dodaje nowy adres e-mail nadawcy" + +#. Translators: Label used to indicate the user has +#. provided no display name for one of their sender +#. email addresses in their account settings. +#: src/client/accounts/accounts-editor-edit-pane.vala:417 +msgid "Name not set" +msgstr "Nie ustawiono nazwy" + +#. Translators: This is used as a placeholder for the +#. display name for an email address when editing a user's +#. sender address preferences for an account. +#: src/client/accounts/accounts-editor-edit-pane.vala:456 +msgid "Sender Name" +msgstr "Nazwa nadawcy" -#: ../src/client/accounts/account-dialog-add-edit-pane.vala:51 -#: ../src/client/components/stock.vala:22 -msgid "_Add" -msgstr "_Dodaj" +#: src/client/accounts/accounts-editor-edit-pane.vala:479 +msgid "Remove" +msgstr "Usuń" -#. reset/clear widgets -#: ../src/client/accounts/account-dialog-edit-alternate-emails-pane.vala:120 +#. Translators: Label used for the display name part of an +#. email address when editing a user's sender address +#. preferences for an account. +#: src/client/accounts/accounts-editor-edit-pane.vala:494 +msgid "Sender name" +msgstr "Nazwa nadawcy" + +#. Translators: Label used as the undo tooltip after adding an +#. new sender email address to an account. The string +#. substitution is the email address added. +#: src/client/accounts/accounts-editor-edit-pane.vala:561 +#, c-format +msgid "Remove “%s”" +msgstr "Usuwa adres „%s”" + +#. Translators: Label used as the undo tooltip after editing a +#. sender address for an account. The string substitution is +#. the email address edited. +#: src/client/accounts/accounts-editor-edit-pane.vala:601 +#, c-format +msgid "Undo changes to “%s”" +msgstr "Cofa zmiany adresu „%s”" + +#. Translators: Label used as the undo tooltip after removing +#. a sender address from an account. The string substitution +#. is the email address edited. +#: src/client/accounts/accounts-editor-edit-pane.vala:688 +#, c-format +msgid "Add “%s” back" +msgstr "Dodaje adres „%s” z powrotem" + +#. Translators: Label used as the undo tooltip after removing +#. a sender address from an account. The string substitution +#. is the email address edited. +#: src/client/accounts/accounts-editor-edit-pane.vala:730 +msgid "Undo signature changes" +msgstr "Cofa zmiany podpisu" + +#. Translators: This label describes the account +#. preference for the length of time (weeks, months or +#. years) that past email should be downloaded. +#: src/client/accounts/accounts-editor-edit-pane.vala:778 +msgid "Download mail" +msgstr "Pobieranie wiadomości" + +#. Translators: Tooltip for undoing a change +#. to the length of time that past email +#. should be downloaded for an account. The +#. string substitution is the duration, +#. e.g. "1 month back". +#: src/client/accounts/accounts-editor-edit-pane.vala:810 #, c-format -msgid "Additional addresses for %s" -msgstr "Dodatkowe adresy dla %s" - -#. Sets min size. -#: ../src/client/accounts/account-dialog.vala:21 -msgid "Accounts" -msgstr "Konta" +msgid "Change download period back to: %s" +msgstr "Zmienia czas pobierania z powrotem na: %s" -#. Copyright 2016 Software Freedom Conservancy Inc. -#. * -#. * This software is licensed under the GNU Lesser General Public License -#. * (version 2.1 or later). See the COPYING file in this distribution. -#. -#. Page for adding or editing an account. -#. / Placeholder text indicating that the user should type their first name and last name -#: ../src/client/accounts/add-edit-page.vala:10 -msgid "First Last" -msgstr "Imię Nazwisko" - -#: ../src/client/accounts/add-edit-page.vala:235 -msgid "Welcome to Geary." -msgstr "Witamy w programie Geary." - -#: ../src/client/accounts/add-edit-page.vala:235 -msgid "Enter your account information to get started." -msgstr "Proszę wprowadzić informacje o koncie, aby rozpocząć." +#: src/client/accounts/accounts-editor-edit-pane.vala:831 +msgid "Everything" +msgstr "Wszystko" -#: ../src/client/accounts/add-edit-page.vala:255 +#: src/client/accounts/accounts-editor-edit-pane.vala:835 msgid "2 weeks back" msgstr "ostatnie 2 tygodnie" -#. IDs are # of days -#: ../src/client/accounts/add-edit-page.vala:256 +#: src/client/accounts/accounts-editor-edit-pane.vala:839 msgid "1 month back" msgstr "ostatni miesiąc" -#: ../src/client/accounts/add-edit-page.vala:257 +#: src/client/accounts/accounts-editor-edit-pane.vala:843 msgid "3 months back" msgstr "ostatnie 3 miesiące" -#: ../src/client/accounts/add-edit-page.vala:258 +#: src/client/accounts/accounts-editor-edit-pane.vala:847 msgid "6 months back" msgstr "ostatnie 6 miesięcy" -#: ../src/client/accounts/add-edit-page.vala:259 +#: src/client/accounts/accounts-editor-edit-pane.vala:851 msgid "1 year back" msgstr "ostatni rok" -#: ../src/client/accounts/add-edit-page.vala:260 +#: src/client/accounts/accounts-editor-edit-pane.vala:855 msgid "2 years back" msgstr "ostatnie 2 lata" -#: ../src/client/accounts/add-edit-page.vala:261 +#: src/client/accounts/accounts-editor-edit-pane.vala:859 msgid "4 years back" msgstr "ostatnie 4 lata" -#. Separator -#: ../src/client/accounts/add-edit-page.vala:263 -msgid "Everything" -msgstr "Wszystko" +#: src/client/accounts/accounts-editor-edit-pane.vala:865 +#, c-format +msgid "%d day back" +msgid_plural "%d days back" +msgstr[0] "ostatni %d dzień" +msgstr[1] "ostatnie %d dni" +msgstr[2] "ostatnie %d dni" + +#: src/client/accounts/accounts-editor-list-pane.vala:243 +msgid "Undo" +msgstr "Cofnij" + +#: src/client/accounts/accounts-editor-list-pane.vala:251 +msgid "Redo" +msgstr "Ponów" + +#: src/client/accounts/accounts-editor-list-pane.vala:345 +#: src/client/accounts/accounts-editor-list-pane.vala:433 +#: src/client/accounts/accounts-editor-row.vala:278 +msgid "Gmail" +msgstr "Gmail" -#: ../src/client/accounts/add-edit-page.vala:283 -msgid "Edit" -msgstr "Modyfikuj" - -#: ../src/client/accounts/add-edit-page.vala:285 -msgid "Preview" -msgstr "Podgląd" - -#: ../src/client/accounts/add-edit-page.vala:751 -msgid "Remem_ber passwords" -msgstr "_Zapamiętywanie haseł" +#: src/client/accounts/accounts-editor-list-pane.vala:349 +#: src/client/accounts/accounts-editor-list-pane.vala:437 +#: src/client/accounts/accounts-editor-row.vala:282 +msgid "Outlook.com" +msgstr "Outlook.com" -#: ../src/client/accounts/add-edit-page.vala:758 ../ui/login.glade.h:7 -msgid "Remem_ber password" -msgstr "_Zapamiętanie hasła" +#: src/client/accounts/accounts-editor-list-pane.vala:353 +#: src/client/accounts/accounts-editor-list-pane.vala:441 +#: src/client/accounts/accounts-editor-row.vala:286 +msgid "Yahoo" +msgstr "Yahoo!" + +#. Translators: Tooltip for accounts that have been +#. loaded but disabled by the user. +#: src/client/accounts/accounts-editor-list-pane.vala:371 +msgid "This account has been disabled" +msgstr "To konto jest wyłączone" + +#. Translators: Tooltip for accounts that have been +#. loaded but because of some error are not able to be +#. used. +#: src/client/accounts/accounts-editor-list-pane.vala:380 +msgid "This account has encountered a problem and is unavailable" +msgstr "Wystąpił problem z tym kontem i jest niedostępne" + +#. Translators: Label for adding a generic email account +#: src/client/accounts/accounts-editor-list-pane.vala:430 +msgid "Other email providers" +msgstr "Inni dostawcy poczty" + +#. Translators: Notification shown after removing an +#. account. The string substitution is the name of the +#. account. +#: src/client/accounts/accounts-editor-list-pane.vala:547 +#, c-format +msgid "Account “%s” removed" +msgstr "Usunięto konto „%s”" + +#. Translators: Notification shown after removing an account +#. is undone. The string substitution is the name of the +#. account. +#: src/client/accounts/accounts-editor-list-pane.vala:554 +#, c-format +msgid "Account “%s” restored" +msgstr "Przywrócono konto „%s”" + +#. Translators: Tooltip for dragging list items +#: src/client/accounts/accounts-editor-row.vala:49 +msgid "Drag to move this item" +msgstr "Przeciągnięcie przesunie ten element" + +#. Translators: Label describes the service provider +#. hosting the email account, e.g. Gmail, Yahoo, or some +#. other generic IMAP service. +#: src/client/accounts/accounts-editor-row.vala:294 +msgid "Service provider" +msgstr "Dostawca poczty" + +#. Translators: This label describes what form of transport +#. security (TLS, StartTLS, etc) used by an account's IMAP or SMTP +#. service. +#: src/client/accounts/accounts-editor-row.vala:467 +msgid "Connection security" +msgstr "Zabezpieczenia połączenia" + +#. Translators: Label used when no auth scheme is used +#. by an account's IMAP or SMTP service. +#: src/client/accounts/accounts-editor-row.vala:478 +#: src/client/accounts/accounts-editor-servers-pane.vala:752 +#: src/client/accounts/accounts-editor-servers-pane.vala:964 +#: src/engine/api/geary-special-folder-type.vala:58 +msgid "None" +msgstr "Brak" -#: ../src/client/accounts/add-edit-page.vala:792 -msgid "Unable to validate:\n" -msgstr "Nie można sprawdzić:\n" - -#: ../src/client/accounts/add-edit-page.vala:794 -msgid " • Invalid account nickname.\n" -msgstr " • Nieprawidłowa etykieta konta.\n" - -#: ../src/client/accounts/add-edit-page.vala:797 -msgid " • Email address already added to Geary.\n" -msgstr " • Już dodano podany adres e-mail.\n" - -#: ../src/client/accounts/add-edit-page.vala:801 -msgid " • IMAP connection error.\n" -msgstr " • Błąd połączenia IMAP.\n" - -#: ../src/client/accounts/add-edit-page.vala:804 -msgid " • IMAP username or password incorrect.\n" -msgstr " • Niepoprawna nazwa użytkownika lub hasło IMAP.\n" - -#: ../src/client/accounts/add-edit-page.vala:807 -msgid " • SMTP connection error.\n" -msgstr " • Błąd połączenia SMTP.\n" - -#: ../src/client/accounts/add-edit-page.vala:810 -msgid " • SMTP username or password incorrect.\n" -msgstr " • Niepoprawna nazwa użytkownika lub hasło SMTP.\n" - -#: ../src/client/accounts/add-edit-page.vala:814 -msgid " • Connection error.\n" -msgstr " • Błąd połączenia.\n" - -#: ../src/client/accounts/add-edit-page.vala:818 -msgid " • Username or password incorrect.\n" -msgstr " • Niepoprawna nazwa użytkownika lub hasło.\n" +#: src/client/accounts/accounts-editor-row.vala:485 +msgid "StartTLS" +msgstr "StartTLS" + +#: src/client/accounts/accounts-editor-row.vala:492 +msgid "TLS" +msgstr "TLS" + +#. Translators: Label for source of SMTP authentication +#. credentials (none, use IMAP, custom) when adding a new +#. account +#. Button label for retrying when a login error has occurred +#: src/client/accounts/accounts-editor-row.vala:533 ui/main-window.ui:455 +msgid "Login" +msgstr "Login" + +#. Translators: ComboBox value for source of SMTP +#. authentication credentials (none) when adding a new +#. account +#: src/client/accounts/accounts-editor-row.vala:540 +msgid "No login needed" +msgstr "Login nie jest wymagany" + +#. Translators: ComboBox value for source of SMTP +#. authentication credentials (use IMAP) when adding a new +#. account +#: src/client/accounts/accounts-editor-row.vala:548 +msgid "Use same login as receiving" +msgstr "Taki sam login jak do odbierania" + +#. Translators: ComboBox value for source of SMTP +#. authentication credentials (custom) when adding a new +#. account +#: src/client/accounts/accounts-editor-row.vala:556 +msgid "Use a different login" +msgstr "Inny login" + +#. Translators: In-app notification label, the +#. string substitution is a more detailed reason. +#: src/client/accounts/accounts-editor-servers-pane.vala:377 +#, c-format +msgid "Account not updated: %s" +msgstr "Nie zaktualizowano konta: %s" + +#. Translators: This label describes the program that +#. created the account, e.g. an SSO service like GOA, or +#. locally by Geary. +#: src/client/accounts/accounts-editor-servers-pane.vala:540 +msgid "Account source" +msgstr "Źródło konta" + +#: src/client/accounts/accounts-editor-servers-pane.vala:552 +msgid "GNOME Online Accounts" +msgstr "Konta online GNOME" + +#. Translators: This label describes an account +#. preference. +#: src/client/accounts/accounts-editor-servers-pane.vala:611 +msgid "Save drafts on server" +msgstr "Zapisywanie szkiców na serwerze" + +#. Translators: This label describes an account +#. preference. +#: src/client/accounts/accounts-editor-servers-pane.vala:666 +msgid "Save sent email on server" +msgstr "Zapisywanie wysłanych wiadomości na serwerze" + +#. Add a suffix for OAuth2 auth so people know they +#. shouldn't expect to be prompted for a password +#. Translators: Label used when an account's IMAP or +#. SMTP service uses OAuth2. The string replacement is +#. the service's login name. +#: src/client/accounts/accounts-editor-servers-pane.vala:950 +#, c-format +msgid "%s using OAuth2" +msgstr "%s za pomocą OAuth2" + +#: src/client/accounts/accounts-editor-servers-pane.vala:960 +msgid "Use receiving server login" +msgstr "Login serwera odbierania" -#: ../src/client/application/geary-application.vala:22 +#: src/client/application/geary-application.vala:24 msgid "Copyright 2016 Software Freedom Conservancy Inc." msgstr "Copyright 2016 Software Freedom Conservancy Inc." -#: ../src/client/application/geary-application.vala:23 -msgid "Copyright 2016-2017 Geary Development Team." -msgstr "Copyright 2016-2017 Zespół programistów Geary" +#: src/client/application/geary-application.vala:25 +msgid "Copyright 2016-2019 Geary Development Team." +msgstr "Copyright 2016-2019 Zespół programistów Geary" -#: ../src/client/application/geary-application.vala:25 +#: src/client/application/geary-application.vala:27 msgid "Visit the Geary web site" msgstr "Witryna programu Geary" -#: ../src/client/application/geary-application.vala:466 +#: src/client/application/geary-application.vala:454 #, c-format msgid "About %s" msgstr "O programie %s" @@ -287,7 +792,7 @@ #. Translators: add your name and email address to receive #. credit in the About dialog For example: Yamada Taro #. -#: ../src/client/application/geary-application.vala:470 +#: src/client/application/geary-application.vala:458 msgid "translator-credits" msgstr "" "scrx , 2012\n" @@ -298,317 +803,112 @@ "wmq , 2012\n" "yorbajim , 2013\n" "zacol , 2012\n" -"Piotr Drąg , 2014-2017\n" -"Aviary.pl , 2014-2017" +"Piotr Drąg , 2014-2019\n" +"Aviary.pl , 2014-2019" -#: ../src/client/application/geary-args.vala:10 +#: src/client/application/geary-args.vala:10 msgid "Start Geary with hidden main window" msgstr "Uruchamia program Geary z ukrytym głównym oknem" -#: ../src/client/application/geary-args.vala:11 +#: src/client/application/geary-args.vala:11 msgid "Output debugging information" msgstr "Wyświetla informacje debugowania" -#: ../src/client/application/geary-args.vala:12 +#: src/client/application/geary-args.vala:12 msgid "Log conversation monitoring" msgstr "Zapisuje w dzienniku monitoring wątków" -#: ../src/client/application/geary-args.vala:13 +#: src/client/application/geary-args.vala:13 msgid "Log network deserialization" msgstr "Zapisuje w dzienniku deserializację sieciową" -#: ../src/client/application/geary-args.vala:14 +#: src/client/application/geary-args.vala:14 msgid "Log network activity" msgstr "Zapisuje w dzienniku aktywność sieciową" #. / The IMAP replay queue is how changes on the server are replicated on the client. #. / It could also be called the IMAP events queue. -#: ../src/client/application/geary-args.vala:17 +#: src/client/application/geary-args.vala:17 msgid "Log IMAP replay queue" msgstr "Zapisuje w dzienniku kolejkę odpowiedzi IMAP" #. / Serialization is how commands and responses are converted into a stream of bytes for #. / network transmission -#: ../src/client/application/geary-args.vala:20 +#: src/client/application/geary-args.vala:20 msgid "Log network serialization" msgstr "Zapisuje w dzienniku serializację sieciową" -#: ../src/client/application/geary-args.vala:21 +#: src/client/application/geary-args.vala:21 msgid "Log periodic activity" msgstr "Zapisuje w dzienniku okresową aktywność" -#: ../src/client/application/geary-args.vala:22 +#: src/client/application/geary-args.vala:22 msgid "Log database queries (generates lots of messages)" msgstr "" "Zapisuje w dzienniku zapytania do bazy danych (generuje dużo komunikatów)" #. / "Normalization" can also be called "synchronization" -#: ../src/client/application/geary-args.vala:24 +#: src/client/application/geary-args.vala:24 msgid "Log folder normalization" msgstr "Zapisuje w dzienniku normalizacje katalogów" -#: ../src/client/application/geary-args.vala:25 +#: src/client/application/geary-args.vala:25 msgid "Allow inspection of WebView" msgstr "Zezwala na badanie WebView" -#: ../src/client/application/geary-args.vala:26 +#: src/client/application/geary-args.vala:26 msgid "Revoke all server certificates with TLS warnings" msgstr "Unieważnia wszystkie certyfikaty serwerów zawierające ostrzeżenia TLS" -#: ../src/client/application/geary-args.vala:27 +#: src/client/application/geary-args.vala:27 msgid "Perform a graceful quit" msgstr "Poprawnie kończy działanie" -#: ../src/client/application/geary-args.vala:28 +#: src/client/application/geary-args.vala:28 msgid "Display program version" msgstr "Wyświetla wersję programu" #. This gives a command-line hint on how to open new composer windows with mailto: -#: ../src/client/application/geary-args.vala:53 +#: src/client/application/geary-args.vala:53 #, c-format msgid "Use %s to open a new composer window" msgstr "Należy użyć %s, aby otworzyć nowe okno tworzenia wiadomości" -#: ../src/client/application/geary-args.vala:56 +#: src/client/application/geary-args.vala:56 msgid "Please report comments, suggestions and bugs to:" msgstr "" "Komentarze, sugestie i błędy prosimy zgłaszać do (w języku angielskim):" #. i18n: Command line arguments are invalid -#: ../src/client/application/geary-args.vala:63 +#: src/client/application/geary-args.vala:63 #, c-format msgid "Failed to parse command line options: %s\n" msgstr "Przetworzenie opcji wiersza poleceń się nie powiodło: %s\n" -#: ../src/client/application/geary-args.vala:74 +#: src/client/application/geary-args.vala:74 #, c-format msgid "Unrecognized command line option “%s”\n" msgstr "Nieznana opcja wiersza poleceń „%s”\n" -#: ../src/client/application/geary-controller.vala:57 -msgid "Delete conversation" -msgstr "Usuń wątek" - -#: ../src/client/application/geary-controller.vala:58 -msgid "Delete conversation (Shift+Delete)" -msgstr "Usuń wątek (Shift+Delete)" - -#: ../src/client/application/geary-controller.vala:59 -msgid "Delete conversations (Shift+Delete)" -msgstr "Usuń wątki (Shift+Delete)" - -#. This refers to the action ("move email to the trash"), not the Trash folder itself -#: ../src/client/application/geary-controller.vala:63 -msgid "Move conversation to Trash (Delete, Backspace)" -msgstr "Przenieś wątek do kosza (Delete, Backspace)" - -#: ../src/client/application/geary-controller.vala:64 -msgid "Move conversations to Trash (Delete, Backspace)" -msgstr "Przenieś wątki do kosza (Delete, Backspace)" - -#. This refers to the action ("archive an email"), not the Archive folder itself -#: ../src/client/application/geary-controller.vala:68 -msgid "_Archive" -msgstr "_Archiwizuj" - -#: ../src/client/application/geary-controller.vala:69 -msgid "Archive conversation (A)" -msgstr "Archiwizuj wątek (A)" - -#: ../src/client/application/geary-controller.vala:70 -msgid "Archive conversations (A)" -msgstr "Archiwizuj wątki (A)" - -#: ../src/client/application/geary-controller.vala:73 -msgid "Mark as S_pam" -msgstr "Oznacz jako _niechciana" - -#: ../src/client/application/geary-controller.vala:74 -msgid "Mark as not S_pam" -msgstr "Oznacz jako p_ożądana" - -#: ../src/client/application/geary-controller.vala:76 -#: ../src/client/application/geary-controller.vala:448 -msgid "Mark conversation" -msgstr "Oznacz wątek" - -#: ../src/client/application/geary-controller.vala:77 -msgid "Mark conversations" -msgstr "Oznacz wątek" - -#: ../src/client/application/geary-controller.vala:78 -msgid "Add label to conversation" -msgstr "Nadaj etykietę wątkowi" - -#: ../src/client/application/geary-controller.vala:79 -msgid "Add label to conversations" -msgstr "Nadaj etykietę wątkom" - -#: ../src/client/application/geary-controller.vala:80 -#: ../src/client/application/geary-controller.vala:487 -msgid "Move conversation" -msgstr "Przenieś wątek" - -#: ../src/client/application/geary-controller.vala:81 -msgid "Move conversations" -msgstr "Przenieś wątki" +#. Translators: File name used in save chooser when saving +#. attachments that do not otherwise have a name. +#: src/client/application/geary-controller.vala:58 +msgid "Untitled" +msgstr "Bez tytułu" -#: ../src/client/application/geary-controller.vala:450 -msgid "_Mark as…" -msgstr "_Oznacz jako…" - -#: ../src/client/application/geary-controller.vala:456 -msgid "Mark as _Read" -msgstr "Oznacz jako p_rzeczytane" - -#: ../src/client/application/geary-controller.vala:462 -msgid "Mark as _Unread" -msgstr "Oznacz jako _nieprzeczytane" - -#: ../src/client/application/geary-controller.vala:468 -msgid "_Star" -msgstr "_Wyróżnij" - -#: ../src/client/application/geary-controller.vala:473 -msgid "U_nstar" -msgstr "Usuń wy_różnienie" - -#: ../src/client/application/geary-controller.vala:483 -msgid "Add label" -msgstr "Dodaj etykietę" - -#: ../src/client/application/geary-controller.vala:484 -msgid "_Label" -msgstr "_Etykieta" - -#: ../src/client/application/geary-controller.vala:488 -msgid "_Move" -msgstr "_Przenieś" - -#: ../src/client/application/geary-controller.vala:492 -msgid "Compose new message (Ctrl+N, N)" -msgstr "Utwórz nową wiadomość (Ctrl+N, N)" - -#: ../src/client/application/geary-controller.vala:496 -#: ../ui/conversation-email-menus.ui.h:1 -msgid "_Reply" -msgstr "_Odpowiedz" - -#: ../src/client/application/geary-controller.vala:497 -msgid "Reply (Ctrl+R, R)" -msgstr "Odpowiedz (Ctrl+R, R)" - -#: ../src/client/application/geary-controller.vala:501 -msgid "R_eply All" -msgstr "O_dpowiedz wszystkim" - -#: ../src/client/application/geary-controller.vala:502 -msgid "Reply all (Ctrl+Shift+R, Shift+R)" -msgstr "Odpowiedz wszystkim (Ctrl+Shift+R, Shift+R)" - -#: ../src/client/application/geary-controller.vala:507 -#: ../ui/conversation-email-menus.ui.h:3 -msgid "_Forward" -msgstr "_Przekaż" - -#: ../src/client/application/geary-controller.vala:508 -msgid "Forward (Ctrl+L, F)" -msgstr "Przekaż (Ctrl+L, F)" - -#: ../src/client/application/geary-controller.vala:538 -msgid "Empty _Spam…" -msgstr "Opróżnij katalog _niechcianych wiadomości…" - -#: ../src/client/application/geary-controller.vala:542 -msgid "Empty _Trash…" -msgstr "Opróżnij _kosz…" - -#. No callback is connected, since we bind the toggle button to the search bar visibility -#: ../src/client/application/geary-controller.vala:574 -msgid "Toggle search bar" -msgstr "Przełącz pasek wyszukiwania" - -#. No callback is connected, since we bind the toggle button to the find bar visibility -#: ../src/client/application/geary-controller.vala:579 -msgid "Toggle find bar" -msgstr "Przełącz pasek wyszukiwania" - -#: ../src/client/application/geary-controller.vala:757 -msgid "Unable to store server trust exception" -msgstr "Nie można przechować wyjątku zaufania serwera" - -#: ../src/client/application/geary-controller.vala:992 -msgid "Your settings are insecure" -msgstr "Ustawienia konta nie są bezpieczne" - -#: ../src/client/application/geary-controller.vala:993 -msgid "" -"Your IMAP and/or SMTP settings do not specify SSL or TLS. This means your " -"username and password could be read by another person on the network. Are " -"you sure you want to do this?" -msgstr "" -"Ustawienia IMAP lub SMTP nie uwzględniają szyfrowania SSL lub TLS. Oznacza " -"to, że nazwa użytkownika i hasło mogą być widoczne dla innych użytkowników " -"sieci. Wprowadzić ustawienia?" - -#: ../src/client/application/geary-controller.vala:994 -msgid "Co_ntinue" -msgstr "_Kontynuuj" - -#: ../src/client/application/geary-controller.vala:1041 -msgid "Error connecting to the server" -msgstr "Błąd podczas łączenia z serwerem" - -#: ../src/client/application/geary-controller.vala:1042 -msgid "" -"Geary encountered an error while connecting to the server. Please try again " -"in a few moments." -msgstr "" -"Wystąpił błąd podczas łączenia z serwerem. Proszę spróbować ponownie za " -"chwilę." - -#. / Displayed in the space-limited status bar when a message fails to be sent due to error. -#: ../src/client/application/geary-controller.vala:1079 -#: ../src/client/components/status-bar.vala:29 -msgid "Error sending email" -msgstr "Błąd podczas wysyłania wiadomości" - -#: ../src/client/application/geary-controller.vala:1080 -msgid "" -"Geary encountered an error sending an email. If the problem persists, " -"please manually delete the email from your Outbox folder." -msgstr "" -"Wystąpił błąd podczas wysyłania wiadomości. Jeśli problem będzie się " -"powtarzał, proszę ręcznie usunąć wiadomość z katalogu Wychodzące." - -#. Displayed in the space-limited status bar when a message fails to be uploaded -#. to Sent Mail after being sent. -#: ../src/client/application/geary-controller.vala:1084 -#: ../src/client/components/status-bar.vala:33 -msgid "Error saving sent mail" -msgstr "Błąd podczas zapisywania wysłanej wiadomości" - -#: ../src/client/application/geary-controller.vala:1085 -msgid "" -"Geary encountered an error saving a sent message to Sent Mail. The message " -"will stay in your Outbox folder until you delete it." -msgstr "" -"Wystąpił błąd podczas zapisywania wysłanej wiadomości e-mail. Wiadomość " -"pozostanie w katalogu Wychodzące do jej usunięcia." - -#: ../src/client/application/geary-controller.vala:1154 +#: src/client/application/geary-controller.vala:919 msgid "Labels" msgstr "Etykiety" #. give the user two options: reset the Account local store, or exit Geary. A third #. could be done to leave the Account in an unopened state, but we don't currently #. have provisions for that. -#: ../src/client/application/geary-controller.vala:1166 +#: src/client/application/geary-controller.vala:932 #, c-format msgid "Unable to open the database for %s" msgstr "Nie można otworzyć bazy danych dla %s" -#: ../src/client/application/geary-controller.vala:1167 +#: src/client/application/geary-controller.vala:933 #, c-format msgid "" "There was an error opening the local mail database for this account. This is " @@ -633,20 +933,20 @@ "Przebudowanie bazy danych usunie wszystkie lokalne wiadomości e-mail i ich " "załączniki. Poczta na serwerze nie zostanie usunięta." -#: ../src/client/application/geary-controller.vala:1169 +#: src/client/application/geary-controller.vala:935 msgid "_Rebuild" msgstr "P_rzebuduj" -#: ../src/client/application/geary-controller.vala:1169 +#: src/client/application/geary-controller.vala:935 msgid "E_xit" msgstr "Za_kończ" -#: ../src/client/application/geary-controller.vala:1178 +#: src/client/application/geary-controller.vala:944 #, c-format msgid "Unable to rebuild database for “%s”" msgstr "Nie można przebudować bazy danych dla „%s”" -#: ../src/client/application/geary-controller.vala:1179 +#: src/client/application/geary-controller.vala:945 #, c-format msgid "" "Error during rebuild:\n" @@ -657,67 +957,15 @@ "\n" "%s" -#. some other problem opening the account ... as with other flow path, can't run -#. Geary today with an account in unopened state, so have to exit -#: ../src/client/application/geary-controller.vala:1201 -#: ../src/client/application/geary-controller.vala:1211 -#: ../src/client/application/geary-controller.vala:1222 -#, c-format -msgid "Unable to open local mailbox for %s" -msgstr "Nie można otworzyć lokalnej skrzynki pocztowej dla %s" - -#: ../src/client/application/geary-controller.vala:1202 -#, c-format -msgid "" -"There was an error opening the local mail database for this account. This is " -"possibly due to a file permissions problem.\n" -"\n" -"Please check that you have read/write permissions for all files in this " -"directory:\n" -"\n" -"%s" -msgstr "" -"Wystąpił błąd podczas otwierania lokalnej bazy danych poczty dla tego konta. " -"Jest to prawdopodobnie spowodowane problemem z uprawnieniami pliku.\n" -"\n" -"Proszę sprawdzić, czy wszystkie pliki w tym katalogu mają uprawnienia do " -"odczytu i zapisu:\n" -"\n" -"%s" - -#: ../src/client/application/geary-controller.vala:1212 -msgid "" -"The version number of the local mail database is formatted for a newer " -"version of Geary. Unfortunately, the database cannot be “rolled back” to " -"work with this version of Geary.\n" -"\n" -"Please install the latest version of Geary and try again." -msgstr "" -"Wersja lokalnej bazy danych poczty jest sformatowana dla nowszej wersji " -"programu Geary. Baza danych nie może zostać przywrócona do działania z tą " -"wersją programu Geary." - -#: ../src/client/application/geary-controller.vala:1223 -msgid "" -"There was an error opening the local account. This is probably due to " -"connectivity issues.\n" -"\n" -"Please check your network connection and restart Geary." -msgstr "" -"Wystąpił błąd podczas otwierania lokalnego konta. Jest to prawdopodobnie " -"spowodowane problemami z połączeniem.\n" -"\n" -"Proszę sprawdzić połączenie internetowe i uruchomić ponownie program Geary." - -#: ../src/client/application/geary-controller.vala:1999 +#: src/client/application/geary-controller.vala:1800 msgid "Undo move (Ctrl+Z)" msgstr "Cofnij przeniesienie (Ctrl+Z)" -#: ../src/client/application/geary-controller.vala:2009 +#: src/client/application/geary-controller.vala:1810 msgid "Are you sure you want to open these attachments?" msgstr "Na pewno otworzyć te załączniki?" -#: ../src/client/application/geary-controller.vala:2010 +#: src/client/application/geary-controller.vala:1811 msgid "" "Attachments may cause damage to your system if opened. Only open files from " "trusted sources." @@ -725,201 +973,489 @@ "Załączniki po otwarciu mogą uszkodzić system. Należy otwierać załączniki " "pochodzące tylko z zaufanych źródeł." -#: ../src/client/application/geary-controller.vala:2011 +#: src/client/application/geary-controller.vala:1812 msgid "Don’t _ask me again" msgstr "_Bez pytania ponownie" -#: ../src/client/application/geary-controller.vala:2121 +#. Translators: Dialog primary label when prompting to +#. overwrite a file. The string substitution is the file'sx +#. name. +#: src/client/application/geary-controller.vala:1941 #, c-format msgid "A file named “%s” already exists. Do you want to replace it?" msgstr "Plik o nazwie „%s” już istnieje. Zastąpić go?" -#: ../src/client/application/geary-controller.vala:2123 +#. Translators: Dialog secondary label when prompting to +#. overwrite a file. The string substitution is the parent +#. folder's name. +#: src/client/application/geary-controller.vala:1948 #, c-format msgid "" "The file already exists in “%s”. Replacing it will overwrite its contents." msgstr "Plik w „%s” już istnieje. Zastąpienie go nadpisze jego zawartość." -#: ../src/client/application/geary-controller.vala:2126 +#: src/client/application/geary-controller.vala:1952 msgid "_Replace" msgstr "_Zastąp" -#. Find out what to do with the inline composers. -#. TODO: Remove this in favor of automatically saving drafts -#: ../src/client/application/geary-controller.vala:2374 -msgid "Close open draft messages?" -msgstr "Zamknąć otwarte szkice?" +#: src/client/application/geary-controller.vala:2228 +msgid "Close the draft message?" +msgid_plural "Close all draft messages?" +msgstr[0] "Zamknąć szkic?" +msgstr[1] "Zamknąć wszystkie szkice?" +msgstr[2] "Zamknąć wszystkie szkice?" -#: ../src/client/application/geary-controller.vala:2496 +#: src/client/application/geary-controller.vala:2354 #, c-format msgid "Empty all email from your %s folder?" msgstr "Usunąć wszystkie wiadomości z katalogu %s?" -#: ../src/client/application/geary-controller.vala:2497 +#: src/client/application/geary-controller.vala:2355 msgid "This removes the email from Geary and your email server." msgstr "Spowoduje to usunięcie wiadomości z programu Geary i serwera poczty." -#: ../src/client/application/geary-controller.vala:2498 +#: src/client/application/geary-controller.vala:2356 msgid "This cannot be undone." msgstr "Tego nie można cofnąć." -#: ../src/client/application/geary-controller.vala:2499 +#: src/client/application/geary-controller.vala:2357 #, c-format msgid "Empty %s" msgstr "Opróżnij %s" -#: ../src/client/application/geary-controller.vala:2516 +#: src/client/application/geary-controller.vala:2374 #, c-format msgid "Error emptying %s" msgstr "Błąd podczas opróżniania %s" -#: ../src/client/application/geary-controller.vala:2546 +#: src/client/application/geary-controller.vala:2406 msgid "Do you want to permanently delete this message?" msgid_plural "Do you want to permanently delete these messages?" msgstr[0] "Trwale usunąć tę wiadomość?" msgstr[1] "Trwale usunąć te wiadomości?" msgstr[2] "Trwale usunąć te wiadomości?" -#: ../src/client/application/geary-controller.vala:2548 +#: src/client/application/geary-controller.vala:2408 msgid "Delete" msgstr "Usuń" -#: ../src/client/application/geary-controller.vala:2580 -msgid "Undo archive (Ctrl+Z)" -msgstr "Cofnij archiwizację (Ctrl+Z)" - -#: ../src/client/application/geary-controller.vala:2595 +#: src/client/application/geary-controller.vala:2422 msgid "Undo trash (Ctrl+Z)" msgstr "Cofnij przeniesienie do kosza (Ctrl+Z)" -#: ../src/client/application/geary-controller.vala:2649 +#: src/client/application/geary-controller.vala:2472 +msgid "Undo archive (Ctrl+Z)" +msgstr "Cofnij archiwizację (Ctrl+Z)" + +#: src/client/application/geary-controller.vala:2517 msgid "Undo (Ctrl+Z)" msgstr "Cofnij (Ctrl+Z)" -#: ../src/client/application/geary-controller.vala:2780 +#. Translators: The label for an in-app notification. The +#. string substitution is a list of recipients of the email. +#: src/client/application/geary-controller.vala:2598 +#, c-format +msgid "Successfully sent mail to %s." +msgstr "Pomyślnie wysłano wiadomość do: %s." + +#: src/client/application/geary-controller.vala:2680 msgid "Failed to open default text editor." msgstr "Otwarcie domyślnego edytora tekstu się nie powiodło." -#: ../src/client/components/main-window.vala:389 +#. Translators: Tooltip used when an entry requires a valid +#. email address to be entered, but one is not provided. +#: src/client/components/components-validator.vala:341 +msgid "An email address is required" +msgstr "Wymagany jest adres e-mail" + +#. Translators: Tooltip used when an entry requires a valid +#. email address to be entered, but the address is invalid. +#: src/client/components/components-validator.vala:345 +msgid "Not a valid email address" +msgstr "Nieprawidłowy adres e-mail" + +#. Translators: Tooltip used when an entry requires a valid, +#. resolvable server name to be entered, but one is not +#. provided. +#: src/client/components/components-validator.vala:391 +msgid "A server name is required" +msgstr "Wymagana jest nazwa serwera" + +#. Translators: Tooltip used when an entry requires a valid +#. server name to be entered, but it was unable to be +#. looked-up in the DNS. +#: src/client/components/components-validator.vala:396 +msgid "Could not look up server name" +msgstr "Nie można wyszukać nazwy serwera" + +#: src/client/components/main-toolbar.vala:151 +msgid "Mark conversation" +msgid_plural "Mark conversations" +msgstr[0] "Oznacza wątek" +msgstr[1] "Oznacza wątki" +msgstr[2] "Oznacza wątki" + +#: src/client/components/main-toolbar.vala:156 +msgid "Add label to conversation" +msgid_plural "Add label to conversations" +msgstr[0] "Nadaje etykietę wątkowi" +msgstr[1] "Nadaje etykietę wątkom" +msgstr[2] "Nadaje etykietę wątkom" + +#: src/client/components/main-toolbar.vala:161 +msgid "Move conversation" +msgid_plural "Move conversations" +msgstr[0] "Przenosi wątek" +msgstr[1] "Przenosi wątki" +msgstr[2] "Przenosi wątki" + +#: src/client/components/main-toolbar.vala:166 +msgid "Archive conversation (A)" +msgid_plural "Archive conversations (A)" +msgstr[0] "Archiwizuje wątek (A)" +msgstr[1] "Archiwizuje wątki (A)" +msgstr[2] "Archiwizuje wątki (A)" + +#: src/client/components/main-toolbar.vala:175 +msgid "Move conversation to Trash (Delete, Backspace)" +msgid_plural "Move conversations to Trash (Delete, Backspace)" +msgstr[0] "Przenosi wątek do kosza (Delete, Backspace)" +msgstr[1] "Przenosi wątki do kosza (Delete, Backspace)" +msgstr[2] "Przenosi wątki do kosza (Delete, Backspace)" + +#: src/client/components/main-toolbar.vala:183 +msgid "Delete conversation (Shift+Delete)" +msgid_plural "Delete conversations (Shift+Delete)" +msgstr[0] "Usuwa wątek (Shift+Delete)" +msgstr[1] "Usuwa wątki (Shift+Delete)" +msgstr[2] "Usuwa wątki (Shift+Delete)" + +#: src/client/components/main-window.vala:503 #, c-format msgid "%s (%d)" msgstr "%s (%d)" -#: ../src/client/components/search-bar.vala:8 -#: ../src/client/folder-list/folder-list-search-branch.vala:38 -#: ../src/engine/api/geary-special-folder-type.vala:51 +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:47 +#, c-format +msgid "Problem connecting to incoming server for %s" +msgstr "Problem podczas łączenia z serwerem poczty przychodzącej dla konta %s" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:49 +#: src/client/components/main-window-info-bar.vala:58 +#, c-format +msgid "" +"Could not connect to %s, check your Internet access and the server name and " +"try again" +msgstr "" +"Nie można połączyć się z %s, proszę sprawdzić dostęp do Internetu " +"i poprawność nazwy serwera, a następnie spróbować ponownie" + +#. Translators: Tooltip label for Retry button +#. Button tooltip for retrying an account problem +#: src/client/components/main-window-info-bar.vala:51 +#: src/client/components/main-window-info-bar.vala:60 +#: src/client/components/main-window-info-bar.vala:69 +#: src/client/components/main-window-info-bar.vala:78 +#: src/client/components/main-window-info-bar.vala:87 +#: src/client/components/main-window-info-bar.vala:96 +#: src/client/components/main-window-info-bar.vala:136 ui/main-window.ui:265 +msgid "Try reconnecting" +msgstr "Ponawia połączenie" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:56 +#, c-format +msgid "Problem connecting to outgoing server for %s" +msgstr "Problem podczas łączenia z serwerem poczty wychodzącej dla konta %s" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:65 +#: src/client/components/main-window-info-bar.vala:83 +#, c-format +msgid "Problem communicating with incoming server for %s" +msgstr "" +"Problem podczas komunikacji z serwerem poczty przychodzącej dla konta %s" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:67 +#: src/client/components/main-window-info-bar.vala:76 +#, c-format +msgid "Network error talking to %s, check your Internet access and try again" +msgstr "" +"Błąd sieci podczas komunikacji z serwerem %s, proszę sprawdzić dostęp do " +"Internetu i spróbować ponownie" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:74 +#: src/client/components/main-window-info-bar.vala:91 +msgid "Problem communicating with outgoing mail server" +msgstr "Problem podczas komunikacji z serwerem poczty wychodzącej" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:85 +#, c-format +msgid "" +"Geary did not understand a message from %s or vice versa, please file a bug " +"report" +msgstr "Nie można przetworzyć komunikatu z serwera %s, proszę zgłosić błąd" + +#. Translators: First string substitution is the server +#. name, second is the account name +#: src/client/components/main-window-info-bar.vala:94 +#, c-format +msgid "" +"Could not communicate with %s for %s, check the server name and try again in " +"a moment" +msgstr "" +"Nie można komunikować się z serwerem %s dla konta %s, proszę sprawdzić " +"poprawność nazwy serwera i spróbować ponownie za chwilę" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:101 +#, c-format +msgid "Incoming mail server password required for %s" +msgstr "Wymagane jest hasło serwera poczty przychodzącej dla konta %s" + +#: src/client/components/main-window-info-bar.vala:102 +msgid "Messages cannot be received without the correct password." +msgstr "Nie można odbierać wiadomości bez właściwego hasła." + +#. Translators: Tooltip label for Retry button +#: src/client/components/main-window-info-bar.vala:104 +msgid "Retry receiving email, you will be prompted for a password" +msgstr "Ponawia odbieranie poczty, zostanie wyświetlona prośba o hasło" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:109 +#, c-format +msgid "Outgoing mail server password required for %s" +msgstr "Wymagane jest hasło serwera poczty wychodzącej dla konta %s" + +#: src/client/components/main-window-info-bar.vala:110 +msgid "Messages cannot be sent without the correct password." +msgstr "Nie można wysyłać wiadomości bez właściwego hasła." + +#. Translators: Tooltip label for Retry button +#: src/client/components/main-window-info-bar.vala:112 +msgid "Retry sending queued messages, you will be prompted for a password" +msgstr "Ponawia wysyłanie poczty, zostanie wyświetlona prośba o hasło" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:117 +#, c-format +msgid "Incoming mail server security is not trusted for %s" +msgstr "" +"Zabezpieczenia serwera poczty przychodzącej dla konta %s nie są zaufane" + +#: src/client/components/main-window-info-bar.vala:118 +msgid "Messages will not be received until checked." +msgstr "" +"Wiadomości nie będą odbierane, dopóki nie zostanie sprawdzona poprawność." + +#. Translators: Tooltip label for Retry button +#: src/client/components/main-window-info-bar.vala:120 +#: src/client/components/main-window-info-bar.vala:128 +msgid "Check security details" +msgstr "Proszę sprawdzić poprawność informacji o zabezpieczeniach" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:125 +#, c-format +msgid "Outgoing mail server security is not trusted for %s" +msgstr "Zabezpieczenia serwera poczty wychodzącej dla konta %s nie są zaufane" + +#: src/client/components/main-window-info-bar.vala:126 +msgid "Messages cannot be sent until checked." +msgstr "" +"Nie można wysyłać wiadomości, dopóki nie zostanie sprawdzona poprawność." + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:133 +#, c-format +msgid "A problem occurred checking mail for %s" +msgstr "Wystąpił problem podczas wyszukiwania nowej poczty dla konta %s" + +#: src/client/components/main-window-info-bar.vala:134 +#: src/client/components/main-window-info-bar.vala:142 +msgid "Something went wrong, please file a bug report if the problem persists" +msgstr "Coś się nie powiodło, proszę zgłosić błąd, jeśli problem się powtarza" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:141 +#, c-format +msgid "A problem occurred sending mail for %s" +msgstr "Wystąpił problem podczas wysyłania poczty dla konta %s" + +#. Translators: Tooltip label for Retry button +#: src/client/components/main-window-info-bar.vala:144 +msgid "Retry sending queued messages" +msgstr "Ponawia wysyłanie wiadomości" + +#: src/client/components/main-window-info-bar.vala:155 +msgid "A database problem has occurred" +msgstr "Wystąpił problem z bazą danych" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:157 +#, c-format +msgid "Messages for %s must be downloaded again." +msgstr "Wiadomości dla konta %s muszą zostać pobrane ponownie." + +#: src/client/components/main-window-info-bar.vala:170 +msgid "Geary has encountered a problem" +msgstr "Wystąpił problem" + +#: src/client/components/main-window-info-bar.vala:171 +msgid "" +"Please check the technical details and report the problem if it persists." +msgstr "" +"Proszę sprawdzić informacje techniczne i zgłosić problem, jeśli się powtarza." + +#: src/client/components/main-window-info-bar.vala:179 +msgid "_Details" +msgstr "_Informacje" + +#. Button tooltip for displaying technical details about an account problem +#: src/client/components/main-window-info-bar.vala:180 ui/main-window.ui:250 +msgid "View technical details about the error" +msgstr "Wyświetla informacje techniczne o błędzie" + +#: src/client/components/main-window-info-bar.vala:184 +msgid "_Retry" +msgstr "_Ponów" + +#: src/client/components/search-bar.vala:8 +#: src/client/folder-list/folder-list-search-branch.vala:38 +#: src/engine/api/geary-special-folder-type.vala:51 msgid "Search" msgstr "Wyszukaj" #. Search entry. -#: ../src/client/components/search-bar.vala:23 +#: src/client/components/search-bar.vala:23 msgid "Search all mail in account for keywords (Ctrl+S)" msgstr "" -"Przeszukaj wszystkie wiadomości z konta pod kątem słów kluczowych (Ctrl+S)" +"Przeszukuje wszystkie wiadomości z konta pod kątem słów kluczowych (Ctrl+S)" -#: ../src/client/components/search-bar.vala:100 +#: src/client/components/search-bar.vala:101 #, c-format msgid "Indexing %s account" msgstr "Indeksowanie konta %s" -#: ../src/client/components/search-bar.vala:111 -#: ../src/client/folder-list/folder-list-search-branch.vala:39 +#: src/client/components/search-bar.vala:112 +#: src/client/folder-list/folder-list-search-branch.vala:39 #, c-format msgid "Search %s account" msgstr "Przeszukiwanie konta %s" #. / Displayed in the space-limited status bar while a message is in the process of being sent. -#: ../src/client/components/status-bar.vala:26 +#: src/client/components/status-bar.vala:26 msgid "Sending…" msgstr "Wysyłanie…" -#: ../src/client/components/stock.vala:18 ../ui/account_cannot_remove.glade.h:3 +#. / Displayed in the space-limited status bar when a message fails to be sent due to error. +#: src/client/components/status-bar.vala:29 +msgid "Error sending email" +msgstr "Błąd podczas wysyłania wiadomości" + +#. Displayed in the space-limited status bar when a message fails to be uploaded +#. to Sent Mail after being sent. +#: src/client/components/status-bar.vala:33 +msgid "Error saving sent mail" +msgstr "Błąd podczas zapisywania wysłanej wiadomości" + +#: src/client/components/stock.vala:18 msgid "_OK" msgstr "_OK" -#: ../src/client/components/stock.vala:19 ../ui/edit_alternate_emails.glade.h:3 -#: ../ui/password-dialog.glade.h:5 ../ui/remove_confirm.glade.h:5 +#: src/client/components/stock.vala:19 ui/password-dialog.glade:196 msgid "_Cancel" msgstr "_Anuluj" -#: ../src/client/components/stock.vala:21 ../ui/gtk/menus.ui.h:6 +#: src/client/components/stock.vala:21 msgid "_About" msgstr "_O programie" -#: ../src/client/components/stock.vala:23 +#: src/client/components/stock.vala:22 +msgid "_Add" +msgstr "_Dodaj" + +#: src/client/components/stock.vala:23 msgid "_Close" msgstr "Za_mknij" -#: ../src/client/components/stock.vala:24 +#: src/client/components/stock.vala:24 msgid "_Discard" msgstr "Od_rzuć" -#: ../src/client/components/stock.vala:25 ../ui/gtk/menus.ui.h:5 +#: src/client/components/stock.vala:25 ui/main-toolbar-menus.ui:56 msgid "_Help" msgstr "Pomo_c" -#: ../src/client/components/stock.vala:26 ../ui/conversation-email-menus.ui.h:9 +#: src/client/components/stock.vala:26 ui/conversation-email-menus.ui:77 msgid "_Open" msgstr "_Otwórz" -#: ../src/client/components/stock.vala:27 ../ui/gtk/menus.ui.h:3 +#: src/client/components/stock.vala:27 ui/main-toolbar-menus.ui:46 msgid "_Preferences" msgstr "P_referencje" -#: ../src/client/components/stock.vala:28 ../ui/conversation-email-menus.ui.h:7 +#. Translators: Menu item to print a single, specific message +#: src/client/components/stock.vala:28 ui/conversation-email-menus.ui:64 msgid "_Print…" msgstr "Wy_drukuj…" -#: ../src/client/components/stock.vala:29 ../ui/gtk/menus.ui.h:7 +#: src/client/components/stock.vala:29 msgid "_Quit" msgstr "Za_kończ" -#: ../src/client/components/stock.vala:30 ../ui/remove_confirm.glade.h:6 +#: src/client/components/stock.vala:30 msgid "_Remove" msgstr "_Usuń" -#: ../src/client/components/stock.vala:32 +#: src/client/components/stock.vala:31 ui/conversation-email-menus.ui:83 +msgid "_Save" +msgstr "_Zapisz" + +#: src/client/components/stock.vala:32 msgid "_Keep" msgstr "_Zachowaj" -#: ../src/client/composer/composer-link-popover.vala:150 +#: src/client/composer/composer-link-popover.vala:149 msgid "Link URL is not correctly formatted, e.g. http://example.com" msgstr "" "Adres URL odnośnika nie jest poprawnie sformatowany, np. http://example.com" -#: ../src/client/composer/composer-link-popover.vala:157 +#: src/client/composer/composer-link-popover.vala:156 msgid "Invalid link URL" msgstr "Nieprawidłowy adres URL odnośnika" -#: ../src/client/composer/composer-link-popover.vala:157 +#: src/client/composer/composer-link-popover.vala:156 msgid "Invalid email address" msgstr "Nieprawidłowy adres e-mail" -#: ../src/client/composer/composer-widget.vala:154 +#: src/client/composer/composer-widget.vala:150 msgid "Saved" msgstr "Zapisano" -#: ../src/client/composer/composer-widget.vala:155 +#: src/client/composer/composer-widget.vala:151 msgid "Saving" msgstr "Zapisywanie" -#: ../src/client/composer/composer-widget.vala:156 +#: src/client/composer/composer-widget.vala:152 msgid "Error saving" msgstr "Błąd podczas zapisywania" -#: ../src/client/composer/composer-widget.vala:157 +#: src/client/composer/composer-widget.vala:153 msgid "Press Backspace to delete quote" msgstr "Naciśnięcie klawisza Backspace usunie cytat" -#: ../src/client/composer/composer-widget.vala:158 -msgid "New Message" -msgstr "Nowa wiadomość" - #. Translators: This is list of keywords, separated by pipe ("|") #. characters, that suggest an attachment; since this is full-word #. checking, include all variants of each word. No spaces are #. allowed. -#: ../src/client/composer/composer-widget.vala:167 +#: src/client/composer/composer-widget.vala:162 msgid "" "attach|attaching|attaches|attachment|attachments|attached|enclose|enclosed|" "enclosing|encloses|enclosure|enclosures" @@ -928,28 +1464,37 @@ "załączyłam|załączono|dołączyłem|dołączyłam|dołączono|zalacznik|zalaczam|" "zalaczylem|zalaczylam|zalaczono|dolaczylem|dolaczylam|dolaczono" -#: ../src/client/composer/composer-widget.vala:1122 -#: ../src/client/composer/composer-widget.vala:1141 -msgid "Do you want to discard this message?" -msgstr "Odrzucić tę wiadomość?" +#. Translators: This dialog text is displayed to the +#. user when closing a composer where the options are +#. Keep, Discard or Cancel. +#: src/client/composer/composer-widget.vala:1121 +msgid "Do you want to keep or discard this draft message?" +msgstr "Zachować lub odrzucić ten szkic?" + +#. Translators: This dialog text is displayed to the +#. user when closing a composer where the options are +#. only Discard or Cancel. +#: src/client/composer/composer-widget.vala:1149 +msgid "Do you want to discard this draft message?" +msgstr "Odrzucić ten szkic?" -#: ../src/client/composer/composer-widget.vala:1245 +#: src/client/composer/composer-widget.vala:1266 msgid "Send message with an empty subject and body?" msgstr "Wysłać wiadomość niezawierającą tematu i treści?" -#: ../src/client/composer/composer-widget.vala:1247 +#: src/client/composer/composer-widget.vala:1268 msgid "Send message with an empty subject?" msgstr "Wysłać wiadomość niezawierającą tematu?" -#: ../src/client/composer/composer-widget.vala:1249 +#: src/client/composer/composer-widget.vala:1270 msgid "Send message with an empty body?" msgstr "Wysłać wiadomość niezawierającą treści?" -#: ../src/client/composer/composer-widget.vala:1253 +#: src/client/composer/composer-widget.vala:1274 msgid "Send message without an attachment?" msgstr "Wysłać wiadomość niezawierającą załączników?" -#: ../src/client/composer/composer-widget.vala:1515 +#: src/client/composer/composer-widget.vala:1579 #, c-format msgid "“%s” already attached for delivery." msgstr "Do wiadomości już załączono „%s”." @@ -959,170 +1504,262 @@ #. description of the document type, the second will #. be a human-friendly size string. For example: #. Document (100.9MB) -#: ../src/client/composer/composer-widget.vala:1523 -#: ../src/client/conversation-viewer/conversation-email.vala:138 +#: src/client/composer/composer-widget.vala:1587 +#: src/client/conversation-viewer/conversation-email.vala:173 #, c-format msgid "%s (%s)" msgstr "%s (%s)" -#: ../src/client/composer/composer-widget.vala:1560 +#: src/client/composer/composer-widget.vala:1624 #, c-format msgid "“%s” could not be found." msgstr "Nie można odnaleźć „%s”." -#: ../src/client/composer/composer-widget.vala:1566 +#: src/client/composer/composer-widget.vala:1630 #, c-format msgid "“%s” is a folder." msgstr "„%s” jest katalogiem." -#: ../src/client/composer/composer-widget.vala:1572 +#: src/client/composer/composer-widget.vala:1636 #, c-format msgid "“%s” is an empty file." msgstr "„%s” jest pustym plikiem." -#: ../src/client/composer/composer-widget.vala:1585 +#: src/client/composer/composer-widget.vala:1649 #, c-format msgid "“%s” could not be opened for reading." msgstr "Nie można otworzyć „%s” do odczytania." -#: ../src/client/composer/composer-widget.vala:1593 +#: src/client/composer/composer-widget.vala:1657 msgid "Cannot add attachment" msgstr "Nie można dodać załącznika" -#: ../src/client/composer/composer-widget.vala:1645 -msgid "To: " -msgstr "Do: " - -#: ../src/client/composer/composer-widget.vala:1648 -msgid "Cc: " -msgstr "DW: " - -#: ../src/client/composer/composer-widget.vala:1651 -msgid "Bcc: " -msgstr "UDW: " +#. Translators: Human-readable version of the RFC 822 To header +#: src/client/composer/composer-widget.vala:1707 +#: src/client/conversation-viewer/conversation-email.vala:975 +#: src/engine/rfc822/rfc822-utils.vala:298 ui/conversation-message.ui:313 +msgid "To:" +msgstr "Do:" -#: ../src/client/composer/composer-widget.vala:1654 +#. Translators: Human-readable version of the RFC 822 CC header +#: src/client/composer/composer-widget.vala:1713 +#: src/client/conversation-viewer/conversation-email.vala:980 +#: src/engine/rfc822/rfc822-utils.vala:303 ui/conversation-message.ui:358 +msgid "Cc:" +msgstr "DW:" + +#. Translators: Human-readable version of the RFC 822 BCC header +#: src/client/composer/composer-widget.vala:1719 +#: src/client/conversation-viewer/conversation-email.vala:985 +#: ui/conversation-message.ui:403 +msgid "Bcc:" +msgstr "UDW:" + +#. Translators: Human-readable version of the RFC 822 Reply-To header +#: src/client/composer/composer-widget.vala:1725 msgid "Reply-To: " msgstr "Odpowiedź do: " -#: ../src/client/composer/composer-widget.vala:1786 +#: src/client/composer/composer-widget.vala:1865 msgid "Select Color" msgstr "Wybór koloru" -#. Displayed in the From dropdown to indicate an "alternate email address" -#. for an account. The first printf argument will be the alternate email -#. address, and the second will be the account's primary email address. -#: ../src/client/composer/composer-widget.vala:1986 +#. Displayed in the From dropdown to indicate an +#. "alternate email address" for an account. The first +#. printf argument will be the alternate email address, +#. and the second will be the account's primary email +#. address. +#: src/client/composer/composer-widget.vala:2055 #, c-format msgid "%1$s via %2$s" msgstr "%1$s przez %2$s" #. Composer label (with mnemonic underscore) for the account selector #. when choosing what address to send a message from. -#: ../src/client/composer/composer-widget.vala:2028 +#: src/client/composer/composer-widget.vala:2116 msgid "_From:" msgstr "_Od:" #. Translators: This is the name of the file chooser filter #. when inserting an image in the composer. -#: ../src/client/composer/composer-widget.vala:2252 +#: src/client/composer/composer-widget.vala:2344 msgid "Images" msgstr "Obrazy" -#: ../src/client/composer/spell-check-popover.vala:117 +#: src/client/composer/composer-window.vala:14 +msgid "New Message" +msgstr "Nowa wiadomość" + +#: src/client/composer/spell-check-popover.vala:117 msgid "Remove this language from the preferred list" msgstr "Usuwa ten język z listy preferowanych" -#: ../src/client/composer/spell-check-popover.vala:121 +#: src/client/composer/spell-check-popover.vala:121 msgid "Add this language to the preferred list" msgstr "Dodaje ten język do listy preferowanych" -#: ../src/client/composer/spell-check-popover.vala:217 +#: src/client/composer/spell-check-popover.vala:217 msgid "Search for more languages" msgstr "Więcej języków" -#: ../src/client/conversation-list/formatted-conversation-data.vala:11 +#: src/client/conversation-list/conversation-list-view.vala:283 +msgid "Delete conversation" +msgstr "Usuń wątek" + +#: src/client/conversation-list/conversation-list-view.vala:286 +#: ui/main-toolbar-menus.ui:5 +msgid "Mark as _Read" +msgstr "Oznacz jako p_rzeczytane" + +#: src/client/conversation-list/conversation-list-view.vala:289 +#: ui/main-toolbar-menus.ui:9 +msgid "Mark as _Unread" +msgstr "Oznacz jako _nieprzeczytane" + +#: src/client/conversation-list/conversation-list-view.vala:292 +#: ui/main-toolbar-menus.ui:17 +msgid "U_nstar" +msgstr "Usuń wy_różnienie" + +#: src/client/conversation-list/conversation-list-view.vala:294 +#: ui/main-toolbar-menus.ui:13 +msgid "_Star" +msgstr "_Wyróżnij" + +#. Translators: Menu item to reply to a specific message. +#: src/client/conversation-list/conversation-list-view.vala:297 +#: ui/conversation-email-menus.ui:9 +msgid "_Reply" +msgstr "_Odpowiedz" + +#: src/client/conversation-list/conversation-list-view.vala:298 +msgid "R_eply All" +msgstr "O_dpowiedz wszystkim" + +#. Translators: Menu item to forward a specific message. +#: src/client/conversation-list/conversation-list-view.vala:299 +#: ui/conversation-email-menus.ui:21 +msgid "_Forward" +msgstr "_Przekaż" + +#: src/client/conversation-list/formatted-conversation-data.vala:11 msgid "Me" msgstr "Ja" #. Translators: This is the file type displayed for #. attachments with unknown file types. -#: ../src/client/conversation-viewer/conversation-email.vala:124 +#: src/client/conversation-viewer/conversation-email.vala:159 msgid "Unknown" msgstr "Nieznany" -#. Preview headers +#. Translators: Human-readable version of the RFC 822 From header +#: src/client/conversation-viewer/conversation-email.vala:970 +#: src/engine/rfc822/rfc822-utils.vala:289 +msgid "From:" +msgstr "Od:" + +#. Translators: Human-readable version of the RFC 822 Date header +#: src/client/conversation-viewer/conversation-email.vala:990 +#: src/engine/rfc822/rfc822-utils.vala:294 +msgid "Date:" +msgstr "Data:" + +#. Translators: Human-readable version of the RFC 822 Subject header +#: src/client/conversation-viewer/conversation-email.vala:995 +#: src/engine/rfc822/rfc822-utils.vala:292 +msgid "Subject:" +msgstr "Temat:" + +#: src/client/conversation-viewer/conversation-message.vala:65 +msgid "This email address may have been forged" +msgstr "Ten adres e-mail może być fałszywy" + +#. Compact headers #. Translators: This is displayed in place of the from address #. when the message has no from address. -#: ../src/client/conversation-viewer/conversation-message.vala:331 +#: src/client/conversation-viewer/conversation-message.vala:394 msgid "No sender" msgstr "Brak nadawcy" #. Translators: This separates multiple 'from' -#. addresses in the header preview for a message. -#: ../src/client/conversation-viewer/conversation-message.vala:586 +#. addresses in the compact header for a message. +#: src/client/conversation-viewer/conversation-message.vala:767 msgid ", " msgstr ", " #. Translators: This string is used as the HTML IMG ALT #. attribute value when displaying an inline image in an email #. that did not specify a file name. E.g. ImageCannot remove account " -msgstr "Nie można usunąć konta " +#: ui/accounts_editor_list_pane.ui:76 +msgid "Welcome to Geary" +msgstr "Witamy w programie Geary" -#: ../ui/account_cannot_remove.glade.h:2 +#. This title is shown to users when confirming if they want to remove an account. The string substitution is replaced with the account's name. +#: ui/accounts_editor_remove_pane.ui:73 +#, c-format +msgid "Confirm removing: %s" +msgstr "Potwierdzenie usunięcia: %s" + +#: ui/accounts_editor_remove_pane.ui:91 msgid "" -"A composer window associated with this account is currently open. Send or " -"discard the message and try again." +"Removing an account will remove it from Geary and delete locally cached " +"email data from your computer, but not from your service provider." msgstr "" -"Otwarto okno tworzenia wiadomości powiązane z tym kontem. Proszę wysłać " -"wiadomość lub przerwać jej tworzenie i spróbować ponownie." - -#: ../ui/account_list.glade.h:1 -msgid "Add account" -msgstr "Dodaj konto" +"Usunięcie konta spowoduje jego usunięcie z programu Geary oraz usunięcie " +"lokalnie zachowanej poczty z komputera, ale nie ze strony dostawcy." -#: ../ui/account_list.glade.h:2 -msgid "Edit account" -msgstr "Modyfikuj konto" - -#: ../ui/account_list.glade.h:3 +#: ui/accounts_editor_remove_pane.ui:122 msgid "Remove account" -msgstr "Usuń konto" +msgstr "Usunięcie konta" + +#: ui/accounts_editor_servers_pane.ui:17 +msgid "Cancel" +msgstr "Anuluj" -#: ../ui/account_spinner.glade.h:1 -msgid "Please wait while Geary validates your account." -msgstr "Proszę czekać, program Geary sprawdza poprawność konta." +#: ui/accounts_editor_servers_pane.ui:47 +msgid "Apply" +msgstr "Zastosuj" -#: ../ui/certificate_warning_dialog.glade.h:1 +#: ui/certificate_warning_dialog.glade:7 msgid "Untrusted Connection" msgstr "Niezaufane połączenie" -#: ../ui/certificate_warning_dialog.glade.h:2 +#: ui/certificate_warning_dialog.glade:29 msgid "_Always Trust This Server" msgstr "_Zawsze ufaj temu serwerowi" -#: ../ui/certificate_warning_dialog.glade.h:3 +#: ui/certificate_warning_dialog.glade:43 msgid "_Trust This Server" msgstr "Z_aufaj temu serwerowi" -#: ../ui/certificate_warning_dialog.glade.h:4 +#: ui/certificate_warning_dialog.glade:57 msgid "_Don’t Trust This Server" msgstr "_Nie ufaj temu serwerowi" -#: ../ui/composer-headerbar.ui.h:1 +#: ui/composer-headerbar.ui:17 ui/composer-headerbar.ui:174 msgid "Detach (Ctrl+D)" msgstr "Odłącza (Ctrl+D)" -#: ../ui/composer-headerbar.ui.h:2 +#: ui/composer-headerbar.ui:57 ui/composer-headerbar.ui:83 msgid "Attach File (Ctrl+T)" msgstr "Załącza plik (Ctrl+T)" -#: ../ui/composer-headerbar.ui.h:3 +#: ui/composer-headerbar.ui:107 msgid "Include Original Attachments" msgstr "Załącza oryginalne załączniki" -#: ../ui/composer-headerbar.ui.h:4 -msgid "Send (Ctrl+Enter)" -msgstr "Wysyła (Ctrl+Enter)" - -#: ../ui/composer-headerbar.ui.h:5 +#: ui/composer-headerbar.ui:202 msgid "_Send" msgstr "_Wyślij" -#: ../ui/composer-headerbar.ui.h:6 +#: ui/composer-headerbar.ui:207 +msgid "Send (Ctrl+Enter)" +msgstr "Wysyła (Ctrl+Enter)" + +#: ui/composer-headerbar.ui:230 msgid "Discard and Close" msgstr "Odrzuca i zamyka" -#: ../ui/composer-headerbar.ui.h:7 +#: ui/composer-headerbar.ui:254 msgid "Save and Close" msgstr "Zapisuje i zamyka" #. Note that this button and the Update button will never be shown at the same time to the user. -#: ../ui/composer-link-popover.ui.h:2 +#: ui/composer-link-popover.ui:41 msgid "Insert the new link with this URL" msgstr "Wstawia nowy odnośnik o tym adresie URL" -#: ../ui/composer-link-popover.ui.h:3 +#: ui/composer-link-popover.ui:52 msgid "Link URL" msgstr "Adres URL odnośnika" #. Note that this button and the Insert button will never be shown at the same time to the user. -#: ../ui/composer-link-popover.ui.h:5 +#: ui/composer-link-popover.ui:66 msgid "Update this link’s URL" msgstr "Aktualizuje adres URL tego odnośnika" -#: ../ui/composer-link-popover.ui.h:6 +#: ui/composer-link-popover.ui:86 msgid "Delete this link" msgstr "Usuwa ten odnośnik" -#: ../ui/composer-link-popover.ui.h:7 +#: ui/composer-link-popover.ui:106 msgid "Open this link" msgstr "Otwiera ten odnośnik" -#: ../ui/composer-menus.ui.h:1 +#: ui/composer-menus.ui:7 msgid "S_ans Serif" msgstr "_Bezszeryfowa" -#: ../ui/composer-menus.ui.h:2 +#: ui/composer-menus.ui:12 msgid "S_erif" msgstr "Sz_eryfowa" -#: ../ui/composer-menus.ui.h:3 +#: ui/composer-menus.ui:17 msgid "_Fixed Width" msgstr "S_tała szerokość" -#: ../ui/composer-menus.ui.h:4 +#: ui/composer-menus.ui:24 msgid "_Small" msgstr "_Mały" -#: ../ui/composer-menus.ui.h:5 +#: ui/composer-menus.ui:29 msgid "_Medium" msgstr "Ś_redni" -#: ../ui/composer-menus.ui.h:6 +#: ui/composer-menus.ui:34 msgid "Lar_ge" msgstr "_Duży" -#: ../ui/composer-menus.ui.h:7 +#: ui/composer-menus.ui:41 msgid "C_olor" msgstr "K_olor" -#: ../ui/composer-menus.ui.h:8 +#: ui/composer-menus.ui:47 ui/composer-menus.ui:62 msgid "_Rich Text" msgstr "Tekst _sformatowany" -#: ../ui/composer-menus.ui.h:9 +#: ui/composer-menus.ui:53 ui/composer-menus.ui:68 msgid "Show Extended Fields" msgstr "Rozszerzone pola" -#: ../ui/composer-menus.ui.h:10 +#: ui/composer-menus.ui:78 msgid "_Undo" msgstr "Cof_nij" -#: ../ui/composer-menus.ui.h:11 +#: ui/composer-menus.ui:82 msgid "_Redo" msgstr "P_onów" -#: ../ui/composer-menus.ui.h:12 +#: ui/composer-menus.ui:88 ui/composer-menus.ui:106 msgid "Cu_t" msgstr "_Wytnij" -#: ../ui/composer-menus.ui.h:13 ../ui/conversation-message-menus.ui.h:7 +#: ui/composer-menus.ui:92 ui/composer-menus.ui:110 +#: ui/conversation-message-menus.ui:37 msgid "_Copy" msgstr "S_kopiuj" -#: ../ui/composer-menus.ui.h:14 +#: ui/composer-menus.ui:96 ui/composer-menus.ui:114 msgid "_Paste" msgstr "Wk_lej" -#: ../ui/composer-menus.ui.h:15 -msgctxt "Clipboard paste with rich text" -msgid "Paste _With Formatting" -msgstr "Wstaw _z formatowaniem" +#: ui/composer-menus.ui:100 +msgctxt "Clipboard paste as plain text" +msgid "Paste _Without Formatting" +msgstr "Wklej be_z formatowania" -#: ../ui/composer-menus.ui.h:16 +#: ui/composer-menus.ui:120 msgid "Select _All" msgstr "Zaznacz _wszystko" -#: ../ui/composer-menus.ui.h:17 ../ui/conversation-message-menus.ui.h:9 +#: ui/composer-menus.ui:127 ui/conversation-message-menus.ui:49 msgid "_Inspect…" msgstr "Z_badaj…" #. Address(es) e-mail is to be sent to -#: ../ui/composer-widget.ui.h:2 +#: ui/composer-widget.ui:56 msgid "_To" msgstr "_Do" -#: ../ui/composer-widget.ui.h:3 +#: ui/composer-widget.ui:75 msgid "_Cc" msgstr "Do _wiadomości" -#: ../ui/composer-widget.ui.h:4 +#: ui/composer-widget.ui:130 msgid "_Subject" msgstr "_Temat" -#: ../ui/composer-widget.ui.h:5 +#: ui/composer-widget.ui:149 msgid "_Bcc" msgstr "_Ukryty do wiadomości" -#: ../ui/composer-widget.ui.h:6 +#: ui/composer-widget.ui:179 msgid "_Reply-To" msgstr "_Odpowiedź do" #. Geary account mail will be sent from -#: ../ui/composer-widget.ui.h:8 +#: ui/composer-widget.ui:208 msgid "From" msgstr "Od" -#: ../ui/composer-widget.ui.h:9 +#: ui/composer-widget.ui:293 msgid "Drop files here" msgstr "Tutaj można upuścić pliki," -#: ../ui/composer-widget.ui.h:10 +#: ui/composer-widget.ui:309 msgid "To add them as attachments" msgstr "aby dodać je jako załączniki." -#: ../ui/composer-widget.ui.h:11 +#: ui/composer-widget.ui:353 msgid "Undo last edit (Ctrl+Z)" msgstr "Cofa ostatnią modyfikację (Ctrl+Z)" -#: ../ui/composer-widget.ui.h:12 +#: ui/composer-widget.ui:377 msgid "Redo last edit (Ctrl+Shift+Z)" msgstr "Ponawia ostatnią modyfikację (Ctrl+Shift+Z)" -#: ../ui/composer-widget.ui.h:13 +#: ui/composer-widget.ui:415 msgid "Bold (Ctrl+B)" msgstr "Pogrubia tekst (Ctrl+B)" -#: ../ui/composer-widget.ui.h:14 +#: ui/composer-widget.ui:439 msgid "Italic (Ctrl+I)" msgstr "Pochyla tekst (Ctrl+I)" -#: ../ui/composer-widget.ui.h:15 +#: ui/composer-widget.ui:463 msgid "Underline (Ctrl+U)" msgstr "Podkreśla tekst (Ctrl+U)" -#: ../ui/composer-widget.ui.h:16 +#: ui/composer-widget.ui:487 msgid "Strikethrough (Ctrl+K)" msgstr "Przekreśla tekst (Ctrl+K)" -#: ../ui/composer-widget.ui.h:17 +#: ui/composer-widget.ui:525 +msgid "Insert unordered list" +msgstr "Wstawia listę" + +#: ui/composer-widget.ui:549 +msgid "Insert ordered list" +msgstr "Wstawia ponumerowaną listę" + +#: ui/composer-widget.ui:587 msgid "Quote text (Ctrl+])" msgstr "Cytuje tekst (Ctrl+])" -#: ../ui/composer-widget.ui.h:18 +#: ui/composer-widget.ui:611 msgid "Unquote text (Ctrl+[)" msgstr "Usuwa cytat (Ctrl+[)" -#: ../ui/composer-widget.ui.h:19 +#: ui/composer-widget.ui:649 msgid "Insert or update selection link (Ctrl+L)" msgstr "Wstawia lub aktualizuje zaznaczony odnośnik (Ctrl+L)" -#: ../ui/composer-widget.ui.h:20 +#: ui/composer-widget.ui:673 msgid "Insert an image (Ctrl+G)" msgstr "Wstawia obraz (Ctrl+G)" -#: ../ui/composer-widget.ui.h:21 +#: ui/composer-widget.ui:707 msgid "Remove selection formatting (Ctrl+Space)" msgstr "Usuwa formatowanie zaznaczonego tekstu (Ctrl+Spacja)" -#: ../ui/composer-widget.ui.h:22 +#: ui/composer-widget.ui:731 msgid "Select spell checking languages" msgstr "Wybiera język sprawdzania pisowni" -#: ../ui/conversation-email.ui.h:1 +#: ui/conversation-email.ui:27 msgid "Save all attachments" msgstr "Zapisuje wszystkie załączniki" #. Note: The application will never show this button at the same time as unstar_button, one will always be hidden. -#: ../ui/conversation-email.ui.h:3 +#: ui/conversation-email.ui:50 msgid "Mark this message as starred" msgstr "Oznacza tę wiadomość jako wyróżnioną" #. Note: The application will never show this button at the same time as star_button, one will always be hidden. -#: ../ui/conversation-email.ui.h:5 +#: ui/conversation-email.ui:72 msgid "Mark this message as not starred" msgstr "Usuwa wyróżnienie tej wiadomości" -#: ../ui/conversation-email.ui.h:6 +#: ui/conversation-email.ui:95 msgid "Display the message menu" msgstr "Wyświetla menu wiadomości" -#: ../ui/conversation-email.ui.h:7 +#: ui/conversation-email.ui:161 msgid "Open selected attachments" msgstr "Otwiera zaznaczone załączniki" -#: ../ui/conversation-email.ui.h:8 +#: ui/conversation-email.ui:178 msgid "Save selected attachments" msgstr "Zapisuje zaznaczone załączniki" -#: ../ui/conversation-email.ui.h:9 +#: ui/conversation-email.ui:195 msgid "Select all attachments" msgstr "Zaznacza wszystkie załączniki" -#: ../ui/conversation-email.ui.h:10 +#: ui/conversation-email.ui:240 msgid "Edit Draft" msgstr "Modyfikuj szkic" -#: ../ui/conversation-email.ui.h:11 +#: ui/conversation-email.ui:267 msgid "Draft message" msgstr "Szkic wiadomości" -#: ../ui/conversation-email.ui.h:12 +#: ui/conversation-email.ui:283 msgid "This message has not yet been sent." msgstr "Ta wiadomość nie została jeszcze wysłana." -#: ../ui/conversation-email.ui.h:13 -msgid "Try Again" -msgstr "Spróbuj ponownie" - -#: ../ui/conversation-email.ui.h:14 +#: ui/conversation-email.ui:329 msgid "Message not saved" msgstr "Niezapisana wiadomość" -#: ../ui/conversation-email.ui.h:15 +#: ui/conversation-email.ui:345 msgid "This message was sent, but has not been saved to your account." msgstr "Wysłano tę wiadomość, ale nie zapisano jej na koncie." -#: ../ui/conversation-email-menus.ui.h:2 +#. Translators: Menu item to reply to a specific message. +#: ui/conversation-email-menus.ui:15 msgid "Reply to _All" msgstr "Odpowiedz _wszystkim" -#: ../ui/conversation-email-menus.ui.h:4 +#. Translators: Menu item to mark a specific message as +#. read. +#: ui/conversation-email-menus.ui:30 msgid "_Mark Read" msgstr "Oznacz jako p_rzeczytane" -#: ../ui/conversation-email-menus.ui.h:5 +#: ui/conversation-email-menus.ui:36 msgid "_Mark Unread" msgstr "Oznacz jako _nieprzeczytane" -#: ../ui/conversation-email-menus.ui.h:6 +#. Translators: Menu item to mark all messages in a +#. conversation from this one as unread. +#: ui/conversation-email-menus.ui:42 msgid "Mark Unread From _Here" msgstr "Oznacz jako nieprzeczytane od tego _miejsca" -#: ../ui/conversation-email-menus.ui.h:8 +#. Translators: Menu item to move a single, specific message +#. to the trash +#: ui/conversation-email-menus.ui:50 +msgid "_Trash" +msgstr "_Przenieś do kosza" + +#. Translators: Menu item to delete a single, specific message +#: ui/conversation-email-menus.ui:57 +msgid "_Delete…" +msgstr "_Usuń…" + +#. Translators: Menu item to view the source for a message +#: ui/conversation-email-menus.ui:69 msgid "_View Source" msgstr "_Wyświetl źródło" -#: ../ui/conversation-email-menus.ui.h:11 +#: ui/conversation-email-menus.ui:87 msgid "_Save All" msgstr "_Zapisz wszystko" -#: ../ui/conversation-message-menus.ui.h:1 +#: ui/conversation-message-menus.ui:7 msgid "_Open Link" msgstr "_Otwórz odnośnik" -#: ../ui/conversation-message-menus.ui.h:2 +#: ui/conversation-message-menus.ui:11 msgid "Copy Link _Address" msgstr "Skopiuj _adres odnośnika" -#: ../ui/conversation-message-menus.ui.h:3 +#: ui/conversation-message-menus.ui:17 msgid "Send New _Message…" msgstr "_Wyślij nową wiadomość…" -#: ../ui/conversation-message-menus.ui.h:4 +#: ui/conversation-message-menus.ui:21 msgid "Copy Email _Address" msgstr "Skopiuj adres _e-mail" -#: ../ui/conversation-message-menus.ui.h:5 +#: ui/conversation-message-menus.ui:27 msgid "Save _Image As…" msgstr "Zapisz _obraz jako…" -#: ../ui/conversation-message-menus.ui.h:6 +#: ui/conversation-message-menus.ui:33 msgid "_Select All" msgstr "Zaznacz _wszystko" -#: ../ui/conversation-message-menus.ui.h:8 +#: ui/conversation-message-menus.ui:43 msgid "Search for messages from" msgstr "Wyszukaj wiadomości od" -#: ../ui/conversation-message.ui.h:1 +#: ui/conversation-message.ui:64 msgid "From " msgstr "Od " -#: ../ui/conversation-message.ui.h:2 +#: ui/conversation-message.ui:80 ui/conversation-message.ui:179 msgid "1/1/1970\t" msgstr "1/1/1970\t" -#: ../ui/conversation-message.ui.h:3 +#: ui/conversation-message.ui:103 msgid "Preview body text." msgstr "Podgląd treści." -#: ../ui/conversation-message.ui.h:4 +#: ui/conversation-message.ui:203 msgid "Sent by:" msgstr "Wysłane przez:" -#: ../ui/conversation-message.ui.h:5 +#: ui/conversation-message.ui:248 msgid "Reply to:" msgstr "Odpowiedź do:" -#: ../ui/conversation-message.ui.h:6 +#: ui/conversation-message.ui:292 msgid "Subject" msgstr "Temat" -#: ../ui/conversation-message.ui.h:7 -msgid "To:" -msgstr "Do:" - -#: ../ui/conversation-message.ui.h:8 -msgid "Cc:" -msgstr "DW:" - -#: ../ui/conversation-message.ui.h:9 -msgid "Bcc:" -msgstr "UDW:" - -#: ../ui/conversation-message.ui.h:10 +#: ui/conversation-message.ui:502 msgid "Show Images" msgstr "Wyświetl obrazy" -#: ../ui/conversation-message.ui.h:11 +#: ui/conversation-message.ui:515 msgid "Always Show From Sender" msgstr "Zawsze wyświetlaj od nadawcy" -#: ../ui/conversation-message.ui.h:12 +#: ui/conversation-message.ui:543 msgid "Remote images not shown" msgstr "Zdalne obrazy nie są wyświetlane" -#: ../ui/conversation-message.ui.h:13 +#: ui/conversation-message.ui:560 msgid "Only show remote images from senders you trust." msgstr "Wyświetlanie zdalnych obrazów tylko od zaufanych nadawców." -#: ../ui/conversation-message.ui.h:14 +#: ui/conversation-message.ui:693 msgid "But actually goes to:" msgstr "Ale tak naprawdę prowadzi do:" -#: ../ui/conversation-message.ui.h:15 +#: ui/conversation-message.ui:724 msgid "The link appears to go to:" msgstr "Odnośnik wydaje się prowadzić do:" -#: ../ui/conversation-message.ui.h:16 +#: ui/conversation-message.ui:736 msgid "Deceptive link found" msgstr "Odnaleziono odnośnik wprowadzający w błąd" -#: ../ui/conversation-message.ui.h:17 +#: ui/conversation-message.ui:751 msgid "The email sender may be leading you to the wrong web site." msgstr "Nadawca wiadomości może prowadzić do złej strony." -#: ../ui/conversation-message.ui.h:18 +#: ui/conversation-message.ui:764 msgid "If unsure, contact the sender and ask before continuing." msgstr "" "W razie niepewności, należy skontaktować się z nadawcą przed kontynuowaniem." -#: ../ui/conversation-viewer.ui.h:1 +#: ui/conversation-viewer.ui:60 msgid "Find in conversation" msgstr "Wyszukiwanie w wątku" -#: ../ui/conversation-viewer.ui.h:2 +#: ui/conversation-viewer.ui:74 msgid "Find the previous occurrence of the search string." msgstr "Wyszukuje poprzednie wystąpienie tekstu." -#: ../ui/conversation-viewer.ui.h:3 +#: ui/conversation-viewer.ui:95 msgid "Find the next occurrence of the search string." msgstr "Wyszukuje następne wystąpienie tekstu." -#: ../ui/edit_alternate_emails.glade.h:1 -msgid "Remove email address" -msgstr "Usuwa adres e-mail" - -#: ../ui/edit_alternate_emails.glade.h:2 -msgid "" -"Some email services require additional addresses be configured on the " -"server. Contact your email provider for more information." -msgstr "" -"Niektórzy dostawcy usług e-mail wymagają skonfigurowania dodatkowych adresów " -"na serwerze. Proszę skontaktować się ze swoim dostawcą, aby dowiedzieć się " -"więcej." - -#: ../ui/edit_alternate_emails.glade.h:4 -msgid "_Update" -msgstr "Z_aktualizuj" - -#: ../ui/find_bar.glade.h:1 +#: ui/find_bar.glade:66 msgid "Find:" msgstr "Wyszukiwanie:" -#: ../ui/find_bar.glade.h:2 +#: ui/find_bar.glade:89 msgid "_Previous" msgstr "_Poprzednie" -#: ../ui/find_bar.glade.h:3 +#: ui/find_bar.glade:107 msgid "_Next" msgstr "_Następne" -#: ../ui/find_bar.glade.h:4 +#: ui/find_bar.glade:125 msgid "_Case sensitive" msgstr "_Rozróżnianie małych i wielkich liter" -#: ../ui/find_bar.glade.h:5 +#: ui/find_bar.glade:145 msgid "label" msgstr "etykieta" -#: ../ui/gtk/help-overlay.ui.h:1 +#: ui/gtk/help-overlay.ui:9 msgid "Conversation Shortcuts" msgstr "Skróty wątków" -#: ../ui/gtk/help-overlay.ui.h:2 +#: ui/gtk/help-overlay.ui:13 ui/gtk/help-overlay.ui:254 msgctxt "shortcut window" msgid "General" msgstr "Ogólne" -#: ../ui/gtk/help-overlay.ui.h:3 +#: ui/gtk/help-overlay.ui:17 msgctxt "shortcut window" msgid "Move focus to the next/previous pane" msgstr "Przejście do następnego/poprzedniego panelu" -#: ../ui/gtk/help-overlay.ui.h:4 +#: ui/gtk/help-overlay.ui:24 msgctxt "shortcut window" msgid "Move focus to conversation list" msgstr "Przejście do listy wątków" -#: ../ui/gtk/help-overlay.ui.h:5 +#: ui/gtk/help-overlay.ui:31 msgctxt "shortcut window" msgid "Detach composer window" msgstr "Odłączenie okna tworzenia wiadomości" -#: ../ui/gtk/help-overlay.ui.h:6 +#: ui/gtk/help-overlay.ui:38 msgctxt "shortcut window" msgid "Close composer window" msgstr "Zamknięcie okna tworzenia wiadomości" -#: ../ui/gtk/help-overlay.ui.h:7 +#: ui/gtk/help-overlay.ui:45 msgctxt "shortcut window" msgid "Show keyboard shortcuts" msgstr "Wyświetlenie skrótów klawiszowych" -#: ../ui/gtk/help-overlay.ui.h:8 +#: ui/gtk/help-overlay.ui:52 msgctxt "shortcut window" msgid "Show help" msgstr "Wyświetlenie pomocy" -#: ../ui/gtk/help-overlay.ui.h:9 +#: ui/gtk/help-overlay.ui:59 msgctxt "shortcut window" msgid "Quit the application" msgstr "Zakończenie działania programu" -#: ../ui/gtk/help-overlay.ui.h:10 +#: ui/gtk/help-overlay.ui:68 msgctxt "shortcut window" msgid "Search" msgstr "Wyszukiwanie" -#: ../ui/gtk/help-overlay.ui.h:11 +#: ui/gtk/help-overlay.ui:72 msgctxt "shortcut window" msgid "Jump to search box" msgstr "Przejście do pola wyszukiwania" -#: ../ui/gtk/help-overlay.ui.h:12 +#: ui/gtk/help-overlay.ui:79 msgctxt "shortcut window" msgid "Find in current conversation" msgstr "Wyszukiwanie w bieżącym wątku" -#: ../ui/gtk/help-overlay.ui.h:13 +#: ui/gtk/help-overlay.ui:86 msgctxt "shortcut window" msgid "Find next/previous in current conversation" msgstr "Wyszukanie następnego/poprzedniego w bieżącym wątku" -#: ../ui/gtk/help-overlay.ui.h:14 +#: ui/gtk/help-overlay.ui:95 ui/gtk/help-overlay.ui:274 msgctxt "shortcut window" msgid "Actions" msgstr "Działania" -#: ../ui/gtk/help-overlay.ui.h:15 +#: ui/gtk/help-overlay.ui:99 msgctxt "shortcut window" msgid "Compose a new message" msgstr "Utworzenie nowej wiadomości" -#: ../ui/gtk/help-overlay.ui.h:16 +#: ui/gtk/help-overlay.ui:106 msgctxt "shortcut window" msgid "Reply to sender " msgstr "Odpowiedź do nadawcy" -#: ../ui/gtk/help-overlay.ui.h:17 +#: ui/gtk/help-overlay.ui:113 msgctxt "shortcut window" msgid "Reply to all" msgstr "Odpowiedź do wszystkich" -#: ../ui/gtk/help-overlay.ui.h:18 +#: ui/gtk/help-overlay.ui:120 msgctxt "shortcut window" msgid "Forward" msgstr "Przekazanie" -#: ../ui/gtk/help-overlay.ui.h:19 +#: ui/gtk/help-overlay.ui:127 msgctxt "shortcut window" msgid "Archive" msgstr "Przeniesie do archiwum" -#: ../ui/gtk/help-overlay.ui.h:20 +#: ui/gtk/help-overlay.ui:134 msgctxt "shortcut window" msgid "Move to trash" msgstr "Przeniesienie do kosza" -#: ../ui/gtk/help-overlay.ui.h:21 +#: ui/gtk/help-overlay.ui:141 msgctxt "shortcut window" msgid "Toggle spam" msgstr "Przełączenie niechcianej wiadomości" -#: ../ui/gtk/help-overlay.ui.h:22 +#: ui/gtk/help-overlay.ui:148 msgctxt "shortcut window" msgid "Move the conversation" msgstr "Przeniesienie wątku" -#: ../ui/gtk/help-overlay.ui.h:23 +#: ui/gtk/help-overlay.ui:155 msgctxt "shortcut window" msgid "Label the conversation" msgstr "Nadanie wątkowi etykiety" -#: ../ui/gtk/help-overlay.ui.h:24 +#: ui/gtk/help-overlay.ui:163 msgctxt "shortcut window" msgid "Mark read" msgstr "Oznaczenie jako przeczytane" -#: ../ui/gtk/help-overlay.ui.h:25 +#: ui/gtk/help-overlay.ui:170 msgctxt "shortcut window" msgid "Mark unread" msgstr "Oznaczenie jako nieprzeczytane" -#: ../ui/gtk/help-overlay.ui.h:26 +#: ui/gtk/help-overlay.ui:179 msgctxt "shortcut window" msgid "View" msgstr "Widok" -#: ../ui/gtk/help-overlay.ui.h:27 +#: ui/gtk/help-overlay.ui:183 msgctxt "shortcut window" msgid "Zoom in" msgstr "Powiększenie" -#: ../ui/gtk/help-overlay.ui.h:28 +#: ui/gtk/help-overlay.ui:190 msgctxt "shortcut window" msgid "Zoom out" msgstr "Pomniejszenie" -#: ../ui/gtk/help-overlay.ui.h:29 +#: ui/gtk/help-overlay.ui:197 msgctxt "shortcut window" msgid "Reset zoom" msgstr "Przywrócenie powiększenia" -#: ../ui/gtk/help-overlay.ui.h:30 +#: ui/gtk/help-overlay.ui:206 msgctxt "shortcut window" msgid "Additional Shortcuts" msgstr "Dodatkowe skróty" -#: ../ui/gtk/help-overlay.ui.h:31 +#: ui/gtk/help-overlay.ui:210 msgctxt "shortcut window" msgid "Star" msgstr "Wyróżnienie" -#: ../ui/gtk/help-overlay.ui.h:32 +#: ui/gtk/help-overlay.ui:217 msgctxt "shortcut window" msgid "Unstar" msgstr "Usunięcie wyróżnienia" -#: ../ui/gtk/help-overlay.ui.h:33 +#: ui/gtk/help-overlay.ui:224 msgctxt "shortcut window" msgid "Delete" msgstr "Usunięcie" -#: ../ui/gtk/help-overlay.ui.h:34 +#: ui/gtk/help-overlay.ui:231 msgctxt "shortcut window" msgid "Jump to next (older) conversation" msgstr "Przejście do następnego (starszego) wątku" -#: ../ui/gtk/help-overlay.ui.h:35 +#: ui/gtk/help-overlay.ui:238 msgctxt "shortcut window" msgid "Jump to previous (newer) conversation" msgstr "Przejście do poprzedniego (nowszego) wątku" -#: ../ui/gtk/help-overlay.ui.h:36 +#: ui/gtk/help-overlay.ui:250 msgid "Composer Shortcuts" msgstr "Skróty okna tworzenia wiadomości" -#: ../ui/gtk/help-overlay.ui.h:37 +#: ui/gtk/help-overlay.ui:258 msgctxt "shortcut window" msgid "Quote text" msgstr "Cytowanie tekstu" -#: ../ui/gtk/help-overlay.ui.h:38 +#: ui/gtk/help-overlay.ui:265 msgctxt "shortcut window" msgid "Unquote text" msgstr "Usunięcie cytatu" -#: ../ui/gtk/help-overlay.ui.h:39 +#: ui/gtk/help-overlay.ui:278 msgctxt "shortcut window" msgid "Send" msgstr "Wysłanie" -#: ../ui/gtk/help-overlay.ui.h:40 +#: ui/gtk/help-overlay.ui:285 msgctxt "shortcut window" msgid "Add attachment" msgstr "Dodanie załącznika" -#: ../ui/gtk/help-overlay.ui.h:41 +#: ui/gtk/help-overlay.ui:294 msgctxt "shortcut window" msgid "Rich text mode" msgstr "Tryb tekstu sformatowanego" -#: ../ui/gtk/help-overlay.ui.h:42 +#: ui/gtk/help-overlay.ui:298 msgctxt "shortcut window" msgid "Bold text" msgstr "Pogrubienie tekstu" -#: ../ui/gtk/help-overlay.ui.h:43 +#: ui/gtk/help-overlay.ui:305 msgctxt "shortcut window" msgid "Italicize text" msgstr "Pochylenie tekstu" -#: ../ui/gtk/help-overlay.ui.h:44 +#: ui/gtk/help-overlay.ui:312 msgctxt "shortcut window" msgid "Underline text" msgstr "Podkreślenie tekstu" -#: ../ui/gtk/help-overlay.ui.h:45 +#: ui/gtk/help-overlay.ui:319 msgctxt "shortcut window" msgid "Strike text" msgstr "Przekreślenie tekstu" -#: ../ui/gtk/help-overlay.ui.h:46 +#: ui/gtk/help-overlay.ui:326 msgctxt "shortcut window" msgid "Insert a link" msgstr "Wstawienie odnośnika" -#: ../ui/gtk/help-overlay.ui.h:47 +#: ui/gtk/help-overlay.ui:333 msgctxt "shortcut window" msgid "Remove formatting" msgstr "Usunięcie formatowania" -#: ../ui/gtk/menus.ui.h:2 -msgid "A_ccounts" -msgstr "_Konta" +#: ui/main-toolbar.ui:23 +msgctxt "tooltip" +msgid "Compose Message" +msgstr "Tworzy wiadomość" -#: ../ui/gtk/menus.ui.h:4 -msgid "_Keyboard Shortcuts" -msgstr "_Skróty klawiszowe" +#: ui/main-toolbar.ui:52 +msgid "Toggle search bar" +msgstr "Przełącza pasek wyszukiwania" -#: ../ui/login.glade.h:1 -msgid "email@example.com" -msgstr "e-mail@example.com" +#: ui/main-toolbar.ui:112 +msgid "Reply" +msgstr "Odpowiada" -#: ../ui/login.glade.h:2 ../ui/password-dialog.glade.h:3 -msgid "Password" -msgstr "Hasło" +#: ui/main-toolbar.ui:135 +msgid "Reply All" +msgstr "Odpowiada wszystkim" -#: ../ui/login.glade.h:3 -msgid "E_mail address" -msgstr "Adres e-_mail" - -#: ../ui/login.glade.h:4 -msgid "_Password" -msgstr "_Hasło" - -#: ../ui/login.glade.h:5 -msgid "S_ervice" -msgstr "_Usługa" - -#: ../ui/login.glade.h:6 -msgid "N_ame" -msgstr "N_azwa" - -#: ../ui/login.glade.h:8 -msgid "N_ickname" -msgstr "Pseudon_im" - -#: ../ui/login.glade.h:9 -msgid "Work, Home, etc." -msgstr "Praca, dom itp." - -#: ../ui/login.glade.h:10 -msgid "_Save sent mail" -msgstr "_Zapisywanie wysłanych wiadomości" - -#: ../ui/login.glade.h:11 -msgid "Addi_tional email addresses…" -msgstr "Doda_tkowe adresy e-mail…" - -#: ../ui/login.glade.h:12 -msgid "IMAP settings" -msgstr "Ustawienia IMAP" - -#: ../ui/login.glade.h:13 -msgid "Se_rver" -msgstr "Se_rwer" +#: ui/main-toolbar.ui:158 +msgid "Forward" +msgstr "Przekazuje" -#: ../ui/login.glade.h:14 -msgid "imap.example.com" -msgstr "imap.example.com" +#: ui/main-toolbar.ui:264 +msgid "Toggle find bar" +msgstr "Przełącza pasek wyszukiwania" -#: ../ui/login.glade.h:15 -msgid "P_ort" -msgstr "P_ort" +#: ui/main-toolbar.ui:306 +msgid "_Archive" +msgstr "_Archiwizuj" -#: ../ui/login.glade.h:16 -msgid "smtp.example.com" -msgstr "smtp.example.com" +#: ui/main-toolbar-menus.ui:21 +msgid "Mark as S_pam" +msgstr "Oznacz jako _niechciana" + +#: ui/main-toolbar-menus.ui:25 +msgid "Mark as not S_pam" +msgstr "Oznacz jako p_ożądana" + +#: ui/main-toolbar-menus.ui:32 +msgid "Empty _Spam…" +msgstr "Opróżnij katalog _niechcianych wiadomości…" + +#: ui/main-toolbar-menus.ui:36 +msgid "Empty _Trash…" +msgstr "Opróżnij _kosz…" + +#: ui/main-toolbar-menus.ui:42 +msgid "_Accounts" +msgstr "_Konta" + +#: ui/main-toolbar-menus.ui:50 +msgid "_Keyboard Shortcuts" +msgstr "_Skróty klawiszowe" -#: ../ui/login.glade.h:17 -msgid "Ser_ver" -msgstr "Ser_wer" - -#: ../ui/login.glade.h:18 -msgid "Por_t" -msgstr "Por_t" - -#: ../ui/login.glade.h:19 -msgid "SMTP settings" -msgstr "Ustawienia SMTP" - -#: ../ui/login.glade.h:20 -msgid "User_name" -msgstr "_Nazwa użytkownika" - -#: ../ui/login.glade.h:21 -msgid "Pass_word" -msgstr "Ha_sło" - -#: ../ui/login.glade.h:22 -msgid "SMTP username" -msgstr "Nazwa użytkownika SMTP" - -#: ../ui/login.glade.h:23 -msgid "SMTP password" -msgstr "Hasło SMTP" - -#: ../ui/login.glade.h:24 -msgid "_Username" -msgstr "Nazwa _użytkownika" - -#: ../ui/login.glade.h:25 -msgid "IMAP username" -msgstr "Nazwa użytkownika IMAP" - -#: ../ui/login.glade.h:26 -msgid "IMAP password" -msgstr "Hasło IMAP" - -#: ../ui/login.glade.h:27 -msgid "Encr_yption" -msgstr "_Szyfrowanie" - -#: ../ui/login.glade.h:28 -msgid "Encrypt_ion" -msgstr "S_zyfrowanie" - -#: ../ui/login.glade.h:30 -msgid "SSL/TLS" -msgstr "SSL/TLS" - -#: ../ui/login.glade.h:31 -msgid "STARTTLS" -msgstr "STARTTLS" - -#: ../ui/login.glade.h:32 -msgid "No authentication re_quired" -msgstr "Bez _wymagania uwierzytelniania" - -#: ../ui/login.glade.h:33 -msgid "Use IMAP cre_dentials" -msgstr "Używanie _danych logowania IMAP" - -#: ../ui/login.glade.h:34 -msgid "Composer" -msgstr "Tworzenie" - -#: ../ui/login.glade.h:35 -msgid "Save dra_fts on server" -msgstr "Zapisywanie sz_kiców na serwerze" - -#: ../ui/login.glade.h:36 -msgid "Si_gn emails (HTML allowed):" -msgstr "_Podpisywanie wiadomości (HTML jest dozwolony):" - -#: ../ui/login.glade.h:37 -msgid "Storage" -msgstr "Przechowywanie" - -#: ../ui/login.glade.h:38 -msgid "_Download mail" -msgstr "_Pobieranie wiadomości" - -#: ../ui/main-toolbar.ui.h:1 -msgid "Empty Spam or Trash folders" -msgstr "Opróżnij katalog niechcianych wiadomości lub kosz" +#: ui/main-toolbar-menus.ui:61 +msgid "_About Geary" +msgstr "_O programie" + +#. Infobar title when one or more accounts are offline +#: ui/main-window.ui:183 +msgid "Working offline" +msgstr "Tryb offline" + +#. Label and tooltip for offline infobar +#: ui/main-window.ui:197 +msgid "" +"Your computer does not appear to be connected to the Internet.\n" +"You will not be able to send or receive email until it is re-connected." +msgstr "" +"Komputer nie jest połączony z Internetem.\n" +"Nie można wysyłać ani odbierać poczty, dopóki nie zostanie połączony." + +#. Label and tooltip for offline infobar +#: ui/main-window.ui:200 +msgid "You will not be able to send or receive email until re-connected." +msgstr "" +"Nie można wysyłać ani odbierać poczty, dopóki komputer nie zostanie " +"połączony." + +#. Button label for displaying technical details about an account problem +#. Dialog title for displaying technical details of a problem. Same as the button that invokes it. +#: ui/main-window.ui:247 ui/problem-details-dialog.ui:13 +msgid "Details" +msgstr "Informacje" + +#. Button label for retrying an account problem +#: ui/main-window.ui:261 +msgid "Retry" +msgstr "Ponów" + +#. Infobar title when one or more accounts have encounted an error +#: ui/main-window.ui:294 +msgid "Account problem" +msgstr "Problem z kontem" + +#. Label and tooltip for account service problem infobar +#: ui/main-window.ui:308 +msgid "" +"Geary encountered a problem connecting to an account.\n" +"Please check your Internet connection, the server configuration and try " +"again." +msgstr "" +"Wystąpił problem podczas łączenia z kontem.\n" +"Proszę sprawdzić połączenie z Internetem, konfigurację serwera, a następnie " +"spróbować ponownie." + +#. Label and tooltip for account service problem infobar +#: ui/main-window.ui:311 +msgid "Geary encountered a problem connecting to an account." +msgstr "Wystąpił problem podczas łączenia z kontem." + +#. Button label for retrying TLS cert validation +#: ui/main-window.ui:358 +msgid "Check" +msgstr "Sprawdź poprawność" + +#. Button tooltip for retrying TLS cert validation +#: ui/main-window.ui:362 +msgid "Check the security details for the connection" +msgstr "Sprawdza poprawność informacji o zabezpieczeniach połączenia" + +#. Infobar title when one or more accounts have a TLS cert validation error +#: ui/main-window.ui:391 +msgid "Security problem" +msgstr "Problem zabezpieczeń" + +#. Label and tooltip for TLS cert validation error infobar +#: ui/main-window.ui:405 +msgid "" +"An account has reported an untrusted server.\n" +"Please check the server configuration and try again." +msgstr "" +"Konto zgłosiło niezaufany serwer.\n" +"Proszę sprawdzić poprawność konfiguracji serwera i spróbować ponownie." + +#. Label and tooltip for TLS cert validation error infobar +#: ui/main-window.ui:408 +msgid "An account has reported an untrusted server." +msgstr "Konto zgłosiło niezaufany serwer." + +#. Button tooltip for retrying when a login error has occurred +#: ui/main-window.ui:459 +msgid "Retry login, you will be prompted for your password" +msgstr "Ponawia logowanie, zostanie wyświetlona prośba o hasło" + +#. Infobar title when one or more accounts have a login error +#: ui/main-window.ui:488 +msgid "Login problem" +msgstr "Problem z logowaniem" + +#. Label and tooltip for authentication problem infobar +#: ui/main-window.ui:502 +msgid "" +"An account has reported an incorrect login or password.\n" +"Please check your login name and try again." +msgstr "" +"Konto zgłosiło niewłaściwy login lub hasło.\n" +"Proszę sprawdzić poprawność loginu i spróbować ponownie." + +#. Label and tooltip for authentication problem infobar +#: ui/main-window.ui:505 +msgid "An account has reported an incorrect login or password." +msgstr "Konto zgłosiło niewłaściwy login lub hasło." -#: ../ui/password-dialog.glade.h:1 +#: ui/password-dialog.glade:74 msgid "SMTP Credentials" msgstr "Dane logowania SMTP" -#: ../ui/password-dialog.glade.h:2 +#: ui/password-dialog.glade:91 msgid "Username" msgstr "Nazwa użytkownika" -#: ../ui/password-dialog.glade.h:4 +#: ui/password-dialog.glade:152 msgid "_Remember password" msgstr "_Zapamiętanie hasła" -#: ../ui/password-dialog.glade.h:6 +#: ui/password-dialog.glade:210 msgid "_Authenticate" msgstr "_Uwierzytelnij" -#: ../ui/preferences-dialog.ui.h:1 +#: ui/preferences-dialog.ui:38 msgid "Reading" msgstr "Czytanie" -#: ../ui/preferences-dialog.ui.h:2 +#: ui/preferences-dialog.ui:51 msgid "_Automatically select next message" msgstr "_Automatyczne wybieranie następnej wiadomości" -#: ../ui/preferences-dialog.ui.h:3 +#: ui/preferences-dialog.ui:70 msgid "_Display conversation preview" msgstr "P_odgląd wątku" -#: ../ui/preferences-dialog.ui.h:4 +#: ui/preferences-dialog.ui:89 msgid "Use _three pane view" msgstr "Widok _trzech paneli" -#: ../ui/preferences-dialog.ui.h:5 +#: ui/preferences-dialog.ui:113 msgid "Notifications" msgstr "Powiadomienia" -#: ../ui/preferences-dialog.ui.h:6 +#: ui/preferences-dialog.ui:126 msgid "_Play notification sounds" msgstr "_Dźwięki powiadomień" -#: ../ui/preferences-dialog.ui.h:7 +#: ui/preferences-dialog.ui:145 msgid "Show _notifications for new mail" msgstr "_Powiadomienia o nowych wiadomościach" -#: ../ui/preferences-dialog.ui.h:8 -msgid "Always _watch for new mail" -msgstr "_Monitorowanie nowych wiadomości" - -#: ../ui/preferences-dialog.ui.h:9 -msgid "Geary will run in the background and notify of new mail" -msgstr "" -"Program Geary będzie działał w tle i powiadamiał o nowych wiadomościach" +#: ui/preferences-dialog.ui:164 +msgid "_Watch for new mail when closed" +msgstr "_Monitorowanie nowych wiadomości po zamknięciu" + +#: ui/preferences-dialog.ui:168 +msgid "Geary will keep running after all windows are closed" +msgstr "Program Geary będzie nadal działał po zamknięciu wszystkich okien" -#: ../ui/preferences-dialog.ui.h:10 +#: ui/preferences-dialog.ui:195 msgid "Preferences" msgstr "Preferencje" -#: ../ui/remove_confirm.glade.h:1 +#. Button label for copying technical information to the clipboard +#: ui/problem-details-dialog.ui:17 +msgid "Copy to Clipboard" +msgstr "Skopiuj do schowka" + +#. Button tooltip for copying technical information to the clipboard +#: ui/problem-details-dialog.ui:21 msgid "" -"Are you sure you want to remove this " -"account? " +"Copy technical details to clipboard for pasting into an email or bug report" msgstr "" -"Na pewno usunąć to konto? " +"Kopiuje informacje techniczne do schowka, aby można je było wkleić do " +"wiadomości e-mail lub zgłoszenia błędu" -#: ../ui/remove_confirm.glade.h:2 +#: ui/problem-details-dialog.ui:73 msgid "" -"All email associated with this account will be removed from your computer. " -"This will not affect email on the server." +"If the problem is serious or persists, please copy and send these details to " +"the mailing list " +"or file a new " +"bug report." msgstr "" -"Cała poczta powiązana z tym kontem zostanie usunięta z komputera. Poczta na " -"serwerze nie zostanie usunięta." - -#: ../ui/remove_confirm.glade.h:3 -msgid "Nickname:" -msgstr "Etykieta:" +"Jeśli problem jest poważny lub się powtarza, to proszę skopiować i wysłać te " +"informacje na listę " +"dyskusyjną lub zgłosić błąd (w języku angielskim)." -#: ../ui/remove_confirm.glade.h:4 -msgid "Email address:" -msgstr "Adres e-mail:" +#: ui/problem-details-dialog.ui:89 +msgid "Details:" +msgstr "Informacje:" -#: ../ui/upgrade_dialog.glade.h:1 +#: ui/upgrade_dialog.glade:60 msgid "Geary update in progress…" msgstr "Trwa aktualizacja programu Geary…" diff -Nru geary-0.12.4/po/POTFILES.in geary-3.32.0/po/POTFILES.in --- geary-0.12.4/po/POTFILES.in 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/po/POTFILES.in 2019-03-17 13:39:29.000000000 +0000 @@ -1,34 +1,39 @@ -[encoding: UTF-8] +# List of source files containing translatable strings. +# Please keep this file sorted alphabetically. +desktop/geary-attach.contract.desktop.in +desktop/geary-autostart.desktop.in desktop/org.gnome.Geary.appdata.xml.in desktop/org.gnome.Geary.desktop.in -desktop/geary-autostart.desktop.in -[type: gettext/ini]desktop/geary-attach.contract.in +desktop/org.gnome.Geary.gschema.xml src/geary-version.vala.in -src/client/accounts/account-dialog-account-list-pane.vala -src/client/accounts/account-dialog-add-edit-pane.vala -src/client/accounts/account-dialog-edit-alternate-emails-pane.vala -src/client/accounts/account-dialog-pane.vala -src/client/accounts/account-dialog-remove-confirm-pane.vala -src/client/accounts/account-dialog-remove-fail-pane.vala -src/client/accounts/account-dialog-spinner-pane.vala -src/client/accounts/account-dialog.vala -src/client/accounts/account-spinner-page.vala -src/client/accounts/add-edit-page.vala -src/client/accounts/login-dialog.vala +src/client/accounts/accounts-editor.vala +src/client/accounts/accounts-editor-add-pane.vala +src/client/accounts/accounts-editor-edit-pane.vala +src/client/accounts/accounts-editor-list-pane.vala +src/client/accounts/accounts-editor-remove-pane.vala +src/client/accounts/accounts-editor-row.vala +src/client/accounts/accounts-editor-servers-pane.vala +src/client/accounts/accounts-manager.vala +src/client/accounts/accounts-signature-web-view.vala +src/client/application/application-avatar-store.vala +src/client/application/application-certificate-manager.vala +src/client/application/application-command.vala src/client/application/autostart-manager.vala src/client/application/geary-application.vala src/client/application/geary-args.vala -src/client/application/geary-config.vala src/client/application/geary-controller.vala +src/client/application/goa-mediator.vala src/client/application/main.vala src/client/application/secret-mediator.vala src/client/components/client-web-view.vala +src/client/components/components-placeholder-pane.vala +src/client/components/components-validator.vala src/client/components/count-badge.vala -src/client/components/empty-placeholder.vala src/client/components/folder-popover.vala src/client/components/icon-factory.vala src/client/components/main-toolbar.vala src/client/components/main-window.vala +src/client/components/main-window-info-bar.vala src/client/components/monitored-progress-bar.vala src/client/components/monitored-spinner.vala src/client/components/search-bar.vala @@ -44,6 +49,7 @@ src/client/composer/composer-window.vala src/client/composer/contact-entry-completion.vala src/client/composer/contact-list-store.vala +src/client/composer/contact-list-store-cache.vala src/client/composer/email-entry.vala src/client/composer/spell-check-popover.vala src/client/conversation-list/conversation-list-cell-renderer.vala @@ -58,6 +64,7 @@ src/client/dialogs/alert-dialog.vala src/client/dialogs/attachment-dialog.vala src/client/dialogs/certificate-warning-dialog.vala +src/client/dialogs/dialogs-problem-details-dialog.vala src/client/dialogs/password-dialog.vala src/client/dialogs/preferences-dialog.vala src/client/dialogs/upgrade-dialog.vala @@ -69,6 +76,7 @@ src/client/folder-list/folder-list-search-branch.vala src/client/folder-list/folder-list-special-grouping.vala src/client/folder-list/folder-list-tree.vala +src/client/notification/in-app-notification.vala src/client/notification/libmessagingmenu.vala src/client/notification/libnotify.vala src/client/notification/new-messages-indicator.vala @@ -80,11 +88,11 @@ src/client/sidebar/sidebar-count-cell-renderer.vala src/client/sidebar/sidebar-entry.vala src/client/sidebar/sidebar-tree.vala +src/client/util/util-avatar.vala src/client/util/util-date.vala src/client/util/util-email.vala src/client/util/util-files.vala src/client/util/util-gio.vala -src/client/util/util-gravatar.vala src/client/util/util-gtk.vala src/client/util/util-international.vala src/client/util/util-migrate.vala @@ -98,6 +106,7 @@ src/engine/api/geary-aggregated-folder-properties.vala src/engine/api/geary-attachment.vala src/engine/api/geary-base-object.vala +src/engine/api/geary-client-service.vala src/engine/api/geary-composed-email.vala src/engine/api/geary-contact-flags.vala src/engine/api/geary-contact-importance.vala @@ -106,6 +115,7 @@ src/engine/api/geary-credentials-mediator.vala src/engine/api/geary-credentials.vala src/engine/api/geary-email-flags.vala +src/engine/api/geary-email-header-set.vala src/engine/api/geary-email-identifier.vala src/engine/api/geary-email-properties.vala src/engine/api/geary-email.vala @@ -125,12 +135,13 @@ src/engine/api/geary-logging.vala src/engine/api/geary-named-flag.vala src/engine/api/geary-named-flags.vala +src/engine/api/geary-problem-report.vala src/engine/api/geary-progress-monitor.vala src/engine/api/geary-revokable.vala src/engine/api/geary-search-folder.vala src/engine/api/geary-search-query.vala +src/engine/api/geary-service-information.vala src/engine/api/geary-service-provider.vala -src/engine/api/geary-service.vala src/engine/api/geary-special-folder-type.vala src/engine/app/app-conversation-monitor.vala src/engine/app/app-conversation.vala @@ -142,7 +153,7 @@ src/engine/app/conversation-monitor/app-conversation-set.vala src/engine/app/conversation-monitor/app-external-append-operation.vala src/engine/app/conversation-monitor/app-fill-window-operation.vala -src/engine/app/conversation-monitor/app-local-load-operation.vala +src/engine/app/conversation-monitor/app-insert-operation.vala src/engine/app/conversation-monitor/app-local-search-operation.vala src/engine/app/conversation-monitor/app-remove-operation.vala src/engine/app/conversation-monitor/app-reseed-operation.vala @@ -153,6 +164,7 @@ src/engine/app/email-store/app-list-operation.vala src/engine/app/email-store/app-mark-operation.vala src/engine/common/common-message-data.vala +src/engine/db/db.vala src/engine/db/db-connection.vala src/engine/db/db-context.vala src/engine/db/db-database-error.vala @@ -164,15 +176,17 @@ src/engine/db/db-transaction-outcome.vala src/engine/db/db-transaction-type.vala src/engine/db/db-versioned-database.vala -src/engine/db/db.vala src/engine/imap/imap.vala src/engine/imap/imap-error.vala -src/engine/imap/api/imap-account.vala +src/engine/imap/api/imap-account-session.vala +src/engine/imap/api/imap-client-service.vala src/engine/imap/api/imap-email-flags.vala src/engine/imap/api/imap-email-properties.vala +src/engine/imap/api/imap-folder.vala src/engine/imap/api/imap-folder-properties.vala src/engine/imap/api/imap-folder-root.vala -src/engine/imap/api/imap-folder.vala +src/engine/imap/api/imap-folder-session.vala +src/engine/imap/api/imap-session-object.vala src/engine/imap/command/imap-append-command.vala src/engine/imap/command/imap-capability-command.vala src/engine/imap/command/imap-close-command.vala @@ -190,6 +204,7 @@ src/engine/imap/command/imap-login-command.vala src/engine/imap/command/imap-logout-command.vala src/engine/imap/command/imap-message-set.vala +src/engine/imap/command/imap-namespace-command.vala src/engine/imap/command/imap-noop-command.vala src/engine/imap/command/imap-search-command.vala src/engine/imap/command/imap-search-criteria.vala @@ -207,14 +222,8 @@ src/engine/imap-db/imap-db-gc.vala src/engine/imap-db/imap-db-message-addresses.vala src/engine/imap-db/imap-db-message-row.vala -src/engine/imap-db/outbox/smtp-outbox-email-identifier.vala -src/engine/imap-db/outbox/smtp-outbox-email-properties.vala -src/engine/imap-db/outbox/smtp-outbox-folder-properties.vala -src/engine/imap-db/outbox/smtp-outbox-folder-root.vala -src/engine/imap-db/outbox/smtp-outbox-folder.vala src/engine/imap-db/search/imap-db-search-email-identifier.vala src/engine/imap-db/search/imap-db-search-folder-properties.vala -src/engine/imap-db/search/imap-db-search-folder-root.vala src/engine/imap-db/search/imap-db-search-folder.vala src/engine/imap-db/search/imap-db-search-query.vala src/engine/imap-db/search/imap-db-search-term.vala @@ -224,10 +233,10 @@ src/engine/imap-engine/gmail/imap-engine-gmail-folder.vala src/engine/imap-engine/gmail/imap-engine-gmail-search-folder.vala src/engine/imap-engine/gmail/imap-engine-gmail-spam-trash-folder.vala +src/engine/imap-engine/imap-engine-account-operation.vala +src/engine/imap-engine/imap-engine-account-processor.vala src/engine/imap-engine/imap-engine-account-synchronizer.vala -src/engine/imap-engine/imap-engine-batch-operations.vala src/engine/imap-engine/imap-engine-contact-store.vala -src/engine/imap-engine/imap-engine-email-flag-watcher.vala src/engine/imap-engine/imap-engine-email-prefetcher.vala src/engine/imap-engine/imap-engine-generic-account.vala src/engine/imap-engine/imap-engine-generic-folder.vala @@ -256,8 +265,8 @@ src/engine/imap-engine/replay-ops/imap-engine-move-email-revoke.vala src/engine/imap-engine/replay-ops/imap-engine-remove-email.vala src/engine/imap-engine/replay-ops/imap-engine-replay-append.vala -src/engine/imap-engine/replay-ops/imap-engine-replay-disconnect.vala src/engine/imap-engine/replay-ops/imap-engine-replay-removal.vala +src/engine/imap-engine/replay-ops/imap-engine-replay-update.vala src/engine/imap-engine/replay-ops/imap-engine-server-search-email.vala src/engine/imap-engine/replay-ops/imap-engine-user-close.vala src/engine/imap-engine/yahoo/imap-engine-yahoo-account.vala @@ -273,11 +282,12 @@ src/engine/imap/message/imap-message-data.vala src/engine/imap/message/imap-message-flag.vala src/engine/imap/message/imap-message-flags.vala +src/engine/imap/message/imap-namespace.vala src/engine/imap/message/imap-sequence-number.vala src/engine/imap/message/imap-status-data-type.vala src/engine/imap/message/imap-tag.vala -src/engine/imap/message/imap-uid-validity.vala src/engine/imap/message/imap-uid.vala +src/engine/imap/message/imap-uid-validity.vala src/engine/imap/parameter/imap-atom-parameter.vala src/engine/imap/parameter/imap-list-parameter.vala src/engine/imap/parameter/imap-literal-parameter.vala @@ -295,6 +305,7 @@ src/engine/imap/response/imap-mailbox-attribute.vala src/engine/imap/response/imap-mailbox-attributes.vala src/engine/imap/response/imap-mailbox-information.vala +src/engine/imap/response/imap-namespace-response.vala src/engine/imap/response/imap-response-code-type.vala src/engine/imap/response/imap-response-code.vala src/engine/imap/response/imap-server-data-type.vala @@ -304,7 +315,6 @@ src/engine/imap/response/imap-status-response.vala src/engine/imap/response/imap-status.vala src/engine/imap/transport/imap-client-connection.vala -src/engine/imap/transport/imap-client-session-manager.vala src/engine/imap/transport/imap-client-session.vala src/engine/imap/transport/imap-deserializer.vala src/engine/imap/transport/imap-serializer.vala @@ -325,15 +335,19 @@ src/engine/mime/mime-disposition-type.vala src/engine/mime/mime-error.vala src/engine/mime/mime-multipart-subtype.vala -src/engine/nonblocking/nonblocking-abstract-semaphore.vala src/engine/nonblocking/nonblocking-batch.vala src/engine/nonblocking/nonblocking-concurrent.vala src/engine/nonblocking/nonblocking-counting-semaphore.vala src/engine/nonblocking/nonblocking-error.vala -src/engine/nonblocking/nonblocking-mailbox.vala +src/engine/nonblocking/nonblocking-lock.vala src/engine/nonblocking/nonblocking-mutex.vala +src/engine/nonblocking/nonblocking-queue.vala src/engine/nonblocking/nonblocking-reporting-semaphore.vala src/engine/nonblocking/nonblocking-variants.vala +src/engine/outbox/outbox-email-identifier.vala +src/engine/outbox/outbox-email-properties.vala +src/engine/outbox/outbox-folder-properties.vala +src/engine/outbox/outbox-folder.vala src/engine/rfc822/rfc822-error.vala src/engine/rfc822/rfc822-gmime-filter-blockquotes.vala src/engine/rfc822/rfc822-gmime-filter-flowed.vala @@ -342,17 +356,20 @@ src/engine/rfc822/rfc822-mailbox-addresses.vala src/engine/rfc822/rfc822-message-data.vala src/engine/rfc822/rfc822-message.vala +src/engine/rfc822/rfc822-part.vala src/engine/rfc822/rfc822-utils.vala src/engine/rfc822/rfc822.vala src/engine/smtp/smtp-authenticator.vala src/engine/smtp/smtp-capabilities.vala src/engine/smtp/smtp-client-connection.vala +src/engine/smtp/smtp-client-service.vala src/engine/smtp/smtp-client-session.vala src/engine/smtp/smtp-command.vala src/engine/smtp/smtp-data-format.vala src/engine/smtp/smtp-error.vala src/engine/smtp/smtp-greeting.vala src/engine/smtp/smtp-login-authenticator.vala +src/engine/smtp/smtp-oauth2-authenticator.vala src/engine/smtp/smtp-plain-authenticator.vala src/engine/smtp/smtp-request.vala src/engine/smtp/smtp-response-code.vala @@ -363,7 +380,9 @@ src/engine/state/state-mapping.vala src/engine/util/util-ascii.vala src/engine/util/util-collection.vala -src/engine/util/util-converter.vala +src/engine/util/util-config-file.vala +src/engine/util/util-connectivity-manager.vala +src/engine/util/util-error-context.vala src/engine/util/util-files.vala src/engine/util/util-generic-capabilities.vala src/engine/util/util-html.vala @@ -383,33 +402,33 @@ src/engine/util/util-timeout-manager.vala src/engine/util/util-trillian.vala src/mailer/main.vala -[type: gettext/glade]ui/accelerators.ui -[type: gettext/glade]ui/account_cannot_remove.glade -[type: gettext/glade]ui/account_list.glade -[type: gettext/glade]ui/account_spinner.glade -[type: gettext/glade]ui/certificate_warning_dialog.glade -[type: gettext/glade]ui/composer-headerbar.ui -[type: gettext/glade]ui/composer-link-popover.ui -[type: gettext/glade]ui/composer-menus.ui -[type: gettext/glade]ui/composer-widget.ui -[type: gettext/glade]ui/conversation-email.ui -[type: gettext/glade]ui/conversation-email-attachment-view.ui -[type: gettext/glade]ui/conversation-email-menus.ui -[type: gettext/glade]ui/conversation-message-menus.ui -[type: gettext/glade]ui/conversation-message.ui -[type: gettext/glade]ui/conversation-viewer.ui -[type: gettext/glade]ui/edit_alternate_emails.glade -[type: gettext/glade]ui/empty-placeholder.ui -[type: gettext/glade]ui/find_bar.glade -[type: gettext/glade]ui/folder-popover.ui -[type: gettext/glade]ui/gtk/help-overlay.ui -[type: gettext/glade]ui/gtk/menus.ui -[type: gettext/glade]ui/login.glade -[type: gettext/glade]ui/main-toolbar.ui -[type: gettext/glade]ui/main-window.ui -[type: gettext/glade]ui/password-dialog.glade -[type: gettext/glade]ui/preferences-dialog.ui -[type: gettext/glade]ui/remove_confirm.glade -[type: gettext/glade]ui/toolbar_empty_menu.ui -[type: gettext/glade]ui/toolbar_mark_menu.ui -[type: gettext/glade]ui/upgrade_dialog.glade +ui/accounts_editor.ui +ui/accounts_editor_add_pane.ui +ui/accounts_editor_edit_pane.ui +ui/accounts_editor_list_pane.ui +ui/accounts_editor_remove_pane.ui +ui/accounts_editor_servers_pane.ui +ui/certificate_warning_dialog.glade +ui/composer-headerbar.ui +ui/composer-link-popover.ui +ui/composer-menus.ui +ui/composer-widget.ui +ui/components-placeholder-pane.ui +ui/conversation-email.ui +ui/conversation-email-attachment-view.ui +ui/conversation-email-menus.ui +ui/conversation-message-menus.ui +ui/conversation-message.ui +ui/conversation-viewer.ui +ui/find_bar.glade +ui/folder-popover.ui +ui/gtk/help-overlay.ui +ui/in-app-notification.ui +ui/main-toolbar.ui +ui/main-toolbar-menus.ui +ui/main-window.ui +ui/main-window-info-bar.ui +ui/password-dialog.glade +ui/preferences-dialog.ui +ui/problem-details-dialog.ui +ui/upgrade_dialog.glade diff -Nru geary-0.12.4/po/POTFILES.skip geary-3.32.0/po/POTFILES.skip --- geary-0.12.4/po/POTFILES.skip 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/po/POTFILES.skip 2019-03-17 13:39:29.000000000 +0000 @@ -1 +1,3 @@ +# List of source files that should *not* be translated. +# Please keep this file sorted alphabetically. build diff -Nru geary-0.12.4/po/pt_BR.po geary-3.32.0/po/pt_BR.po --- geary-0.12.4/po/pt_BR.po 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/po/pt_BR.po 2019-03-17 13:39:29.000000000 +0000 @@ -1,5 +1,5 @@ # Brazilian Portuguese translation for geary. -# Copyright 2017 Software Freedom Conservancy Inc. +# Copyright 2019 Software Freedom Conservancy Inc. # This file is distributed under the same license as the geary package. # Translators (Transifex): # leonardolemos , 2012 @@ -11,47 +11,76 @@ # José Agnaldo Jr. , 2013 # Translators (GNOME): # Enrico Nicoletto , 2014, 2016. -# Rafael Fontenelle , 2014, 2015, 2016, 2017. # Freire de Almeida , 2016. # Ronan Arraes Jardim Chagas , 2016. +# Isaac Ferreira Filho , 2018. +# Rafael Fontenelle , 2014-2019. +# msgid "" msgstr "" "Project-Id-Version: geary\n" -"Report-Msgid-Bugs-To: https://bugzilla.gnome.org/enter_bug.cgi?product=geary&" -"keywords=I18N+L10N&component=internationalization\n" -"POT-Creation-Date: 2017-10-02 14:41+0000\n" -"PO-Revision-Date: 2017-10-07 03:52-0200\n" +"Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/geary/issues\n" +"POT-Creation-Date: 2019-02-24 16:03+0000\n" +"PO-Revision-Date: 2019-03-06 17:26-0300\n" "Last-Translator: Rafael Fontenelle \n" -"Language-Team: Brazilian Portuguese \n" +"Language-Team: Portuguese - Brazil \n" "Language: pt_BR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n > 1);\n" -"X-Generator: Virtaal 1.0.0-beta1\n" +"Plural-Forms: nplurals=2; plural=(n > 1)\n" +"X-Generator: Gtranslator 3.31.90\n" "X-Project-Style: gnome\n" +#: desktop/geary-attach.contract.desktop.in:3 +msgid "Send by email" +msgstr "Enviar por e-mail" + +#. Translators: Do NOT translate or transliterate this text (this is an icon file name)! +#: desktop/geary-attach.contract.desktop.in:5 +msgid "mail-send" +msgstr "mail-send" + +#: desktop/geary-attach.contract.desktop.in:6 +msgid "Send files using Geary" +msgstr "Envia arquivos usando Geary" + #. Translators: The application name -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:2 -#: ../desktop/org.gnome.Geary.desktop.in.h:1 -#: ../desktop/geary-autostart.desktop.in.h:1 +#: desktop/geary-autostart.desktop.in:3 +#: desktop/org.gnome.Geary.appdata.xml.in:11 +#: desktop/org.gnome.Geary.desktop.in:3 +#: src/client/accounts/accounts-editor-servers-pane.vala:555 msgid "Geary" msgstr "Geary" -#. Translators: The development team's name -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:4 -msgid "Geary Development Team" -msgstr "Equipe de Desenvolvimento do Geary" +#: desktop/geary-autostart.desktop.in:4 desktop/org.gnome.Geary.desktop.in:4 +msgid "Email" +msgstr "E-mail" #. Translators: The application's summary / tagline -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:6 -#: ../desktop/org.gnome.Geary.desktop.in.h:4 -#: ../desktop/geary-autostart.desktop.in.h:4 -#: ../src/client/application/geary-application.vala:21 +#: desktop/geary-autostart.desktop.in:5 +#: desktop/org.gnome.Geary.appdata.xml.in:15 +#: desktop/org.gnome.Geary.desktop.in:5 +#: src/client/application/geary-application.vala:23 msgid "Send and receive email" msgstr "Envie e receba e-mail" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:7 +#. Translators: These are desktop search terms. Do not translate semicolons, end line with a semicolon. +#: desktop/geary-autostart.desktop.in:7 +msgid "Email;E-mail;Mail;" +msgstr "Email;E-mail;Mail;Correio eletrônico;e-mails;mensagens;" + +#. Translators: Do NOT translate or transliterate this text (this is an icon file name)! +#: desktop/geary-autostart.desktop.in:9 desktop/org.gnome.Geary.desktop.in:9 +msgid "org.gnome.Geary" +msgstr "org.gnome.Geary" + +#. Translators: The development team's name +#: desktop/org.gnome.Geary.appdata.xml.in:13 +msgid "Geary Development Team" +msgstr "Equipe de Desenvolvimento do Geary" + +#: desktop/org.gnome.Geary.appdata.xml.in:17 msgid "" "Geary is an email application built around conversations, for the GNOME 3 " "desktop. It allows you to read, find and send email with a straightforward, " @@ -61,7 +90,7 @@ "GNOME 3. Ele permite que você leia, localize e envie e-mail com uma " "interface simples e moderna." -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:8 +#: desktop/org.gnome.Geary.appdata.xml.in:22 msgid "" "Conversations allow you to read a complete discussion without having to find " "and click from message to message." @@ -69,220 +98,708 @@ "As conversas permitem que você leia uma discussão completa sem ter que " "procurar e clicar de mensagem em mensagem." -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:9 +#: desktop/org.gnome.Geary.appdata.xml.in:26 msgid "Geary’s features include:" msgstr "Os recursos do Geary incluem:" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:10 +#: desktop/org.gnome.Geary.appdata.xml.in:28 msgid "Quick email account setup" msgstr "Configuração rápida de conta de e-mail" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:11 +#: desktop/org.gnome.Geary.appdata.xml.in:29 msgid "Shows related messages together in conversations" msgstr "Mostrar mensagens relacionadas juntas, nas conversas" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:12 +#: desktop/org.gnome.Geary.appdata.xml.in:30 msgid "Fast, full text and keyword search" msgstr "Pesquisa rápida, de texto completo e de palavra-chave" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:13 +#: desktop/org.gnome.Geary.appdata.xml.in:31 msgid "Full-featured HTML and plain text message composer" msgstr "Compositor repleto de recursos para mensagens em HTML e texto simples" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:14 +#: desktop/org.gnome.Geary.appdata.xml.in:32 msgid "Desktop notification of new mail" msgstr "Notificação na área de trabalho de novo e-mail" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:15 +#: desktop/org.gnome.Geary.appdata.xml.in:33 msgid "Compatible with GMail, Yahoo! Mail, Outlook.com and other IMAP servers" msgstr "" "Compatível com GMail, Yahoo! Mail, Outlook.com e outros servidores IMAP" #. Translators: A screenshot description. -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:17 +#: desktop/org.gnome.Geary.appdata.xml.in:47 msgid "Geary displaying a conversation" msgstr "Geary exibindo uma conversa" #. Translators: A screenshot description. -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:19 +#: desktop/org.gnome.Geary.appdata.xml.in:52 msgid "Geary showing the rich text composer" msgstr "Geary mostrando um compositor de texto rico" -#: ../desktop/org.gnome.Geary.desktop.in.h:2 -#: ../desktop/geary-autostart.desktop.in.h:2 -msgid "Email" -msgstr "E-mail" - -#: ../desktop/org.gnome.Geary.desktop.in.h:3 -msgid "Geary Email" -msgstr "E-mail Geary" - #. Translators: These are desktop search terms. Do not translate semicolons, end line with a semicolon. -#: ../desktop/org.gnome.Geary.desktop.in.h:6 +#: desktop/org.gnome.Geary.desktop.in:7 msgid "Mail;E-mail;IMAP;GMail;Yahoo;Hotmail;Outlook;" msgstr "Correio;E-mail;IMAP;GMail;Yahoo;Hotmail;Outlook;" -#: ../desktop/org.gnome.Geary.desktop.in.h:7 ../ui/gtk/menus.ui.h:1 +#: desktop/org.gnome.Geary.desktop.in:21 msgid "Compose Message" msgstr "Escrever mensagem" -#: ../desktop/geary-autostart.desktop.in.h:3 -msgid "Geary Mail" -msgstr "Correio eletrônico - Geary" - -#: ../desktop/geary-autostart.desktop.in.h:5 -msgid "Email;E-mail;Mail;" -msgstr "Email;E-mail;Mail;Correio eletrônico;e-mails;mensagens;" - -#: ../desktop/geary-attach.contract.in.h:1 -msgid "Send by email" -msgstr "Enviar por e-mail" - -#: ../desktop/geary-attach.contract.in.h:2 -msgid "Send files using Geary" -msgstr "Envia arquivos usando Geary" +#: desktop/org.gnome.Geary.gschema.xml:8 +msgid "Maximize window" +msgstr "Maximizar janela" + +#: desktop/org.gnome.Geary.gschema.xml:9 +msgid "True if the application window is maximized, false otherwise." +msgstr "" +"Verdadeiro se a janela do aplicativo está maximizada; do contrário, falso." + +#: desktop/org.gnome.Geary.gschema.xml:14 +msgid "Width of window" +msgstr "Largura da janela" + +#: desktop/org.gnome.Geary.gschema.xml:15 +msgid "The last recorded width of the application window." +msgstr "A última largura registrada da janela do aplicativo." + +#: desktop/org.gnome.Geary.gschema.xml:20 +msgid "Height of window" +msgstr "Altura da janela" + +#: desktop/org.gnome.Geary.gschema.xml:21 +msgid "The last recorded height of the application window." +msgstr "A última altura registrada da janela do aplicativo." + +#: desktop/org.gnome.Geary.gschema.xml:26 +msgid "Position of folder list pane" +msgstr "Posição do painel de lista de pastas" + +# O termo "Paned" se refere a gtk.Paned, uma class for painéis. +# "Grabber" é termo usado no desenvolvimento para função da captura de frame +#: desktop/org.gnome.Geary.gschema.xml:27 +msgid "Position of the folder list Paned grabber." +msgstr "Posição do grabber do Paned da lista de pastas." + +#: desktop/org.gnome.Geary.gschema.xml:32 +msgid "Position of folder list pane when horizontal" +msgstr "Posição do painel de lista de pastas quando horizontal" + +# O termo "Paned" se refere a gtk.Paned, uma class for painéis. +# "Grabber" é termo usado no desenvolvimento para função da captura de frame +#: desktop/org.gnome.Geary.gschema.xml:33 +msgid "" +"Position of the folder list Paned grabber in the horizontal orientation." +msgstr "" +"Posição do grabber do Paned da lista de pastas na orientação horizontal." + +#: desktop/org.gnome.Geary.gschema.xml:38 +msgid "Position of folder list pane when vertical" +msgstr "Posição do painel de lista de pastas quando vertical" + +# O termo "Paned" se refere a gtk.Paned, uma class for painéis. +# "Grabber" é termo usado no desenvolvimento para função da captura de frame +#: desktop/org.gnome.Geary.gschema.xml:39 +msgid "Position of the folder list Paned grabber in the vertical orientation." +msgstr "Posição do grabber do Paned da lista de pastas na orientação vertical." + +#: desktop/org.gnome.Geary.gschema.xml:44 +msgid "Orientation of the folder list pane" +msgstr "Orientação do painel de lista de pastas" + +# O termo "Paned" se refere a gtk.Paned, uma class for painéis. +# "Grabber" é termo usado no desenvolvimento para função da captura de frame +#: desktop/org.gnome.Geary.gschema.xml:45 +msgid "True if the folder list Paned is in the horizontal orientation." +msgstr "" +"Verdadeiro se o Paned da lista de pastas estiver na orientação horizontal." + +#: desktop/org.gnome.Geary.gschema.xml:50 +msgid "Position of message list pane" +msgstr "Posição do painel de lista de mensagens" + +# O termo "Paned" se refere a gtk.Paned, uma class for painéis. +# "Grabber" é termo usado no desenvolvimento para função da captura de frame +#: desktop/org.gnome.Geary.gschema.xml:51 +msgid "Position of the message list Paned grabber." +msgstr "Posição do grabber do Paned da lista de mensagens." + +#: desktop/org.gnome.Geary.gschema.xml:56 +msgid "Autoselect next message" +msgstr "Autosselecionar a próxima mensagem" + +#: desktop/org.gnome.Geary.gschema.xml:57 +msgid "True if we should autoselect the next available conversation." +msgstr "Verdadeiro se devemos autosselecionar a próxima conversa disponível." + +#: desktop/org.gnome.Geary.gschema.xml:62 +msgid "Display message previews" +msgstr "Exibir visualizações de mensagens" + +#: desktop/org.gnome.Geary.gschema.xml:63 +msgid "True if we should display a short preview of each message." +msgstr "Verdadeiro se devemos exibir uma visualização curta de cada mensagem." + +#: desktop/org.gnome.Geary.gschema.xml:68 +msgid "Languages that shall be used in the spell checker" +msgstr "Idiomas que devem ser usados na verificação ortográfica" + +#: desktop/org.gnome.Geary.gschema.xml:69 +msgid "List of the languages to use in the spell checker." +msgstr "Lista de idiomas para usar na verificação ortográfica." + +#: desktop/org.gnome.Geary.gschema.xml:74 +msgid "Languages that are displayed in the spell checker popover" +msgstr "" +"Idiomas que são exibidos na janela sobreposta de verificação ortográfica" + +#: desktop/org.gnome.Geary.gschema.xml:75 +msgid "" +"List of languages that are always displayed in the popover of the spell " +"checker." +msgstr "" +"Lista de idiomas que sempre são exibidos na janela sobreposta da verificação " +"ortográfica." + +#: desktop/org.gnome.Geary.gschema.xml:80 +msgid "Enable notification sounds" +msgstr "Habilitar sons de notificação" + +#: desktop/org.gnome.Geary.gschema.xml:81 +msgid "True to play sounds for notifications and sending." +msgstr "Verdadeiro para reproduzir sons para notificações e enviar." + +#: desktop/org.gnome.Geary.gschema.xml:86 +msgid "Show notifications for new mail" +msgstr "Exibir notificações para novo e-mail" + +#: desktop/org.gnome.Geary.gschema.xml:87 +msgid "True to show notification bubbles." +msgstr "Verdadeiro para mostrar bolhas de notificação." + +#: desktop/org.gnome.Geary.gschema.xml:92 +msgid "Notify of new mail at startup" +msgstr "Notificar sobre novo e-mail na inicialização" + +#: desktop/org.gnome.Geary.gschema.xml:93 +msgid "True to notify of new mail at startup." +msgstr "Verdadeiro para notificar sobre novo e-mail na inicialização." + +#: desktop/org.gnome.Geary.gschema.xml:98 +msgid "Ask when opening an attachment" +msgstr "Perguntar ao abrir um anexo" + +#: desktop/org.gnome.Geary.gschema.xml:99 +msgid "True to ask when opening an attachment." +msgstr "Verdadeiro para perguntar ao abrir um anexo." + +#: desktop/org.gnome.Geary.gschema.xml:104 +msgid "Whether to compose emails in HTML" +msgstr "Se deve compor e-mails em HTML" + +#: desktop/org.gnome.Geary.gschema.xml:105 +msgid "True to compose emails in HTML; false for plain text." +msgstr "Verdadeiro para compor e-mails em HTML; falso para texto simples." + +#: desktop/org.gnome.Geary.gschema.xml:110 +msgid "Advisory strategy for full-text searching" +msgstr "Estratégia consultiva para pesquisa de texto completo" + +#: desktop/org.gnome.Geary.gschema.xml:111 +msgid "" +"Acceptable values are “exact”, “conservative”, “aggressive”, and “horizon”." +msgstr "" +"Valores aceitáveis são “exact”, “conservative”, “aggressive” e “horizon”." + +#: desktop/org.gnome.Geary.gschema.xml:116 +msgid "Zoom of conversation viewer" +msgstr "Ampliação do visualizador de conversa" + +#: desktop/org.gnome.Geary.gschema.xml:117 +msgid "The zoom to apply on the conservation view." +msgstr "A ampliação a ser aplicada no visualizador de conversa." + +#: desktop/org.gnome.Geary.gschema.xml:122 +msgid "Size of detached composer window" +msgstr "Tamanho da janela do compositor destacada" + +#: desktop/org.gnome.Geary.gschema.xml:123 +msgid "The last recorded size of the detached composer window." +msgstr "O último tamanho registrado da janela do compositor destacada." + +#: desktop/org.gnome.Geary.gschema.xml:128 +msgid "Base URL to look up contact avatars" +msgstr "URL base para procurar por avatares de contato" + +#: desktop/org.gnome.Geary.gschema.xml:129 +msgid "" +"A Gravatar or Libravatar compatible URL, set to the empty string to disable." +msgstr "" +"Uma URL compatível com Gravatar ou Libravatar, definida para string vazia " +"para desabilitar." + +#: desktop/org.gnome.Geary.gschema.xml:134 +msgid "Whether we migrated the old settings" +msgstr "Se migramos as configurações antigas" + +#: desktop/org.gnome.Geary.gschema.xml:135 +msgid "" +"False to check for the old “org.yorba.geary”-schema and copy its values." +msgstr "" +"Falso para verificar pelo antigo esquema “org.yorba.geary” e copiar seus " +"valores." + +#. Translators: In-app notification label, when +#. the app had a problem pinning an otherwise +#. untrusted TLS certificate +#: src/client/accounts/accounts-editor.vala:203 +msgid "Failed to store certificate" +msgstr "Falha ao armazenar o certificado" + +#. Translators: Label for adding an email account +#. account for a generic IMAP service provider. +#: src/client/accounts/accounts-editor-add-pane.vala:109 +msgid "All others" +msgstr "Todos os outros" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:196 +#: src/client/accounts/accounts-editor-servers-pane.vala:316 +msgid "Check your receiving login and password" +msgstr "Verifique seu login e senha de recebimento" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:211 +#: src/client/accounts/accounts-editor-servers-pane.vala:329 +msgid "Check your receiving server details" +msgstr "Verifique seus detalhes de servidor de recebimento" + +#. Translators: In-app notification label +#. There was an SMTP auth error, but IMAP already +#. succeeded, so the user probably needs to +#. specify custom creds here +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:233 +#: src/client/accounts/accounts-editor-servers-pane.vala:350 +msgid "Check your sending login and password" +msgstr "Verifique seu login e senha de recebimento" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:247 +#: src/client/accounts/accounts-editor-servers-pane.vala:363 +msgid "Check your sending server details" +msgstr "Verifique seus detalhes de envio" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:262 +msgid "Check your email address and password" +msgstr "Verifique seu endereço de e-mail e senha" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:273 +msgid "Could not connect, check your network" +msgstr "Não foi possível conectar, verifique sua rede" + +#. Translators: In-app notification label for a +#. generic error creating an account +#: src/client/accounts/accounts-editor-add-pane.vala:286 +msgid "An unexpected problem occurred" +msgstr "Ocorreu um problema inesperado" + +#. Translators: In-app notification label, the +#. string substitution is a more detailed reason. +#: src/client/accounts/accounts-editor-add-pane.vala:304 +#, c-format +msgid "Account not created: %s" +msgstr "A conta não foi criada: %s" + +#. Translators: Label for the person's actual name when adding +#. an account +#: src/client/accounts/accounts-editor-add-pane.vala:551 +msgid "Your name" +msgstr "Seu nome" + +#. Translators: Label used for the address part of an +#. email address when editing a user's sender address +#. preferences for an account. +#: src/client/accounts/accounts-editor-add-pane.vala:568 +#: src/client/accounts/accounts-editor-edit-pane.vala:501 +msgid "Email address" +msgstr "Endereço de e-mail" + +#. Translators: Placeholder for the default sender address +#. when adding an account +#. Translators: This is used as a placeholder for the +#. address part of an email address when editing a user's +#. sender address preferences for an account. +#: src/client/accounts/accounts-editor-add-pane.vala:571 +#: src/client/accounts/accounts-editor-edit-pane.vala:469 +msgid "person@example.com" +msgstr "pessoa@exemplo.com" + +#. Translators: Label for an IMAP/SMTP service login/user name +#. when adding an account +#. Translators: Label for the user's login name for an +#. IMAP, SMTP, etc service +#: src/client/accounts/accounts-editor-add-pane.vala:585 +#: src/client/accounts/accounts-editor-servers-pane.vala:880 +msgid "Login name" +msgstr "Nome de login" + +#. Translators: Label for the user's password for an IMAP, +#. SMTP, etc service +#: src/client/accounts/accounts-editor-add-pane.vala:599 +#: src/client/accounts/accounts-editor-servers-pane.vala:999 +#: ui/password-dialog.glade:108 +msgid "Password" +msgstr "Senha" -#: ../src/client/accounts/account-dialog-add-edit-pane.vala:51 -#: ../src/client/components/stock.vala:31 -#: ../ui/conversation-email-menus.ui.h:10 -msgid "_Save" -msgstr "_Salvar" +#. Translators: Label for the IMAP server hostname when +#. adding an account. +#. Translators: This label describes the host name or IP +#. address and port used by an account's IMAP service. +#: src/client/accounts/accounts-editor-add-pane.vala:621 +#: src/client/accounts/accounts-editor-servers-pane.vala:727 +msgid "IMAP server" +msgstr "Servidor IMAP" + +#. Translators: Placeholder for the IMAP server hostname +#. when adding an account. +#: src/client/accounts/accounts-editor-add-pane.vala:624 +msgid "imap.example.com" +msgstr "imap.exemplo.com" -#: ../src/client/accounts/account-dialog-add-edit-pane.vala:51 -#: ../src/client/components/stock.vala:22 -msgid "_Add" -msgstr "_Adicionar" +#. Translators: Label for the SMTP server hostname when +#. adding an account. +#. Translators: This label describes the host name or IP +#. address and port used by an account's SMTP service. +#: src/client/accounts/accounts-editor-add-pane.vala:630 +#: src/client/accounts/accounts-editor-servers-pane.vala:733 +msgid "SMTP server" +msgstr "Servidor SMTP" + +#. Translators: Placeholder for the SMTP server hostname +#. when adding an account. +#: src/client/accounts/accounts-editor-add-pane.vala:633 +msgid "smtp.example.com" +msgstr "smtp.exemplo.com" -#. reset/clear widgets -#: ../src/client/accounts/account-dialog-edit-alternate-emails-pane.vala:120 +#. Translators: Label in the account editor for the user's +#. custom name for an account. +#: src/client/accounts/accounts-editor-edit-pane.vala:278 +#: ui/accounts_editor_remove_pane.ui:123 +msgid "Account name" +msgstr "Nome da conta" + +#. Translators: Tooltip used to undo changing +#. the name of an account. The string +#. substitution is the old name of the +#. account. +#: src/client/accounts/accounts-editor-edit-pane.vala:312 +#, c-format +msgid "Change account name back to “%s”" +msgstr "Altera o nome da conta de volta para “%s”" + +#. Translators: Tooltip for adding a new email sender/from +#. address's address to an account +#: src/client/accounts/accounts-editor-edit-pane.vala:336 +msgid "Add a new sender email address" +msgstr "Adiciona um novo endereço de e-mail de remetente" + +#. Translators: Label used to indicate the user has +#. provided no display name for one of their sender +#. email addresses in their account settings. +#: src/client/accounts/accounts-editor-edit-pane.vala:417 +msgid "Name not set" +msgstr "Nome não definido" + +#. Translators: This is used as a placeholder for the +#. display name for an email address when editing a user's +#. sender address preferences for an account. +#: src/client/accounts/accounts-editor-edit-pane.vala:456 +msgid "Sender Name" +msgstr "Nome do remetente" + +#: src/client/accounts/accounts-editor-edit-pane.vala:479 +msgid "Remove" +msgstr "Remover" + +#. Translators: Label used for the display name part of an +#. email address when editing a user's sender address +#. preferences for an account. +#: src/client/accounts/accounts-editor-edit-pane.vala:494 +msgid "Sender name" +msgstr "Nome do remetente" + +#. Translators: Label used as the undo tooltip after adding an +#. new sender email address to an account. The string +#. substitution is the email address added. +#: src/client/accounts/accounts-editor-edit-pane.vala:561 +#, c-format +msgid "Remove “%s”" +msgstr "Remover “%s”" + +#. Translators: Label used as the undo tooltip after editing a +#. sender address for an account. The string substitution is +#. the email address edited. +#: src/client/accounts/accounts-editor-edit-pane.vala:601 +#, c-format +msgid "Undo changes to “%s”" +msgstr "Desfazer alterações a “%s”" + +#. Translators: Label used as the undo tooltip after removing +#. a sender address from an account. The string substitution +#. is the email address edited. +#: src/client/accounts/accounts-editor-edit-pane.vala:688 +#, c-format +msgid "Add “%s” back" +msgstr "Adicione “%s” de volta" + +#. Translators: Label used as the undo tooltip after removing +#. a sender address from an account. The string substitution +#. is the email address edited. +#: src/client/accounts/accounts-editor-edit-pane.vala:730 +msgid "Undo signature changes" +msgstr "Desfazer alterações a assinatura" + +#. Translators: This label describes the account +#. preference for the length of time (weeks, months or +#. years) that past email should be downloaded. +#: src/client/accounts/accounts-editor-edit-pane.vala:778 +msgid "Download mail" +msgstr "Baixar e-mail" + +#. Translators: Tooltip for undoing a change +#. to the length of time that past email +#. should be downloaded for an account. The +#. string substitution is the duration, +#. e.g. "1 month back". +#: src/client/accounts/accounts-editor-edit-pane.vala:810 #, c-format -msgid "Additional addresses for %s" -msgstr "Endereço adicional para %s" - -#. Sets min size. -#: ../src/client/accounts/account-dialog.vala:21 -msgid "Accounts" -msgstr "Contas" +msgid "Change download period back to: %s" +msgstr "Altera o período de download de volta para: %s" -#. Copyright 2016 Software Freedom Conservancy Inc. -#. * -#. * This software is licensed under the GNU Lesser General Public License -#. * (version 2.1 or later). See the COPYING file in this distribution. -#. -#. Page for adding or editing an account. -#. / Placeholder text indicating that the user should type their first name and last name -#: ../src/client/accounts/add-edit-page.vala:10 -msgid "First Last" -msgstr "Nome Sobrenome" - -#: ../src/client/accounts/add-edit-page.vala:235 -msgid "Welcome to Geary." -msgstr "Bem-vindo ao Geary." - -#: ../src/client/accounts/add-edit-page.vala:235 -msgid "Enter your account information to get started." -msgstr "Entre com as informações de sua conta para iniciar." +#: src/client/accounts/accounts-editor-edit-pane.vala:831 +msgid "Everything" +msgstr "Tudo" -#: ../src/client/accounts/add-edit-page.vala:255 +#: src/client/accounts/accounts-editor-edit-pane.vala:835 msgid "2 weeks back" msgstr "2 semanas atrás" -#. IDs are # of days -#: ../src/client/accounts/add-edit-page.vala:256 +#: src/client/accounts/accounts-editor-edit-pane.vala:839 msgid "1 month back" msgstr "1 mês atrás" -#: ../src/client/accounts/add-edit-page.vala:257 +#: src/client/accounts/accounts-editor-edit-pane.vala:843 msgid "3 months back" msgstr "3 meses atrás" -#: ../src/client/accounts/add-edit-page.vala:258 +#: src/client/accounts/accounts-editor-edit-pane.vala:847 msgid "6 months back" msgstr "6 meses atrás" -#: ../src/client/accounts/add-edit-page.vala:259 +#: src/client/accounts/accounts-editor-edit-pane.vala:851 msgid "1 year back" msgstr "1 ano atrás" -#: ../src/client/accounts/add-edit-page.vala:260 +#: src/client/accounts/accounts-editor-edit-pane.vala:855 msgid "2 years back" msgstr "2 anos atrás" -#: ../src/client/accounts/add-edit-page.vala:261 +#: src/client/accounts/accounts-editor-edit-pane.vala:859 msgid "4 years back" msgstr "4 anos atrás" -#. Separator -#: ../src/client/accounts/add-edit-page.vala:263 -msgid "Everything" -msgstr "Tudo" +#: src/client/accounts/accounts-editor-edit-pane.vala:865 +#, c-format +msgid "%d day back" +msgid_plural "%d days back" +msgstr[0] "%d dia atrás" +msgstr[1] "%d dias atrás" + +#: src/client/accounts/accounts-editor-list-pane.vala:243 +msgid "Undo" +msgstr "Desfazer" + +#: src/client/accounts/accounts-editor-list-pane.vala:251 +msgid "Redo" +msgstr "Refazer" + +#: src/client/accounts/accounts-editor-list-pane.vala:345 +#: src/client/accounts/accounts-editor-list-pane.vala:433 +#: src/client/accounts/accounts-editor-row.vala:278 +msgid "Gmail" +msgstr "Gmail" -#: ../src/client/accounts/add-edit-page.vala:283 -msgid "Edit" -msgstr "Editar" - -#: ../src/client/accounts/add-edit-page.vala:285 -msgid "Preview" -msgstr "Visualizar" - -#: ../src/client/accounts/add-edit-page.vala:751 -msgid "Remem_ber passwords" -msgstr "Lem_brar senhas" - -#: ../src/client/accounts/add-edit-page.vala:758 ../ui/login.glade.h:7 -msgid "Remem_ber password" -msgstr "Lem_brar senha" - -#: ../src/client/accounts/add-edit-page.vala:792 -msgid "Unable to validate:\n" -msgstr "Incapaz de validar:\n" - -#: ../src/client/accounts/add-edit-page.vala:794 -msgid " • Invalid account nickname.\n" -msgstr " • Apelido de conta inválido.\n" - -#: ../src/client/accounts/add-edit-page.vala:797 -msgid " • Email address already added to Geary.\n" -msgstr " • Endereço de e-mail já adicionado ao Geary.\n" - -#: ../src/client/accounts/add-edit-page.vala:801 -msgid " • IMAP connection error.\n" -msgstr " • Erro de conexão ao IMAP.\n" - -#: ../src/client/accounts/add-edit-page.vala:804 -msgid " • IMAP username or password incorrect.\n" -msgstr " • Nome de usuário ou senha de IMAP incorretos.\n" - -#: ../src/client/accounts/add-edit-page.vala:807 -msgid " • SMTP connection error.\n" -msgstr " • Erro de conexão ao SMTP.\n" - -#: ../src/client/accounts/add-edit-page.vala:810 -msgid " • SMTP username or password incorrect.\n" -msgstr " • Nome de usuário ou senha SMTP incorretos.\n" - -#: ../src/client/accounts/add-edit-page.vala:814 -msgid " • Connection error.\n" -msgstr " • Erro de conexão.\n" - -#: ../src/client/accounts/add-edit-page.vala:818 -msgid " • Username or password incorrect.\n" -msgstr " • Nome de usuário ou senha incorretos.\n" +#: src/client/accounts/accounts-editor-list-pane.vala:349 +#: src/client/accounts/accounts-editor-list-pane.vala:437 +#: src/client/accounts/accounts-editor-row.vala:282 +msgid "Outlook.com" +msgstr "Outlook.com" -#: ../src/client/application/geary-application.vala:22 +#: src/client/accounts/accounts-editor-list-pane.vala:353 +#: src/client/accounts/accounts-editor-list-pane.vala:441 +#: src/client/accounts/accounts-editor-row.vala:286 +msgid "Yahoo" +msgstr "Yahoo" + +#. Translators: Tooltip for accounts that have been +#. loaded but disabled by the user. +#: src/client/accounts/accounts-editor-list-pane.vala:371 +msgid "This account has been disabled" +msgstr "Essa conta foi desabilitada" + +#. Translators: Tooltip for accounts that have been +#. loaded but because of some error are not able to be +#. used. +#: src/client/accounts/accounts-editor-list-pane.vala:380 +msgid "This account has encountered a problem and is unavailable" +msgstr "Essa conta encontrou um problema e está indisponível" + +#. Translators: Label for adding a generic email account +#: src/client/accounts/accounts-editor-list-pane.vala:430 +msgid "Other email providers" +msgstr "Outros provedores de e-mail" + +#. Translators: Notification shown after removing an +#. account. The string substitution is the name of the +#. account. +#: src/client/accounts/accounts-editor-list-pane.vala:547 +#, c-format +msgid "Account “%s” removed" +msgstr "Conta “%s” removida" + +#. Translators: Notification shown after removing an account +#. is undone. The string substitution is the name of the +#. account. +#: src/client/accounts/accounts-editor-list-pane.vala:554 +#, c-format +msgid "Account “%s” restored" +msgstr "Conta “%s” restaurada" + +#. Translators: Tooltip for dragging list items +#: src/client/accounts/accounts-editor-row.vala:49 +msgid "Drag to move this item" +msgstr "Arraste para mover esse item" + +#. Translators: Label describes the service provider +#. hosting the email account, e.g. Gmail, Yahoo, or some +#. other generic IMAP service. +#: src/client/accounts/accounts-editor-row.vala:294 +msgid "Service provider" +msgstr "Provedor do serviço" + +#. Translators: This label describes what form of transport +#. security (TLS, StartTLS, etc) used by an account's IMAP or SMTP +#. service. +#: src/client/accounts/accounts-editor-row.vala:467 +msgid "Connection security" +msgstr "Segurança da conexão" + +#. Translators: Label used when no auth scheme is used +#. by an account's IMAP or SMTP service. +#: src/client/accounts/accounts-editor-row.vala:478 +#: src/client/accounts/accounts-editor-servers-pane.vala:752 +#: src/client/accounts/accounts-editor-servers-pane.vala:964 +#: src/engine/api/geary-special-folder-type.vala:58 +msgid "None" +msgstr "Nenhum" + +#: src/client/accounts/accounts-editor-row.vala:485 +msgid "StartTLS" +msgstr "StartTLS" + +#: src/client/accounts/accounts-editor-row.vala:492 +msgid "TLS" +msgstr "TLS" + +#. Translators: Label for source of SMTP authentication +#. credentials (none, use IMAP, custom) when adding a new +#. account +#. Button label for retrying when a login error has occurred +#: src/client/accounts/accounts-editor-row.vala:533 ui/main-window.ui:455 +msgid "Login" +msgstr "Login" + +#. Translators: ComboBox value for source of SMTP +#. authentication credentials (none) when adding a new +#. account +#: src/client/accounts/accounts-editor-row.vala:540 +msgid "No login needed" +msgstr "Nenhum login necessário" + +#. Translators: ComboBox value for source of SMTP +#. authentication credentials (use IMAP) when adding a new +#. account +#: src/client/accounts/accounts-editor-row.vala:548 +msgid "Use same login as receiving" +msgstr "Usar o mesmo login como recebimento" + +#. Translators: ComboBox value for source of SMTP +#. authentication credentials (custom) when adding a new +#. account +#: src/client/accounts/accounts-editor-row.vala:556 +msgid "Use a different login" +msgstr "Usa um login diferente" + +#. Translators: In-app notification label, the +#. string substitution is a more detailed reason. +#: src/client/accounts/accounts-editor-servers-pane.vala:377 +#, c-format +msgid "Account not updated: %s" +msgstr "Conta não atualizada: %s" + +#. Translators: This label describes the program that +#. created the account, e.g. an SSO service like GOA, or +#. locally by Geary. +#: src/client/accounts/accounts-editor-servers-pane.vala:540 +msgid "Account source" +msgstr "Origem da conta" + +#: src/client/accounts/accounts-editor-servers-pane.vala:552 +msgid "GNOME Online Accounts" +msgstr "Contas On-line do GNOME" + +#. Translators: This label describes an account +#. preference. +#: src/client/accounts/accounts-editor-servers-pane.vala:611 +msgid "Save drafts on server" +msgstr "Salvar rascunhos no servidor" + +#. Translators: This label describes an account +#. preference. +#: src/client/accounts/accounts-editor-servers-pane.vala:666 +msgid "Save sent email on server" +msgstr "Salvar e-mails enviados no servidor" + +#. Add a suffix for OAuth2 auth so people know they +#. shouldn't expect to be prompted for a password +#. Translators: Label used when an account's IMAP or +#. SMTP service uses OAuth2. The string replacement is +#. the service's login name. +#: src/client/accounts/accounts-editor-servers-pane.vala:950 +#, c-format +msgid "%s using OAuth2" +msgstr "%s usando OAuth2" + +#: src/client/accounts/accounts-editor-servers-pane.vala:960 +msgid "Use receiving server login" +msgstr "usar login do servidor de recebimento" + +#: src/client/application/geary-application.vala:24 msgid "Copyright 2016 Software Freedom Conservancy Inc." msgstr "Copyright 2016 Software Freedom Conservancy Inc." -#: ../src/client/application/geary-application.vala:23 -#| msgid "Geary Development Team" -msgid "Copyright 2016-2017 Geary Development Team." -msgstr "Copyright 2016-2017 Equipe de Desenvolvimento do Geary." +#: src/client/application/geary-application.vala:25 +msgid "Copyright 2016-2019 Geary Development Team." +msgstr "Copyright 2016-2019 Geary Development Team." -#: ../src/client/application/geary-application.vala:25 +#: src/client/application/geary-application.vala:27 msgid "Visit the Geary web site" msgstr "Visite o website do Geary" -#: ../src/client/application/geary-application.vala:466 +#: src/client/application/geary-application.vala:454 #, c-format msgid "About %s" msgstr "Sobre %s" @@ -290,7 +807,7 @@ #. Translators: add your name and email address to receive #. credit in the About dialog For example: Yamada Taro #. -#: ../src/client/application/geary-application.vala:470 +#: src/client/application/geary-application.vala:458 msgid "translator-credits" msgstr "" "Leonardo Lemos \n" @@ -299,312 +816,107 @@ "Rafael Fontenelle \n" "Ronan Arraes Jardim Chagas " -#: ../src/client/application/geary-args.vala:10 +#: src/client/application/geary-args.vala:10 msgid "Start Geary with hidden main window" msgstr "Inicia o Geary com a janela principal oculta" -#: ../src/client/application/geary-args.vala:11 +#: src/client/application/geary-args.vala:11 msgid "Output debugging information" msgstr "Emite informações de depuração" -#: ../src/client/application/geary-args.vala:12 +#: src/client/application/geary-args.vala:12 msgid "Log conversation monitoring" msgstr "Registra monitoramento de conversa" -#: ../src/client/application/geary-args.vala:13 +#: src/client/application/geary-args.vala:13 msgid "Log network deserialization" msgstr "Registra desserialização de rede" -#: ../src/client/application/geary-args.vala:14 +#: src/client/application/geary-args.vala:14 msgid "Log network activity" msgstr "Registra atividade de rede" #. / The IMAP replay queue is how changes on the server are replicated on the client. #. / It could also be called the IMAP events queue. -#: ../src/client/application/geary-args.vala:17 +#: src/client/application/geary-args.vala:17 msgid "Log IMAP replay queue" msgstr "Registra lista de repetição do IMAP" #. / Serialization is how commands and responses are converted into a stream of bytes for #. / network transmission -#: ../src/client/application/geary-args.vala:20 +#: src/client/application/geary-args.vala:20 msgid "Log network serialization" msgstr "Registra serialização de rede" -#: ../src/client/application/geary-args.vala:21 +#: src/client/application/geary-args.vala:21 msgid "Log periodic activity" msgstr "Registra atividade periódica" -#: ../src/client/application/geary-args.vala:22 +#: src/client/application/geary-args.vala:22 msgid "Log database queries (generates lots of messages)" msgstr "Registra consultas ao banco de dados (gera muitas mensagens)" #. / "Normalization" can also be called "synchronization" -#: ../src/client/application/geary-args.vala:24 +#: src/client/application/geary-args.vala:24 msgid "Log folder normalization" msgstr "Registra normalização de pasta" -#: ../src/client/application/geary-args.vala:25 +#: src/client/application/geary-args.vala:25 msgid "Allow inspection of WebView" msgstr "Permite inspeção do WebView" -#: ../src/client/application/geary-args.vala:26 +#: src/client/application/geary-args.vala:26 msgid "Revoke all server certificates with TLS warnings" msgstr "Revoga todos os certificados de servidor com avisos de TLS" -#: ../src/client/application/geary-args.vala:27 +#: src/client/application/geary-args.vala:27 msgid "Perform a graceful quit" msgstr "Realiza uma saída graciosa" -#: ../src/client/application/geary-args.vala:28 +#: src/client/application/geary-args.vala:28 msgid "Display program version" msgstr "Mostra versão do programa" #. This gives a command-line hint on how to open new composer windows with mailto: -#: ../src/client/application/geary-args.vala:53 +#: src/client/application/geary-args.vala:53 #, c-format msgid "Use %s to open a new composer window" msgstr "Use %s para abrir uma nova janela do compositor" -#: ../src/client/application/geary-args.vala:56 +#: src/client/application/geary-args.vala:56 msgid "Please report comments, suggestions and bugs to:" msgstr "Por favor, envie sugestões, comentários ou erros para:" #. i18n: Command line arguments are invalid -#: ../src/client/application/geary-args.vala:63 +#: src/client/application/geary-args.vala:63 #, c-format msgid "Failed to parse command line options: %s\n" msgstr "Falha ao analisar as opções de linha de comando: %s\n" -#: ../src/client/application/geary-args.vala:74 +#: src/client/application/geary-args.vala:74 #, c-format msgid "Unrecognized command line option “%s”\n" msgstr "Opção de linha de comando não reconhecida “%s”\n" -#: ../src/client/application/geary-controller.vala:57 -msgid "Delete conversation" -msgstr "Excluir conversa" - -#: ../src/client/application/geary-controller.vala:58 -msgid "Delete conversation (Shift+Delete)" -msgstr "Excluir a conversa (Shift+Delete)" - -#: ../src/client/application/geary-controller.vala:59 -msgid "Delete conversations (Shift+Delete)" -msgstr "Excluir as conversas (Shift+Delete)" - -#. This refers to the action ("move email to the trash"), not the Trash folder itself -#: ../src/client/application/geary-controller.vala:63 -msgid "Move conversation to Trash (Delete, Backspace)" -msgstr "Mover conversa para a lixeira (Delete, Backspace)" - -#: ../src/client/application/geary-controller.vala:64 -msgid "Move conversations to Trash (Delete, Backspace)" -msgstr "Mover conversas para a lixeira (Delete, Backspace)" - -#. This refers to the action ("archive an email"), not the Archive folder itself -#: ../src/client/application/geary-controller.vala:68 -msgid "_Archive" -msgstr "_Arquivar" - -#: ../src/client/application/geary-controller.vala:69 -msgid "Archive conversation (A)" -msgstr "Arquivar conversa (A)" - -#: ../src/client/application/geary-controller.vala:70 -msgid "Archive conversations (A)" -msgstr "Arquivar conversas (A)" - -#: ../src/client/application/geary-controller.vala:73 -msgid "Mark as S_pam" -msgstr "Marcar como S_pam" - -#: ../src/client/application/geary-controller.vala:74 -msgid "Mark as not S_pam" -msgstr "Marcar como não S_pam" - -#: ../src/client/application/geary-controller.vala:76 -#: ../src/client/application/geary-controller.vala:448 -msgid "Mark conversation" -msgstr "Marcar conversação" - -#: ../src/client/application/geary-controller.vala:77 -msgid "Mark conversations" -msgstr "Marcar conversas" - -#: ../src/client/application/geary-controller.vala:78 -msgid "Add label to conversation" -msgstr "Adicionar marcador à conversa" - -#: ../src/client/application/geary-controller.vala:79 -msgid "Add label to conversations" -msgstr "Adicionar marcador às conversas" - -#: ../src/client/application/geary-controller.vala:80 -#: ../src/client/application/geary-controller.vala:487 -msgid "Move conversation" -msgstr "Mover conversa" - -#: ../src/client/application/geary-controller.vala:81 -msgid "Move conversations" -msgstr "Mover conversas" - -#: ../src/client/application/geary-controller.vala:450 -msgid "_Mark as…" -msgstr "_Marcar como…" - -#: ../src/client/application/geary-controller.vala:456 -msgid "Mark as _Read" -msgstr "Marcar como l_ida" - -#: ../src/client/application/geary-controller.vala:462 -msgid "Mark as _Unread" -msgstr "Marcar como _não lida" - -#: ../src/client/application/geary-controller.vala:468 -msgid "_Star" -msgstr "_Marcar estrela" - -#: ../src/client/application/geary-controller.vala:473 -msgid "U_nstar" -msgstr "_Tirar estrela" - -#: ../src/client/application/geary-controller.vala:483 -msgid "Add label" -msgstr "Adicionar marcador" - -#: ../src/client/application/geary-controller.vala:484 -msgid "_Label" -msgstr "Ma_rcador" - -#: ../src/client/application/geary-controller.vala:488 -msgid "_Move" -msgstr "_Mover" - -#: ../src/client/application/geary-controller.vala:492 -msgid "Compose new message (Ctrl+N, N)" -msgstr "Compor nova mensagem (Ctrl+N, N)" - -#: ../src/client/application/geary-controller.vala:496 -#: ../ui/conversation-email-menus.ui.h:1 -msgid "_Reply" -msgstr "_Responder" - -#: ../src/client/application/geary-controller.vala:497 -msgid "Reply (Ctrl+R, R)" -msgstr "Responder (Ctrl+R, R)" - -#: ../src/client/application/geary-controller.vala:501 -msgid "R_eply All" -msgstr "R_esponder todos" - -#: ../src/client/application/geary-controller.vala:502 -msgid "Reply all (Ctrl+Shift+R, Shift+R)" -msgstr "Responder tudo (Ctrl+Shift+R, Shift+R)" - -#: ../src/client/application/geary-controller.vala:507 -#: ../ui/conversation-email-menus.ui.h:3 -msgid "_Forward" -msgstr "_Encaminhar" - -#: ../src/client/application/geary-controller.vala:508 -msgid "Forward (Ctrl+L, F)" -msgstr "Encaminhar (Ctrl+L, F)" - -#: ../src/client/application/geary-controller.vala:538 -msgid "Empty _Spam…" -msgstr "Esvaziar _spam…" - -#: ../src/client/application/geary-controller.vala:542 -msgid "Empty _Trash…" -msgstr "Esvaziar _lixeira…" - -#. No callback is connected, since we bind the toggle button to the search bar visibility -#: ../src/client/application/geary-controller.vala:574 -msgid "Toggle search bar" -msgstr "Ativar/desativar barra de pesquisa" +#. Translators: File name used in save chooser when saving +#. attachments that do not otherwise have a name. +#: src/client/application/geary-controller.vala:58 +msgid "Untitled" +msgstr "Sem_título" -#. No callback is connected, since we bind the toggle button to the find bar visibility -#: ../src/client/application/geary-controller.vala:579 -msgid "Toggle find bar" -msgstr "Ativar/desativar barra de busca" - -#: ../src/client/application/geary-controller.vala:757 -msgid "Unable to store server trust exception" -msgstr "Não foi possível armazenar exceção de confiança de servidor" - -#: ../src/client/application/geary-controller.vala:992 -msgid "Your settings are insecure" -msgstr "Suas configurações são inseguras" - -#: ../src/client/application/geary-controller.vala:993 -msgid "" -"Your IMAP and/or SMTP settings do not specify SSL or TLS. This means your " -"username and password could be read by another person on the network. Are " -"you sure you want to do this?" -msgstr "" -"As suas configurações de IMAP e/ou SMTP não especificam um SSL ou TLS. Isso " -"significa que seu usuário e senha poderão ser lidos por outra pessoa na " -"rede. Você tem certeza que deseja fazer isso?" - -#: ../src/client/application/geary-controller.vala:994 -msgid "Co_ntinue" -msgstr "Co_ntinuar" - -#: ../src/client/application/geary-controller.vala:1041 -msgid "Error connecting to the server" -msgstr "Erro ao conectar com o servidor" - -#: ../src/client/application/geary-controller.vala:1042 -msgid "" -"Geary encountered an error while connecting to the server. Please try again " -"in a few moments." -msgstr "" -"Geary encontrou um erro enquanto tentava conectar com o servidor. Por favor, " -"tente novamente em alguns instantes." - -#. / Displayed in the space-limited status bar when a message fails to be sent due to error. -#: ../src/client/application/geary-controller.vala:1079 -#: ../src/client/components/status-bar.vala:29 -msgid "Error sending email" -msgstr "Erro ao enviar e-mail" - -#: ../src/client/application/geary-controller.vala:1080 -msgid "" -"Geary encountered an error sending an email. If the problem persists, " -"please manually delete the email from your Outbox folder." -msgstr "" -"O Geary encontrou um erro ao enviar um e-mail. Se o problema persistir, por " -"favor exclua manualmente o e-mail da sua pasta Caixa de saída." - -#. Displayed in the space-limited status bar when a message fails to be uploaded -#. to Sent Mail after being sent. -#: ../src/client/application/geary-controller.vala:1084 -#: ../src/client/components/status-bar.vala:33 -msgid "Error saving sent mail" -msgstr "Erro ao salvar e-mail enviado" - -#: ../src/client/application/geary-controller.vala:1085 -msgid "" -"Geary encountered an error saving a sent message to Sent Mail. The message " -"will stay in your Outbox folder until you delete it." -msgstr "" -"O Geary encontrou um erro ao salvar um e-mail na pasta de Correios enviados. " -"A mensagem vai permanecer em sua pasta Caixa de saída até você excluí-la." - -#: ../src/client/application/geary-controller.vala:1154 +#: src/client/application/geary-controller.vala:919 msgid "Labels" msgstr "Marcadores" #. give the user two options: reset the Account local store, or exit Geary. A third #. could be done to leave the Account in an unopened state, but we don't currently #. have provisions for that. -#: ../src/client/application/geary-controller.vala:1166 +#: src/client/application/geary-controller.vala:932 #, c-format msgid "Unable to open the database for %s" msgstr "Incapaz de abrir o banco de dados para %s" -#: ../src/client/application/geary-controller.vala:1167 +#: src/client/application/geary-controller.vala:933 #, c-format msgid "" "There was an error opening the local mail database for this account. This is " @@ -629,20 +941,20 @@ "Reconstruir o banco de dados irá destruir todos e-mails locais e seus " "anexos. Os e-mails no seu servidor não serão afetados." -#: ../src/client/application/geary-controller.vala:1169 +#: src/client/application/geary-controller.vala:935 msgid "_Rebuild" msgstr "_Reconstruir" -#: ../src/client/application/geary-controller.vala:1169 +#: src/client/application/geary-controller.vala:935 msgid "E_xit" msgstr "S_air" -#: ../src/client/application/geary-controller.vala:1178 +#: src/client/application/geary-controller.vala:944 #, c-format msgid "Unable to rebuild database for “%s”" msgstr "Incapaz de reconstruir o banco de dados para “%s”" -#: ../src/client/application/geary-controller.vala:1179 +#: src/client/application/geary-controller.vala:945 #, c-format msgid "" "Error during rebuild:\n" @@ -653,69 +965,15 @@ "\n" "%s" -#. some other problem opening the account ... as with other flow path, can't run -#. Geary today with an account in unopened state, so have to exit -#: ../src/client/application/geary-controller.vala:1201 -#: ../src/client/application/geary-controller.vala:1211 -#: ../src/client/application/geary-controller.vala:1222 -#, c-format -msgid "Unable to open local mailbox for %s" -msgstr "Incapaz de abrir a caixa de e-mail local para %s" - -#: ../src/client/application/geary-controller.vala:1202 -#, c-format -msgid "" -"There was an error opening the local mail database for this account. This is " -"possibly due to a file permissions problem.\n" -"\n" -"Please check that you have read/write permissions for all files in this " -"directory:\n" -"\n" -"%s" -msgstr "" -"Ocorreu um erro ao abrir o banco de dados do e-mail local para esta conta. " -"Isto se deve possivelmente a problema de permissões de arquivo.\n" -"\n" -"Por favor, verifique se você tem permissão de leitura/gravação para todos os " -"arquivos neste diretório:\n" -"\n" -"%s" - -#: ../src/client/application/geary-controller.vala:1212 -msgid "" -"The version number of the local mail database is formatted for a newer " -"version of Geary. Unfortunately, the database cannot be “rolled back” to " -"work with this version of Geary.\n" -"\n" -"Please install the latest version of Geary and try again." -msgstr "" -"O número da versão do banco de dados do correio local está formatado para " -"uma versão mais recente do Geary. Infelizmente, o banco de dados não pode " -"ser “revertido” para trabalhar com esta versão do Geary.\n" -"\n" -"Por favor, instale a última versão do Geary e tente outra vez." - -#: ../src/client/application/geary-controller.vala:1223 -msgid "" -"There was an error opening the local account. This is probably due to " -"connectivity issues.\n" -"\n" -"Please check your network connection and restart Geary." -msgstr "" -"Ocorreu um erro ao abrir a conta local. Isto se deve provavelmente a " -"problemas de conectividade.\n" -"\n" -"Por favor, verifique sua conexão de rede e reinicie o Geary." - -#: ../src/client/application/geary-controller.vala:1999 +#: src/client/application/geary-controller.vala:1800 msgid "Undo move (Ctrl+Z)" msgstr "Desfazer movimento (Ctrl+Z)" -#: ../src/client/application/geary-controller.vala:2009 +#: src/client/application/geary-controller.vala:1810 msgid "Are you sure you want to open these attachments?" msgstr "Você tem certeza de que deseja abrir esses anexos?" -#: ../src/client/application/geary-controller.vala:2010 +#: src/client/application/geary-controller.vala:1811 msgid "" "Attachments may cause damage to your system if opened. Only open files from " "trusted sources." @@ -723,199 +981,491 @@ "Anexos podem causar danos ao seu sistema se abertos. Apenas abra arquivos de " "fontes confiáveis." -#: ../src/client/application/geary-controller.vala:2011 +#: src/client/application/geary-controller.vala:1812 msgid "Don’t _ask me again" msgstr "Não me _pergunte novamente" -#: ../src/client/application/geary-controller.vala:2121 +#. Translators: Dialog primary label when prompting to +#. overwrite a file. The string substitution is the file'sx +#. name. +#: src/client/application/geary-controller.vala:1941 #, c-format msgid "A file named “%s” already exists. Do you want to replace it?" msgstr "Um arquivo chamado “%s” já existe. Você deseja substituí-lo?" -#: ../src/client/application/geary-controller.vala:2123 +#. Translators: Dialog secondary label when prompting to +#. overwrite a file. The string substitution is the parent +#. folder's name. +#: src/client/application/geary-controller.vala:1948 #, c-format msgid "" "The file already exists in “%s”. Replacing it will overwrite its contents." msgstr "" "O arquivo já existe em “%s”. Substituí-lo irá sobrescrever o seu conteúdo." -#: ../src/client/application/geary-controller.vala:2126 +#: src/client/application/geary-controller.vala:1952 msgid "_Replace" msgstr "_Substituir" -#. Find out what to do with the inline composers. -#. TODO: Remove this in favor of automatically saving drafts -#: ../src/client/application/geary-controller.vala:2374 -msgid "Close open draft messages?" -msgstr "Fechar mensagens de rascunhos abertas?" +#: src/client/application/geary-controller.vala:2228 +msgid "Close the draft message?" +msgid_plural "Close all draft messages?" +msgstr[0] "Fechar a mensagem de rascunho?" +msgstr[1] "Fechar as mensagens de rascunho?" -#: ../src/client/application/geary-controller.vala:2496 +#: src/client/application/geary-controller.vala:2354 #, c-format msgid "Empty all email from your %s folder?" msgstr "Esvaziar todos os e-mails de sua pasta %s?" -#: ../src/client/application/geary-controller.vala:2497 +#: src/client/application/geary-controller.vala:2355 msgid "This removes the email from Geary and your email server." msgstr "Isto remove o e-mail do Geary e do seu servidor de e-mail." -#: ../src/client/application/geary-controller.vala:2498 +#: src/client/application/geary-controller.vala:2356 msgid "This cannot be undone." msgstr "Isso não pode ser desfeito." -#: ../src/client/application/geary-controller.vala:2499 +#: src/client/application/geary-controller.vala:2357 #, c-format msgid "Empty %s" msgstr "Esvaziar %s" -#: ../src/client/application/geary-controller.vala:2516 +#: src/client/application/geary-controller.vala:2374 #, c-format msgid "Error emptying %s" msgstr "Erro ao esvaziar %s" -#: ../src/client/application/geary-controller.vala:2546 +#: src/client/application/geary-controller.vala:2406 msgid "Do you want to permanently delete this message?" msgid_plural "Do you want to permanently delete these messages?" msgstr[0] "Você deseja descartar permanentemente essa mensagem?" msgstr[1] "Você deseja descartar permanentemente essas mensagens?" -#: ../src/client/application/geary-controller.vala:2548 +#: src/client/application/geary-controller.vala:2408 msgid "Delete" msgstr "Excluir" -#: ../src/client/application/geary-controller.vala:2580 -msgid "Undo archive (Ctrl+Z)" -msgstr "Desfazer arquivamento (Ctrl+Z)" - -#: ../src/client/application/geary-controller.vala:2595 +#: src/client/application/geary-controller.vala:2422 msgid "Undo trash (Ctrl+Z)" msgstr "Desfazer exclusão (Ctrl+Z)" -#: ../src/client/application/geary-controller.vala:2649 +#: src/client/application/geary-controller.vala:2472 +msgid "Undo archive (Ctrl+Z)" +msgstr "Desfazer arquivamento (Ctrl+Z)" + +#: src/client/application/geary-controller.vala:2517 msgid "Undo (Ctrl+Z)" msgstr "Desfazer (Ctrl+Z)" -#: ../src/client/application/geary-controller.vala:2780 +#. Translators: The label for an in-app notification. The +#. string substitution is a list of recipients of the email. +#: src/client/application/geary-controller.vala:2598 +#, c-format +msgid "Successfully sent mail to %s." +msgstr "E-mail enviado com sucesso para %s." + +#: src/client/application/geary-controller.vala:2680 msgid "Failed to open default text editor." msgstr "Falha ao abrir o editor de texto padrão." -#: ../src/client/components/main-window.vala:389 +#. Translators: Tooltip used when an entry requires a valid +#. email address to be entered, but one is not provided. +#: src/client/components/components-validator.vala:341 +msgid "An email address is required" +msgstr "Um endereço de e-mail é necessário" + +#. Translators: Tooltip used when an entry requires a valid +#. email address to be entered, but the address is invalid. +#: src/client/components/components-validator.vala:345 +msgid "Not a valid email address" +msgstr "Não é um endereço de e-mail válido" + +#. Translators: Tooltip used when an entry requires a valid, +#. resolvable server name to be entered, but one is not +#. provided. +#: src/client/components/components-validator.vala:391 +msgid "A server name is required" +msgstr "Um nome de servidor é necessário" + +#. Translators: Tooltip used when an entry requires a valid +#. server name to be entered, but it was unable to be +#. looked-up in the DNS. +#: src/client/components/components-validator.vala:396 +msgid "Could not look up server name" +msgstr "Não foi possível procurar por nome do servidor" + +#: src/client/components/main-toolbar.vala:151 +#| msgid "Mark conversation" +msgid "Mark conversation" +msgid_plural "Mark conversations" +msgstr[0] "Marca a conversa" +msgstr[1] "Marca as conversas" + +#: src/client/components/main-toolbar.vala:156 +#| msgid "Add label to conversation" +msgid "Add label to conversation" +msgid_plural "Add label to conversations" +msgstr[0] "Adiciona marcador à conversa" +msgstr[1] "Adiciona marcador às conversas" + +#: src/client/components/main-toolbar.vala:161 +#| msgid "Move conversation" +msgid "Move conversation" +msgid_plural "Move conversations" +msgstr[0] "Move a conversa" +msgstr[1] "Move as conversas" + +#: src/client/components/main-toolbar.vala:166 +#| msgid "Archive conversation (A)" +msgid "Archive conversation (A)" +msgid_plural "Archive conversations (A)" +msgstr[0] "Arquiva a conversa (A)" +msgstr[1] "Arquiva as conversas (A)" + +#: src/client/components/main-toolbar.vala:175 +#| msgid "Move conversation to Trash (Delete, Backspace)" +msgid "Move conversation to Trash (Delete, Backspace)" +msgid_plural "Move conversations to Trash (Delete, Backspace)" +msgstr[0] "Move a conversa para a lixeira (Delete, Backspace)" +msgstr[1] "Move as conversas para a lixeira (Delete, Backspace)" + +#: src/client/components/main-toolbar.vala:183 +#| msgid "Delete conversation (Shift+Delete)" +msgid "Delete conversation (Shift+Delete)" +msgid_plural "Delete conversations (Shift+Delete)" +msgstr[0] "Exclui a conversa (Shift+Delete)" +msgstr[1] "Exclui as conversas (Shift+Delete)" + +#: src/client/components/main-window.vala:503 #, c-format msgid "%s (%d)" msgstr "%s (%d)" -#: ../src/client/components/search-bar.vala:8 -#: ../src/client/folder-list/folder-list-search-branch.vala:38 -#: ../src/engine/api/geary-special-folder-type.vala:51 +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:47 +#, c-format +msgid "Problem connecting to incoming server for %s" +msgstr "Problema ao se conectar com o servidor de entrada para %s" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:49 +#: src/client/components/main-window-info-bar.vala:58 +#, c-format +msgid "" +"Could not connect to %s, check your Internet access and the server name and " +"try again" +msgstr "" +"Não foi possível se conectar a %s; verifique seu acesso à Internet e o nome " +"do servidor e tente novamente" + +#. Translators: Tooltip label for Retry button +#. Button tooltip for retrying an account problem +#: src/client/components/main-window-info-bar.vala:51 +#: src/client/components/main-window-info-bar.vala:60 +#: src/client/components/main-window-info-bar.vala:69 +#: src/client/components/main-window-info-bar.vala:78 +#: src/client/components/main-window-info-bar.vala:87 +#: src/client/components/main-window-info-bar.vala:96 +#: src/client/components/main-window-info-bar.vala:136 ui/main-window.ui:265 +msgid "Try reconnecting" +msgstr "Tentar reconexão" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:56 +#, c-format +msgid "Problem connecting to outgoing server for %s" +msgstr "Problema ao se conectar com o servidor de saída para %s" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:65 +#: src/client/components/main-window-info-bar.vala:83 +#, c-format +msgid "Problem communicating with incoming server for %s" +msgstr "Problema ao ser comunicar com o servidor de entrada para %s" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:67 +#: src/client/components/main-window-info-bar.vala:76 +#, c-format +msgid "Network error talking to %s, check your Internet access and try again" +msgstr "" +"Erro de rede ao falar com %s; verifique seu acesso à Internet e tente " +"novamente" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:74 +#: src/client/components/main-window-info-bar.vala:91 +msgid "Problem communicating with outgoing mail server" +msgstr "Problema ao se comunicar com o servidor de saída de e-mail" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:85 +#, c-format +msgid "" +"Geary did not understand a message from %s or vice versa, please file a bug " +"report" +msgstr "" +"O geary não entendeu uma mensagem de %s ou vice-versa; por favor, preencha " +"um relatório de erro" + +#. Translators: First string substitution is the server +#. name, second is the account name +#: src/client/components/main-window-info-bar.vala:94 +#, c-format +msgid "" +"Could not communicate with %s for %s, check the server name and try again in " +"a moment" +msgstr "" +"Não foi possível se comunicar com %s por %s, verifique o nome do servidor e " +"tente novamente em um momento" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:101 +#, c-format +msgid "Incoming mail server password required for %s" +msgstr "Senha de servidor de e-mail de entrada exigida para %s" + +#: src/client/components/main-window-info-bar.vala:102 +msgid "Messages cannot be received without the correct password." +msgstr "Mensagens não podem ser recebidas sem a senha correta." + +#. Translators: Tooltip label for Retry button +#: src/client/components/main-window-info-bar.vala:104 +msgid "Retry receiving email, you will be prompted for a password" +msgstr "" +"Tentar novamente recebimento de e-mail; você será solicitado a inserir uma " +"senha" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:109 +#, c-format +msgid "Outgoing mail server password required for %s" +msgstr "Senha de servidor de e-mail de saída exigida para %s" + +#: src/client/components/main-window-info-bar.vala:110 +msgid "Messages cannot be sent without the correct password." +msgstr "Mensagens não podem ser enviadas sem a senha correta." + +#. Translators: Tooltip label for Retry button +#: src/client/components/main-window-info-bar.vala:112 +msgid "Retry sending queued messages, you will be prompted for a password" +msgstr "" +"Tentar novamente enviar mensagens enfileiradas; você será solicitado a " +"inserir uma senha" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:117 +#, c-format +msgid "Incoming mail server security is not trusted for %s" +msgstr "A segurança de servidor de entrada de e-mail não é confiado para %s" + +#: src/client/components/main-window-info-bar.vala:118 +msgid "Messages will not be received until checked." +msgstr "Mensagens não serão recebidas até estar verificado." + +#. Translators: Tooltip label for Retry button +#: src/client/components/main-window-info-bar.vala:120 +#: src/client/components/main-window-info-bar.vala:128 +msgid "Check security details" +msgstr "Verificar detalhes de segurança" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:125 +#, c-format +msgid "Outgoing mail server security is not trusted for %s" +msgstr "A segurança de servidor de saída de e-mail não é confiado para %s" + +#: src/client/components/main-window-info-bar.vala:126 +msgid "Messages cannot be sent until checked." +msgstr "Mensagens não podem ser enviadas até estar verificado." + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:133 +#, c-format +msgid "A problem occurred checking mail for %s" +msgstr "Um problema ocorreu a verificar e-mail para %s" + +#: src/client/components/main-window-info-bar.vala:134 +#: src/client/components/main-window-info-bar.vala:142 +msgid "Something went wrong, please file a bug report if the problem persists" +msgstr "" +"Algo deu errado; por favor, preencha um relatório de erro se o problema " +"persistir" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:141 +#, c-format +msgid "A problem occurred sending mail for %s" +msgstr "Um problema ocorreu a enviar e-mail para %s" + +#. Translators: Tooltip label for Retry button +#: src/client/components/main-window-info-bar.vala:144 +msgid "Retry sending queued messages" +msgstr "Tentar novamente enviar mensagens enfileiradas" + +#: src/client/components/main-window-info-bar.vala:155 +msgid "A database problem has occurred" +msgstr "Ocorreu um problema de banco de dados" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:157 +#, c-format +msgid "Messages for %s must be downloaded again." +msgstr "Mensagens para %s devem ser baixadas novamente." + +#: src/client/components/main-window-info-bar.vala:170 +msgid "Geary has encountered a problem" +msgstr "Geary encontrou um problema" + +#: src/client/components/main-window-info-bar.vala:171 +msgid "" +"Please check the technical details and report the problem if it persists." +msgstr "" +"Por favor, verifique os detalhes técnicos e relate o problema se ele " +"persistir." + +#: src/client/components/main-window-info-bar.vala:179 +msgid "_Details" +msgstr "_Detalhes" + +#. Button tooltip for displaying technical details about an account problem +#: src/client/components/main-window-info-bar.vala:180 ui/main-window.ui:250 +msgid "View technical details about the error" +msgstr "Veja detalhes técnicos sobre o erro" + +#: src/client/components/main-window-info-bar.vala:184 +msgid "_Retry" +msgstr "_Tentar novamente" + +#: src/client/components/search-bar.vala:8 +#: src/client/folder-list/folder-list-search-branch.vala:38 +#: src/engine/api/geary-special-folder-type.vala:51 msgid "Search" msgstr "Pesquisar" #. Search entry. -#: ../src/client/components/search-bar.vala:23 +#: src/client/components/search-bar.vala:23 msgid "Search all mail in account for keywords (Ctrl+S)" msgstr "Pesquisar todos os e-mails na conta por palavras-chave (Ctrl+S)" -#: ../src/client/components/search-bar.vala:100 +#: src/client/components/search-bar.vala:101 #, c-format msgid "Indexing %s account" msgstr "Indexando a conta %s" -#: ../src/client/components/search-bar.vala:111 -#: ../src/client/folder-list/folder-list-search-branch.vala:39 +#: src/client/components/search-bar.vala:112 +#: src/client/folder-list/folder-list-search-branch.vala:39 #, c-format msgid "Search %s account" msgstr "Pesquisar conta %s" #. / Displayed in the space-limited status bar while a message is in the process of being sent. -#: ../src/client/components/status-bar.vala:26 +#: src/client/components/status-bar.vala:26 msgid "Sending…" msgstr "Enviando…" -#: ../src/client/components/stock.vala:18 ../ui/account_cannot_remove.glade.h:3 +#. / Displayed in the space-limited status bar when a message fails to be sent due to error. +#: src/client/components/status-bar.vala:29 +msgid "Error sending email" +msgstr "Erro ao enviar e-mail" + +#. Displayed in the space-limited status bar when a message fails to be uploaded +#. to Sent Mail after being sent. +#: src/client/components/status-bar.vala:33 +msgid "Error saving sent mail" +msgstr "Erro ao salvar e-mail enviado" + +#: src/client/components/stock.vala:18 msgid "_OK" msgstr "_OK" -#: ../src/client/components/stock.vala:19 ../ui/edit_alternate_emails.glade.h:3 -#: ../ui/password-dialog.glade.h:5 ../ui/remove_confirm.glade.h:5 +#: src/client/components/stock.vala:19 ui/password-dialog.glade:196 msgid "_Cancel" msgstr "_Cancelar" -#: ../src/client/components/stock.vala:21 ../ui/gtk/menus.ui.h:6 +#: src/client/components/stock.vala:21 msgid "_About" msgstr "_Sobre" -#: ../src/client/components/stock.vala:23 +#: src/client/components/stock.vala:22 +msgid "_Add" +msgstr "_Adicionar" + +#: src/client/components/stock.vala:23 msgid "_Close" msgstr "_Fechar" -#: ../src/client/components/stock.vala:24 +#: src/client/components/stock.vala:24 msgid "_Discard" msgstr "_Descartar" -#: ../src/client/components/stock.vala:25 ../ui/gtk/menus.ui.h:5 +#: src/client/components/stock.vala:25 ui/main-toolbar-menus.ui:56 msgid "_Help" msgstr "_Ajuda" -#: ../src/client/components/stock.vala:26 ../ui/conversation-email-menus.ui.h:9 +#: src/client/components/stock.vala:26 ui/conversation-email-menus.ui:77 msgid "_Open" msgstr "_Abrir" -#: ../src/client/components/stock.vala:27 ../ui/gtk/menus.ui.h:3 +#: src/client/components/stock.vala:27 ui/main-toolbar-menus.ui:46 msgid "_Preferences" msgstr "_Preferências" -#: ../src/client/components/stock.vala:28 ../ui/conversation-email-menus.ui.h:7 +#. Translators: Menu item to print a single, specific message +#: src/client/components/stock.vala:28 ui/conversation-email-menus.ui:64 msgid "_Print…" msgstr "_Imprimir…" -#: ../src/client/components/stock.vala:29 ../ui/gtk/menus.ui.h:7 +#: src/client/components/stock.vala:29 msgid "_Quit" msgstr "_Fechar" -#: ../src/client/components/stock.vala:30 ../ui/remove_confirm.glade.h:6 +#: src/client/components/stock.vala:30 msgid "_Remove" msgstr "_Remover" -#: ../src/client/components/stock.vala:32 +#: src/client/components/stock.vala:31 ui/conversation-email-menus.ui:83 +msgid "_Save" +msgstr "_Salvar" + +#: src/client/components/stock.vala:32 msgid "_Keep" msgstr "_Manter" -#: ../src/client/composer/composer-link-popover.vala:150 +#: src/client/composer/composer-link-popover.vala:149 msgid "Link URL is not correctly formatted, e.g. http://example.com" msgstr "URL de link não está corretamente formatado, ex.: http://example.com" -#: ../src/client/composer/composer-link-popover.vala:157 +#: src/client/composer/composer-link-popover.vala:156 msgid "Invalid link URL" msgstr "URL de link inválido" -#: ../src/client/composer/composer-link-popover.vala:157 +#: src/client/composer/composer-link-popover.vala:156 msgid "Invalid email address" msgstr "Endereço de e-mail inválido" -#: ../src/client/composer/composer-widget.vala:154 +#: src/client/composer/composer-widget.vala:150 msgid "Saved" msgstr "Salvo" -#: ../src/client/composer/composer-widget.vala:155 +#: src/client/composer/composer-widget.vala:151 msgid "Saving" msgstr "Salvando" -#: ../src/client/composer/composer-widget.vala:156 +#: src/client/composer/composer-widget.vala:152 msgid "Error saving" msgstr "Erro ao salvar" -#: ../src/client/composer/composer-widget.vala:157 +#: src/client/composer/composer-widget.vala:153 msgid "Press Backspace to delete quote" msgstr "Pressione Backspace para excluir citação" -#: ../src/client/composer/composer-widget.vala:158 -msgid "New Message" -msgstr "Nova mensagem" - #. Translators: This is list of keywords, separated by pipe ("|") #. characters, that suggest an attachment; since this is full-word #. checking, include all variants of each word. No spaces are #. allowed. -#: ../src/client/composer/composer-widget.vala:167 +#: src/client/composer/composer-widget.vala:162 msgid "" "attach|attaching|attaches|attachment|attachments|attached|enclose|enclosed|" "enclosing|encloses|enclosure|enclosures" @@ -923,28 +1473,37 @@ "anexar|anexando|anexa|anexo|anexos|anexado|anexada|inserir|inserido|" "inserindo|insere|inserido|inserida|inseridos|inseridas" -#: ../src/client/composer/composer-widget.vala:1122 -#: ../src/client/composer/composer-widget.vala:1141 -msgid "Do you want to discard this message?" -msgstr "Você deseja descartar esta mensagem?" +#. Translators: This dialog text is displayed to the +#. user when closing a composer where the options are +#. Keep, Discard or Cancel. +#: src/client/composer/composer-widget.vala:1121 +msgid "Do you want to keep or discard this draft message?" +msgstr "Você deseja manter ou descartar esta mensagem de rascunho?" + +#. Translators: This dialog text is displayed to the +#. user when closing a composer where the options are +#. only Discard or Cancel. +#: src/client/composer/composer-widget.vala:1149 +msgid "Do you want to discard this draft message?" +msgstr "Você deseja descartar esta mensagem de rascunho?" -#: ../src/client/composer/composer-widget.vala:1245 +#: src/client/composer/composer-widget.vala:1266 msgid "Send message with an empty subject and body?" msgstr "Enviar mensagem com o assunto e corpo vazio?" -#: ../src/client/composer/composer-widget.vala:1247 +#: src/client/composer/composer-widget.vala:1268 msgid "Send message with an empty subject?" msgstr "Enviar mensagem com o assunto vazio?" -#: ../src/client/composer/composer-widget.vala:1249 +#: src/client/composer/composer-widget.vala:1270 msgid "Send message with an empty body?" msgstr "Enviar mensagem com o corpo vazio?" -#: ../src/client/composer/composer-widget.vala:1253 +#: src/client/composer/composer-widget.vala:1274 msgid "Send message without an attachment?" msgstr "Enviar mensagem sem anexo?" -#: ../src/client/composer/composer-widget.vala:1515 +#: src/client/composer/composer-widget.vala:1579 #, c-format msgid "“%s” already attached for delivery." msgstr "“%s” já foi anexado para envio." @@ -954,169 +1513,261 @@ #. description of the document type, the second will #. be a human-friendly size string. For example: #. Document (100.9MB) -#: ../src/client/composer/composer-widget.vala:1523 -#: ../src/client/conversation-viewer/conversation-email.vala:138 +#: src/client/composer/composer-widget.vala:1587 +#: src/client/conversation-viewer/conversation-email.vala:173 #, c-format msgid "%s (%s)" msgstr "%s (%s)" -#: ../src/client/composer/composer-widget.vala:1560 +#: src/client/composer/composer-widget.vala:1624 #, c-format msgid "“%s” could not be found." msgstr "“%s” não foi encontrado." -#: ../src/client/composer/composer-widget.vala:1566 +#: src/client/composer/composer-widget.vala:1630 #, c-format msgid "“%s” is a folder." msgstr "“%s” é uma pasta." -#: ../src/client/composer/composer-widget.vala:1572 +#: src/client/composer/composer-widget.vala:1636 #, c-format msgid "“%s” is an empty file." msgstr "“%s” é um arquivo vazio." -#: ../src/client/composer/composer-widget.vala:1585 +#: src/client/composer/composer-widget.vala:1649 #, c-format msgid "“%s” could not be opened for reading." msgstr "“%s” não pôde ser aberto para leitura." -#: ../src/client/composer/composer-widget.vala:1593 +#: src/client/composer/composer-widget.vala:1657 msgid "Cannot add attachment" msgstr "Não é possível adicionar anexo" -#: ../src/client/composer/composer-widget.vala:1645 -msgid "To: " -msgstr "Para: " - -#: ../src/client/composer/composer-widget.vala:1648 -msgid "Cc: " -msgstr "Cc: " - -#: ../src/client/composer/composer-widget.vala:1651 -msgid "Bcc: " -msgstr "Cco: " +#. Translators: Human-readable version of the RFC 822 To header +#: src/client/composer/composer-widget.vala:1707 +#: src/client/conversation-viewer/conversation-email.vala:975 +#: src/engine/rfc822/rfc822-utils.vala:298 ui/conversation-message.ui:313 +msgid "To:" +msgstr "Para:" + +#. Translators: Human-readable version of the RFC 822 CC header +#: src/client/composer/composer-widget.vala:1713 +#: src/client/conversation-viewer/conversation-email.vala:980 +#: src/engine/rfc822/rfc822-utils.vala:303 ui/conversation-message.ui:358 +msgid "Cc:" +msgstr "Cc:" + +#. Translators: Human-readable version of the RFC 822 BCC header +#: src/client/composer/composer-widget.vala:1719 +#: src/client/conversation-viewer/conversation-email.vala:985 +#: ui/conversation-message.ui:403 +msgid "Bcc:" +msgstr "Cco:" -#: ../src/client/composer/composer-widget.vala:1654 +#. Translators: Human-readable version of the RFC 822 Reply-To header +#: src/client/composer/composer-widget.vala:1725 msgid "Reply-To: " msgstr "Responder-a: " -#: ../src/client/composer/composer-widget.vala:1786 +#: src/client/composer/composer-widget.vala:1865 msgid "Select Color" msgstr "Selecione a cor" -#. Displayed in the From dropdown to indicate an "alternate email address" -#. for an account. The first printf argument will be the alternate email -#. address, and the second will be the account's primary email address. -#: ../src/client/composer/composer-widget.vala:1986 +#. Displayed in the From dropdown to indicate an +#. "alternate email address" for an account. The first +#. printf argument will be the alternate email address, +#. and the second will be the account's primary email +#. address. +#: src/client/composer/composer-widget.vala:2055 #, c-format msgid "%1$s via %2$s" msgstr "%1$s via %2$s" #. Composer label (with mnemonic underscore) for the account selector #. when choosing what address to send a message from. -#: ../src/client/composer/composer-widget.vala:2028 +#: src/client/composer/composer-widget.vala:2116 msgid "_From:" msgstr "_De:" #. Translators: This is the name of the file chooser filter #. when inserting an image in the composer. -#: ../src/client/composer/composer-widget.vala:2252 +#: src/client/composer/composer-widget.vala:2344 msgid "Images" msgstr "Imagens" -#: ../src/client/composer/spell-check-popover.vala:117 +#: src/client/composer/composer-window.vala:14 +msgid "New Message" +msgstr "Nova mensagem" + +#: src/client/composer/spell-check-popover.vala:117 msgid "Remove this language from the preferred list" msgstr "Remover esse idioma da lista de preferência" -#: ../src/client/composer/spell-check-popover.vala:121 +#: src/client/composer/spell-check-popover.vala:121 msgid "Add this language to the preferred list" msgstr "Adicionar esse idioma à lista de preferência" -#: ../src/client/composer/spell-check-popover.vala:217 +#: src/client/composer/spell-check-popover.vala:217 msgid "Search for more languages" msgstr "Procurar por mais idiomas" -#: ../src/client/conversation-list/formatted-conversation-data.vala:11 +#: src/client/conversation-list/conversation-list-view.vala:283 +msgid "Delete conversation" +msgstr "Excluir conversa" + +#: src/client/conversation-list/conversation-list-view.vala:286 +#: ui/main-toolbar-menus.ui:5 +msgid "Mark as _Read" +msgstr "Marcar como l_ida" + +#: src/client/conversation-list/conversation-list-view.vala:289 +#: ui/main-toolbar-menus.ui:9 +msgid "Mark as _Unread" +msgstr "Marcar como _não lida" + +#: src/client/conversation-list/conversation-list-view.vala:292 +#: ui/main-toolbar-menus.ui:17 +msgid "U_nstar" +msgstr "_Tirar estrela" + +#: src/client/conversation-list/conversation-list-view.vala:294 +#: ui/main-toolbar-menus.ui:13 +msgid "_Star" +msgstr "_Marcar estrela" + +#. Translators: Menu item to reply to a specific message. +#: src/client/conversation-list/conversation-list-view.vala:297 +#: ui/conversation-email-menus.ui:9 +msgid "_Reply" +msgstr "_Responder" + +#: src/client/conversation-list/conversation-list-view.vala:298 +msgid "R_eply All" +msgstr "R_esponder todos" + +#. Translators: Menu item to forward a specific message. +#: src/client/conversation-list/conversation-list-view.vala:299 +#: ui/conversation-email-menus.ui:21 +msgid "_Forward" +msgstr "_Encaminhar" + +#: src/client/conversation-list/formatted-conversation-data.vala:11 msgid "Me" -msgstr "Mim" +msgstr "Eu" #. Translators: This is the file type displayed for #. attachments with unknown file types. -#: ../src/client/conversation-viewer/conversation-email.vala:124 +#: src/client/conversation-viewer/conversation-email.vala:159 msgid "Unknown" msgstr "Desconhecido" -#. Preview headers +#. Translators: Human-readable version of the RFC 822 From header +#: src/client/conversation-viewer/conversation-email.vala:970 +#: src/engine/rfc822/rfc822-utils.vala:289 +msgid "From:" +msgstr "De:" + +#. Translators: Human-readable version of the RFC 822 Date header +#: src/client/conversation-viewer/conversation-email.vala:990 +#: src/engine/rfc822/rfc822-utils.vala:294 +msgid "Date:" +msgstr "Data:" + +#. Translators: Human-readable version of the RFC 822 Subject header +#: src/client/conversation-viewer/conversation-email.vala:995 +#: src/engine/rfc822/rfc822-utils.vala:292 +msgid "Subject:" +msgstr "Assunto:" + +#: src/client/conversation-viewer/conversation-message.vala:65 +msgid "This email address may have been forged" +msgstr "Esse endereço de e-mail foi forjado" + +#. Compact headers #. Translators: This is displayed in place of the from address #. when the message has no from address. -#: ../src/client/conversation-viewer/conversation-message.vala:331 +#: src/client/conversation-viewer/conversation-message.vala:394 msgid "No sender" msgstr "Sem remetente" #. Translators: This separates multiple 'from' -#. addresses in the header preview for a message. -#: ../src/client/conversation-viewer/conversation-message.vala:586 +#. addresses in the compact header for a message. +#: src/client/conversation-viewer/conversation-message.vala:767 msgid ", " msgstr ", " #. Translators: This string is used as the HTML IMG ALT #. attribute value when displaying an inline image in an email #. that did not specify a file name. E.g. ImageCannot remove account " -msgstr "" -"Não foi possível remover a conta " +msgid "Confirm removing: %s" +msgstr "Confirmar remoção: %s" -#: ../ui/account_cannot_remove.glade.h:2 +#: ui/accounts_editor_remove_pane.ui:91 msgid "" -"A composer window associated with this account is currently open. Send or " -"discard the message and try again." +"Removing an account will remove it from Geary and delete locally cached " +"email data from your computer, but not from your service provider." msgstr "" -"Uma janela de redação, associada com esta conta, está atualmente aberta. " -"Envie ou descarte a mensagem e tente novamente." - -#: ../ui/account_list.glade.h:1 -msgid "Add account" -msgstr "Adiciona conta" +"A remoção de uma conta a removerá do Geary e excluirá os dados de e-mail " +"armazenados em cache localmente do seu computador, mas não do provedor de " +"serviços." -#: ../ui/account_list.glade.h:2 -msgid "Edit account" -msgstr "Edita conta" - -#: ../ui/account_list.glade.h:3 +#: ui/accounts_editor_remove_pane.ui:122 msgid "Remove account" msgstr "Remove conta" -#: ../ui/account_spinner.glade.h:1 -msgid "Please wait while Geary validates your account." -msgstr "Por favor, aguarde enquanto o Geary valida sua conta." +#: ui/accounts_editor_servers_pane.ui:17 +msgid "Cancel" +msgstr "Cancelar" + +#: ui/accounts_editor_servers_pane.ui:47 +msgid "Apply" +msgstr "Aplicar" -#: ../ui/certificate_warning_dialog.glade.h:1 +#: ui/certificate_warning_dialog.glade:7 msgid "Untrusted Connection" msgstr "Conexão não confiável" -#: ../ui/certificate_warning_dialog.glade.h:2 +#: ui/certificate_warning_dialog.glade:29 msgid "_Always Trust This Server" msgstr "_Sempre confiar nesse servidor" -#: ../ui/certificate_warning_dialog.glade.h:3 +#: ui/certificate_warning_dialog.glade:43 msgid "_Trust This Server" msgstr "_Confiar nesse servidor" -#: ../ui/certificate_warning_dialog.glade.h:4 +#: ui/certificate_warning_dialog.glade:57 msgid "_Don’t Trust This Server" msgstr "_Não confiar nesse servidor" -#: ../ui/composer-headerbar.ui.h:1 +#: ui/composer-headerbar.ui:17 ui/composer-headerbar.ui:174 msgid "Detach (Ctrl+D)" msgstr "Desanexa (Ctrl+D)" -#: ../ui/composer-headerbar.ui.h:2 +#: ui/composer-headerbar.ui:57 ui/composer-headerbar.ui:83 msgid "Attach File (Ctrl+T)" msgstr "Anexa um arquivo (Ctrl+T)" -#: ../ui/composer-headerbar.ui.h:3 +#: ui/composer-headerbar.ui:107 msgid "Include Original Attachments" msgstr "Inclui anexos originais" -#: ../ui/composer-headerbar.ui.h:4 -msgid "Send (Ctrl+Enter)" -msgstr "Envia (Ctrl+Enter)" - -#: ../ui/composer-headerbar.ui.h:5 +#: ui/composer-headerbar.ui:202 msgid "_Send" msgstr "_Enviar" -#: ../ui/composer-headerbar.ui.h:6 +#: ui/composer-headerbar.ui:207 +msgid "Send (Ctrl+Enter)" +msgstr "Envia (Ctrl+Enter)" + +#: ui/composer-headerbar.ui:230 msgid "Discard and Close" msgstr "Descarta e fecha" -#: ../ui/composer-headerbar.ui.h:7 +#: ui/composer-headerbar.ui:254 msgid "Save and Close" msgstr "Salva e fecha" #. Note that this button and the Update button will never be shown at the same time to the user. -#: ../ui/composer-link-popover.ui.h:2 +#: ui/composer-link-popover.ui:41 msgid "Insert the new link with this URL" msgstr "Insere o novo link com esse URL" -#: ../ui/composer-link-popover.ui.h:3 +#: ui/composer-link-popover.ui:52 msgid "Link URL" msgstr "URL do link" #. Note that this button and the Insert button will never be shown at the same time to the user. -#: ../ui/composer-link-popover.ui.h:5 +#: ui/composer-link-popover.ui:66 msgid "Update this link’s URL" msgstr "Atualiza a URL desse link" -#: ../ui/composer-link-popover.ui.h:6 +#: ui/composer-link-popover.ui:86 msgid "Delete this link" msgstr "Exclui esse link" -#: ../ui/composer-link-popover.ui.h:7 +#: ui/composer-link-popover.ui:106 msgid "Open this link" msgstr "Abre esse link" -#: ../ui/composer-menus.ui.h:1 +#: ui/composer-menus.ui:7 msgid "S_ans Serif" msgstr "S_ans Serif" -#: ../ui/composer-menus.ui.h:2 +#: ui/composer-menus.ui:12 msgid "S_erif" msgstr "S_erif" -#: ../ui/composer-menus.ui.h:3 +#: ui/composer-menus.ui:17 msgid "_Fixed Width" msgstr "Largura _fixa" -#: ../ui/composer-menus.ui.h:4 +#: ui/composer-menus.ui:24 msgid "_Small" msgstr "_Pequeno" -#: ../ui/composer-menus.ui.h:5 +#: ui/composer-menus.ui:29 msgid "_Medium" msgstr "_Médio" -#: ../ui/composer-menus.ui.h:6 +#: ui/composer-menus.ui:34 msgid "Lar_ge" msgstr "_Grande" -#: ../ui/composer-menus.ui.h:7 +#: ui/composer-menus.ui:41 msgid "C_olor" msgstr "C_or" -#: ../ui/composer-menus.ui.h:8 +#: ui/composer-menus.ui:47 ui/composer-menus.ui:62 msgid "_Rich Text" msgstr "Texto _rico" -#: ../ui/composer-menus.ui.h:9 +#: ui/composer-menus.ui:53 ui/composer-menus.ui:68 msgid "Show Extended Fields" msgstr "Mostras campos estendidos" -#: ../ui/composer-menus.ui.h:10 +#: ui/composer-menus.ui:78 msgid "_Undo" msgstr "_Desfazer" -#: ../ui/composer-menus.ui.h:11 +#: ui/composer-menus.ui:82 msgid "_Redo" msgstr "_Refazer" -#: ../ui/composer-menus.ui.h:12 +#: ui/composer-menus.ui:88 ui/composer-menus.ui:106 msgid "Cu_t" msgstr "Recor_tar" -#: ../ui/composer-menus.ui.h:13 ../ui/conversation-message-menus.ui.h:7 +#: ui/composer-menus.ui:92 ui/composer-menus.ui:110 +#: ui/conversation-message-menus.ui:37 msgid "_Copy" msgstr "_Copiar" -#: ../ui/composer-menus.ui.h:14 +#: ui/composer-menus.ui:96 ui/composer-menus.ui:114 msgid "_Paste" msgstr "_Colar" -#: ../ui/composer-menus.ui.h:15 -msgctxt "Clipboard paste with rich text" -msgid "Paste _With Formatting" -msgstr "Colar _com formatação" +#: ui/composer-menus.ui:100 +msgctxt "Clipboard paste as plain text" +msgid "Paste _Without Formatting" +msgstr "Colar _sem formatação" -#: ../ui/composer-menus.ui.h:16 +#: ui/composer-menus.ui:120 msgid "Select _All" msgstr "Selecionar _tudo" -#: ../ui/composer-menus.ui.h:17 ../ui/conversation-message-menus.ui.h:9 +#: ui/composer-menus.ui:127 ui/conversation-message-menus.ui:49 msgid "_Inspect…" msgstr "_Inspecionar…" #. Address(es) e-mail is to be sent to -#: ../ui/composer-widget.ui.h:2 +#: ui/composer-widget.ui:56 msgid "_To" msgstr "_Para" -#: ../ui/composer-widget.ui.h:3 +#: ui/composer-widget.ui:75 msgid "_Cc" msgstr "_Cc" -#: ../ui/composer-widget.ui.h:4 +#: ui/composer-widget.ui:130 msgid "_Subject" msgstr "A_ssunto" -#: ../ui/composer-widget.ui.h:5 +#: ui/composer-widget.ui:149 msgid "_Bcc" msgstr "Cc_o" -#: ../ui/composer-widget.ui.h:6 +#: ui/composer-widget.ui:179 msgid "_Reply-To" msgstr "_Responder-a" #. Geary account mail will be sent from -#: ../ui/composer-widget.ui.h:8 +#: ui/composer-widget.ui:208 msgid "From" msgstr "De" -#: ../ui/composer-widget.ui.h:9 +#: ui/composer-widget.ui:293 msgid "Drop files here" msgstr "Solte arquivos aqui" -#: ../ui/composer-widget.ui.h:10 +#: ui/composer-widget.ui:309 msgid "To add them as attachments" msgstr "Para adicionar eles como anexos" -#: ../ui/composer-widget.ui.h:11 +#: ui/composer-widget.ui:353 msgid "Undo last edit (Ctrl+Z)" msgstr "Desfaz a última edição (Ctrl+Z)" -#: ../ui/composer-widget.ui.h:12 +#: ui/composer-widget.ui:377 msgid "Redo last edit (Ctrl+Shift+Z)" msgstr "Refaz a última edição (Ctrl+Shift+Z)" -#: ../ui/composer-widget.ui.h:13 +#: ui/composer-widget.ui:415 msgid "Bold (Ctrl+B)" msgstr "Negrito (Ctrl+B)" -#: ../ui/composer-widget.ui.h:14 +#: ui/composer-widget.ui:439 msgid "Italic (Ctrl+I)" msgstr "Itálico (Ctrl+I)" -#: ../ui/composer-widget.ui.h:15 +#: ui/composer-widget.ui:463 msgid "Underline (Ctrl+U)" msgstr "Sublinhado (Ctrl+U)" -#: ../ui/composer-widget.ui.h:16 +#: ui/composer-widget.ui:487 msgid "Strikethrough (Ctrl+K)" msgstr "Tachado (Ctrl+K)" -#: ../ui/composer-widget.ui.h:17 +#: ui/composer-widget.ui:525 +msgid "Insert unordered list" +msgstr "Inserir lista não ordenada" + +#: ui/composer-widget.ui:549 +msgid "Insert ordered list" +msgstr "Inserir lista ordenada" + +#: ui/composer-widget.ui:587 msgid "Quote text (Ctrl+])" msgstr "Texto citado (Ctrl+])" -#: ../ui/composer-widget.ui.h:18 +#: ui/composer-widget.ui:611 msgid "Unquote text (Ctrl+[)" -msgstr "Texto não-citado (Ctrl=[)" +msgstr "Texto não citado (Ctrl=[)" -#: ../ui/composer-widget.ui.h:19 +#: ui/composer-widget.ui:649 msgid "Insert or update selection link (Ctrl+L)" msgstr "Inserir ou atualizar seleção de link (Ctrl+L)" -#: ../ui/composer-widget.ui.h:20 +#: ui/composer-widget.ui:673 msgid "Insert an image (Ctrl+G)" msgstr "Insere uma imagem (Ctrl+G)" -#: ../ui/composer-widget.ui.h:21 +#: ui/composer-widget.ui:707 msgid "Remove selection formatting (Ctrl+Space)" msgstr "Remove a formatação de seleção (Ctrl+Space)" -#: ../ui/composer-widget.ui.h:22 +#: ui/composer-widget.ui:731 msgid "Select spell checking languages" msgstr "Seleciona idiomas para verificação ortográfica" -#: ../ui/conversation-email.ui.h:1 +#: ui/conversation-email.ui:27 msgid "Save all attachments" msgstr "Salva todos os anexos" #. Note: The application will never show this button at the same time as unstar_button, one will always be hidden. -#: ../ui/conversation-email.ui.h:3 +#: ui/conversation-email.ui:50 msgid "Mark this message as starred" msgstr "Marca essa mensagem com estrela" #. Note: The application will never show this button at the same time as star_button, one will always be hidden. -#: ../ui/conversation-email.ui.h:5 +#: ui/conversation-email.ui:72 msgid "Mark this message as not starred" msgstr "Marca essa mensagem como sem estrela" -#: ../ui/conversation-email.ui.h:6 +#: ui/conversation-email.ui:95 msgid "Display the message menu" -msgstr "Exibe o menu de mensagem" +msgstr "Exibe o menu da mensagem" -#: ../ui/conversation-email.ui.h:7 +#: ui/conversation-email.ui:161 msgid "Open selected attachments" msgstr "Abre os anexos selecionados" -#: ../ui/conversation-email.ui.h:8 +#: ui/conversation-email.ui:178 msgid "Save selected attachments" msgstr "Salva os anexos selecionados" -#: ../ui/conversation-email.ui.h:9 +#: ui/conversation-email.ui:195 msgid "Select all attachments" msgstr "Seleciona todos os anexos" -#: ../ui/conversation-email.ui.h:10 +#: ui/conversation-email.ui:240 msgid "Edit Draft" -msgstr "Edita rascunho" +msgstr "Editar rascunho" -#: ../ui/conversation-email.ui.h:11 +#: ui/conversation-email.ui:267 msgid "Draft message" -msgstr "Rascunhar" +msgstr "Rascunho de mensagem" -#: ../ui/conversation-email.ui.h:12 +#: ui/conversation-email.ui:283 msgid "This message has not yet been sent." msgstr "Essa mensagem ainda não foi enviada." -#: ../ui/conversation-email.ui.h:13 -msgid "Try Again" -msgstr "Tente novamente" - -#: ../ui/conversation-email.ui.h:14 +#: ui/conversation-email.ui:329 msgid "Message not saved" msgstr "A mensagem não foi salva" -#: ../ui/conversation-email.ui.h:15 +#: ui/conversation-email.ui:345 msgid "This message was sent, but has not been saved to your account." msgstr "Essa mensagem foi enviada, mas não pôde ser salva em sua conta." -#: ../ui/conversation-email-menus.ui.h:2 +#. Translators: Menu item to reply to a specific message. +#: ui/conversation-email-menus.ui:15 msgid "Reply to _All" msgstr "Responder a _todos" -#: ../ui/conversation-email-menus.ui.h:4 +#. Translators: Menu item to mark a specific message as +#. read. +#: ui/conversation-email-menus.ui:30 msgid "_Mark Read" msgstr "_Marcar como lida" -#: ../ui/conversation-email-menus.ui.h:5 +#: ui/conversation-email-menus.ui:36 msgid "_Mark Unread" msgstr "_Marcar como não lida" -#: ../ui/conversation-email-menus.ui.h:6 +#. Translators: Menu item to mark all messages in a +#. conversation from this one as unread. +#: ui/conversation-email-menus.ui:42 msgid "Mark Unread From _Here" msgstr "Marcar como não lida a partir _daqui" -#: ../ui/conversation-email-menus.ui.h:8 +#. Translators: Menu item to move a single, specific message +#. to the trash +#: ui/conversation-email-menus.ui:50 +msgid "_Trash" +msgstr "Li_xeira" + +#. Translators: Menu item to delete a single, specific message +#: ui/conversation-email-menus.ui:57 +msgid "_Delete…" +msgstr "E_xcluir…" + +#. Translators: Menu item to view the source for a message +#: ui/conversation-email-menus.ui:69 msgid "_View Source" msgstr "_Visualizar fonte" -#: ../ui/conversation-email-menus.ui.h:11 +#: ui/conversation-email-menus.ui:87 msgid "_Save All" msgstr "_Salvar tudo" -#: ../ui/conversation-message-menus.ui.h:1 +#: ui/conversation-message-menus.ui:7 msgid "_Open Link" msgstr "_Abrir link" -#: ../ui/conversation-message-menus.ui.h:2 +#: ui/conversation-message-menus.ui:11 msgid "Copy Link _Address" msgstr "Copiar endereço do _link" -#: ../ui/conversation-message-menus.ui.h:3 +#: ui/conversation-message-menus.ui:17 msgid "Send New _Message…" msgstr "Enviar nova _mensagem…" -#: ../ui/conversation-message-menus.ui.h:4 +#: ui/conversation-message-menus.ui:21 msgid "Copy Email _Address" msgstr "Copiar endereço de _e-mail" -#: ../ui/conversation-message-menus.ui.h:5 +#: ui/conversation-message-menus.ui:27 msgid "Save _Image As…" msgstr "Salvar _imagem como…" -#: ../ui/conversation-message-menus.ui.h:6 +#: ui/conversation-message-menus.ui:33 msgid "_Select All" msgstr "_Selecionar tudo" -#: ../ui/conversation-message-menus.ui.h:8 +#: ui/conversation-message-menus.ui:43 msgid "Search for messages from" msgstr "Procurar por mensagens de" -#: ../ui/conversation-message.ui.h:1 +#: ui/conversation-message.ui:64 msgid "From " msgstr "De " -#: ../ui/conversation-message.ui.h:2 +#: ui/conversation-message.ui:80 ui/conversation-message.ui:179 msgid "1/1/1970\t" msgstr "1/1/1970\t" -#: ../ui/conversation-message.ui.h:3 +#: ui/conversation-message.ui:103 msgid "Preview body text." msgstr "Visualização do corpo do texto." -#: ../ui/conversation-message.ui.h:4 +#: ui/conversation-message.ui:203 msgid "Sent by:" msgstr "Enviado por:" -#: ../ui/conversation-message.ui.h:5 +#: ui/conversation-message.ui:248 msgid "Reply to:" msgstr "Responder para:" -#: ../ui/conversation-message.ui.h:6 +#: ui/conversation-message.ui:292 msgid "Subject" msgstr "Assunto" -#: ../ui/conversation-message.ui.h:7 -msgid "To:" -msgstr "Para:" - -#: ../ui/conversation-message.ui.h:8 -msgid "Cc:" -msgstr "Cc:" - -#: ../ui/conversation-message.ui.h:9 -msgid "Bcc:" -msgstr "Cco:" - -#: ../ui/conversation-message.ui.h:10 +#: ui/conversation-message.ui:502 msgid "Show Images" msgstr "Mostrar imagens" -#: ../ui/conversation-message.ui.h:11 +#: ui/conversation-message.ui:515 msgid "Always Show From Sender" msgstr "Mostrar sempre do remetente" -#: ../ui/conversation-message.ui.h:12 +#: ui/conversation-message.ui:543 msgid "Remote images not shown" msgstr "Imagens remotas não mostradas" # Traduzido como se fosse uma ação do sistema, porém se possível verificar se isto se trata de uma descrição de tela para o usuário (hint) --Enrico -#: ../ui/conversation-message.ui.h:13 +#: ui/conversation-message.ui:560 msgid "Only show remote images from senders you trust." msgstr "Mostrar somente imagens remotas de remetentes que você confia." -#: ../ui/conversation-message.ui.h:14 +#: ui/conversation-message.ui:693 msgid "But actually goes to:" msgstr "Mas na verdade vai para:" -#: ../ui/conversation-message.ui.h:15 +#: ui/conversation-message.ui:724 msgid "The link appears to go to:" msgstr "O link parece ir para:" -#: ../ui/conversation-message.ui.h:16 +#: ui/conversation-message.ui:736 msgid "Deceptive link found" msgstr "Link suspeito localizado" -#: ../ui/conversation-message.ui.h:17 +#: ui/conversation-message.ui:751 msgid "The email sender may be leading you to the wrong web site." msgstr "O remetente de e-mail pode estar levando você ao site errado." -#: ../ui/conversation-message.ui.h:18 +#: ui/conversation-message.ui:764 msgid "If unsure, contact the sender and ask before continuing." msgstr "" "Se estiver incerto, contate o remetente e pergunte-o antes de continuar." -#: ../ui/conversation-viewer.ui.h:1 +#: ui/conversation-viewer.ui:60 msgid "Find in conversation" msgstr "Pesquisar na conversa" -#: ../ui/conversation-viewer.ui.h:2 +#: ui/conversation-viewer.ui:74 msgid "Find the previous occurrence of the search string." msgstr "Pesquisar a ocorrência anterior do texto de pesquisa." -#: ../ui/conversation-viewer.ui.h:3 +#: ui/conversation-viewer.ui:95 msgid "Find the next occurrence of the search string." msgstr "Pesquisar a próxima ocorrência do texto de pesquisa." -#: ../ui/edit_alternate_emails.glade.h:1 -msgid "Remove email address" -msgstr "Remover endereço de e-mail" - -#: ../ui/edit_alternate_emails.glade.h:2 -msgid "" -"Some email services require additional addresses be configured on the " -"server. Contact your email provider for more information." -msgstr "" -"Alguns serviços de e-mail requerem que endereços adicionais sejam " -"configurados no servidor. Contate seu provedor de e-mail para mais " -"informação." - -#: ../ui/edit_alternate_emails.glade.h:4 -msgid "_Update" -msgstr "Atuali_zar" - -#: ../ui/find_bar.glade.h:1 +#: ui/find_bar.glade:66 msgid "Find:" msgstr "Pesquisar:" -#: ../ui/find_bar.glade.h:2 +#: ui/find_bar.glade:89 msgid "_Previous" msgstr "_Anterior" -#: ../ui/find_bar.glade.h:3 +#: ui/find_bar.glade:107 msgid "_Next" msgstr "_Próximo" -#: ../ui/find_bar.glade.h:4 +#: ui/find_bar.glade:125 msgid "_Case sensitive" msgstr "_Dif. maiúsculas" -#: ../ui/find_bar.glade.h:5 +#: ui/find_bar.glade:145 msgid "label" msgstr "marcador" -#: ../ui/gtk/help-overlay.ui.h:1 +#: ui/gtk/help-overlay.ui:9 msgid "Conversation Shortcuts" msgstr "Atalhos da conversa" -#: ../ui/gtk/help-overlay.ui.h:2 +#: ui/gtk/help-overlay.ui:13 ui/gtk/help-overlay.ui:254 msgctxt "shortcut window" msgid "General" msgstr "Geral" -#: ../ui/gtk/help-overlay.ui.h:3 +#: ui/gtk/help-overlay.ui:17 msgctxt "shortcut window" msgid "Move focus to the next/previous pane" msgstr "Move o foco para o painel próximo/anterior" -#: ../ui/gtk/help-overlay.ui.h:4 +#: ui/gtk/help-overlay.ui:24 msgctxt "shortcut window" msgid "Move focus to conversation list" msgstr "Move o foco para a lista de conversas" -#: ../ui/gtk/help-overlay.ui.h:5 +#: ui/gtk/help-overlay.ui:31 msgctxt "shortcut window" msgid "Detach composer window" msgstr "Destaca a janela do compositor" -#: ../ui/gtk/help-overlay.ui.h:6 +#: ui/gtk/help-overlay.ui:38 msgctxt "shortcut window" msgid "Close composer window" msgstr "Fecha a janela do compositor" -#: ../ui/gtk/help-overlay.ui.h:7 +#: ui/gtk/help-overlay.ui:45 msgctxt "shortcut window" msgid "Show keyboard shortcuts" msgstr "Mostra os atalhos do teclado" -#: ../ui/gtk/help-overlay.ui.h:8 +#: ui/gtk/help-overlay.ui:52 msgctxt "shortcut window" msgid "Show help" msgstr "Mostra ajuda" -#: ../ui/gtk/help-overlay.ui.h:9 +#: ui/gtk/help-overlay.ui:59 msgctxt "shortcut window" msgid "Quit the application" msgstr "Sai do aplicativo" -#: ../ui/gtk/help-overlay.ui.h:10 +#: ui/gtk/help-overlay.ui:68 msgctxt "shortcut window" msgid "Search" msgstr "Pesquisa" -#: ../ui/gtk/help-overlay.ui.h:11 +#: ui/gtk/help-overlay.ui:72 msgctxt "shortcut window" msgid "Jump to search box" msgstr "Vai para o campo de pesquisa" -#: ../ui/gtk/help-overlay.ui.h:12 +#: ui/gtk/help-overlay.ui:79 msgctxt "shortcut window" msgid "Find in current conversation" msgstr "Pesquisa na conversa atual" -#: ../ui/gtk/help-overlay.ui.h:13 +#: ui/gtk/help-overlay.ui:86 msgctxt "shortcut window" msgid "Find next/previous in current conversation" msgstr "Pesquisa próximo/anterior na conversa atual" -#: ../ui/gtk/help-overlay.ui.h:14 +#: ui/gtk/help-overlay.ui:95 ui/gtk/help-overlay.ui:274 msgctxt "shortcut window" msgid "Actions" msgstr "Ações" -#: ../ui/gtk/help-overlay.ui.h:15 +#: ui/gtk/help-overlay.ui:99 msgctxt "shortcut window" msgid "Compose a new message" msgstr "Escreve uma nova mensagem" -#: ../ui/gtk/help-overlay.ui.h:16 +#: ui/gtk/help-overlay.ui:106 msgctxt "shortcut window" msgid "Reply to sender " msgstr "Responde para o remetente " -#: ../ui/gtk/help-overlay.ui.h:17 +#: ui/gtk/help-overlay.ui:113 msgctxt "shortcut window" msgid "Reply to all" msgstr "Responde para todos" -#: ../ui/gtk/help-overlay.ui.h:18 +#: ui/gtk/help-overlay.ui:120 msgctxt "shortcut window" msgid "Forward" msgstr "Encaminha" -#: ../ui/gtk/help-overlay.ui.h:19 +#: ui/gtk/help-overlay.ui:127 msgctxt "shortcut window" msgid "Archive" msgstr "Arquiva" -#: ../ui/gtk/help-overlay.ui.h:20 +#: ui/gtk/help-overlay.ui:134 msgctxt "shortcut window" msgid "Move to trash" msgstr "Move para a lixeira" -#: ../ui/gtk/help-overlay.ui.h:21 +#: ui/gtk/help-overlay.ui:141 msgctxt "shortcut window" msgid "Toggle spam" msgstr "Marca/desmarca como spam" -#: ../ui/gtk/help-overlay.ui.h:22 +#: ui/gtk/help-overlay.ui:148 msgctxt "shortcut window" msgid "Move the conversation" msgstr "Move a conversa" -#: ../ui/gtk/help-overlay.ui.h:23 +#: ui/gtk/help-overlay.ui:155 msgctxt "shortcut window" msgid "Label the conversation" msgstr "Rotula a conversa" -#: ../ui/gtk/help-overlay.ui.h:24 +#: ui/gtk/help-overlay.ui:163 msgctxt "shortcut window" msgid "Mark read" msgstr "Marca como lida" -#: ../ui/gtk/help-overlay.ui.h:25 +#: ui/gtk/help-overlay.ui:170 msgctxt "shortcut window" msgid "Mark unread" msgstr "Marca como não lida" -#: ../ui/gtk/help-overlay.ui.h:26 +#: ui/gtk/help-overlay.ui:179 msgctxt "shortcut window" msgid "View" msgstr "Visão" -#: ../ui/gtk/help-overlay.ui.h:27 +#: ui/gtk/help-overlay.ui:183 msgctxt "shortcut window" msgid "Zoom in" msgstr "Amplia" -#: ../ui/gtk/help-overlay.ui.h:28 +#: ui/gtk/help-overlay.ui:190 msgctxt "shortcut window" msgid "Zoom out" msgstr "Reduz" -#: ../ui/gtk/help-overlay.ui.h:29 +#: ui/gtk/help-overlay.ui:197 msgctxt "shortcut window" msgid "Reset zoom" msgstr "Redefine ampliação" -#: ../ui/gtk/help-overlay.ui.h:30 +#: ui/gtk/help-overlay.ui:206 msgctxt "shortcut window" msgid "Additional Shortcuts" msgstr "Atalhos adicionais" -#: ../ui/gtk/help-overlay.ui.h:31 +#: ui/gtk/help-overlay.ui:210 msgctxt "shortcut window" msgid "Star" msgstr "Marca estrela" -#: ../ui/gtk/help-overlay.ui.h:32 +#: ui/gtk/help-overlay.ui:217 msgctxt "shortcut window" msgid "Unstar" msgstr "Remove estrela" -#: ../ui/gtk/help-overlay.ui.h:33 +#: ui/gtk/help-overlay.ui:224 msgctxt "shortcut window" msgid "Delete" msgstr "Exclui" -#: ../ui/gtk/help-overlay.ui.h:34 +#: ui/gtk/help-overlay.ui:231 msgctxt "shortcut window" msgid "Jump to next (older) conversation" msgstr "Vai para a próxima conversa (mais antiga)" -#: ../ui/gtk/help-overlay.ui.h:35 +#: ui/gtk/help-overlay.ui:238 msgctxt "shortcut window" msgid "Jump to previous (newer) conversation" msgstr "Vai para a conversa anterior (mais nova)" -#: ../ui/gtk/help-overlay.ui.h:36 +#: ui/gtk/help-overlay.ui:250 msgid "Composer Shortcuts" msgstr "Atalhos do redator" -#: ../ui/gtk/help-overlay.ui.h:37 +#: ui/gtk/help-overlay.ui:258 msgctxt "shortcut window" msgid "Quote text" msgstr "Texto citado" -#: ../ui/gtk/help-overlay.ui.h:38 +#: ui/gtk/help-overlay.ui:265 msgctxt "shortcut window" msgid "Unquote text" msgstr "Texto não-citado" -#: ../ui/gtk/help-overlay.ui.h:39 +#: ui/gtk/help-overlay.ui:278 msgctxt "shortcut window" msgid "Send" msgstr "Envia" -#: ../ui/gtk/help-overlay.ui.h:40 +#: ui/gtk/help-overlay.ui:285 msgctxt "shortcut window" msgid "Add attachment" msgstr "Adiciona anexo" -#: ../ui/gtk/help-overlay.ui.h:41 +#: ui/gtk/help-overlay.ui:294 msgctxt "shortcut window" msgid "Rich text mode" msgstr "Modo de texto rico" -#: ../ui/gtk/help-overlay.ui.h:42 +#: ui/gtk/help-overlay.ui:298 msgctxt "shortcut window" msgid "Bold text" msgstr "Negrito" -#: ../ui/gtk/help-overlay.ui.h:43 +#: ui/gtk/help-overlay.ui:305 msgctxt "shortcut window" msgid "Italicize text" msgstr "Itálico" -#: ../ui/gtk/help-overlay.ui.h:44 +#: ui/gtk/help-overlay.ui:312 msgctxt "shortcut window" msgid "Underline text" msgstr "Sublinhado" -#: ../ui/gtk/help-overlay.ui.h:45 +#: ui/gtk/help-overlay.ui:319 msgctxt "shortcut window" msgid "Strike text" msgstr "Tachado" -#: ../ui/gtk/help-overlay.ui.h:46 +#: ui/gtk/help-overlay.ui:326 msgctxt "shortcut window" msgid "Insert a link" msgstr "Insere um link" -#: ../ui/gtk/help-overlay.ui.h:47 +#: ui/gtk/help-overlay.ui:333 msgctxt "shortcut window" msgid "Remove formatting" msgstr "Remove formatação" -#: ../ui/gtk/menus.ui.h:2 -msgid "A_ccounts" -msgstr "_Contas" +#: ui/main-toolbar.ui:23 +#| msgid "Compose Message" +msgctxt "tooltip" +msgid "Compose Message" +msgstr "Escreve mensagem" -#: ../ui/gtk/menus.ui.h:4 -msgid "_Keyboard Shortcuts" -msgstr "Atalhos de _teclado" +#: ui/main-toolbar.ui:52 +msgid "Toggle search bar" +msgstr "Ativa/desativa a barra de pesquisa" -#: ../ui/login.glade.h:1 -msgid "email@example.com" -msgstr "email@exemplo.com" +#: ui/main-toolbar.ui:112 +msgid "Reply" +msgstr "Responde" -#: ../ui/login.glade.h:2 ../ui/password-dialog.glade.h:3 -msgid "Password" -msgstr "Senha" +#: ui/main-toolbar.ui:135 +msgid "Reply All" +msgstr "Responde a todos" -#: ../ui/login.glade.h:3 -msgid "E_mail address" -msgstr "Endereço de e-_mail" - -#: ../ui/login.glade.h:4 -msgid "_Password" -msgstr "_Senha" - -#: ../ui/login.glade.h:5 -msgid "S_ervice" -msgstr "S_erviço" - -#: ../ui/login.glade.h:6 -msgid "N_ame" -msgstr "N_ome" - -#: ../ui/login.glade.h:8 -msgid "N_ickname" -msgstr "A_pelido" - -#: ../ui/login.glade.h:9 -msgid "Work, Home, etc." -msgstr "Trabalho, Casa, etc." - -#: ../ui/login.glade.h:10 -msgid "_Save sent mail" -msgstr "_Salvar e-mails enviados" - -#: ../ui/login.glade.h:11 -msgid "Addi_tional email addresses…" -msgstr "Endereços de e-mail a_dicionais…" - -#: ../ui/login.glade.h:12 -msgid "IMAP settings" -msgstr "Configurações de IMAP" - -#: ../ui/login.glade.h:13 -msgid "Se_rver" -msgstr "Se_rvidor" +#: ui/main-toolbar.ui:158 +msgid "Forward" +msgstr "Encaminha" -#: ../ui/login.glade.h:14 -msgid "imap.example.com" -msgstr "imap.exemplo.com" +#: ui/main-toolbar.ui:264 +msgid "Toggle find bar" +msgstr "Ativa/desativa a barra de busca" -#: ../ui/login.glade.h:15 -msgid "P_ort" -msgstr "P_orta" +#: ui/main-toolbar.ui:306 +msgid "_Archive" +msgstr "_Arquivar" -#: ../ui/login.glade.h:16 -msgid "smtp.example.com" -msgstr "smtp.exemplo.com" +#: ui/main-toolbar-menus.ui:21 +msgid "Mark as S_pam" +msgstr "Marcar como S_pam" -#: ../ui/login.glade.h:17 -msgid "Ser_ver" -msgstr "Ser_vidor" - -#: ../ui/login.glade.h:18 -msgid "Por_t" -msgstr "Por_ta" - -#: ../ui/login.glade.h:19 -msgid "SMTP settings" -msgstr "Configurações de SMTP" - -#: ../ui/login.glade.h:20 -msgid "User_name" -msgstr "Nome de _usuário" - -#: ../ui/login.glade.h:21 -msgid "Pass_word" -msgstr "Senh_a" - -#: ../ui/login.glade.h:22 -msgid "SMTP username" -msgstr "Usuário SMTP" - -#: ../ui/login.glade.h:23 -msgid "SMTP password" -msgstr "Senha SMTP" - -#: ../ui/login.glade.h:24 -msgid "_Username" -msgstr "_Nome de usuário" - -#: ../ui/login.glade.h:25 -msgid "IMAP username" -msgstr "Usuário IMAP" - -#: ../ui/login.glade.h:26 -msgid "IMAP password" -msgstr "Senha IMAP" - -#: ../ui/login.glade.h:27 -msgid "Encr_yption" -msgstr "Encr_iptação" - -#: ../ui/login.glade.h:28 -msgid "Encrypt_ion" -msgstr "Encript_ação" - -#: ../ui/login.glade.h:30 -msgid "SSL/TLS" -msgstr "SSL/TLS" - -#: ../ui/login.glade.h:31 -msgid "STARTTLS" -msgstr "STARTTLS" - -#: ../ui/login.glade.h:32 -msgid "No authentication re_quired" -msgstr "Nenhuma autenticação re_querida" - -#: ../ui/login.glade.h:33 -msgid "Use IMAP cre_dentials" -msgstr "Usar cre_denciais de IMAP" - -#: ../ui/login.glade.h:34 -msgid "Composer" -msgstr "Redator" - -#: ../ui/login.glade.h:35 -msgid "Save dra_fts on server" -msgstr "Salvar rascun_ho no servidor" - -#: ../ui/login.glade.h:36 -msgid "Si_gn emails (HTML allowed):" -msgstr "Assi_nar e-mails (HTML permitido):" - -#: ../ui/login.glade.h:37 -msgid "Storage" -msgstr "Armazenamento" - -#: ../ui/login.glade.h:38 -msgid "_Download mail" -msgstr "Bai_xar e-mail" - -#: ../ui/main-toolbar.ui.h:1 -msgid "Empty Spam or Trash folders" -msgstr "Esvazia as pastas de spam e lixeira" +#: ui/main-toolbar-menus.ui:25 +msgid "Mark as not S_pam" +msgstr "Marcar como não S_pam" + +#: ui/main-toolbar-menus.ui:32 +msgid "Empty _Spam…" +msgstr "Esvaziar _spam…" + +#: ui/main-toolbar-menus.ui:36 +msgid "Empty _Trash…" +msgstr "Esvaziar _lixeira…" + +#: ui/main-toolbar-menus.ui:42 +#| msgid "Accounts" +msgid "_Accounts" +msgstr "Cont_as" -#: ../ui/password-dialog.glade.h:1 +#: ui/main-toolbar-menus.ui:50 +msgid "_Keyboard Shortcuts" +msgstr "Atalhos de _teclado" + +#: ui/main-toolbar-menus.ui:61 +#| msgid "_About" +msgid "_About Geary" +msgstr "_Sobre o Geary" + +#. Infobar title when one or more accounts are offline +#: ui/main-window.ui:183 +msgid "Working offline" +msgstr "Trabalhar desconectado" + +#. Label and tooltip for offline infobar +#: ui/main-window.ui:197 +msgid "" +"Your computer does not appear to be connected to the Internet.\n" +"You will not be able to send or receive email until it is re-connected." +msgstr "" +"Seu computador não parece estar conectado à Internet.\n" +"Você não poderá enviar ou receber e-mails até que seja reconectado." + +#. Label and tooltip for offline infobar +#: ui/main-window.ui:200 +msgid "You will not be able to send or receive email until re-connected." +msgstr "Você não poderá enviar ou receber e-mails até que seja reconectado." + +#. Button label for displaying technical details about an account problem +#. Dialog title for displaying technical details of a problem. Same as the button that invokes it. +#: ui/main-window.ui:247 ui/problem-details-dialog.ui:13 +msgid "Details" +msgstr "Detalhes" + +#. Button label for retrying an account problem +#: ui/main-window.ui:261 +msgid "Retry" +msgstr "Tentar novamente" + +#. Infobar title when one or more accounts have encounted an error +#: ui/main-window.ui:294 +msgid "Account problem" +msgstr "Problema na conta" + +#. Label and tooltip for account service problem infobar +#: ui/main-window.ui:308 +msgid "" +"Geary encountered a problem connecting to an account.\n" +"Please check your Internet connection, the server configuration and try " +"again." +msgstr "" +"Geary encontrou um problema ao se conectar a uma conta.\n" +"Por favor, verifique sua conexão com a Internet, a configuração do servidor " +"e tente novamente." + +#. Label and tooltip for account service problem infobar +#: ui/main-window.ui:311 +msgid "Geary encountered a problem connecting to an account." +msgstr "Geary encontrou um problema ao conectar a uma conta." + +#. Button label for retrying TLS cert validation +#: ui/main-window.ui:358 +msgid "Check" +msgstr "Verificar" + +#. Button tooltip for retrying TLS cert validation +#: ui/main-window.ui:362 +msgid "Check the security details for the connection" +msgstr "Verifica os detalhes de segurança da conexão" + +#. Infobar title when one or more accounts have a TLS cert validation error +#: ui/main-window.ui:391 +msgid "Security problem" +msgstr "Problema de segurança" + +#. Label and tooltip for TLS cert validation error infobar +#: ui/main-window.ui:405 +msgid "" +"An account has reported an untrusted server.\n" +"Please check the server configuration and try again." +msgstr "" +"Uma conta relatou um servidor não confiável.\n" +"Por favor, verifique a configuração do servidor e tente novamente." + +#. Label and tooltip for TLS cert validation error infobar +#: ui/main-window.ui:408 +msgid "An account has reported an untrusted server." +msgstr "Uma conta relatou um servidor não confiável." + +#. Button tooltip for retrying when a login error has occurred +#: ui/main-window.ui:459 +msgid "Retry login, you will be prompted for your password" +msgstr "Tente novamente, você será solicitado a inserir uma senha" + +#. Infobar title when one or more accounts have a login error +#: ui/main-window.ui:488 +msgid "Login problem" +msgstr "Problema de login" + +#. Label and tooltip for authentication problem infobar +#: ui/main-window.ui:502 +msgid "" +"An account has reported an incorrect login or password.\n" +"Please check your login name and try again." +msgstr "" +"Uma conta relatou um login ou senha incorretos.\n" +"Por favor, verifique o seu nome de login e tente novamente." + +#. Label and tooltip for authentication problem infobar +#: ui/main-window.ui:505 +msgid "An account has reported an incorrect login or password." +msgstr "Uma conta relatou um login ou senha incorretos." + +#: ui/password-dialog.glade:74 msgid "SMTP Credentials" msgstr "Credenciais de SMTP" -#: ../ui/password-dialog.glade.h:2 +#: ui/password-dialog.glade:91 msgid "Username" msgstr "Nome do usuário" -#: ../ui/password-dialog.glade.h:4 +#: ui/password-dialog.glade:152 msgid "_Remember password" msgstr "Lemb_rar senha" -#: ../ui/password-dialog.glade.h:6 +#: ui/password-dialog.glade:210 msgid "_Authenticate" msgstr "_Autenticar" -#: ../ui/preferences-dialog.ui.h:1 +#: ui/preferences-dialog.ui:38 msgid "Reading" -msgstr "Lendo" +msgstr "Leitura" -#: ../ui/preferences-dialog.ui.h:2 +#: ui/preferences-dialog.ui:51 msgid "_Automatically select next message" msgstr "Selecionar _automaticamente a próxima mensagem" -#: ../ui/preferences-dialog.ui.h:3 +#: ui/preferences-dialog.ui:70 msgid "_Display conversation preview" msgstr "_Exibir visualização de conversa" -#: ../ui/preferences-dialog.ui.h:4 +#: ui/preferences-dialog.ui:89 msgid "Use _three pane view" msgstr "Usar visão de _três painéis" -#: ../ui/preferences-dialog.ui.h:5 +#: ui/preferences-dialog.ui:113 msgid "Notifications" msgstr "Notificações" -#: ../ui/preferences-dialog.ui.h:6 +#: ui/preferences-dialog.ui:126 msgid "_Play notification sounds" msgstr "_Tocar sons de notificação" -#: ../ui/preferences-dialog.ui.h:7 +#: ui/preferences-dialog.ui:145 msgid "Show _notifications for new mail" msgstr "Exibir _notificações para novo e-mail" -#: ../ui/preferences-dialog.ui.h:8 -msgid "Always _watch for new mail" -msgstr "Sempre _monitorar por novo e-mail" +#: ui/preferences-dialog.ui:164 +msgid "_Watch for new mail when closed" +msgstr "_Monitorar por um novo e-mail quando fechado" + +#: ui/preferences-dialog.ui:168 +msgid "Geary will keep running after all windows are closed" +msgstr "Geary continuará em execução após todas as janelas estarem fechadas" -#: ../ui/preferences-dialog.ui.h:9 -msgid "Geary will run in the background and notify of new mail" -msgstr "" -"Geary será executado em segundo plano e notificará recebimento de novo e-mail" - -#: ../ui/preferences-dialog.ui.h:10 +#: ui/preferences-dialog.ui:195 msgid "Preferences" msgstr "Preferências" -#: ../ui/remove_confirm.glade.h:1 +#. Button label for copying technical information to the clipboard +#: ui/problem-details-dialog.ui:17 +msgid "Copy to Clipboard" +msgstr "Copiar para área de transferência" + +#. Button tooltip for copying technical information to the clipboard +#: ui/problem-details-dialog.ui:21 msgid "" -"Are you sure you want to remove this " -"account? " +"Copy technical details to clipboard for pasting into an email or bug report" msgstr "" -"Você tem certeza que quer remover esta " -"conta? " +"Copia os detalhes técnicos para a área de transferência para colar em um e-" +"mail ou relatório de e-mail" -#: ../ui/remove_confirm.glade.h:2 +#: ui/problem-details-dialog.ui:73 msgid "" -"All email associated with this account will be removed from your computer. " -"This will not affect email on the server." +"If the problem is serious or persists, please copy and send these details to " +"the mailing list " +"or file a new " +"bug report." msgstr "" -"Todos os e-mails associados a esta conta serão removidos do seu computador. " -"Isso não afetará os e-mails no servidor." - -#: ../ui/remove_confirm.glade.h:3 -msgid "Nickname:" -msgstr "Apelido:" +"Se o problema for sério ou persistir, por favor copie e envie esses detalhes " +"para a lista de " +"discussão ou preencha um novo relatório de erro." -#: ../ui/remove_confirm.glade.h:4 -msgid "Email address:" -msgstr "Endereço de e-mail:" +#: ui/problem-details-dialog.ui:89 +msgid "Details:" +msgstr "Detalhes:" -#: ../ui/upgrade_dialog.glade.h:1 +#: ui/upgrade_dialog.glade:60 msgid "Geary update in progress…" msgstr "Atualização do Geary em progresso…" +#~ msgid "Delete conversations (Shift+Delete)" +#~ msgstr "Exclui as conversas (Shift+Delete)" + +#~ msgid "Move conversations to Trash (Delete, Backspace)" +#~ msgstr "Move as conversas para a lixeira (Delete, Backspace)" + +#~ msgid "Archive conversations (A)" +#~ msgstr "Arquiva as conversas (A)" + +#~ msgid "Mark conversations" +#~ msgstr "Marca as conversas" + +#~ msgid "Add label to conversations" +#~ msgstr "Adiciona marcador às conversas" + +#~ msgid "Move conversations" +#~ msgstr "Move a conversas" + +#~ msgid "A_ccounts" +#~ msgstr "_Contas" + +#~ msgid "Empty Spam or Trash folders" +#~ msgstr "Esvazia as pastas de spam e lixeira" + +#~ msgid "Retry connecting now" +#~ msgstr "Tentar conexão novamente agora" + +#~ msgid "Try reconnecting now" +#~ msgstr "Tentar reconexão agora" + +#~ msgid "Problem with connection to incoming server for %s" +#~ msgstr "Problema com a conexão com o servidor de entrada para %s" + +#~ msgid "Problem with connection to outgoing server for %s" +#~ msgstr "Problema com a conexão com o servidor de saída para %s" + +#~ msgid "To: " +#~ msgstr "Para: " + +#~ msgid "Cc: " +#~ msgstr "Cc: " + +#~ msgid "Bcc: " +#~ msgstr "Cco: " + +#~ msgid "From: %s\n" +#~ msgstr "De: %s\n" + +#~ msgid "Subject: %s\n" +#~ msgstr "Assunto: %s\n" + +#~ msgid "Date: %s\n" +#~ msgstr "Data: %s\n" + +#~ msgid "To: %s\n" +#~ msgstr "Para: %s\n" + +#~ msgid "Cc: %s\n" +#~ msgstr "Cc: %s\n" + +#~ msgid "Additional addresses for %s" +#~ msgstr "Endereço adicional para %s" + +#~ msgid "First Last" +#~ msgstr "Nome Sobrenome" + +#~ msgid "Enter your account information to get started." +#~ msgstr "Entre com as informações de sua conta para iniciar." + +#~ msgid "Edit" +#~ msgstr "Editar" + +#~ msgid "Preview" +#~ msgstr "Visualizar" + +#~ msgid "Remem_ber passwords" +#~ msgstr "Lem_brar senhas" + +#~ msgid "Remem_ber password" +#~ msgstr "Lem_brar senha" + +#~ msgid "Unable to validate:\n" +#~ msgstr "Incapaz de validar:\n" + +#~ msgid " • Invalid account nickname.\n" +#~ msgstr " • Apelido de conta inválido.\n" + +#~ msgid " • Email address already added to Geary.\n" +#~ msgstr " • Endereço de e-mail já adicionado ao Geary.\n" + +#~ msgid " • IMAP connection error.\n" +#~ msgstr " • Erro de conexão ao IMAP.\n" + +#~ msgid " • IMAP username or password incorrect.\n" +#~ msgstr " • Nome de usuário ou senha de IMAP incorretos.\n" + +#~ msgid " • SMTP connection error.\n" +#~ msgstr " • Erro de conexão ao SMTP.\n" + +#~ msgid " • SMTP username or password incorrect.\n" +#~ msgstr " • Nome de usuário ou senha SMTP incorretos.\n" + +#~ msgid " • Connection error.\n" +#~ msgstr " • Erro de conexão.\n" + +#~ msgid " • Username or password incorrect.\n" +#~ msgstr " • Nome de usuário ou senha incorretos.\n" + +#~ msgid "Unable to store server trust exception" +#~ msgstr "Não foi possível armazenar exceção de confiança de servidor" + +#~ msgid "Your settings are insecure" +#~ msgstr "Suas configurações são inseguras" + +#~ msgid "" +#~ "Your IMAP and/or SMTP settings do not specify SSL or TLS. This means " +#~ "your username and password could be read by another person on the " +#~ "network. Are you sure you want to do this?" +#~ msgstr "" +#~ "As suas configurações de IMAP e/ou SMTP não especificam um SSL ou TLS. " +#~ "Isso significa que seu usuário e senha poderão ser lidos por outra pessoa " +#~ "na rede. Você tem certeza que deseja fazer isso?" + +#~ msgid "Co_ntinue" +#~ msgstr "Co_ntinuar" + +#~ msgid "" +#~ "Geary encountered an error sending an email. If the problem persists, " +#~ "please manually delete the email from your Outbox folder." +#~ msgstr "" +#~ "O Geary encontrou um erro ao enviar um e-mail. Se o problema persistir, " +#~ "por favor exclua manualmente o e-mail da sua pasta Caixa de saída." + +#~ msgid "" +#~ "Geary encountered an error saving a sent message to Sent Mail. The " +#~ "message will stay in your Outbox folder until you delete it." +#~ msgstr "" +#~ "O Geary encontrou um erro ao salvar um e-mail na pasta de Correios " +#~ "enviados. A mensagem vai permanecer em sua pasta Caixa de saída até você " +#~ "excluí-la." + +#~ msgid "Unable to open local mailbox for %s" +#~ msgstr "Incapaz de abrir a caixa de e-mail local para %s" + +#~ msgid "" +#~ "There was an error opening the local mail database for this account. This " +#~ "is possibly due to a file permissions problem.\n" +#~ "\n" +#~ "Please check that you have read/write permissions for all files in this " +#~ "directory:\n" +#~ "\n" +#~ "%s" +#~ msgstr "" +#~ "Ocorreu um erro ao abrir o banco de dados do e-mail local para esta " +#~ "conta. Isto se deve possivelmente a problema de permissões de arquivo.\n" +#~ "\n" +#~ "Por favor, verifique se você tem permissão de leitura/gravação para todos " +#~ "os arquivos neste diretório:\n" +#~ "\n" +#~ "%s" + +#~ msgid "" +#~ "The version number of the local mail database is formatted for a newer " +#~ "version of Geary. Unfortunately, the database cannot be “rolled back” to " +#~ "work with this version of Geary.\n" +#~ "\n" +#~ "Please install the latest version of Geary and try again." +#~ msgstr "" +#~ "O número da versão do banco de dados do e-mails local está formatado para " +#~ "uma versão mais recente do Geary. Infelizmente, o banco de dados não pode " +#~ "ser “revertido” para trabalhar com esta versão do Geary.\n" +#~ "\n" +#~ "Por favor, instale a última versão do Geary e tente outra vez." + +#~ msgid "" +#~ "There was an error opening the local account. This is probably due to " +#~ "connectivity issues.\n" +#~ "\n" +#~ "Please check your network connection and restart Geary." +#~ msgstr "" +#~ "Ocorreu um erro ao abrir a conta local. Isto se deve provavelmente a " +#~ "problemas de conectividade.\n" +#~ "\n" +#~ "Por favor, verifique sua conexão de rede e reinicie o Geary." + +#~ msgid "Geary will exit if you have no other open email accounts." +#~ msgstr "Geary vai sair, se você não tiver outra conta de e-mail aberta." + +#~ msgid "IMAP" +#~ msgstr "IMAP" + +#~ msgid "SMTP" +#~ msgstr "SMTP" + +#~ msgid "Other" +#~ msgstr "Outro" + +#~ msgid "Cannot remove account " +#~ msgstr "" +#~ "Não foi possível remover a conta " + +#~ msgid "" +#~ "A composer window associated with this account is currently open. Send or " +#~ "discard the message and try again." +#~ msgstr "" +#~ "Uma janela de redação, associada com esta conta, está atualmente aberta. " +#~ "Envie ou descarte a mensagem e tente novamente." + +#~ msgid "Please wait while Geary validates your account." +#~ msgstr "Por favor, aguarde enquanto o Geary valida sua conta." + +#~ msgid "" +#~ "Some email services require additional addresses be configured on the " +#~ "server. Contact your email provider for more information." +#~ msgstr "" +#~ "Alguns serviços de e-mail requerem que endereços adicionais sejam " +#~ "configurados no servidor. Contate seu provedor de e-mail para mais " +#~ "informação." + +#~ msgid "_Update" +#~ msgstr "Atuali_zar" + +#~ msgid "E_mail address" +#~ msgstr "Endereço de e-_mail" + +#~ msgid "_Password" +#~ msgstr "_Senha" + +#~ msgid "S_ervice" +#~ msgstr "S_erviço" + +#~ msgid "N_ame" +#~ msgstr "N_ome" + +#~ msgid "N_ickname" +#~ msgstr "A_pelido" + +#~ msgid "Work, Home, etc." +#~ msgstr "Trabalho, Casa, etc." + +#~ msgid "Addi_tional email addresses…" +#~ msgstr "Endereços de e-mail a_dicionais…" + +#~ msgid "IMAP settings" +#~ msgstr "Configurações de IMAP" + +#~ msgid "Se_rver" +#~ msgstr "Se_rvidor" + +#~ msgid "P_ort" +#~ msgstr "P_orta" + +#~ msgid "Ser_ver" +#~ msgstr "Ser_vidor" + +#~ msgid "Por_t" +#~ msgstr "Por_ta" + +#~ msgid "User_name" +#~ msgstr "Nome de _usuário" + +#~ msgid "Pass_word" +#~ msgstr "Senh_a" + +#~ msgid "SMTP password" +#~ msgstr "Senha SMTP" + +#~ msgid "_Username" +#~ msgstr "_Nome de usuário" + +#~ msgid "IMAP password" +#~ msgstr "Senha IMAP" + +#~ msgid "Encr_yption" +#~ msgstr "Encr_iptação" + +#~ msgid "Encrypt_ion" +#~ msgstr "Encript_ação" + +#~ msgid "STARTTLS" +#~ msgstr "STARTTLS" + +#~ msgid "No authentication re_quired" +#~ msgstr "Nenhuma autenticação re_querida" + +#~ msgid "Use IMAP cre_dentials" +#~ msgstr "Usar cre_denciais de IMAP" + +#~ msgid "Composer" +#~ msgstr "Redator" + +#~ msgid "Si_gn emails (HTML allowed):" +#~ msgstr "Assi_nar e-mails (HTML permitido):" + +#~ msgid "Storage" +#~ msgstr "Armazenamento" + +#~ msgid "" +#~ "Are you sure you want to remove " +#~ "this account? " +#~ msgstr "" +#~ "Você tem certeza que quer remover " +#~ "esta conta? " + +#~ msgid "" +#~ "All email associated with this account will be removed from your " +#~ "computer. This will not affect email on the server." +#~ msgstr "" +#~ "Todos os e-mails associados a esta conta serão removidos do seu " +#~ "computador. Isso não afetará os e-mails no servidor." + +#~ msgid "Nickname:" +#~ msgstr "Apelido:" + +#~ msgid "Default attachments directory" +#~ msgstr "Diretório padrão de anexos" + +#~ msgid "Location used when opening and saving attachments." +#~ msgstr "Localização usada ao abrir e salvar anexos." + +#~ msgid "Default print output directory" +#~ msgstr "Diretório padrão de saída de impressão" + +#~ msgid "Location used when printing to a file." +#~ msgstr "Localização usada ao imprimir para um arquivos." + +#~ msgid "Geary will run in the background and notify of new mail" +#~ msgstr "" +#~ "Geary será executado em segundo plano e notificará recebimento de novo e-" +#~ "mail" + +#~ msgid "Geary Email" +#~ msgstr "E-mail Geary" + +#~ msgid "Geary Mail" +#~ msgstr "Correio eletrônico - Geary" + +#~ msgid "_Mark as…" +#~ msgstr "_Marcar como…" + +#~ msgid "Add label" +#~ msgstr "Adicionar marcador" + +#~ msgid "_Label" +#~ msgstr "Ma_rcador" + +#~ msgid "_Move" +#~ msgstr "_Mover" + +#~ msgid "Compose new message (Ctrl+N, N)" +#~ msgstr "Compor nova mensagem (Ctrl+N, N)" + +#~ msgid "Reply (Ctrl+R, R)" +#~ msgstr "Responder (Ctrl+R, R)" + +#~ msgid "Reply all (Ctrl+Shift+R, Shift+R)" +#~ msgstr "Responder tudo (Ctrl+Shift+R, Shift+R)" + +#~ msgid "Forward (Ctrl+L, F)" +#~ msgstr "Encaminhar (Ctrl+L, F)" + +#~ msgid "" +#~ "Geary encountered an error while connecting to the server. Please try " +#~ "again in a few moments." +#~ msgstr "" +#~ "Geary encontrou um erro enquanto tentava conectar com o servidor. Por " +#~ "favor, tente novamente em alguns instantes." + +#~ msgid "Try Again" +#~ msgstr "Tente novamente" + #~ msgid "Mail Client" #~ msgstr "Cliente de e-mail" @@ -2726,12 +3774,6 @@ #~ msgid "No search results found." #~ msgstr "Nenhum resultado encontrado." -#~ msgid "From:" -#~ msgstr "De:" - -#~ msgid "Date:" -#~ msgstr "Data:" - #~ msgid "Select _Message" #~ msgstr "Selecionar _Mensagem" @@ -2827,18 +3869,9 @@ #~ msgid "_Donate" #~ msgstr "_Doação" -#~ msgid "_Delete" -#~ msgstr "E_xcluir" - -#~ msgid "_Trash" -#~ msgstr "Li_xeira" - #~ msgid "Do you want to discard the unsaved message?" #~ msgstr "Você deseja descartar a mensagem não salva?" -#~ msgid "Notify of new mail at start_up" -#~ msgstr "Notificar sobre novo correio à _inicialização" - #~ msgid "attach|enclosed|enclosing|cover letter" #~ msgstr "anexar|fechado|encerrando|carta de apresentação" @@ -2851,9 +3884,6 @@ #~ msgid "Password:" #~ msgstr "Senha:" -#~ msgid "Edit recipients" -#~ msgstr "Editar destinatários" - #~ msgid "C_lose" #~ msgstr "_Fechar" @@ -2878,8 +3908,5 @@ #~ msgid "SSL/TLS encryption:" #~ msgstr "Encriptação de SLL/TLS:" -#~ msgid "_Details" -#~ msgstr "_Detalhes" - #~ msgid "Archive conversation (Delete, Backspace, A)" #~ msgstr "Arquivar a conversa (Delete, Backspace, A)" diff -Nru geary-0.12.4/po/ro.po geary-3.32.0/po/ro.po --- geary-0.12.4/po/ro.po 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/po/ro.po 2019-03-17 13:39:29.000000000 +0000 @@ -10,10 +10,9 @@ msgid "" msgstr "" "Project-Id-Version: geary-0.4.1\n" -"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?product=geary&k" -"eywords=I18N+L10N&component=general\n" -"POT-Creation-Date: 2016-06-19 12:29+0000\n" -"PO-Revision-Date: 2016-06-20 19:30+0200\n" +"Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/geary/issues\n" +"POT-Creation-Date: 2019-03-11 08:07+0000\n" +"PO-Revision-Date: 2019-03-11 22:46+0100\n" "Last-Translator: Daniel Șerbănescu \n" "Language-Team: Gnome Romanian Translation Team\n" "Language: ro\n" @@ -21,497 +20,875 @@ "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < " -"20)) ? 1 : 2);;\n" -"X-Generator: Virtaal 0.7.1\n" +"20)) ? 1 : 2);\n" +"X-Generator: Poedit 2.2.1\n" "X-Project-Style: gnome\n" -#: ../desktop/geary.desktop.in.h:1 ../desktop/geary-autostart.desktop.in.h:1 +#: desktop/geary-attach.contract.desktop.in:3 +msgid "Send by email" +msgstr "Trimite prin email" + +#. Translators: Do NOT translate or transliterate this text (this is an icon file name)! +#: desktop/geary-attach.contract.desktop.in:5 +msgid "mail-send" +msgstr "mail-send" + +#: desktop/geary-attach.contract.desktop.in:6 +msgid "Send files using Geary" +msgstr "Trimite fișiere folosind Geary" + +#. Translators: The application name +#: desktop/geary-autostart.desktop.in:3 +#: desktop/org.gnome.Geary.appdata.xml.in:11 +#: desktop/org.gnome.Geary.desktop.in:3 +#: src/client/accounts/accounts-editor-servers-pane.vala:555 msgid "Geary" msgstr "Geary" -#: ../desktop/geary.desktop.in.h:2 ../desktop/geary-autostart.desktop.in.h:2 -#: ../src/client/application/geary-application.vala:18 -msgid "Mail Client" -msgstr "Client de email" - -#: ../desktop/geary.desktop.in.h:3 ../desktop/geary-autostart.desktop.in.h:3 -msgid "Geary Mail" -msgstr "Geary Mail" - -#: ../desktop/geary.desktop.in.h:4 ../desktop/geary-autostart.desktop.in.h:4 +#: desktop/geary-autostart.desktop.in:4 desktop/org.gnome.Geary.desktop.in:4 +msgid "Email" +msgstr "Email" + +#. Translators: The application's summary / tagline +#: desktop/geary-autostart.desktop.in:5 +#: desktop/org.gnome.Geary.appdata.xml.in:15 +#: desktop/org.gnome.Geary.desktop.in:5 +#: src/client/application/geary-application.vala:23 msgid "Send and receive email" msgstr "Trimite și primește emailuri" -#: ../desktop/geary.desktop.in.h:5 ../desktop/geary-autostart.desktop.in.h:5 +#. Translators: These are desktop search terms. Do not translate semicolons, end line with a semicolon. +#: desktop/geary-autostart.desktop.in:7 msgid "Email;E-mail;Mail;" msgstr "Email;E-mail;Mail;Poștă;Electronică;" -#: ../desktop/geary.desktop.in.h:6 +#. Translators: Do NOT translate or transliterate this text (this is an icon file name)! +#: desktop/geary-autostart.desktop.in:9 desktop/org.gnome.Geary.desktop.in:9 +msgid "org.gnome.Geary" +msgstr "org.gnome.Geary" + +#. Translators: The development team's name +#: desktop/org.gnome.Geary.appdata.xml.in:13 +msgid "Geary Development Team" +msgstr "Echipa de dezvoltare Geary" + +#: desktop/org.gnome.Geary.appdata.xml.in:17 +msgid "" +"Geary is an email application built around conversations, for the GNOME 3 " +"desktop. It allows you to read, find and send email with a straightforward, " +"modern interface." +msgstr "" +"Geary este o aplicație de email construită în jurul conversațiilor pentru " +"desktopul GNOME 3. Vă permite să citiți, găsiți și trimite email-uri dintr-o " +"interfață modernă și simplă." + +#: desktop/org.gnome.Geary.appdata.xml.in:22 +msgid "" +"Conversations allow you to read a complete discussion without having to find " +"and click from message to message." +msgstr "" +"Conversațiile vă permit să citiți o discuție completă fără a trebui să " +"căutați și să navigați de la mesaj la mesaj." + +#: desktop/org.gnome.Geary.appdata.xml.in:26 +msgid "Geary’s features include:" +msgstr "Funcționalitățile lui Geary includ:" + +#: desktop/org.gnome.Geary.appdata.xml.in:28 +msgid "Quick email account setup" +msgstr "Configurare rapidă a contului de email" + +#: desktop/org.gnome.Geary.appdata.xml.in:29 +msgid "Shows related messages together in conversations" +msgstr "Arată mesajele înrudite în conversații" + +#: desktop/org.gnome.Geary.appdata.xml.in:30 +msgid "Fast, full text and keyword search" +msgstr "Căutare rapidă după un termen în tot textul" + +#: desktop/org.gnome.Geary.appdata.xml.in:31 +msgid "Full-featured HTML and plain text message composer" +msgstr "Compunător de mesaje text și HTML cu multiple funcționalități" + +#: desktop/org.gnome.Geary.appdata.xml.in:32 +msgid "Desktop notification of new mail" +msgstr "Notificări desktop pentru noile emailuri" + +#: desktop/org.gnome.Geary.appdata.xml.in:33 +msgid "Compatible with GMail, Yahoo! Mail, Outlook.com and other IMAP servers" +msgstr "Compatibil cu GMail, Yahoo! Mail, Outlook.com și alte servere IMAP" + +#. Translators: A screenshot description. +#: desktop/org.gnome.Geary.appdata.xml.in:47 +msgid "Geary displaying a conversation" +msgstr "Geary afișând o conversație" + +#. Translators: A screenshot description. +#: desktop/org.gnome.Geary.appdata.xml.in:52 +msgid "Geary showing the rich text composer" +msgstr "Geary afișând compunătorul de text bogat" + +#. Translators: These are desktop search terms. Do not translate semicolons, end line with a semicolon. +#: desktop/org.gnome.Geary.desktop.in:7 +msgid "Mail;E-mail;IMAP;GMail;Yahoo;Hotmail;Outlook;" +msgstr "Mesaje;Mail;E-mail;IMAP;GMail;Yahoo;Hotmail;Outlook;" + +#: desktop/org.gnome.Geary.desktop.in:21 msgid "Compose Message" msgstr "Compune un mesaj" -#: ../desktop/geary-attach.contract.in.h:1 -msgid "Send by email" -msgstr "Trimite prin email" - -#: ../desktop/geary-attach.contract.in.h:2 -msgid "Send files using Geary" -msgstr "Trimite fișiere folosind Geary" - -#: ../src/client/accounts/account-dialog-add-edit-pane.vala:51 -#: ../src/client/components/stock.vala:31 -msgid "_Save" -msgstr "_Salvează" - -#: ../src/client/accounts/account-dialog-add-edit-pane.vala:51 -#: ../src/client/components/stock.vala:22 -msgid "_Add" -msgstr "_Adaugă" +#: desktop/org.gnome.Geary.gschema.xml:8 +msgid "Maximize window" +msgstr "Maximizează fereastra" + +#: desktop/org.gnome.Geary.gschema.xml:9 +msgid "True if the application window is maximized, false otherwise." +msgstr "" +"Adevărat dacă fereastra aplicației este maximizată, fals în mod contrar." + +#: desktop/org.gnome.Geary.gschema.xml:14 +msgid "Width of window" +msgstr "Lățimea ferestrei" + +#: desktop/org.gnome.Geary.gschema.xml:15 +msgid "The last recorded width of the application window." +msgstr "Ultima lățime memorată a ferestrei aplicației." + +#: desktop/org.gnome.Geary.gschema.xml:20 +msgid "Height of window" +msgstr "Înălțimea ferestrei" + +#: desktop/org.gnome.Geary.gschema.xml:21 +msgid "The last recorded height of the application window." +msgstr "Ultima înălțime memorată a ferestrei aplicației." + +#: desktop/org.gnome.Geary.gschema.xml:26 +msgid "Position of folder list pane" +msgstr "Poziția panoului cu lista dosarelor" + +#: desktop/org.gnome.Geary.gschema.xml:27 +msgid "Position of the folder list Paned grabber." +msgstr "Poziția mânerului panoului cu lista dosarelor." + +#: desktop/org.gnome.Geary.gschema.xml:32 +msgid "Position of folder list pane when horizontal" +msgstr "Poziția panoului cu lista dosarelor când este orizontal" + +#: desktop/org.gnome.Geary.gschema.xml:33 +msgid "" +"Position of the folder list Paned grabber in the horizontal orientation." +msgstr "" +"Poziția mânerului panoului cu lista dosarelor în orientarea orizontală." + +#: desktop/org.gnome.Geary.gschema.xml:38 +msgid "Position of folder list pane when vertical" +msgstr "Poziția panoului cu lista dosarelor când este vertical" + +#: desktop/org.gnome.Geary.gschema.xml:39 +msgid "Position of the folder list Paned grabber in the vertical orientation." +msgstr "Poziția mânerului panoului cu lista dosarelor în orientarea verticală." + +#: desktop/org.gnome.Geary.gschema.xml:44 +msgid "Orientation of the folder list pane" +msgstr "Orientarea panoului cu lista dosarelor" + +#: desktop/org.gnome.Geary.gschema.xml:45 +msgid "True if the folder list Paned is in the horizontal orientation." +msgstr "" +"Adevărat dacă mânerul panoului cu lista dosarelor este în orientare " +"orizontală." + +#: desktop/org.gnome.Geary.gschema.xml:50 +msgid "Position of message list pane" +msgstr "Poziția panoului cu lista mesajelor" + +#: desktop/org.gnome.Geary.gschema.xml:51 +msgid "Position of the message list Paned grabber." +msgstr "Poziția mânerului panoului cu lista mesajelor." + +#: desktop/org.gnome.Geary.gschema.xml:56 +msgid "Autoselect next message" +msgstr "Autoselectează noul mesaj" + +#: desktop/org.gnome.Geary.gschema.xml:57 +msgid "True if we should autoselect the next available conversation." +msgstr "Adevărat dacă ar trebui detectată următoarea conversație disponibilă." + +#: desktop/org.gnome.Geary.gschema.xml:62 +msgid "Display message previews" +msgstr "Afișează previzualizări pentru mesaje" + +#: desktop/org.gnome.Geary.gschema.xml:63 +msgid "True if we should display a short preview of each message." +msgstr "" +"Adevărat dacă ar trebui să arătăm o previzualizare scurtă pentru fiecare " +"mesaj." + +#: desktop/org.gnome.Geary.gschema.xml:68 +msgid "Languages that shall be used in the spell checker" +msgstr "Limbi care ar trebui folosite în corectorul ortografic" + +#: desktop/org.gnome.Geary.gschema.xml:69 +msgid "List of the languages to use in the spell checker." +msgstr "Listă de limbi de folosit în corectorul ortografic." + +#: desktop/org.gnome.Geary.gschema.xml:74 +msgid "Languages that are displayed in the spell checker popover" +msgstr "Limbi care sunt afișate în fereastra popup a corectorului ortografic" + +#: desktop/org.gnome.Geary.gschema.xml:75 +msgid "" +"List of languages that are always displayed in the popover of the spell " +"checker." +msgstr "" +"Listă de limbi care sunt afișate întotdeauna în fereastra popup a " +"corectorului ortografic." + +#: desktop/org.gnome.Geary.gschema.xml:80 +msgid "Enable notification sounds" +msgstr "Activează notificările sonore" + +#: desktop/org.gnome.Geary.gschema.xml:81 +msgid "True to play sounds for notifications and sending." +msgstr "Adevărat pentru a reda sunete pentru notificări și trimitere." + +#: desktop/org.gnome.Geary.gschema.xml:86 +msgid "Show notifications for new mail" +msgstr "Arată notificările pentru emailurile noi" + +#: desktop/org.gnome.Geary.gschema.xml:87 +msgid "True to show notification bubbles." +msgstr "Adevărat pentru a arăta bule de notificare." + +#: desktop/org.gnome.Geary.gschema.xml:92 +msgid "Notify of new mail at startup" +msgstr "Notifică despre emailuri noi la pornire" + +#: desktop/org.gnome.Geary.gschema.xml:93 +msgid "True to notify of new mail at startup." +msgstr "Adevărat pentru a notifica despre noile emailuri la pornire." + +#: desktop/org.gnome.Geary.gschema.xml:98 +msgid "Ask when opening an attachment" +msgstr "Întreabă când se deschide un atașament" + +#: desktop/org.gnome.Geary.gschema.xml:99 +msgid "True to ask when opening an attachment." +msgstr "Adevărat pentru a întreba la deschiderea unui fișier atașat." + +#: desktop/org.gnome.Geary.gschema.xml:104 +msgid "Whether to compose emails in HTML" +msgstr "Dacă să se compună email-uri în HTML" + +#: desktop/org.gnome.Geary.gschema.xml:105 +msgid "True to compose emails in HTML; false for plain text." +msgstr "Adevărat pentru a compune email-uri În HTML; fals pentru text simplu." + +#: desktop/org.gnome.Geary.gschema.xml:110 +msgid "Advisory strategy for full-text searching" +msgstr "Strategie consultativă pentru căutarea în întreg textul" + +#: desktop/org.gnome.Geary.gschema.xml:111 +msgid "" +"Acceptable values are “exact”, “conservative”, “aggressive”, and “horizon”." +msgstr "" +"Valorile acceptabile sunt „exact”, „conservative”, „aggressive” și „horizon”." + +#: desktop/org.gnome.Geary.gschema.xml:116 +msgid "Zoom of conversation viewer" +msgstr "Nivelul de zoom al vizualizatorului conversației" + +#: desktop/org.gnome.Geary.gschema.xml:117 +msgid "The zoom to apply on the conservation view." +msgstr "Nivelul de zoom de aplicat vizualizării conversației." + +#: desktop/org.gnome.Geary.gschema.xml:122 +msgid "Size of detached composer window" +msgstr "Dimensiunea ferestrei detașată a compozitorului" + +#: desktop/org.gnome.Geary.gschema.xml:123 +msgid "The last recorded size of the detached composer window." +msgstr "Ultima dimensiune înregistrată a ferestrei detașate a compozitorului." + +#: desktop/org.gnome.Geary.gschema.xml:128 +msgid "Whether we migrated the old settings" +msgstr "Dacă am migrat configurările vechi" + +#: desktop/org.gnome.Geary.gschema.xml:129 +msgid "" +"False to check for the old “org.yorba.geary”-schema and copy its values." +msgstr "" +"Fals pentru a verifica vechea schemă „org.yorba.geary” și copia valorile " +"sale." + +#. Translators: In-app notification label, when +#. the app had a problem pinning an otherwise +#. untrusted TLS certificate +#: src/client/accounts/accounts-editor.vala:203 +msgid "Failed to store certificate" +msgstr "Eșec la stocarea certificatului" + +#. Translators: Label for adding an email account +#. account for a generic IMAP service provider. +#: src/client/accounts/accounts-editor-add-pane.vala:109 +msgid "All others" +msgstr "Toate celelalte" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:196 +#: src/client/accounts/accounts-editor-servers-pane.vala:316 +msgid "Check your receiving login and password" +msgstr "Verificați numele și parola contului de primire" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:211 +#: src/client/accounts/accounts-editor-servers-pane.vala:329 +msgid "Check your receiving server details" +msgstr "Verificați detaliile serverului de primire" + +#. Translators: In-app notification label +#. There was an SMTP auth error, but IMAP already +#. succeeded, so the user probably needs to +#. specify custom creds here +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:233 +#: src/client/accounts/accounts-editor-servers-pane.vala:350 +msgid "Check your sending login and password" +msgstr "Verificați numele și parola contului de trimitere" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:247 +#: src/client/accounts/accounts-editor-servers-pane.vala:363 +msgid "Check your sending server details" +msgstr "Verificați detaliile serverului de trimitere" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:262 +msgid "Check your email address and password" +msgstr "Verificați adresa de email și parola" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:273 +msgid "Could not connect, check your network" +msgstr "Nu s-a putut conecta, verificați rețeaua" + +#. Translators: In-app notification label for a +#. generic error creating an account +#: src/client/accounts/accounts-editor-add-pane.vala:286 +msgid "An unexpected problem occurred" +msgstr "A apărut o problemă neașteptată" + +#. Translators: In-app notification label, the +#. string substitution is a more detailed reason. +#: src/client/accounts/accounts-editor-add-pane.vala:304 +#, c-format +msgid "Account not created: %s" +msgstr "Contul nu a fost creeat: %s" + +#. Translators: Label for the person's actual name when adding +#. an account +#: src/client/accounts/accounts-editor-add-pane.vala:551 +msgid "Your name" +msgstr "Numele dumneavoastră" + +#. Translators: Label used for the address part of an +#. email address when editing a user's sender address +#. preferences for an account. +#: src/client/accounts/accounts-editor-add-pane.vala:568 +#: src/client/accounts/accounts-editor-edit-pane.vala:501 +msgid "Email address" +msgstr "Adresă de email" + +#. Translators: Placeholder for the default sender address +#. when adding an account +#. Translators: This is used as a placeholder for the +#. address part of an email address when editing a user's +#. sender address preferences for an account. +#: src/client/accounts/accounts-editor-add-pane.vala:571 +#: src/client/accounts/accounts-editor-edit-pane.vala:469 +msgid "person@example.com" +msgstr "persoană@exemplu.ro" + +#. Translators: Label for an IMAP/SMTP service login/user name +#. when adding an account +#. Translators: Label for the user's login name for an +#. IMAP, SMTP, etc service +#: src/client/accounts/accounts-editor-add-pane.vala:585 +#: src/client/accounts/accounts-editor-servers-pane.vala:880 +msgid "Login name" +msgstr "Nume de autentificare" + +#. Translators: Label for the user's password for an IMAP, +#. SMTP, etc service +#: src/client/accounts/accounts-editor-add-pane.vala:599 +#: src/client/accounts/accounts-editor-servers-pane.vala:999 +#: ui/password-dialog.glade:108 +msgid "Password" +msgstr "Parolă" -#. reset/clear widgets -#: ../src/client/accounts/account-dialog-edit-alternate-emails-pane.vala:124 +#. Translators: Label for the IMAP server hostname when +#. adding an account. +#. Translators: This label describes the host name or IP +#. address and port used by an account's IMAP service. +#: src/client/accounts/accounts-editor-add-pane.vala:621 +#: src/client/accounts/accounts-editor-servers-pane.vala:727 +msgid "IMAP server" +msgstr "Server IMAP" + +#. Translators: Placeholder for the IMAP server hostname +#. when adding an account. +#: src/client/accounts/accounts-editor-add-pane.vala:624 +msgid "imap.example.com" +msgstr "imap.exemplu.ro" + +#. Translators: Label for the SMTP server hostname when +#. adding an account. +#. Translators: This label describes the host name or IP +#. address and port used by an account's SMTP service. +#: src/client/accounts/accounts-editor-add-pane.vala:630 +#: src/client/accounts/accounts-editor-servers-pane.vala:733 +msgid "SMTP server" +msgstr "Server SMTP" + +#. Translators: Placeholder for the SMTP server hostname +#. when adding an account. +#: src/client/accounts/accounts-editor-add-pane.vala:633 +msgid "smtp.example.com" +msgstr "smtp.exemplu.ro" + +#. Translators: Label in the account editor for the user's +#. custom name for an account. +#: src/client/accounts/accounts-editor-edit-pane.vala:278 +#: ui/accounts_editor_remove_pane.ui:123 +msgid "Account name" +msgstr "Nume de cont" + +#. Translators: Tooltip used to undo changing +#. the name of an account. The string +#. substitution is the old name of the +#. account. +#: src/client/accounts/accounts-editor-edit-pane.vala:312 +#, c-format +msgid "Change account name back to “%s”" +msgstr "Schimbă numele contului înapoi la „%s”" + +#. Translators: Tooltip for adding a new email sender/from +#. address's address to an account +#: src/client/accounts/accounts-editor-edit-pane.vala:336 +msgid "Add a new sender email address" +msgstr "Adaugă o nouă adresă de email pentru expeditor" + +#. Translators: Label used to indicate the user has +#. provided no display name for one of their sender +#. email addresses in their account settings. +#: src/client/accounts/accounts-editor-edit-pane.vala:417 +msgid "Name not set" +msgstr "Numele nu este configurat" + +#. Translators: This is used as a placeholder for the +#. display name for an email address when editing a user's +#. sender address preferences for an account. +#: src/client/accounts/accounts-editor-edit-pane.vala:456 +msgid "Sender Name" +msgstr "Numele expeditorului" + +#: src/client/accounts/accounts-editor-edit-pane.vala:479 +msgid "Remove" +msgstr "Elimină" + +#. Translators: Label used for the display name part of an +#. email address when editing a user's sender address +#. preferences for an account. +#: src/client/accounts/accounts-editor-edit-pane.vala:494 +msgid "Sender name" +msgstr "Nume expeditor" + +#. Translators: Label used as the undo tooltip after adding an +#. new sender email address to an account. The string +#. substitution is the email address added. +#: src/client/accounts/accounts-editor-edit-pane.vala:561 +#, c-format +msgid "Remove “%s”" +msgstr "Elimină „%s”" + +#. Translators: Label used as the undo tooltip after editing a +#. sender address for an account. The string substitution is +#. the email address edited. +#: src/client/accounts/accounts-editor-edit-pane.vala:601 +#, c-format +msgid "Undo changes to “%s”" +msgstr "Anulează modificările la „%s”" + +#. Translators: Label used as the undo tooltip after removing +#. a sender address from an account. The string substitution +#. is the email address edited. +#: src/client/accounts/accounts-editor-edit-pane.vala:688 +#, c-format +msgid "Add “%s” back" +msgstr "Adaugă înapoi „%s”" + +#. Translators: Label used as the undo tooltip after removing +#. a sender address from an account. The string substitution +#. is the email address edited. +#: src/client/accounts/accounts-editor-edit-pane.vala:730 +msgid "Undo signature changes" +msgstr "Anulează modificările de semnătură" + +#. Translators: This label describes the account +#. preference for the length of time (weeks, months or +#. years) that past email should be downloaded. +#: src/client/accounts/accounts-editor-edit-pane.vala:778 +msgid "Download mail" +msgstr "Descarcă email" + +#. Translators: Tooltip for undoing a change +#. to the length of time that past email +#. should be downloaded for an account. The +#. string substitution is the duration, +#. e.g. "1 month back". +#: src/client/accounts/accounts-editor-edit-pane.vala:810 #, c-format -msgid "Additional addresses for %s" -msgstr "Adrese adiționale pentru %s" - -#. Sets min size. -#: ../src/client/accounts/account-dialog.vala:21 -msgid "Accounts" -msgstr "Conturi" +msgid "Change download period back to: %s" +msgstr "Modifică perioada de descărcare înapoi la: %s" -#. Copyright 2016 Software Freedom Conservancy Inc. -#. * -#. * This software is licensed under the GNU Lesser General Public License -#. * (version 2.1 or later). See the COPYING file in this distribution. -#. -#. Page for adding or editing an account. -#. / Placeholder text indicating that the user should type their first name and last name -#: ../src/client/accounts/add-edit-page.vala:10 -msgid "First Last" -msgstr "Prenume Nume" - -#: ../src/client/accounts/add-edit-page.vala:233 -msgid "Welcome to Geary." -msgstr "Bine ați venit la Geary." - -#: ../src/client/accounts/add-edit-page.vala:233 -msgid "Enter your account information to get started." -msgstr "Introduceți detaliile contului pentru a începe." +#: src/client/accounts/accounts-editor-edit-pane.vala:831 +msgid "Everything" +msgstr "Totul" -#: ../src/client/accounts/add-edit-page.vala:253 +#: src/client/accounts/accounts-editor-edit-pane.vala:835 msgid "2 weeks back" msgstr "acum 2 săptămâni" -#. IDs are # of days -#: ../src/client/accounts/add-edit-page.vala:254 +#: src/client/accounts/accounts-editor-edit-pane.vala:839 msgid "1 month back" msgstr "acum o lună" -#: ../src/client/accounts/add-edit-page.vala:255 +#: src/client/accounts/accounts-editor-edit-pane.vala:843 msgid "3 months back" msgstr "acum 3 luni" -#: ../src/client/accounts/add-edit-page.vala:256 +#: src/client/accounts/accounts-editor-edit-pane.vala:847 msgid "6 months back" msgstr "acum 6 luni" -#: ../src/client/accounts/add-edit-page.vala:257 +#: src/client/accounts/accounts-editor-edit-pane.vala:851 msgid "1 year back" msgstr "acum 1 an" -#: ../src/client/accounts/add-edit-page.vala:258 +#: src/client/accounts/accounts-editor-edit-pane.vala:855 msgid "2 years back" msgstr "acum 2 ani" -#: ../src/client/accounts/add-edit-page.vala:259 +#: src/client/accounts/accounts-editor-edit-pane.vala:859 msgid "4 years back" msgstr "acum 4 ani" -#. Separator -#: ../src/client/accounts/add-edit-page.vala:261 -msgid "Everything" -msgstr "Totul" +#: src/client/accounts/accounts-editor-edit-pane.vala:865 +#, c-format +msgid "%d day back" +msgid_plural "%d days back" +msgstr[0] "acum %d zi" +msgstr[1] "acum %d zile" +msgstr[2] "acum %d de zile" + +#: src/client/accounts/accounts-editor-list-pane.vala:243 +msgid "Undo" +msgstr "Anulează" + +#: src/client/accounts/accounts-editor-list-pane.vala:251 +msgid "Redo" +msgstr "Refă" + +#: src/client/accounts/accounts-editor-list-pane.vala:345 +#: src/client/accounts/accounts-editor-list-pane.vala:433 +#: src/client/accounts/accounts-editor-row.vala:278 +msgid "Gmail" +msgstr "Gmail" -#: ../src/client/accounts/add-edit-page.vala:280 -msgid "Edit" -msgstr "Editare" - -#: ../src/client/accounts/add-edit-page.vala:282 -msgid "Preview" -msgstr "Previzualizează" - -#: ../src/client/accounts/add-edit-page.vala:723 -msgid "Remem_ber passwords" -msgstr "_Memorează parolele" +#: src/client/accounts/accounts-editor-list-pane.vala:349 +#: src/client/accounts/accounts-editor-list-pane.vala:437 +#: src/client/accounts/accounts-editor-row.vala:282 +msgid "Outlook.com" +msgstr "Outlook.com" -#: ../src/client/accounts/add-edit-page.vala:730 ../ui/login.glade.h:6 -msgid "Remem_ber password" -msgstr "_Memorează parola" +#: src/client/accounts/accounts-editor-list-pane.vala:353 +#: src/client/accounts/accounts-editor-list-pane.vala:441 +#: src/client/accounts/accounts-editor-row.vala:286 +msgid "Yahoo" +msgstr "Yahoo" + +#. Translators: Tooltip for accounts that have been +#. loaded but disabled by the user. +#: src/client/accounts/accounts-editor-list-pane.vala:371 +msgid "This account has been disabled" +msgstr "Acest cont a fost dezactivat" + +#. Translators: Tooltip for accounts that have been +#. loaded but because of some error are not able to be +#. used. +#: src/client/accounts/accounts-editor-list-pane.vala:380 +msgid "This account has encountered a problem and is unavailable" +msgstr "Acest cont a întâmpinat o problemă și este indisponibil" + +#. Translators: Label for adding a generic email account +#: src/client/accounts/accounts-editor-list-pane.vala:430 +msgid "Other email providers" +msgstr "Alți furnizori de email" + +#. Translators: Notification shown after removing an +#. account. The string substitution is the name of the +#. account. +#: src/client/accounts/accounts-editor-list-pane.vala:547 +#, c-format +msgid "Account “%s” removed" +msgstr "Contul „%s” a fost eliminat" + +#. Translators: Notification shown after removing an account +#. is undone. The string substitution is the name of the +#. account. +#: src/client/accounts/accounts-editor-list-pane.vala:554 +#, c-format +msgid "Account “%s” restored" +msgstr "Contul „%s” a fost restaurat" + +#. Translators: Tooltip for dragging list items +#: src/client/accounts/accounts-editor-row.vala:49 +msgid "Drag to move this item" +msgstr "Trageți pentru a muta acest element" + +#. Translators: Label describes the service provider +#. hosting the email account, e.g. Gmail, Yahoo, or some +#. other generic IMAP service. +#: src/client/accounts/accounts-editor-row.vala:294 +msgid "Service provider" +msgstr "Furnizor servicii" + +#. Translators: This label describes what form of transport +#. security (TLS, StartTLS, etc) used by an account's IMAP or SMTP +#. service. +#: src/client/accounts/accounts-editor-row.vala:467 +msgid "Connection security" +msgstr "Securitatea conexiunii" + +#. Translators: Label used when no auth scheme is used +#. by an account's IMAP or SMTP service. +#: src/client/accounts/accounts-editor-row.vala:478 +#: src/client/accounts/accounts-editor-servers-pane.vala:752 +#: src/client/accounts/accounts-editor-servers-pane.vala:964 +#: src/engine/api/geary-special-folder-type.vala:58 +msgid "None" +msgstr "Niciuna" -#: ../src/client/accounts/add-edit-page.vala:764 -msgid "Unable to validate:\n" -msgstr "Nu s-a putut valida:\n" - -#: ../src/client/accounts/add-edit-page.vala:766 -msgid " • Invalid account nickname.\n" -msgstr " • Pseudonimul nu este valid.\n" - -#: ../src/client/accounts/add-edit-page.vala:769 -msgid " • Email address already added to Geary.\n" -msgstr " • Adresa de email este deja adăugată în Geary.\n" - -#: ../src/client/accounts/add-edit-page.vala:773 -msgid " • IMAP connection error.\n" -msgstr " • Eroare la conexiunea IMAP.\n" - -#: ../src/client/accounts/add-edit-page.vala:776 -msgid " • IMAP username or password incorrect.\n" -msgstr " • Nume utilizator sau parolă IMAP incorecte.\n" - -#: ../src/client/accounts/add-edit-page.vala:779 -msgid " • SMTP connection error.\n" -msgstr " • Eroare la conexiunea SMTP.\n" - -#: ../src/client/accounts/add-edit-page.vala:782 -msgid " • SMTP username or password incorrect.\n" -msgstr " • Numele de utilizator sau parola SMTP sunt incorecte.\n" - -#: ../src/client/accounts/add-edit-page.vala:786 -msgid " • Connection error.\n" -msgstr " • Eroare de conexiune.\n" - -#: ../src/client/accounts/add-edit-page.vala:790 -msgid " • Username or password incorrect.\n" -msgstr " • Numele de utilizator sau parola sunt incorecte.\n" +#: src/client/accounts/accounts-editor-row.vala:485 +msgid "StartTLS" +msgstr "StartTLS" + +#: src/client/accounts/accounts-editor-row.vala:492 +msgid "TLS" +msgstr "TLS" + +#. Translators: Label for source of SMTP authentication +#. credentials (none, use IMAP, custom) when adding a new +#. account +#. Button label for retrying when a login error has occurred +#: src/client/accounts/accounts-editor-row.vala:533 ui/main-window.ui:455 +msgid "Login" +msgstr "Autentificare" + +#. Translators: ComboBox value for source of SMTP +#. authentication credentials (none) when adding a new +#. account +#: src/client/accounts/accounts-editor-row.vala:540 +msgid "No login needed" +msgstr "Nicio autentificare necesară" + +#. Translators: ComboBox value for source of SMTP +#. authentication credentials (use IMAP) when adding a new +#. account +#: src/client/accounts/accounts-editor-row.vala:548 +msgid "Use same login as receiving" +msgstr "Utilizează aceeași autentificare ca la primire" + +#. Translators: ComboBox value for source of SMTP +#. authentication credentials (custom) when adding a new +#. account +#: src/client/accounts/accounts-editor-row.vala:556 +msgid "Use a different login" +msgstr "Utilizează o autentificare diferită" + +#. Translators: In-app notification label, the +#. string substitution is a more detailed reason. +#: src/client/accounts/accounts-editor-servers-pane.vala:377 +#, c-format +msgid "Account not updated: %s" +msgstr "Contul nu a fost actualizat: %s" + +#. Translators: This label describes the program that +#. created the account, e.g. an SSO service like GOA, or +#. locally by Geary. +#: src/client/accounts/accounts-editor-servers-pane.vala:540 +msgid "Account source" +msgstr "Sursa contului" + +#: src/client/accounts/accounts-editor-servers-pane.vala:552 +msgid "GNOME Online Accounts" +msgstr "Conturi online GNOME" + +#. Translators: This label describes an account +#. preference. +#: src/client/accounts/accounts-editor-servers-pane.vala:611 +msgid "Save drafts on server" +msgstr "Salvează ciornele pe server" + +#. Translators: This label describes an account +#. preference. +#: src/client/accounts/accounts-editor-servers-pane.vala:666 +msgid "Save sent email on server" +msgstr "Salvează mailul trimis pe server" + +#. Add a suffix for OAuth2 auth so people know they +#. shouldn't expect to be prompted for a password +#. Translators: Label used when an account's IMAP or +#. SMTP service uses OAuth2. The string replacement is +#. the service's login name. +#: src/client/accounts/accounts-editor-servers-pane.vala:950 +#, c-format +msgid "%s using OAuth2" +msgstr "%s folosind OAuth2" + +#: src/client/accounts/accounts-editor-servers-pane.vala:960 +msgid "Use receiving server login" +msgstr "Utilizează autentificarea serverului de primire" -#: ../src/client/application/geary-application.vala:19 +#: src/client/application/geary-application.vala:24 msgid "Copyright 2016 Software Freedom Conservancy Inc." msgstr "Drepturi de autor 2016 Software Freedom Conservancy Inc." -#: ../src/client/application/geary-application.vala:21 +#: src/client/application/geary-application.vala:25 +msgid "Copyright 2016-2019 Geary Development Team." +msgstr "Drepturi de autor 2016-2019 Echipa de dezvoltare a lui Geary." + +#: src/client/application/geary-application.vala:27 msgid "Visit the Geary web site" msgstr "Vizitează situl Geary" -#: ../src/client/application/geary-args.vala:10 +#: src/client/application/geary-application.vala:454 +#, c-format +msgid "About %s" +msgstr "Despre %s" + +#. Translators: add your name and email address to receive +#. credit in the About dialog For example: Yamada Taro +#. +#: src/client/application/geary-application.vala:458 +msgid "translator-credits" +msgstr "" +"CoolGoose \n" +"Corneliu Dascălu \n" +"Viorel-Cătălin Răpițeanu \n" +"Daniel Șerbănescu , 2019" + +#: src/client/application/geary-args.vala:10 msgid "Start Geary with hidden main window" msgstr "Pornește Geary cu fereastra principală ascunsă" -#: ../src/client/application/geary-args.vala:11 +#: src/client/application/geary-args.vala:11 msgid "Output debugging information" msgstr "Afișează informații de depanare" -#: ../src/client/application/geary-args.vala:12 +#: src/client/application/geary-args.vala:12 msgid "Log conversation monitoring" msgstr "Înregistrează monitorizarea conversațiilor" -#: ../src/client/application/geary-args.vala:13 +#: src/client/application/geary-args.vala:13 msgid "Log network deserialization" msgstr "Înregistrează deserializarea rețelei" -#: ../src/client/application/geary-args.vala:14 +#: src/client/application/geary-args.vala:14 msgid "Log network activity" msgstr "Îregistrează activitatea rețelei" #. / The IMAP replay queue is how changes on the server are replicated on the client. #. / It could also be called the IMAP events queue. -#: ../src/client/application/geary-args.vala:17 +#: src/client/application/geary-args.vala:17 msgid "Log IMAP replay queue" msgstr "Înregistrează coada de evenimente IMAP" #. / Serialization is how commands and responses are converted into a stream of bytes for #. / network transmission -#: ../src/client/application/geary-args.vala:20 +#: src/client/application/geary-args.vala:20 msgid "Log network serialization" msgstr "Înregistrează serializarea rețelei" -#: ../src/client/application/geary-args.vala:21 +#: src/client/application/geary-args.vala:21 msgid "Log periodic activity" msgstr "Înregistrează activitatea periodică" -#: ../src/client/application/geary-args.vala:22 +#: src/client/application/geary-args.vala:22 msgid "Log database queries (generates lots of messages)" msgstr "Înregistrează interogările bazei de date (generează multe mesajee)" #. / "Normalization" can also be called "synchronization" -#: ../src/client/application/geary-args.vala:24 +#: src/client/application/geary-args.vala:24 msgid "Log folder normalization" msgstr "Înregistrează normalizarea directorului" -#: ../src/client/application/geary-args.vala:25 +#: src/client/application/geary-args.vala:25 msgid "Allow inspection of WebView" msgstr "Permite inspecția WebView-ului" -#: ../src/client/application/geary-args.vala:26 +#: src/client/application/geary-args.vala:26 msgid "Revoke all server certificates with TLS warnings" msgstr "Revocă toate certificatele serverelor cu avertizări TLS" -#: ../src/client/application/geary-args.vala:27 +#: src/client/application/geary-args.vala:27 +msgid "Perform a graceful quit" +msgstr "Efectuează o ieșire elegantă" + +#: src/client/application/geary-args.vala:28 msgid "Display program version" msgstr "Afișează versiunea programului" #. This gives a command-line hint on how to open new composer windows with mailto: -#: ../src/client/application/geary-args.vala:51 +#: src/client/application/geary-args.vala:53 #, c-format msgid "Use %s to open a new composer window" msgstr "Utilizați %s pentru a deschide o nouă fereastră de compunere" -#: ../src/client/application/geary-args.vala:52 +#: src/client/application/geary-args.vala:56 msgid "Please report comments, suggestions and bugs to:" msgstr "Raportați comentarii, sugestii și defecte la:" #. i18n: Command line arguments are invalid -#: ../src/client/application/geary-args.vala:59 +#: src/client/application/geary-args.vala:63 #, c-format msgid "Failed to parse command line options: %s\n" msgstr "Eroare la parsarea opțiunilor din linia de comandă: %s\n" -#: ../src/client/application/geary-args.vala:70 +#: src/client/application/geary-args.vala:74 #, c-format -msgid "Unrecognized command line option \"%s\"\n" -msgstr "Opțiune de linie de comandă nerecunoscută \"%s\"\n" - -#: ../src/client/application/geary-controller.vala:60 -msgid "Delete conversation" -msgstr "Șterge conversația" - -#: ../src/client/application/geary-controller.vala:61 -msgid "Delete conversation (Shift+Delete)" -msgstr "Șterge conversația (Shift+Delete)" - -#: ../src/client/application/geary-controller.vala:62 -msgid "Delete conversations (Shift+Delete)" -msgstr "Șterge conversațiile (Shift+Delete)" - -#. This refers to the action ("move email to the trash"), not the Trash folder itself -#: ../src/client/application/geary-controller.vala:66 -msgid "Move conversation to Trash (Delete, Backspace)" -msgstr "Mută conversația la Gunoi (Delete, Backspace)" - -#: ../src/client/application/geary-controller.vala:67 -msgid "Move conversations to Trash (Delete, Backspace)" -msgstr "Mută conversațiile la Gunoi (Delete, Backspace)" - -#. This refers to the action ("archive an email"), not the Archive folder itself -#: ../src/client/application/geary-controller.vala:71 -msgid "_Archive" -msgstr "_Arhivează" - -#: ../src/client/application/geary-controller.vala:72 -msgid "Archive conversation (A)" -msgstr "Arhivează conversația (A)" - -#: ../src/client/application/geary-controller.vala:73 -msgid "Archive conversations (A)" -msgstr "Arhivează conversațiile (A)" - -#: ../src/client/application/geary-controller.vala:76 -msgid "Mark as S_pam" -msgstr "Marchează ca s_pam" - -#: ../src/client/application/geary-controller.vala:77 -msgid "Mark as not S_pam" -msgstr "Marchează ca non-s_pam" - -#: ../src/client/application/geary-controller.vala:79 -#: ../src/client/application/geary-controller.vala:393 -msgid "Mark conversation" -msgstr "Marchează conversația" - -#: ../src/client/application/geary-controller.vala:80 -msgid "Mark conversations" -msgstr "Marchează conversațiile" - -#: ../src/client/application/geary-controller.vala:81 -msgid "Add label to conversation" -msgstr "Adaugă etichetă conversației" - -#: ../src/client/application/geary-controller.vala:82 -msgid "Add label to conversations" -msgstr "Adaugă etichetă conversațiilor" - -#: ../src/client/application/geary-controller.vala:83 -#: ../src/client/application/geary-controller.vala:432 -msgid "Move conversation" -msgstr "Mută conversația" - -#: ../src/client/application/geary-controller.vala:84 -msgid "Move conversations" -msgstr "Mută conversațiile" - -#: ../src/client/application/geary-controller.vala:373 -#: ../ui/app_menu.interface.h:1 -msgid "A_ccounts" -msgstr "_Conturi" - -#: ../src/client/application/geary-controller.vala:378 -#: ../src/client/components/stock.vala:27 ../ui/app_menu.interface.h:2 -msgid "_Preferences" -msgstr "_Preferințe" - -#: ../src/client/application/geary-controller.vala:382 -#: ../src/client/components/stock.vala:25 ../ui/app_menu.interface.h:3 -msgid "_Help" -msgstr "_Ajutor" - -#: ../src/client/application/geary-controller.vala:386 -#: ../src/client/components/stock.vala:21 ../ui/app_menu.interface.h:4 -msgid "_About" -msgstr "_Despre" - -#: ../src/client/application/geary-controller.vala:390 -#: ../src/client/components/stock.vala:29 ../ui/app_menu.interface.h:5 -msgid "_Quit" -msgstr "_Ieșire" - -#: ../src/client/application/geary-controller.vala:395 -msgid "_Mark as..." -msgstr "_Marchează ca..." - -#: ../src/client/application/geary-controller.vala:401 -msgid "Mark as _Read" -msgstr "Ma_rchează ca citit" - -#: ../src/client/application/geary-controller.vala:407 -msgid "Mark as _Unread" -msgstr "Marchează ca _necitit" - -#: ../src/client/application/geary-controller.vala:413 -msgid "_Star" -msgstr "Marchează cu o _stea" - -#: ../src/client/application/geary-controller.vala:418 -msgid "U_nstar" -msgstr "Eliminați _steaua" - -#: ../src/client/application/geary-controller.vala:428 -msgid "Add label" -msgstr "Adaugă etichetă" - -#: ../src/client/application/geary-controller.vala:429 -msgid "_Label" -msgstr "_Etichetă" - -#: ../src/client/application/geary-controller.vala:433 -msgid "_Move" -msgstr "_Mută" - -#: ../src/client/application/geary-controller.vala:437 -msgid "Compose new message (Ctrl+N, N)" -msgstr "Compune un mesaj nou (Ctrl+N, N)" - -#. Reply to a message. -#: ../src/client/application/geary-controller.vala:441 -#: ../src/client/conversation-viewer/conversation-viewer.vala:1884 -msgid "_Reply" -msgstr "_Răspunde" - -#: ../src/client/application/geary-controller.vala:442 -msgid "Reply (Ctrl+R, R)" -msgstr "Răspunde (Ctrl+R, R)" - -#: ../src/client/application/geary-controller.vala:446 -msgid "R_eply All" -msgstr "Răspund_e tuturor" - -#: ../src/client/application/geary-controller.vala:447 -msgid "Reply all (Ctrl+Shift+R, Shift+R)" -msgstr "Răspunde tuturor (Ctrl+Shift+R, Shift+R)" - -#. Forward a message. -#: ../src/client/application/geary-controller.vala:452 -#: ../src/client/conversation-viewer/conversation-viewer.vala:1894 -msgid "_Forward" -msgstr "Î_naintează" - -#: ../src/client/application/geary-controller.vala:453 -msgid "Forward (Ctrl+L, F)" -msgstr "Înaintează (Ctrl+L, F)" - -#: ../src/client/application/geary-controller.vala:492 -msgid "Empty" -msgstr "Golește" - -#: ../src/client/application/geary-controller.vala:493 -msgid "Empty Spam or Trash folders" -msgstr "Golește dosarele Spam sau Gunoi" - -#: ../src/client/application/geary-controller.vala:497 -msgid "Empty _Spam…" -msgstr "Golește _Spam…" - -#: ../src/client/application/geary-controller.vala:501 -msgid "Empty _Trash…" -msgstr "Golește _Gunoi…" - -#. No callback is connected, since we bind the toggle button to the search bar visibility -#: ../src/client/application/geary-controller.vala:530 -msgid "Toggle search bar" -msgstr "Comută bara de căutare" - -#: ../src/client/application/geary-controller.vala:725 -msgid "Unable to store server trust exception" -msgstr "Nu s-a putut stoca excepția la servere de încredere" - -#: ../src/client/application/geary-controller.vala:962 -msgid "Your settings are insecure" -msgstr "Configurările tale sunt nesigure" - -#: ../src/client/application/geary-controller.vala:963 -msgid "" -"Your IMAP and/or SMTP settings do not specify SSL or TLS. This means your " -"username and password could be read by another person on the network. Are " -"you sure you want to do this?" -msgstr "" -"Configurările IMAP și/sau SMTP nu specifică SSL sau TLS. Acest lucru permite " -"unei alte persoane de pe rețea să îți citească utilizatorul și parola. " -"Sunteți sigur că vreți să faceți asta?" - -#: ../src/client/application/geary-controller.vala:964 -msgid "Co_ntinue" -msgstr "Co_ntinuă" +msgid "Unrecognized command line option “%s”\n" +msgstr "Opțiune de linie de comandă nerecunoscută „%s”\n" -#. / Displayed in the space-limited status bar when a message fails to be sent due to error. -#: ../src/client/application/geary-controller.vala:1042 -#: ../src/client/components/status-bar.vala:29 -msgid "Error sending email" -msgstr "Eroare la trimiterea emailului" - -#: ../src/client/application/geary-controller.vala:1043 -msgid "" -"Geary encountered an error sending an email. If the problem persists, " -"please manually delete the email from your Outbox folder." -msgstr "" -"Geary a întâmpinat o eroare la trimiterea unui email. Dacă problema " -"persistă, ștergeți manual emailul din directorul Outbox." - -#. Displayed in the space-limited status bar when a message fails to be uploaded -#. to Sent Mail after being sent. -#: ../src/client/application/geary-controller.vala:1047 -#: ../src/client/components/status-bar.vala:33 -msgid "Error saving sent mail" -msgstr "Eroare la salvarea emailului trimis" - -#: ../src/client/application/geary-controller.vala:1048 -msgid "" -"Geary encountered an error saving a sent message to Sent Mail. The message " -"will stay in your Outbox folder until you delete it." -msgstr "" -"Geary a întâmpinat o eroare la salvarea unui mesaj trimis în dosarul „Mesaje " -"trimise”. Mesajul va rămâne în dosarul Outbox până îl veți șterge." +#. Translators: File name used in save chooser when saving +#. attachments that do not otherwise have a name. +#: src/client/application/geary-controller.vala:58 +msgid "Untitled" +msgstr "Nedenumit" -#: ../src/client/application/geary-controller.vala:1117 +#: src/client/application/geary-controller.vala:954 msgid "Labels" msgstr "Etichete" #. give the user two options: reset the Account local store, or exit Geary. A third #. could be done to leave the Account in an unopened state, but we don't currently #. have provisions for that. -#: ../src/client/application/geary-controller.vala:1129 +#: src/client/application/geary-controller.vala:967 #, c-format msgid "Unable to open the database for %s" msgstr "Nu s-a reușit deschiderea bazei de date pentru %s" -#: ../src/client/application/geary-controller.vala:1130 +#: src/client/application/geary-controller.vala:968 #, c-format msgid "" "There was an error opening the local mail database for this account. This is " @@ -536,20 +913,20 @@ "Reconstrucția bazei de date va șterge toate emailurile locale și fișierele " "lor atașate. Emailurile de pe server nu vor fi afectate." -#: ../src/client/application/geary-controller.vala:1132 +#: src/client/application/geary-controller.vala:970 msgid "_Rebuild" msgstr "_Reconstruiește" -#: ../src/client/application/geary-controller.vala:1132 +#: src/client/application/geary-controller.vala:970 msgid "E_xit" msgstr "_Ieșire" -#: ../src/client/application/geary-controller.vala:1141 +#: src/client/application/geary-controller.vala:979 #, c-format -msgid "Unable to rebuild database for \"%s\"" -msgstr "Nu s-a reușit reconstrucția bazei de date pentru „%s”" +msgid "Unable to rebuild database for “%s”" +msgstr "Nu s-a reușit reconstruirea bazei de date pentru „%s”" -#: ../src/client/application/geary-controller.vala:1142 +#: src/client/application/geary-controller.vala:980 #, c-format msgid "" "Error during rebuild:\n" @@ -560,85 +937,15 @@ "\n" "%s" -#. some other problem opening the account ... as with other flow path, can't run -#. Geary today with an account in unopened state, so have to exit -#: ../src/client/application/geary-controller.vala:1164 -#: ../src/client/application/geary-controller.vala:1174 -#: ../src/client/application/geary-controller.vala:1185 -#, c-format -msgid "Unable to open local mailbox for %s" -msgstr "Nu s-a reușit deschiderea cutiei poștale locale pentru %s" - -#: ../src/client/application/geary-controller.vala:1165 -#, c-format -msgid "" -"There was an error opening the local mail database for this account. This is " -"possibly due to a file permissions problem.\n" -"\n" -"Please check that you have read/write permissions for all files in this " -"directory:\n" -"\n" -"%s" -msgstr "" -"S-a întâmpinat o eroare la deschiderea bazei de date locală pentru email. " -"Aceasta este probabil cauzată de o problemă cu permisiunile pentru fișiere.\n" -"\n" -"Verificați dacă aveți drepturi de citire/scriere pentru toate fișierele din " -"director.\n" -"\n" -"%s" - -#: ../src/client/application/geary-controller.vala:1175 -msgid "" -"The version number of the local mail database is formatted for a newer " -"version of Geary. Unfortunately, the database cannot be \"rolled back\" to " -"work with this version of Geary.\n" -"\n" -"Please install the latest version of Geary and try again." -msgstr "" -"Numărul versiunii bazei de date locală pentru email este formatată pentru o " -"versiune mai nouă de Geary. Din nefericire, baza de date nu poate fi " -"„restaurată” pentru a funcționa cu această versiune de Geary.\n" -"\n" -"Instalați ultima versiune de Geary și încercați din nou." - -#: ../src/client/application/geary-controller.vala:1186 -msgid "" -"There was an error opening the local account. This is probably due to " -"connectivity issues.\n" -"\n" -"Please check your network connection and restart Geary." -msgstr "" -"Am întâmpinat o eroare la deschiderea contului local. Aceasta este probabil " -"din cauza problemelor de conectivitate.\n" -"\n" -"Te rog verifică-ți conexiunea la internet și repornește Geary." - -#: ../src/client/application/geary-controller.vala:1698 -#, c-format -msgid "About %s" -msgstr "Despre %s" - -#. / Translators: add your name and email address to receive credit in the About dialog -#. / For example: Yamada Taro -#: ../src/client/application/geary-controller.vala:1701 -msgid "translator-credits" -msgstr "" -"CoolGoose \n" -"Corneliu Dascălu \n" -"Viorel-Cătălin Răpițeanu \n" -"Daniel Șerbănescu " - -#: ../src/client/application/geary-controller.vala:1958 +#: src/client/application/geary-controller.vala:1835 msgid "Undo move (Ctrl+Z)" msgstr "Refă mutarea (Ctrl+Z)" -#: ../src/client/application/geary-controller.vala:1968 -#, c-format -msgid "Are you sure you want to open \"%s\"?" -msgstr "Sigur doriți să deschideți „%s”?" +#: src/client/application/geary-controller.vala:1845 +msgid "Are you sure you want to open these attachments?" +msgstr "Sigur doriți să deschideți aceste atașamente?" -#: ../src/client/application/geary-controller.vala:1969 +#: src/client/application/geary-controller.vala:1846 msgid "" "Attachments may cause damage to your system if opened. Only open files from " "trusted sources." @@ -646,523 +953,822 @@ "Fișierele atașate pot cauza probleme sistemului dumneavoastră dacă sunt " "deschise. Deschideți doar fișierele atașate din surse sigure." -#: ../src/client/application/geary-controller.vala:1970 -msgid "Don't _ask me again" +#: src/client/application/geary-controller.vala:1847 +msgid "Don’t _ask me again" msgstr "Nu întreb_a din nou" -#: ../src/client/application/geary-controller.vala:1988 +#. Translators: Dialog primary label when prompting to +#. overwrite a file. The string substitution is the file'sx +#. name. +#: src/client/application/geary-controller.vala:1976 #, c-format -msgid "A file named \"%s\" already exists. Do you want to replace it?" -msgstr "Un fișier numit „%s” există deja. Doriți să-l înlocuiți?" +msgid "A file named “%s” already exists. Do you want to replace it?" +msgstr "Un fișier numit „%s” există deja. Doriți să-l înlocuiți?" -#: ../src/client/application/geary-controller.vala:1990 +#. Translators: Dialog secondary label when prompting to +#. overwrite a file. The string substitution is the parent +#. folder's name. +#: src/client/application/geary-controller.vala:1983 #, c-format msgid "" -"The file already exists in \"%s\". Replacing it will overwrite its contents." +"The file already exists in “%s”. Replacing it will overwrite its contents." msgstr "" "Fișierul deja există în „%s”. Înlocuirea va suprascrie conținutul lui." -#: ../src/client/application/geary-controller.vala:1993 +#: src/client/application/geary-controller.vala:1987 msgid "_Replace" msgstr "Î_nlocuiește" -#. Find out what to do with the inline composers. -#. TODO: Remove this in favor of automatically saving drafts -#: ../src/client/application/geary-controller.vala:2291 -msgid "Close open draft messages?" -msgstr "Închideți mesajele deschise de tip ciornă?" +#: src/client/application/geary-controller.vala:2263 +msgid "Close the draft message?" +msgid_plural "Close all draft messages?" +msgstr[0] "Închideți mesajul de tip ciornă?" +msgstr[1] "Închideți mesajele de tip ciornă?" +msgstr[2] "Închideți mesajele de tip ciornă?" -#: ../src/client/application/geary-controller.vala:2421 +#: src/client/application/geary-controller.vala:2389 #, c-format msgid "Empty all email from your %s folder?" msgstr "Goliți toate emailurile din dosarul %s?" -#: ../src/client/application/geary-controller.vala:2422 +#: src/client/application/geary-controller.vala:2390 msgid "This removes the email from Geary and your email server." msgstr "Aceasta elimină emailul din Geary și de pe serverul de email." -#: ../src/client/application/geary-controller.vala:2423 +#: src/client/application/geary-controller.vala:2391 msgid "This cannot be undone." msgstr "Aceasta nu poate fi anulată." -#: ../src/client/application/geary-controller.vala:2424 +#: src/client/application/geary-controller.vala:2392 #, c-format msgid "Empty %s" msgstr "Golește %s" -#: ../src/client/application/geary-controller.vala:2441 +#: src/client/application/geary-controller.vala:2409 #, c-format msgid "Error emptying %s" msgstr "Eroare la golirea %s" -#: ../src/client/application/geary-controller.vala:2471 +#: src/client/application/geary-controller.vala:2441 msgid "Do you want to permanently delete this message?" msgid_plural "Do you want to permanently delete these messages?" msgstr[0] "Doriți să ștergeți permanent acest mesaj?" msgstr[1] "Doriți să ștergeți permanent aceste mesaje?" msgstr[2] "Doriți să ștergeți permanent aceste mesaje?" -#: ../src/client/application/geary-controller.vala:2473 +#: src/client/application/geary-controller.vala:2443 msgid "Delete" msgstr "Șterge" -#: ../src/client/application/geary-controller.vala:2504 -msgid "Undo archive (Ctrl+Z)" -msgstr "Refă arhiva (Ctrl+Z)" - -#: ../src/client/application/geary-controller.vala:2519 +#: src/client/application/geary-controller.vala:2457 msgid "Undo trash (Ctrl+Z)" msgstr "Refă gunoiul (Ctrl+Z)" -#: ../src/client/application/geary-controller.vala:2572 +#: src/client/application/geary-controller.vala:2507 +msgid "Undo archive (Ctrl+Z)" +msgstr "Refă arhiva (Ctrl+Z)" + +#: src/client/application/geary-controller.vala:2552 msgid "Undo (Ctrl+Z)" msgstr "Refă (Ctrl+Z)" -#: ../src/client/components/conversation-find-bar.vala:214 +#. Translators: The label for an in-app notification. The +#. string substitution is a list of recipients of the email. +#: src/client/application/geary-controller.vala:2633 #, c-format -msgid "%i match" -msgid_plural "%i matches" -msgstr[0] "%i potrivire" -msgstr[1] "%i potriviri" -msgstr[2] "%i de potriviri" - -#: ../src/client/components/conversation-find-bar.vala:216 -#, c-format -msgid "%i match (wrapped)" -msgid_plural "%i matches (wrapped)" -msgstr[0] "%i potrivire (wrapped)" -msgstr[1] "%i potriviri (wrapped)" -msgstr[2] "%i de potriviri (wrapped)" - -#: ../src/client/components/conversation-find-bar.vala:218 -msgid "not found" -msgstr "nu a fost găsit" +msgid "Successfully sent mail to %s." +msgstr "S-a trimis email cu succes către %s." + +#: src/client/application/geary-controller.vala:2715 +msgid "Failed to open default text editor." +msgstr "Eroare la deschiderea editorului de text implicit." + +#. Translators: Tooltip used when an entry requires a valid +#. email address to be entered, but one is not provided. +#: src/client/components/components-validator.vala:378 +msgid "An email address is required" +msgstr "Adresa de email este obligatorie" + +#. Translators: Tooltip used when an entry requires a valid +#. email address to be entered, but the address is invalid. +#: src/client/components/components-validator.vala:382 +msgid "Not a valid email address" +msgstr "Adresa de email este nevalidă" + +#. Translators: Tooltip used when an entry requires a valid, +#. resolvable server name to be entered, but one is not +#. provided. +#: src/client/components/components-validator.vala:428 +msgid "A server name is required" +msgstr "Numele de server este obligatoriu" + +#. Translators: Tooltip used when an entry requires a valid +#. server name to be entered, but it was unable to be +#. looked-up in the DNS. +#: src/client/components/components-validator.vala:433 +msgid "Could not look up server name" +msgstr "Nu s-a putut detecta numele serverului" + +#: src/client/components/main-toolbar.vala:151 +msgid "Mark conversation" +msgid_plural "Mark conversations" +msgstr[0] "Marchează conversația" +msgstr[1] "Marchează conversațiile" +msgstr[2] "Marchează conversațiile" + +#: src/client/components/main-toolbar.vala:156 +msgid "Add label to conversation" +msgid_plural "Add label to conversations" +msgstr[0] "Adaugă etichetă la conversație" +msgstr[1] "Adaugă etichete la conversații" +msgstr[2] "Adaugă etichete la conversații" + +#: src/client/components/main-toolbar.vala:161 +msgid "Move conversation" +msgid_plural "Move conversations" +msgstr[0] "Mută conversația" +msgstr[1] "Mută conversațiile" +msgstr[2] "Mută conversațiile" + +#: src/client/components/main-toolbar.vala:166 +msgid "Archive conversation (A)" +msgid_plural "Archive conversations (A)" +msgstr[0] "Arhivează conversația (A)" +msgstr[1] "Arhivează conversațiile (A)" +msgstr[2] "Arhivează conversațiile (A)" + +#: src/client/components/main-toolbar.vala:175 +msgid "Move conversation to Trash (Delete, Backspace)" +msgid_plural "Move conversations to Trash (Delete, Backspace)" +msgstr[0] "Mută conversația la Gunoi (Delete, Backspace)" +msgstr[1] "Mută conversațiile la Gunoi (Delete, Backspace)" +msgstr[2] "Mută conversațiile la Gunoi (Delete, Backspace)" + +#: src/client/components/main-toolbar.vala:183 +msgid "Delete conversation (Shift+Delete)" +msgid_plural "Delete conversations (Shift+Delete)" +msgstr[0] "Șterge conversația (Shift+Delete)" +msgstr[1] "Șterge conversațiile (Shift+Delete)" +msgstr[2] "Șterge conversațiile (Shift+Delete)" -#: ../src/client/components/main-window.vala:359 +#: src/client/components/main-window.vala:503 #, c-format msgid "%s (%d)" msgstr "%s (%d)" -#: ../src/client/components/search-bar.vala:10 -#: ../src/client/folder-list/folder-list-search-branch.vala:38 -#: ../src/engine/api/geary-special-folder-type.vala:51 -msgid "Search" -msgstr "Caută" +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:47 +#, c-format +msgid "Problem connecting to incoming server for %s" +msgstr "Problemă la conectarea serverului de primire pentru %s" -#. Search entry. -#: ../src/client/components/search-bar.vala:25 -msgid "Search all mail in account for keywords (Ctrl+S)" -msgstr "Caută cuvintele cheie în toate emailurile din cont (Ctrl+S)" +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:49 +#: src/client/components/main-window-info-bar.vala:58 +#, c-format +msgid "" +"Could not connect to %s, check your Internet access and the server name and " +"try again" +msgstr "" +"Nu s-a putut conecta la %s, verificați conexiunea la Internet și numele " +"serverului apoi încercați din nou" -#: ../src/client/components/search-bar.vala:118 +#. Translators: Tooltip label for Retry button +#. Button tooltip for retrying an account problem +#: src/client/components/main-window-info-bar.vala:51 +#: src/client/components/main-window-info-bar.vala:60 +#: src/client/components/main-window-info-bar.vala:69 +#: src/client/components/main-window-info-bar.vala:78 +#: src/client/components/main-window-info-bar.vala:87 +#: src/client/components/main-window-info-bar.vala:96 +#: src/client/components/main-window-info-bar.vala:136 ui/main-window.ui:265 +msgid "Try reconnecting" +msgstr "Încearcă reconectarea" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:56 #, c-format -msgid "Indexing %s account" -msgstr "Se indexează contul %s" +msgid "Problem connecting to outgoing server for %s" +msgstr "Problemă la contactarea serverului de trimitere pentru %s" -#: ../src/client/components/search-bar.vala:129 -#: ../src/client/folder-list/folder-list-search-branch.vala:39 +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:65 +#: src/client/components/main-window-info-bar.vala:83 #, c-format -msgid "Search %s account" -msgstr "Caută în contul %s" +msgid "Problem communicating with incoming server for %s" +msgstr "Problemă la comunicarea cu serverul de primire pentru %s" -#. / Displayed in the space-limited status bar while a message is in the process of being sent. -#: ../src/client/components/status-bar.vala:26 -msgid "Sending..." -msgstr "Se trimite..." +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:67 +#: src/client/components/main-window-info-bar.vala:76 +#, c-format +msgid "Network error talking to %s, check your Internet access and try again" +msgstr "" +"Eroare de rețea la comunicarea cu %s, verificați accesul la internet și " +"încercați din nou" -#: ../src/client/components/stock.vala:18 ../ui/account_cannot_remove.glade.h:3 -msgid "_OK" -msgstr "_OK" +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:74 +#: src/client/components/main-window-info-bar.vala:91 +msgid "Problem communicating with outgoing mail server" +msgstr "Problemă la comunicarea cu serverul de trimitere email" -#: ../src/client/components/stock.vala:19 ../ui/edit_alternate_emails.glade.h:3 -#: ../ui/password-dialog.glade.h:5 ../ui/remove_confirm.glade.h:5 -msgid "_Cancel" -msgstr "_Anulează" +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:85 +#, c-format +msgid "" +"Geary did not understand a message from %s or vice versa, please file a bug " +"report" +msgstr "" +"Geary nu a înțeles mesajul de la %s sau vice versa, completați un report de " +"defecțiune" -#: ../src/client/components/stock.vala:23 -msgid "_Close" -msgstr "În_chide" +#. Translators: First string substitution is the server +#. name, second is the account name +#: src/client/components/main-window-info-bar.vala:94 +#, c-format +msgid "" +"Could not communicate with %s for %s, check the server name and try again in " +"a moment" +msgstr "" +"Nu s-a putut comunica cu %s pentru %s, verificați numele serverului și " +"încercați din nu imediat" -#: ../src/client/components/stock.vala:24 -msgid "_Discard" -msgstr "_Renunță" +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:101 +#, c-format +msgid "Incoming mail server password required for %s" +msgstr "Parolă necesară pentru serverul de primire email pentru %s" -#: ../src/client/components/stock.vala:26 -msgid "_Open" -msgstr "_Deschide" +#: src/client/components/main-window-info-bar.vala:102 +msgid "Messages cannot be received without the correct password." +msgstr "Mesajele nu pot fi primite fără parola corectă." -#: ../src/client/components/stock.vala:28 -msgid "_Print..." -msgstr "Ti_părește..." +#. Translators: Tooltip label for Retry button +#: src/client/components/main-window-info-bar.vala:104 +msgid "Retry receiving email, you will be prompted for a password" +msgstr "Reîncercați primirea de emailuri, vi se va cere o parolă" -#: ../src/client/components/stock.vala:30 ../ui/remove_confirm.glade.h:6 -msgid "_Remove" -msgstr "_Șterge" +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:109 +#, c-format +msgid "Outgoing mail server password required for %s" +msgstr "Parolă necesară pentru serverul de trimitere email pentru %s" -#. Select all. -#: ../src/client/components/stock.vala:32 -#: ../src/client/conversation-viewer/conversation-viewer.vala:1350 -msgid "Select _All" -msgstr "Selectează t_ot" +#: src/client/components/main-window-info-bar.vala:110 +msgid "Messages cannot be sent without the correct password." +msgstr "Mesajele nu pot fi trimise fără parola corectă." -#: ../src/client/components/stock.vala:33 -msgid "_Keep" -msgstr "_Păstrează" +#. Translators: Tooltip label for Retry button +#: src/client/components/main-window-info-bar.vala:112 +msgid "Retry sending queued messages, you will be prompted for a password" +msgstr "Reîncercați trimiterea mesajelor de la coadă, vi se va cere o parolă" -#: ../src/client/composer/composer-widget.vala:73 -msgid "Saved" -msgstr "Salvat" +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:117 +#, c-format +msgid "Incoming mail server security is not trusted for %s" +msgstr "Securitatea serverului de primire email nu este de încredere pentru %s" -#: ../src/client/composer/composer-widget.vala:74 -msgid "Saving" -msgstr "Se salvează" +#: src/client/components/main-window-info-bar.vala:118 +msgid "Messages will not be received until checked." +msgstr "Mesajele nu vor fi primite până când va fi verificată." -#: ../src/client/composer/composer-widget.vala:75 -msgid "Error saving" -msgstr "Salvare eșuată" +#. Translators: Tooltip label for Retry button +#: src/client/components/main-window-info-bar.vala:120 +#: src/client/components/main-window-info-bar.vala:128 +msgid "Check security details" +msgstr "Verifică detaliile de securitate" -#: ../src/client/composer/composer-widget.vala:76 -msgid "Press Backspace to delete quote" -msgstr "Apăsați Backspace pentru a șterge citatul" +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:125 +#, c-format +msgid "Outgoing mail server security is not trusted for %s" +msgstr "" +"Securitatea serverului de trimitere email nu este de înceredere pentru %s" -#: ../src/client/composer/composer-widget.vala:77 -msgid "New Message" -msgstr "Mesaj nou" +#: src/client/components/main-window-info-bar.vala:126 +msgid "Messages cannot be sent until checked." +msgstr "Mesajele nu pot fi trimise până este verificată." -#. A list of keywords, separated by pipe ("|") characters, that suggest an attachment; since -#. this is full-word checking, include all variants of each word. No spaces are allowed. -#: ../src/client/composer/composer-widget.vala:138 -msgid "" -"attach|attaching|attaches|attachment|attachments|attached|enclose|enclosed|" -"enclosing|encloses|enclosure|enclosures" +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:133 +#, c-format +msgid "A problem occurred checking mail for %s" +msgstr "A apărut o problemă la verificarea email-urilor pentru %s" + +#: src/client/components/main-window-info-bar.vala:134 +#: src/client/components/main-window-info-bar.vala:142 +msgid "Something went wrong, please file a bug report if the problem persists" +msgstr "" +"Ceva nu a funcționat cum trebuie, completați un raport de defecțiune dacă " +"problema persistă" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:141 +#, c-format +msgid "A problem occurred sending mail for %s" +msgstr "A apărut o problemă la verificarea email-urilor pentru %s" + +#. Translators: Tooltip label for Retry button +#: src/client/components/main-window-info-bar.vala:144 +msgid "Retry sending queued messages" +msgstr "Reîncearcă trimiterea mesajelor de la coadă" + +#: src/client/components/main-window-info-bar.vala:155 +msgid "A database problem has occurred" +msgstr "A apărut o problemă la baza de date" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:157 +#, c-format +msgid "Messages for %s must be downloaded again." +msgstr "Mesajele pentru %s trebuiesc descărcate din nou." + +#: src/client/components/main-window-info-bar.vala:170 +msgid "Geary has encountered a problem" +msgstr "Geary a întâmpinat o problemă" + +#: src/client/components/main-window-info-bar.vala:171 +msgid "" +"Please check the technical details and report the problem if it persists." +msgstr "Verificați detaliile tehnice și raportați problema dacă persistă." + +#: src/client/components/main-window-info-bar.vala:179 +msgid "_Details" +msgstr "_Detalii" + +#. Button tooltip for displaying technical details about an account problem +#: src/client/components/main-window-info-bar.vala:180 ui/main-window.ui:250 +msgid "View technical details about the error" +msgstr "Vizualizează detaliile tehnice despre eroare" + +#: src/client/components/main-window-info-bar.vala:184 +msgid "_Retry" +msgstr "_Reîncearcă" + +#: src/client/components/search-bar.vala:8 +#: src/client/folder-list/folder-list-search-branch.vala:38 +#: src/engine/api/geary-special-folder-type.vala:51 +msgid "Search" +msgstr "Caută" + +#. Search entry. +#: src/client/components/search-bar.vala:23 +msgid "Search all mail in account for keywords (Ctrl+S)" +msgstr "Caută cuvintele cheie în toate emailurile din cont (Ctrl+S)" + +#: src/client/components/search-bar.vala:101 +#, c-format +msgid "Indexing %s account" +msgstr "Se indexează contul %s" + +#: src/client/components/search-bar.vala:112 +#: src/client/folder-list/folder-list-search-branch.vala:39 +#, c-format +msgid "Search %s account" +msgstr "Caută în contul %s" + +#. / Displayed in the space-limited status bar while a message is in the process of being sent. +#: src/client/components/status-bar.vala:26 +msgid "Sending…" +msgstr "Se trimite…" + +#. / Displayed in the space-limited status bar when a message fails to be sent due to error. +#: src/client/components/status-bar.vala:29 +msgid "Error sending email" +msgstr "Eroare la trimiterea emailului" + +#. Displayed in the space-limited status bar when a message fails to be uploaded +#. to Sent Mail after being sent. +#: src/client/components/status-bar.vala:33 +msgid "Error saving sent mail" +msgstr "Eroare la salvarea emailului trimis" + +#: src/client/components/stock.vala:18 +msgid "_OK" +msgstr "_OK" + +#: src/client/components/stock.vala:19 ui/password-dialog.glade:196 +msgid "_Cancel" +msgstr "_Anulează" + +#: src/client/components/stock.vala:21 +msgid "_About" +msgstr "_Despre" + +#: src/client/components/stock.vala:22 +msgid "_Add" +msgstr "_Adaugă" + +#: src/client/components/stock.vala:23 +msgid "_Close" +msgstr "În_chide" + +#: src/client/components/stock.vala:24 +msgid "_Discard" +msgstr "_Renunță" + +#: src/client/components/stock.vala:25 ui/main-toolbar-menus.ui:56 +msgid "_Help" +msgstr "_Ajutor" + +#: src/client/components/stock.vala:26 ui/conversation-email-menus.ui:77 +msgid "_Open" +msgstr "_Deschide" + +#: src/client/components/stock.vala:27 ui/main-toolbar-menus.ui:46 +msgid "_Preferences" +msgstr "_Preferințe" + +#. Translators: Menu item to print a single, specific message +#: src/client/components/stock.vala:28 ui/conversation-email-menus.ui:64 +msgid "_Print…" +msgstr "Ti_părește…" + +#: src/client/components/stock.vala:29 +msgid "_Quit" +msgstr "_Ieșire" + +#: src/client/components/stock.vala:30 +msgid "_Remove" +msgstr "_Șterge" + +#: src/client/components/stock.vala:31 ui/conversation-email-menus.ui:83 +msgid "_Save" +msgstr "_Salvează" + +#: src/client/components/stock.vala:32 +msgid "_Keep" +msgstr "_Păstrează" + +#: src/client/composer/composer-link-popover.vala:149 +msgid "Link URL is not correctly formatted, e.g. http://example.com" +msgstr "Legătura URL nu este formatată corect; ex. http://exemplu.ro" + +#: src/client/composer/composer-link-popover.vala:156 +msgid "Invalid link URL" +msgstr "Legătură URL nevalidă" + +#: src/client/composer/composer-link-popover.vala:156 +msgid "Invalid email address" +msgstr "Adresă de email nevalidă" + +#: src/client/composer/composer-widget.vala:158 +msgid "Saved" +msgstr "Salvat" + +#: src/client/composer/composer-widget.vala:159 +msgid "Saving" +msgstr "Se salvează" + +#: src/client/composer/composer-widget.vala:160 +msgid "Error saving" +msgstr "Salvare eșuată" + +#: src/client/composer/composer-widget.vala:161 +msgid "Press Backspace to delete quote" +msgstr "Apăsați Backspace pentru a șterge citatul" + +#. Translators: This is list of keywords, separated by pipe ("|") +#. characters, that suggest an attachment; since this is full-word +#. checking, include all variants of each word. No spaces are +#. allowed. +#: src/client/composer/composer-widget.vala:170 +msgid "" +"attach|attaching|attaches|attachment|attachments|attached|enclose|enclosed|" +"enclosing|encloses|enclosure|enclosures" msgstr "" "attach|attaching|attaches|attachment|attachments|attached|enclose|enclosed|" "enclosing|encloses|enclosure|enclosures|atașament|atașamente|atașamentele|" "fișier|fișiere|fișierele" -#: ../src/client/composer/composer-widget.vala:1114 -#: ../src/client/composer/composer-widget.vala:1118 -msgid "Do you want to discard this message?" -msgstr "Renunțați la acest mesaj?" +#. Translators: This dialog text is displayed to the +#. user when closing a composer where the options are +#. Keep, Discard or Cancel. +#: src/client/composer/composer-widget.vala:1130 +msgid "Do you want to keep or discard this draft message?" +msgstr "Păstrați sau renunțați la acest mesaj de tip ciornă?" + +#. Translators: This dialog text is displayed to the +#. user when closing a composer where the options are +#. only Discard or Cancel. +#: src/client/composer/composer-widget.vala:1158 +msgid "Do you want to discard this draft message?" +msgstr "Renunțați la acest mesaj de tip ciornă?" -#: ../src/client/composer/composer-widget.vala:1274 +#: src/client/composer/composer-widget.vala:1275 msgid "Send message with an empty subject and body?" msgstr "Trimiteți mesajul fără subiect și conținut?" -#: ../src/client/composer/composer-widget.vala:1276 +#: src/client/composer/composer-widget.vala:1277 msgid "Send message with an empty subject?" msgstr "Trimiteți mesajul fără subiect?" -#: ../src/client/composer/composer-widget.vala:1278 +#: src/client/composer/composer-widget.vala:1279 msgid "Send message with an empty body?" msgstr "Trimiteți mesajul fără conținut?" -#: ../src/client/composer/composer-widget.vala:1280 +#: src/client/composer/composer-widget.vala:1283 msgid "Send message without an attachment?" msgstr "Trimiteți mesajul fără niciun fișier atașat?" -#: ../src/client/composer/composer-widget.vala:1542 -msgid "Cannot add attachment" -msgstr "Nu se poate atașa fișierul" +#: src/client/composer/composer-widget.vala:1588 +#, c-format +msgid "“%s” already attached for delivery." +msgstr "„%s” este deja atașat pentru trimitere." + +#. / In the composer, the filename followed by its filesize, i.e. "notes.txt (1.12KB)" +#. Translators: The first argument will be a +#. description of the document type, the second will +#. be a human-friendly size string. For example: +#. Document (100.9MB) +#: src/client/composer/composer-widget.vala:1596 +#: src/client/conversation-viewer/conversation-email.vala:173 +#, c-format +msgid "%s (%s)" +msgstr "%s (%s)" -#: ../src/client/composer/composer-widget.vala:1553 +#: src/client/composer/composer-widget.vala:1633 #, c-format -msgid "\"%s\" could not be found." +msgid "“%s” could not be found." msgstr "„%s” nu a putut fi găsit." -#: ../src/client/composer/composer-widget.vala:1560 +#: src/client/composer/composer-widget.vala:1639 #, c-format -msgid "\"%s\" is a folder." +msgid "“%s” is a folder." msgstr "„%s” este un dosar." -#: ../src/client/composer/composer-widget.vala:1567 +#: src/client/composer/composer-widget.vala:1645 #, c-format -msgid "\"%s\" is an empty file." +msgid "“%s” is an empty file." msgstr "„%s” este un fișier gol." -#: ../src/client/composer/composer-widget.vala:1581 +#: src/client/composer/composer-widget.vala:1658 #, c-format -msgid "\"%s\" could not be opened for reading." +msgid "“%s” could not be opened for reading." msgstr "„%s” nu a putut fi deschis pentru citire." -#: ../src/client/composer/composer-widget.vala:1588 -#, c-format -msgid "\"%s\" already attached for delivery." -msgstr "„%s” este deja atașat pentru trimitere." +#: src/client/composer/composer-widget.vala:1666 +msgid "Cannot add attachment" +msgstr "Nu se poate atașa fișierul" -#. / In the composer, the filename followed by its filesize, i.e. "notes.txt (1.12KB)" -#: ../src/client/composer/composer-widget.vala:1597 -#, c-format -msgid "%s (%s)" -msgstr "%s (%s)" +#. Translators: Human-readable version of the RFC 822 To header +#: src/client/composer/composer-widget.vala:1716 +#: src/client/conversation-viewer/conversation-email.vala:981 +#: src/engine/rfc822/rfc822-utils.vala:298 ui/conversation-message.ui:312 +msgid "To:" +msgstr "Către:" + +#. Translators: Human-readable version of the RFC 822 CC header +#: src/client/composer/composer-widget.vala:1722 +#: src/client/conversation-viewer/conversation-email.vala:986 +#: src/engine/rfc822/rfc822-utils.vala:303 ui/conversation-message.ui:357 +msgid "Cc:" +msgstr "Cc:" -#: ../src/client/composer/composer-widget.vala:1658 -msgid "To: " -msgstr "Către: " - -#: ../src/client/composer/composer-widget.vala:1661 -msgid "Cc: " -msgstr "Cc: " - -#: ../src/client/composer/composer-widget.vala:1664 -msgid "Bcc: " -msgstr "Bcc: " +#. Translators: Human-readable version of the RFC 822 BCC header +#: src/client/composer/composer-widget.vala:1728 +#: src/client/conversation-viewer/conversation-email.vala:991 +#: ui/conversation-message.ui:402 +msgid "Bcc:" +msgstr "Bcc:" -#: ../src/client/composer/composer-widget.vala:1667 +#. Translators: Human-readable version of the RFC 822 Reply-To header +#: src/client/composer/composer-widget.vala:1734 msgid "Reply-To: " -msgstr "Răspunde la:" +msgstr "Răspunde la: " -#: ../src/client/composer/composer-widget.vala:1900 +#: src/client/composer/composer-widget.vala:1874 msgid "Select Color" msgstr "Selectează culoarea" -#. Displayed in the From dropdown to indicate an "alternate email address" -#. for an account. The first printf argument will be the alternate email -#. address, and the second will be the account's primary email address. -#: ../src/client/composer/composer-widget.vala:2347 +#. Displayed in the From dropdown to indicate an +#. "alternate email address" for an account. The first +#. printf argument will be the alternate email address, +#. and the second will be the account's primary email +#. address. +#: src/client/composer/composer-widget.vala:2064 #, c-format msgid "%1$s via %2$s" msgstr "%1$s via %2$s" #. Composer label (with mnemonic underscore) for the account selector #. when choosing what address to send a message from. -#: ../src/client/composer/composer-widget.vala:2389 +#: src/client/composer/composer-widget.vala:2125 msgid "_From:" msgstr "_De la:" -#: ../src/client/composer/spell-check-popover.vala:210 +#. Translators: This is the name of the file chooser filter +#. when inserting an image in the composer. +#: src/client/composer/composer-widget.vala:2357 +msgid "Images" +msgstr "Imagini" + +#: src/client/composer/composer-window.vala:14 +msgid "New Message" +msgstr "Mesaj nou" + +#: src/client/composer/spell-check-popover.vala:117 +msgid "Remove this language from the preferred list" +msgstr "Elimină această limbă de pe lista de preferate" + +#: src/client/composer/spell-check-popover.vala:121 +msgid "Add this language to the preferred list" +msgstr "Adaugă această limbă la lista de preferate" + +#: src/client/composer/spell-check-popover.vala:217 msgid "Search for more languages" msgstr "Caută după mai multe limbi" -#: ../src/client/conversation-list/formatted-conversation-data.vala:11 -msgid "Me" -msgstr "Eu" +#: src/client/conversation-list/conversation-list-view.vala:283 +msgid "Delete conversation" +msgstr "Șterge conversația" -#: ../src/client/conversation-viewer/conversation-viewer.vala:413 -msgid "No conversations selected." -msgstr "Nicio conversație selectată." - -#: ../src/client/conversation-viewer/conversation-viewer.vala:415 -#, c-format -msgid "%u conversation selected." -msgid_plural "%u conversations selected." -msgstr[0] "%u discuție selectată." -msgstr[1] "%u discuții selectate." -msgstr[2] "%u de discuții selectate." - -#: ../src/client/conversation-viewer/conversation-viewer.vala:444 -msgid "No search results found." -msgstr "Niciun rezultat la căutare." - -#: ../src/client/conversation-viewer/conversation-viewer.vala:446 -msgid "No conversations in folder." -msgstr "Nicio conversație în dosar." - -#: ../src/client/conversation-viewer/conversation-viewer.vala:713 -msgid "This message contains remote images." -msgstr "Acest mesaj conține imagini de pe un alt server." +#: src/client/conversation-list/conversation-list-view.vala:286 +#: ui/main-toolbar-menus.ui:5 +msgid "Mark as _Read" +msgstr "Ma_rchează ca citit" -#: ../src/client/conversation-viewer/conversation-viewer.vala:713 -msgid "Show Images" -msgstr "Arată imagini" +#: src/client/conversation-list/conversation-list-view.vala:289 +#: ui/main-toolbar-menus.ui:9 +msgid "Mark as _Unread" +msgstr "Marchează ca _necitit" -#: ../src/client/conversation-viewer/conversation-viewer.vala:714 -msgid "Always Show From Sender" -msgstr "Afișează întotdeauna de la acest expeditor" +#: src/client/conversation-list/conversation-list-view.vala:292 +#: ui/main-toolbar-menus.ui:17 +msgid "U_nstar" +msgstr "Eliminați _steaua" -#: ../src/client/conversation-viewer/conversation-viewer.vala:738 -msgid "Edit Draft" -msgstr "Editează ciorna" +#: src/client/conversation-list/conversation-list-view.vala:294 +#: ui/main-toolbar-menus.ui:13 +msgid "_Star" +msgstr "Marchează cu o _stea" -#: ../src/client/conversation-viewer/conversation-viewer.vala:831 -msgid "From:" -msgstr "De la:" +#. Translators: Menu item to reply to a specific message. +#: src/client/conversation-list/conversation-list-view.vala:297 +#: ui/conversation-email-menus.ui:9 +msgid "_Reply" +msgstr "_Răspunde" -#: ../src/client/conversation-viewer/conversation-viewer.vala:834 -msgid "To:" -msgstr "Către:" +#: src/client/conversation-list/conversation-list-view.vala:298 +msgid "R_eply All" +msgstr "Răspund_e tuturor" -#: ../src/client/conversation-viewer/conversation-viewer.vala:837 -msgid "Cc:" -msgstr "Cc:" +#. Translators: Menu item to forward a specific message. +#: src/client/conversation-list/conversation-list-view.vala:299 +#: ui/conversation-email-menus.ui:21 +msgid "_Forward" +msgstr "Î_naintează" -#: ../src/client/conversation-viewer/conversation-viewer.vala:840 -msgid "Bcc:" -msgstr "Bcc:" +#: src/client/conversation-list/formatted-conversation-data.vala:11 +msgid "Me" +msgstr "Eu" -#: ../src/client/conversation-viewer/conversation-viewer.vala:843 -msgid "Subject:" -msgstr "Subiect:" +#. Translators: This is the file type displayed for +#. attachments with unknown file types. +#: src/client/conversation-viewer/conversation-email.vala:159 +msgid "Unknown" +msgstr "Necunoscut" + +#. Translators: Human-readable version of the RFC 822 From header +#: src/client/conversation-viewer/conversation-email.vala:976 +#: src/engine/rfc822/rfc822-utils.vala:289 +msgid "From:" +msgstr "De la:" -#: ../src/client/conversation-viewer/conversation-viewer.vala:846 +#. Translators: Human-readable version of the RFC 822 Date header +#: src/client/conversation-viewer/conversation-email.vala:996 +#: src/engine/rfc822/rfc822-utils.vala:294 msgid "Date:" msgstr "Data:" -#: ../src/client/conversation-viewer/conversation-viewer.vala:1165 -#, c-format -msgid "%u read message" -msgid_plural "%u read messages" -msgstr[0] "%u mesaj citit" -msgstr[1] "%u mesaje citite" -msgstr[2] "%u de mesaje citite" - -#: ../src/client/conversation-viewer/conversation-viewer.vala:1297 -#, c-format -msgid "This message was sent successfully, but could not be saved to %s." -msgstr "Acest mesaj a fost trimis cu succes, dar nu a putut fi salvat în %s." - -#. Add a menu item for copying the current selection. -#: ../src/client/conversation-viewer/conversation-viewer.vala:1323 -#: ../ui/composer.glade.h:4 -msgid "_Copy" -msgstr "_Copiază" - -#. Add a menu item for copying the address. -#: ../src/client/conversation-viewer/conversation-viewer.vala:1331 -msgid "Copy _Email Address" -msgstr "Copiază adresa de _email" - -#. Add a menu item for copying the link. -#: ../src/client/conversation-viewer/conversation-viewer.vala:1336 -#: ../ui/composer.glade.h:17 -msgid "Copy _Link" -msgstr "Copiază _legătura" - -#. Select message. -#: ../src/client/conversation-viewer/conversation-viewer.vala:1344 -msgid "Select _Message" -msgstr "Selectează _mesajul" - -#. Inspect. -#: ../src/client/conversation-viewer/conversation-viewer.vala:1356 -msgid "_Inspect" -msgstr "_Inspectează" - -#: ../src/client/conversation-viewer/conversation-viewer.vala:1583 -msgid "This link appears to go to" -msgstr "Acestă legătură pare să indice spre" - -#: ../src/client/conversation-viewer/conversation-viewer.vala:1584 -msgid "but actually goes to" -msgstr "dar de fapt trimite la" - -#. If href doesn't look like a URL, something is fishy, so warn the user -#: ../src/client/conversation-viewer/conversation-viewer.vala:1639 -msgid " (Invalid?)" -msgstr " (Nevalid?)" - -#: ../src/client/conversation-viewer/conversation-viewer.vala:1745 -msgid "_Save Image As..." -msgstr "_Salvează imaginea ca..." - -#: ../src/client/conversation-viewer/conversation-viewer.vala:1849 -msgid "_Save As..." -msgstr "_Salvează ca..." - -#: ../src/client/conversation-viewer/conversation-viewer.vala:1854 -msgid "Save All A_ttachments..." -msgstr "Salvează toate fișierele a_tașate..." - -#: ../src/client/conversation-viewer/conversation-viewer.vala:1874 -msgid "Save A_ttachment..." -msgid_plural "Save All A_ttachments..." -msgstr[0] "Salvează fișierul a_tașat..." -msgstr[1] "Salvează toate fișierele a_tașate..." -msgstr[2] "Salvează toate fișierele a_tașate..." - -#. Reply to all on a message. -#: ../src/client/conversation-viewer/conversation-viewer.vala:1889 -msgid "Reply to _All" -msgstr "Răspunde _tuturor" - -#. Mark as read/unread. -#: ../src/client/conversation-viewer/conversation-viewer.vala:1906 -msgid "_Mark as Read" -msgstr "_Marchează ca citit" - -#: ../src/client/conversation-viewer/conversation-viewer.vala:1910 -msgid "_Mark as Unread" -msgstr "_Marchează ca necitit" - -#: ../src/client/conversation-viewer/conversation-viewer.vala:1916 -msgid "Mark Unread From _Here" -msgstr "Marc_hează ca necitit de aici" - -#. Separator. -#. View original message source. -#: ../src/client/conversation-viewer/conversation-viewer.vala:1931 -msgid "_View Source" -msgstr "_Vizualizare sursă" - -#. Generate the attachment table. -#. / Placeholder filename for attachments with no filename. -#: ../src/client/conversation-viewer/conversation-viewer.vala:2255 -#: ../src/engine/rfc822/rfc822-utils.vala:392 -msgid "none" -msgstr "fără" - -#: ../src/client/conversation-viewer/conversation-viewer.vala:2390 -msgid "Failed to open default text editor." -msgstr "Eroare la deschiderea editorului de text implicit." +#. Translators: Human-readable version of the RFC 822 Subject header +#: src/client/conversation-viewer/conversation-email.vala:1001 +#: src/engine/rfc822/rfc822-utils.vala:292 +msgid "Subject:" +msgstr "Subiect:" -#: ../src/client/conversation-viewer/conversation-web-view.vala:322 -#, c-format -msgid "%s - Conversation Inspector" -msgstr "%s - Inspector conversație" +#: src/client/conversation-viewer/conversation-message.vala:65 +msgid "This email address may have been forged" +msgstr "Această adresă de email poate fi falsificată" + +#. Compact headers +#. Translators: This is displayed in place of the from address +#. when the message has no from address. +#: src/client/conversation-viewer/conversation-message.vala:394 +msgid "No sender" +msgstr "Fără expeditor" + +#. Translators: This separates multiple 'from' +#. addresses in the compact header for a message. +#: src/client/conversation-viewer/conversation-message.vala:766 +msgid ", " +msgstr ", " + +#. Translators: This string is used as the HTML IMG ALT +#. attribute value when displaying an inline image in an email +#. that did not specify a file name. E.g. ImageCannot remove account " -msgstr "" -"Contul nu poate fi eliminat " +#: ui/accounts_editor_list_pane.ui:63 +msgid "To get started, select an email provider below." +msgstr "Pentru a începe, selectați furnizorul de email de mai jos." -#: ../ui/account_cannot_remove.glade.h:2 -msgid "" -"A composer window associated with this account is currently open. Send or " -"discard the message and try again." -msgstr "" -"O fereastră de compunere asociată cu acest cont este deschisă. Trimiteți sau " -"anulați mesajul și încearcați din nou." +#: ui/accounts_editor_list_pane.ui:76 +msgid "Welcome to Geary" +msgstr "Bine ați venit la Geary" -#: ../ui/account_list.glade.h:1 -msgid "Add account" -msgstr "Adaugă un cont" +#. This title is shown to users when confirming if they want to remove an account. The string substitution is replaced with the account's name. +#: ui/accounts_editor_remove_pane.ui:73 +#, c-format +msgid "Confirm removing: %s" +msgstr "Confirmați eliminarea: %s" -#: ../ui/account_list.glade.h:2 -msgid "Edit account" -msgstr "Editează contul" +#: ui/accounts_editor_remove_pane.ui:91 +msgid "" +"Removing an account will remove it from Geary and delete locally cached " +"email data from your computer, but not from your service provider." +msgstr "" +"Eliminând un cont îl va elimina din Geary și șterge datele de email salvate " +"local în calculator, dar nu de la furnizorul de serviciu." -#: ../ui/account_list.glade.h:3 +#: ui/accounts_editor_remove_pane.ui:122 msgid "Remove account" msgstr "Șterge contul" -#: ../ui/account_spinner.glade.h:1 -msgid "Please wait while Geary validates your account." -msgstr "Așteptați cât timp Geary vă validează contul." +#: ui/accounts_editor_servers_pane.ui:17 +msgid "Cancel" +msgstr "Anulează" + +#: ui/accounts_editor_servers_pane.ui:47 +msgid "Apply" +msgstr "Aplică" -#: ../ui/certificate_warning_dialog.glade.h:1 +#: ui/certificate_warning_dialog.glade:7 msgid "Untrusted Connection" msgstr "Conexiune fără încredere" -#: ../ui/certificate_warning_dialog.glade.h:2 +#: ui/certificate_warning_dialog.glade:29 msgid "_Always Trust This Server" msgstr "Am încredere întotdeauna în _acest server" -#: ../ui/certificate_warning_dialog.glade.h:3 +#: ui/certificate_warning_dialog.glade:43 msgid "_Trust This Server" msgstr "Am încredere în aces_t server" -#: ../ui/certificate_warning_dialog.glade.h:4 -msgid "_Don't Trust This Server" -msgstr "Nu am încre_fdere în acest server" +#: ui/certificate_warning_dialog.glade:57 +msgid "_Don’t Trust This Server" +msgstr "Nu am încre_dere în acest server" -#: ../ui/composer.glade.h:1 -msgid "_Undo" -msgstr "An_ulează" +#: ui/composer-headerbar.ui:17 ui/composer-headerbar.ui:174 +msgid "Detach (Ctrl+D)" +msgstr "Detașează (Ctrl+D)" -#: ../ui/composer.glade.h:2 -msgid "_Redo" -msgstr "_Refă" +#: ui/composer-headerbar.ui:57 ui/composer-headerbar.ui:83 +msgid "Attach File (Ctrl+T)" +msgstr "Atașează fișier (Ctrl+T)" -#: ../ui/composer.glade.h:3 -msgid "Cu_t" -msgstr "_Taie" +#: ui/composer-headerbar.ui:107 +msgid "Include Original Attachments" +msgstr "Include atașamentele originale" -#: ../ui/composer.glade.h:5 -msgid "_Paste" -msgstr "Li_pește" +#: ui/composer-headerbar.ui:202 +msgid "_Send" +msgstr "_Trimite" -#: ../ui/composer.glade.h:6 -msgid "_Left" -msgstr "_Stânga" - -#: ../ui/composer.glade.h:7 -msgid "_Right" -msgstr "_Dreapta" - -#: ../ui/composer.glade.h:8 -msgid "_Center" -msgstr "_Centru" - -#: ../ui/composer.glade.h:9 -msgid "_Justify" -msgstr "_Aliniază la margini" - -#: ../ui/composer.glade.h:10 -msgid "Link (Ctrl+L)" -msgstr "Legătură (Ctrl+L)" +#: ui/composer-headerbar.ui:207 +msgid "Send (Ctrl+Enter)" +msgstr "Trimite (Ctrl+Enter)" -#: ../ui/composer.glade.h:11 -msgid "C_olor" -msgstr "Cul_oare" +#: ui/composer-headerbar.ui:230 +msgid "Discard and Close" +msgstr "Anulează și închide" + +#: ui/composer-headerbar.ui:254 +msgid "Save and Close" +msgstr "Salvează și închide" + +#. Note that this button and the Update button will never be shown at the same time to the user. +#: ui/composer-link-popover.ui:41 +msgid "Insert the new link with this URL" +msgstr "Introduceți noua legătură cu acest URL" + +#: ui/composer-link-popover.ui:52 +msgid "Link URL" +msgstr "Legătură URL" + +#. Note that this button and the Insert button will never be shown at the same time to the user. +#: ui/composer-link-popover.ui:66 +msgid "Update this link’s URL" +msgstr "Actualizați URL-ul legăturii" + +#: ui/composer-link-popover.ui:86 +msgid "Delete this link" +msgstr "Șterge această legătură" + +#: ui/composer-link-popover.ui:106 +msgid "Open this link" +msgstr "Deschide această legătură" -#: ../ui/composer.glade.h:12 -msgid "More options" -msgstr "Mai multe opțiuni" +#: ui/composer-menus.ui:7 +msgid "S_ans Serif" +msgstr "Fără s_erife" -#: ../ui/composer.glade.h:13 -msgid "Quote text (Ctrl+])" -msgstr "Citează text (Ctrl+])" +#: ui/composer-menus.ui:12 +msgid "S_erif" +msgstr "Cu s_erife" -#: ../ui/composer.glade.h:14 -msgid "Unquote text (Ctrl+[)" -msgstr "Anulează citatul (Ctrl+[)" +#: ui/composer-menus.ui:17 +msgid "_Fixed Width" +msgstr "Lățime _fixă" -#: ../ui/composer.glade.h:15 -msgid "Remove formatting (Ctrl+Space)" -msgstr "Elimină fomatarea (Ctrl+Space)" - -#: ../ui/composer.glade.h:16 -msgctxt "Clipboard paste with rich text" -msgid "Paste _With Formatting" -msgstr "Lipește cu _formatare" +#: ui/composer-menus.ui:24 +msgid "_Small" +msgstr "_Mic" -#: ../ui/composer.glade.h:18 -msgid "Bold (Ctrl+B)" -msgstr "Îngroșat (Ctrl+B)" +#: ui/composer-menus.ui:29 +msgid "_Medium" +msgstr "_Mediu" -#: ../ui/composer.glade.h:19 -msgid "Italic (Ctrl+I)" -msgstr "Italic (Ctrl+I)" - -#: ../ui/composer.glade.h:20 -msgid "Underline (Ctrl+U)" -msgstr "Subliniat (Ctrl+U)" +#: ui/composer-menus.ui:34 +msgid "Lar_ge" +msgstr "Ma_re" -#: ../ui/composer.glade.h:21 -msgid "Strikethrough (Ctrl+K)" -msgstr "Tăiat (Ctrl+K)" +#: ui/composer-menus.ui:41 +msgid "C_olor" +msgstr "Cul_oare" -#: ../ui/composer.glade.h:22 +#: ui/composer-menus.ui:47 ui/composer-menus.ui:62 msgid "_Rich Text" msgstr "Text cu fo_rmatare" -#: ../ui/composer.glade.h:23 +#: ui/composer-menus.ui:53 ui/composer-menus.ui:68 msgid "Show Extended Fields" msgstr "Arată câmpurile extinse" -#: ../ui/composer.glade.h:24 -msgctxt "Label" -msgid "Close and Save" -msgstr "Închide și salvează" - -#: ../ui/composer.glade.h:25 -msgctxt "Short Label" -msgid "Close and Save" -msgstr "Închide și salvează" - -#: ../ui/composer.glade.h:26 -msgctxt "Tooltip" -msgid "Close and Save" -msgstr "Închide și salvează" - -#: ../ui/composer.glade.h:27 -msgctxt "Label" -msgid "Close and Discard" -msgstr "Închide și anulează" - -#: ../ui/composer.glade.h:28 -msgctxt "Short Label" -msgid "Close and Discard" -msgstr "Închide și anulează" - -#: ../ui/composer.glade.h:29 -msgctxt "Tooltip" -msgid "Close and Discard" -msgstr "Închide și anulează" - -#: ../ui/composer.glade.h:30 -msgid "Lar_ge" -msgstr "Ma_re" - -#: ../ui/composer.glade.h:31 -msgid "Large" -msgstr "Mare" - -#: ../ui/composer.glade.h:32 -msgid "_Medium" -msgstr "_Mediu" - -#: ../ui/composer.glade.h:33 -msgid "Medium" -msgstr "Mediu" - -#: ../ui/composer.glade.h:34 -msgid "_Small" -msgstr "_Mic" - -#: ../ui/composer.glade.h:35 -msgid "Small" -msgstr "Mic" - -#: ../ui/composer.glade.h:36 -msgid "S_ans Serif" -msgstr "Fără s_erife" - -#: ../ui/composer.glade.h:37 -msgid "Sans Serif" -msgstr "Fără serife" - -#: ../ui/composer.glade.h:38 -msgid "S_erif" -msgstr "Cu s_erife" - -#: ../ui/composer.glade.h:39 -msgid "Serif" -msgstr "Cu serife" - -#: ../ui/composer.glade.h:40 -msgid "_Fixed Width" -msgstr "Lățime _fixă" - -#: ../ui/composer.glade.h:41 -msgid "Fixed Width" -msgstr "Lățime fixă" - -#: ../ui/composer.glade.h:42 -msgid "Detach" -msgstr "Detașează" - -#: ../ui/composer.glade.h:43 -msgid "Detach (Ctrl+D)" -msgstr "Detașează (Ctrl+D)" - -#: ../ui/composer.glade.h:44 -msgid "_Send" -msgstr "_Trimite" +#: ui/composer-menus.ui:78 +msgid "_Undo" +msgstr "An_ulează" -#: ../ui/composer.glade.h:45 -msgid "Send" -msgstr "Trimite" +#: ui/composer-menus.ui:82 +msgid "_Redo" +msgstr "_Refă" -#: ../ui/composer.glade.h:46 -msgid "Send (Ctrl+Enter)" -msgstr "Trimite (Ctrl+Enter)" +#: ui/composer-menus.ui:88 ui/composer-menus.ui:106 +msgid "Cu_t" +msgstr "_Taie" -#: ../ui/composer.glade.h:47 -msgid "_Attach File" -msgstr "_Atașează fișier" - -#: ../ui/composer.glade.h:48 -msgid "Attach File" -msgstr "Atașează fișier" +#: ui/composer-menus.ui:92 ui/composer-menus.ui:110 +#: ui/conversation-message-menus.ui:37 +msgid "_Copy" +msgstr "_Copiază" -#: ../ui/composer.glade.h:49 -#| msgid "Attach File" -msgid "Attach File (Ctrl+T)" -msgstr "Atașează fișier (Ctrl+T)" +#: ui/composer-menus.ui:96 ui/composer-menus.ui:114 +msgid "_Paste" +msgstr "Li_pește" -#: ../ui/composer.glade.h:50 -msgid "_Include Original Attachments" -msgstr "_Include fișierele atașate originale" +#: ui/composer-menus.ui:100 +msgctxt "Clipboard paste as plain text" +msgid "Paste _Without Formatting" +msgstr "Lipește _fără formatare" -#: ../ui/composer.glade.h:51 -msgid "Include Original Attachments" -msgstr "Include atașamentele originale" +#: ui/composer-menus.ui:120 +msgid "Select _All" +msgstr "Selectează t_ot" -#: ../ui/composer.glade.h:52 -#| msgid "Enable _spell checking" -msgid "Select spell checking language" -msgstr "Selectează limba pentru corectarea ortografică" - -#: ../ui/composer.glade.h:53 -msgid "Spelling language" -msgstr "Limba pentru corectarea ortografică" +#: ui/composer-menus.ui:127 ui/conversation-message-menus.ui:49 +msgid "_Inspect…" +msgstr "_Inspectează…" #. Address(es) e-mail is to be sent to -#: ../ui/composer.glade.h:55 +#: ui/composer-widget.ui:56 msgid "_To" msgstr "Că_tre" -#: ../ui/composer.glade.h:56 +#: ui/composer-widget.ui:75 msgid "_Cc" msgstr "_Cc" -#: ../ui/composer.glade.h:57 +#: ui/composer-widget.ui:130 msgid "_Subject" msgstr "_Subiect" -#: ../ui/composer.glade.h:58 +#: ui/composer-widget.ui:149 msgid "_Bcc" msgstr "_Bcc" -#: ../ui/composer.glade.h:59 +#: ui/composer-widget.ui:179 msgid "_Reply-To" msgstr "_Răspunde la" #. Geary account mail will be sent from -#: ../ui/composer.glade.h:61 +#: ui/composer-widget.ui:208 msgid "From" msgstr "De la" -#: ../ui/composer.glade.h:62 +#: ui/composer-widget.ui:293 msgid "Drop files here" msgstr "Trage fișiere aici" -#: ../ui/composer.glade.h:63 +#: ui/composer-widget.ui:309 msgid "To add them as attachments" msgstr "Pentru a le atașa" -#: ../ui/edit_alternate_emails.glade.h:1 -msgid "Remove email address" -msgstr "Elimină adresa de email" +#: ui/composer-widget.ui:353 +msgid "Undo last edit (Ctrl+Z)" +msgstr "Refă ultima editare (Ctrl+Z)" + +#: ui/composer-widget.ui:377 +msgid "Redo last edit (Ctrl+Shift+Z)" +msgstr "Refă ultima editare (Ctrl+Shift+Z)" -#: ../ui/edit_alternate_emails.glade.h:2 -msgid "" -"Some email services require additional addresses be configured on the " -"server. Contact your email provider for more information." +#: ui/composer-widget.ui:415 +msgid "Bold (Ctrl+B)" +msgstr "Îngroșat (Ctrl+B)" + +#: ui/composer-widget.ui:439 +msgid "Italic (Ctrl+I)" +msgstr "Italic (Ctrl+I)" + +#: ui/composer-widget.ui:463 +msgid "Underline (Ctrl+U)" +msgstr "Subliniat (Ctrl+U)" + +#: ui/composer-widget.ui:487 +msgid "Strikethrough (Ctrl+K)" +msgstr "Tăiat (Ctrl+K)" + +#: ui/composer-widget.ui:525 +msgid "Insert unordered list" +msgstr "Introdu listă neordonată" + +#: ui/composer-widget.ui:549 +msgid "Insert ordered list" +msgstr "Introdu listă ordonată" + +#: ui/composer-widget.ui:587 +msgid "Quote text (Ctrl+])" +msgstr "Citează text (Ctrl+])" + +#: ui/composer-widget.ui:611 +msgid "Unquote text (Ctrl+[)" +msgstr "Anulează citatul (Ctrl+[)" + +#: ui/composer-widget.ui:649 +msgid "Insert or update selection link (Ctrl+L)" +msgstr "Introdu sau actualizează selecția legăturii (Ctrl+L)" + +#: ui/composer-widget.ui:673 +msgid "Insert an image (Ctrl+G)" +msgstr "Introdu o imagine (Ctrl+G)" + +#: ui/composer-widget.ui:707 +msgid "Remove selection formatting (Ctrl+Space)" +msgstr "Elimină formatarea selecției (Ctrl+Spațiu)" + +#: ui/composer-widget.ui:731 +msgid "Select spell checking languages" +msgstr "Selectați limbile pentru corectarea ortografică" + +#: ui/conversation-email.ui:27 +msgid "Save all attachments" +msgstr "Salvează toate fișierele atașate" + +#. Note: The application will never show this button at the same time as unstar_button, one will always be hidden. +#: ui/conversation-email.ui:50 +msgid "Mark this message as starred" +msgstr "Marchează acest mesaj cu stea" + +#. Note: The application will never show this button at the same time as star_button, one will always be hidden. +#: ui/conversation-email.ui:72 +msgid "Mark this message as not starred" +msgstr "Demarchează acest mesaj cu stea" + +#: ui/conversation-email.ui:95 +msgid "Display the message menu" +msgstr "Afișează meniul mesajului" + +#: ui/conversation-email.ui:161 +msgid "Open selected attachments" +msgstr "Deschide fișierele atașate selectate" + +#: ui/conversation-email.ui:178 +msgid "Save selected attachments" +msgstr "Salvează fișierele atașate selectate" + +#: ui/conversation-email.ui:195 +msgid "Select all attachments" +msgstr "Salvează toate fișierele atașate" + +#: ui/conversation-email.ui:240 +msgid "Edit Draft" +msgstr "Editează ciorna" + +#: ui/conversation-email.ui:267 +msgid "Draft message" +msgstr "Mesaj ciornă" + +#: ui/conversation-email.ui:283 +msgid "This message has not yet been sent." +msgstr "Acest mesaj nu a fost trimis încă." + +#: ui/conversation-email.ui:329 +msgid "Message not saved" +msgstr "Mesaj nesalvat" + +#: ui/conversation-email.ui:345 +msgid "This message was sent, but has not been saved to your account." +msgstr "" +"Acest mesaj a fost trimis cu succes, dar nu a fost salvat în contul " +"dumneavoastră." + +#. Translators: Menu item to reply to a specific message. +#: ui/conversation-email-menus.ui:15 +msgid "Reply to _All" +msgstr "Răspunde _tuturor" + +#. Translators: Menu item to mark a specific message as +#. read. +#: ui/conversation-email-menus.ui:30 +msgid "_Mark Read" +msgstr "_Marchează ca citit" + +#: ui/conversation-email-menus.ui:36 +msgid "_Mark Unread" +msgstr "_Marchează ca necitit" + +#. Translators: Menu item to mark all messages in a +#. conversation from this one as unread. +#: ui/conversation-email-menus.ui:42 +msgid "Mark Unread From _Here" +msgstr "Marc_hează ca necitit de aici" + +#. Translators: Menu item to move a single, specific message +#. to the trash +#: ui/conversation-email-menus.ui:50 +msgid "_Trash" +msgstr "Mu_tă la gunoi" + +#. Translators: Menu item to delete a single, specific message +#: ui/conversation-email-menus.ui:57 +msgid "_Delete…" +msgstr "Ș_terge…" + +#. Translators: Menu item to view the source for a message +#: ui/conversation-email-menus.ui:69 +msgid "_View Source" +msgstr "_Vizualizare sursă" + +#: ui/conversation-email-menus.ui:87 +msgid "_Save All" +msgstr "_Salvează tot" + +#: ui/conversation-message-menus.ui:7 +msgid "_Open Link" +msgstr "Deschide legătura" + +#: ui/conversation-message-menus.ui:11 +msgid "Copy Link _Address" +msgstr "Copiază _adresa legăturii" + +#: ui/conversation-message-menus.ui:17 +msgid "Send New _Message…" +msgstr "Trimite mesaj nou…" + +#: ui/conversation-message-menus.ui:21 +msgid "Copy Email _Address" +msgstr "Copiază _adresa de email" + +#: ui/conversation-message-menus.ui:27 +msgid "Save _Image As…" +msgstr "Salvează _imaginea ca…" + +#: ui/conversation-message-menus.ui:33 +msgid "_Select All" +msgstr "_Selectează tot" + +#: ui/conversation-message-menus.ui:43 +msgid "Search for messages from" +msgstr "Caută după mesaje de la" + +#: ui/conversation-message.ui:63 +msgid "From " +msgstr "De la " + +#: ui/conversation-message.ui:79 ui/conversation-message.ui:178 +msgid "1/1/1970\t" +msgstr "1/1/1970\t" + +#: ui/conversation-message.ui:102 +msgid "Preview body text." +msgstr "Previzualizează textul conținutului." + +#: ui/conversation-message.ui:202 +msgid "Sent by:" +msgstr "Trimis de:" + +#: ui/conversation-message.ui:247 +msgid "Reply to:" +msgstr "Răspunde către:" + +#: ui/conversation-message.ui:291 +msgid "Subject" +msgstr "Subiect" + +#: ui/conversation-message.ui:501 +msgid "Show Images" +msgstr "Arată imagini" + +#: ui/conversation-message.ui:514 +msgid "Always Show From Sender" +msgstr "Afișează întotdeauna de la acest expeditor" + +#: ui/conversation-message.ui:542 +msgid "Remote images not shown" +msgstr "Imaginile de la distanță nu sunt afișate" + +#: ui/conversation-message.ui:559 +msgid "Only show remote images from senders you trust." msgstr "" -"Unele servicii de email necesită ca adrese adiționale să fie configurate pe " -"server. Contactați furnizorul de email pentru mai multe informații." +"Afișează imaginile de la distanță numai pentru expeditorii de încredere." + +#: ui/conversation-message.ui:692 +msgid "But actually goes to:" +msgstr "Dar de fapt trimite la:" -#: ../ui/edit_alternate_emails.glade.h:4 -msgid "_Update" -msgstr "Act_ualizează" +#: ui/conversation-message.ui:723 +msgid "The link appears to go to:" +msgstr "Acestă legătură pare să indice spre:" + +#: ui/conversation-message.ui:735 +msgid "Deceptive link found" +msgstr "Legătură înșelătoare detectată" + +#: ui/conversation-message.ui:750 +msgid "The email sender may be leading you to the wrong web site." +msgstr "Expeditorul email-ului vă poate îndrepta către un site capcană." + +#: ui/conversation-message.ui:763 +msgid "If unsure, contact the sender and ask before continuing." +msgstr "" +"Dacă sunteți nesigur, contactați expeditorul și întrebați înainte de a " +"continua." -#: ../ui/find_bar.glade.h:1 +#: ui/conversation-viewer.ui:60 +msgid "Find in conversation" +msgstr "Caută în conversație" + +#: ui/conversation-viewer.ui:74 +msgid "Find the previous occurrence of the search string." +msgstr "Găsește aparența anterioară a șirului căutat." + +#: ui/conversation-viewer.ui:95 +msgid "Find the next occurrence of the search string." +msgstr "Găsește aparența următoare a șirului căutat." + +#: ui/find_bar.glade:66 msgid "Find:" msgstr "Caută:" -#: ../ui/find_bar.glade.h:2 +#: ui/find_bar.glade:89 msgid "_Previous" msgstr "_Precedentul" -#: ../ui/find_bar.glade.h:3 +#: ui/find_bar.glade:107 msgid "_Next" msgstr "_Următorul" -#: ../ui/find_bar.glade.h:4 +#: ui/find_bar.glade:125 msgid "_Case sensitive" msgstr "Majus_cule semnificative" -#: ../ui/find_bar.glade.h:5 +#: ui/find_bar.glade:145 msgid "label" msgstr "etichetă" -#: ../ui/login.glade.h:1 ../ui/password-dialog.glade.h:3 -msgid "Password" -msgstr "Parolă" +#: ui/gtk/help-overlay.ui:9 +msgid "Conversation Shortcuts" +msgstr "Scurtături conversație" + +#: ui/gtk/help-overlay.ui:13 ui/gtk/help-overlay.ui:254 +msgctxt "shortcut window" +msgid "General" +msgstr "Generale" + +#: ui/gtk/help-overlay.ui:17 +msgctxt "shortcut window" +msgid "Move focus to the next/previous pane" +msgstr "Mută focusul la panoul următor/anterior" + +#: ui/gtk/help-overlay.ui:24 +msgctxt "shortcut window" +msgid "Move focus to conversation list" +msgstr "Mută focusul la lista de conversații" + +#: ui/gtk/help-overlay.ui:31 +msgctxt "shortcut window" +msgid "Detach composer window" +msgstr "Detașează fereastra compozitorului" + +#: ui/gtk/help-overlay.ui:38 +msgctxt "shortcut window" +msgid "Close composer window" +msgstr "Închide fereastra compozitorului" + +#: ui/gtk/help-overlay.ui:45 +msgctxt "shortcut window" +msgid "Show keyboard shortcuts" +msgstr "Arată scurtături de tastatură" + +#: ui/gtk/help-overlay.ui:52 +msgctxt "shortcut window" +msgid "Show help" +msgstr "Arată ajutoruñ" + +#: ui/gtk/help-overlay.ui:59 +msgctxt "shortcut window" +msgid "Quit the application" +msgstr "Închide aplicația" + +#: ui/gtk/help-overlay.ui:68 +msgctxt "shortcut window" +msgid "Search" +msgstr "Căutare" + +#: ui/gtk/help-overlay.ui:72 +msgctxt "shortcut window" +msgid "Jump to search box" +msgstr "Navighează la caseta de căutare" + +#: ui/gtk/help-overlay.ui:79 +msgctxt "shortcut window" +msgid "Find in current conversation" +msgstr "Caută în conversația curentă" + +#: ui/gtk/help-overlay.ui:86 +msgctxt "shortcut window" +msgid "Find next/previous in current conversation" +msgstr "Găsește următorul/anteriorul în conversația curentă" + +#: ui/gtk/help-overlay.ui:95 ui/gtk/help-overlay.ui:274 +msgctxt "shortcut window" +msgid "Actions" +msgstr "Acțiuni" + +#: ui/gtk/help-overlay.ui:99 +msgctxt "shortcut window" +msgid "Compose a new message" +msgstr "Compune un mesaj nou" + +#: ui/gtk/help-overlay.ui:106 +msgctxt "shortcut window" +msgid "Reply to sender " +msgstr "Răspunde expeditorului " + +#: ui/gtk/help-overlay.ui:113 +msgctxt "shortcut window" +msgid "Reply to all" +msgstr "Răspunde tuturor" + +#: ui/gtk/help-overlay.ui:120 +msgctxt "shortcut window" +msgid "Forward" +msgstr "Înaintează" + +#: ui/gtk/help-overlay.ui:127 +msgctxt "shortcut window" +msgid "Archive" +msgstr "Arhivează" + +#: ui/gtk/help-overlay.ui:134 +msgctxt "shortcut window" +msgid "Move to trash" +msgstr "Mută la gunoi" + +#: ui/gtk/help-overlay.ui:141 +msgctxt "shortcut window" +msgid "Toggle spam" +msgstr "Comută spam" + +#: ui/gtk/help-overlay.ui:148 +msgctxt "shortcut window" +msgid "Move the conversation" +msgstr "Mută conversația" + +#: ui/gtk/help-overlay.ui:155 +msgctxt "shortcut window" +msgid "Label the conversation" +msgstr "Etichetează conversația" + +#: ui/gtk/help-overlay.ui:163 +msgctxt "shortcut window" +msgid "Mark read" +msgstr "Marchează ca citit" + +#: ui/gtk/help-overlay.ui:170 +msgctxt "shortcut window" +msgid "Mark unread" +msgstr "Marchează ca necitit" + +#: ui/gtk/help-overlay.ui:179 +msgctxt "shortcut window" +msgid "View" +msgstr "Vizualizează" + +#: ui/gtk/help-overlay.ui:183 +msgctxt "shortcut window" +msgid "Zoom in" +msgstr "Mărește" + +#: ui/gtk/help-overlay.ui:190 +msgctxt "shortcut window" +msgid "Zoom out" +msgstr "Micșorează" + +#: ui/gtk/help-overlay.ui:197 +msgctxt "shortcut window" +msgid "Reset zoom" +msgstr "Restabilește zoom-ul" + +#: ui/gtk/help-overlay.ui:206 +msgctxt "shortcut window" +msgid "Additional Shortcuts" +msgstr "Scurtături adiționale" + +#: ui/gtk/help-overlay.ui:210 +msgctxt "shortcut window" +msgid "Star" +msgstr "Marchează cu o stea" + +#: ui/gtk/help-overlay.ui:217 +msgctxt "shortcut window" +msgid "Unstar" +msgstr "Eliminați steaua" + +#: ui/gtk/help-overlay.ui:224 +msgctxt "shortcut window" +msgid "Delete" +msgstr "Șterge" + +#: ui/gtk/help-overlay.ui:231 +msgctxt "shortcut window" +msgid "Jump to next (older) conversation" +msgstr "Navighează la următoarea (mai veche) conversație" + +#: ui/gtk/help-overlay.ui:238 +msgctxt "shortcut window" +msgid "Jump to previous (newer) conversation" +msgstr "Navighează la anterioara (mai recentă) conversație" + +#: ui/gtk/help-overlay.ui:250 +msgid "Composer Shortcuts" +msgstr "Scurtături compunător" + +#: ui/gtk/help-overlay.ui:258 +msgctxt "shortcut window" +msgid "Quote text" +msgstr "Citează textul" + +#: ui/gtk/help-overlay.ui:265 +msgctxt "shortcut window" +msgid "Unquote text" +msgstr "Anulează citatul" + +#: ui/gtk/help-overlay.ui:278 +msgctxt "shortcut window" +msgid "Send" +msgstr "Trimite" + +#: ui/gtk/help-overlay.ui:285 +msgctxt "shortcut window" +msgid "Add attachment" +msgstr "Adaugă atașament" + +#: ui/gtk/help-overlay.ui:294 +msgctxt "shortcut window" +msgid "Rich text mode" +msgstr "Text cu formatare" + +#: ui/gtk/help-overlay.ui:298 +msgctxt "shortcut window" +msgid "Bold text" +msgstr "Face textul aldin" + +#: ui/gtk/help-overlay.ui:305 +msgctxt "shortcut window" +msgid "Italicize text" +msgstr "Face textul cursiv" + +#: ui/gtk/help-overlay.ui:312 +msgctxt "shortcut window" +msgid "Underline text" +msgstr "Subliniază textul" + +#: ui/gtk/help-overlay.ui:319 +msgctxt "shortcut window" +msgid "Strike text" +msgstr "Face textul tăiat" + +#: ui/gtk/help-overlay.ui:326 +msgctxt "shortcut window" +msgid "Insert a link" +msgstr "Introduce o legătură" + +#: ui/gtk/help-overlay.ui:333 +msgctxt "shortcut window" +msgid "Remove formatting" +msgstr "Elimină fomatarea" + +#: ui/main-toolbar.ui:23 +msgctxt "tooltip" +msgid "Compose Message" +msgstr "Compune un mesaj" + +#: ui/main-toolbar.ui:52 +msgid "Toggle search bar" +msgstr "Comută bara de căutare" + +#: ui/main-toolbar.ui:112 +msgid "Reply" +msgstr "Răspunde" + +#: ui/main-toolbar.ui:135 +msgid "Reply All" +msgstr "Răspunde tuturor" + +#: ui/main-toolbar.ui:158 +msgid "Forward" +msgstr "Înaintează" + +#: ui/main-toolbar.ui:264 +msgid "Toggle find bar" +msgstr "Comută bara de căutare" + +#: ui/main-toolbar.ui:306 +msgid "_Archive" +msgstr "_Arhivează" + +#: ui/main-toolbar-menus.ui:21 +msgid "Mark as S_pam" +msgstr "Marchează ca s_pam" + +#: ui/main-toolbar-menus.ui:25 +msgid "Mark as not S_pam" +msgstr "Marchează ca non-s_pam" + +#: ui/main-toolbar-menus.ui:32 +msgid "Empty _Spam…" +msgstr "Golește _Spam…" + +#: ui/main-toolbar-menus.ui:36 +msgid "Empty _Trash…" +msgstr "Golește _Gunoi…" + +#: ui/main-toolbar-menus.ui:42 +msgid "_Accounts" +msgstr "_Conturi" -#: ../ui/login.glade.h:2 -msgid "E_mail address" -msgstr "Adresă de e_mail" - -#: ../ui/login.glade.h:3 -msgid "_Password" -msgstr "_Parolă" - -#: ../ui/login.glade.h:4 -msgid "S_ervice" -msgstr "S_erviciu" - -#: ../ui/login.glade.h:5 -msgid "N_ame" -msgstr "N_ume" - -#: ../ui/login.glade.h:7 -msgid "N_ickname" -msgstr "Pseu_donim" - -#: ../ui/login.glade.h:8 -msgid "Work, Home, etc." -msgstr "Muncă, Acasă, etc." - -#: ../ui/login.glade.h:9 -msgid "_Save sent mail" -msgstr "_Salvează mailul trimis" - -#: ../ui/login.glade.h:10 -msgid "Addi_tional email addresses…" -msgstr "Adrese email a_diționale…" - -#: ../ui/login.glade.h:11 -msgid "IMAP settings" -msgstr "Configurări IMAP" - -#: ../ui/login.glade.h:12 -msgid "Se_rver" -msgstr "Se_rver" - -#: ../ui/login.glade.h:13 -msgid "P_ort" -msgstr "P_ort" - -#: ../ui/login.glade.h:14 -msgid "Ser_ver" -msgstr "Ser_ver" - -#: ../ui/login.glade.h:15 -msgid "Por_t" -msgstr "Por_t" - -#: ../ui/login.glade.h:16 -msgid "SMTP settings" -msgstr "Configurări SMTP" - -#: ../ui/login.glade.h:17 -msgid "User_name" -msgstr "_Nume utilizator" - -#: ../ui/login.glade.h:18 -msgid "Pass_word" -msgstr "Pa_rolă" - -#: ../ui/login.glade.h:19 -msgid "SMTP username" -msgstr "Nume utilizator SMTP" - -#: ../ui/login.glade.h:20 -msgid "SMTP password" -msgstr "Parolă SMTP" - -#: ../ui/login.glade.h:21 -msgid "_Username" -msgstr "Nume _utilizator" - -#: ../ui/login.glade.h:22 -msgid "IMAP username" -msgstr "Nume utilizator IMAP" - -#: ../ui/login.glade.h:23 -msgid "IMAP password" -msgstr "Parolă IMAP" - -#: ../ui/login.glade.h:24 -msgid "Encr_yption" -msgstr "Crip_tare" - -#: ../ui/login.glade.h:25 -msgid "Encrypt_ion" -msgstr "Cri_ptare" - -#: ../ui/login.glade.h:27 -msgid "SSL/TLS" -msgstr "SSL/TLS" - -#: ../ui/login.glade.h:28 -msgid "STARTTLS" -msgstr "STARTTLS" - -#: ../ui/login.glade.h:29 -msgid "No authentication re_quired" -msgstr "Nu este necesară autentificarea" - -#: ../ui/login.glade.h:30 -msgid "Use IMAP cre_dentials" -msgstr "Folosește cre_dențiale IMAP" - -#: ../ui/login.glade.h:31 ../ui/preferences.glade.h:5 -msgid "Composer" -msgstr "Compunere" - -#: ../ui/login.glade.h:32 -msgid "Save dra_fts on server" -msgstr "Salvează cior_nele pe server" - -#: ../ui/login.glade.h:33 -msgid "Si_gn emails (HTML allowed):" -msgstr "Se_mnează emailuri (HTML permis):" - -#: ../ui/login.glade.h:34 -msgid "Storage" -msgstr "Păstrare" - -#: ../ui/login.glade.h:35 -msgid "_Download mail" -msgstr "_Descarcă email" +#: ui/main-toolbar-menus.ui:50 +msgid "_Keyboard Shortcuts" +msgstr "Scurtături de _tastatură" + +#: ui/main-toolbar-menus.ui:61 +msgid "_About Geary" +msgstr "_Despre Geary" + +#. Infobar title when one or more accounts are offline +#: ui/main-window.ui:183 +msgid "Working offline" +msgstr "Se lucrează deconectat" + +#. Label and tooltip for offline infobar +#: ui/main-window.ui:197 +msgid "" +"Your computer does not appear to be connected to the Internet.\n" +"You will not be able to send or receive email until it is re-connected." +msgstr "" +"Calculatorul dumneavoastră nu pare a fi conectat la Internet\n" +"Nu veți putea trimite sau primi email-uri până când este reconectat." + +#. Label and tooltip for offline infobar +#: ui/main-window.ui:200 +msgid "You will not be able to send or receive email until re-connected." +msgstr "Nu veți putea trimite sau primi email-uri până la reconectare." + +#. Button label for displaying technical details about an account problem +#. Dialog title for displaying technical details of a problem. Same as the button that invokes it. +#: ui/main-window.ui:247 ui/problem-details-dialog.ui:13 +msgid "Details" +msgstr "Detalii" + +#. Button label for retrying an account problem +#: ui/main-window.ui:261 +msgid "Retry" +msgstr "Reîncearcă" + +#. Infobar title when one or more accounts have encounted an error +#: ui/main-window.ui:294 +msgid "Account problem" +msgstr "Problemă cu contul" + +#. Label and tooltip for account service problem infobar +#: ui/main-window.ui:308 +msgid "" +"Geary encountered a problem connecting to an account.\n" +"Please check your Internet connection, the server configuration and try " +"again." +msgstr "" +"Geary a întâmpinat o problemă la conectarea la un cont.\n" +"Verificați conexiunea la Internet, configurația serverului și încercați din " +"nou." + +#. Label and tooltip for account service problem infobar +#: ui/main-window.ui:311 +msgid "Geary encountered a problem connecting to an account." +msgstr "Geary a întâmpinat o problemă la conectarea la un cont." + +#. Button label for retrying TLS cert validation +#: ui/main-window.ui:358 +msgid "Check" +msgstr "Verifică" + +#. Button tooltip for retrying TLS cert validation +#: ui/main-window.ui:362 +msgid "Check the security details for the connection" +msgstr "Verifică detaliile de securitate pentru conexiune" + +#. Infobar title when one or more accounts have a TLS cert validation error +#: ui/main-window.ui:391 +msgid "Security problem" +msgstr "Problemă de securitate" + +#. Label and tooltip for TLS cert validation error infobar +#: ui/main-window.ui:405 +msgid "" +"An account has reported an untrusted server.\n" +"Please check the server configuration and try again." +msgstr "" +"Un cont a raportat un server de neîncredere.\n" +"Verificați configurația serverului și încercați din nou." + +#. Label and tooltip for TLS cert validation error infobar +#: ui/main-window.ui:408 +msgid "An account has reported an untrusted server." +msgstr "Un cont a raportat un server de neîncredere." + +#. Button tooltip for retrying when a login error has occurred +#: ui/main-window.ui:459 +msgid "Retry login, you will be prompted for your password" +msgstr "Reîncercați autentificarea, vi se va cere parola" + +#. Infobar title when one or more accounts have a login error +#: ui/main-window.ui:488 +msgid "Login problem" +msgstr "Problemă de autentificare" + +#. Label and tooltip for authentication problem infobar +#: ui/main-window.ui:502 +msgid "" +"An account has reported an incorrect login or password.\n" +"Please check your login name and try again." +msgstr "" +"Un cont a raportat o autentificare sau parolă incorectă.\n" +"Verificați numele de autentificare și încercați din nou." + +#. Label and tooltip for authentication problem infobar +#: ui/main-window.ui:505 +msgid "An account has reported an incorrect login or password." +msgstr "Un cont a raportat o autentificare sau parolă incorectă." -#: ../ui/password-dialog.glade.h:1 +#: ui/password-dialog.glade:74 msgid "SMTP Credentials" msgstr "Detalii cont SMTP" -#: ../ui/password-dialog.glade.h:2 +#: ui/password-dialog.glade:91 msgid "Username" msgstr "Nume de utilizator" -#: ../ui/password-dialog.glade.h:4 +#: ui/password-dialog.glade:152 msgid "_Remember password" msgstr "_Memorează parola" -#: ../ui/password-dialog.glade.h:6 +#: ui/password-dialog.glade:210 msgid "_Authenticate" msgstr "_Autentificare" -#: ../ui/preferences.glade.h:1 +#: ui/preferences-dialog.ui:38 msgid "Reading" msgstr "Citire" -#: ../ui/preferences.glade.h:2 +#: ui/preferences-dialog.ui:51 msgid "_Automatically select next message" msgstr "_Selectează automat următorul mesaj" -#: ../ui/preferences.glade.h:3 +#: ui/preferences-dialog.ui:70 msgid "_Display conversation preview" msgstr "_Afișează previzualizarea conversației" -#: ../ui/preferences.glade.h:4 +#: ui/preferences-dialog.ui:89 msgid "Use _three pane view" msgstr "Utilizează vizualizarea pe _trei panouri" -#: ../ui/preferences.glade.h:6 -msgid "Enable _spell checking" -msgstr "Activează _verificarea ortografică" - -#: ../ui/preferences.glade.h:7 +#: ui/preferences-dialog.ui:113 msgid "Notifications" msgstr "Notificări" -#: ../ui/preferences.glade.h:8 +#: ui/preferences-dialog.ui:126 msgid "_Play notification sounds" msgstr "_Redă notificările sonore" -#: ../ui/preferences.glade.h:9 +#: ui/preferences-dialog.ui:145 msgid "Show _notifications for new mail" msgstr "Afișează _notificări pentru emailuri noi" -#: ../ui/preferences.glade.h:10 -msgid "Always _watch for new mail" -msgstr "_Verifică întotdeauna după emailuri noi" - -#: ../ui/preferences.glade.h:11 -msgid "Geary will run in the background and notify of new mail" -msgstr "Geary va rula în fundal și vă va notifica când sunt emailuri noi" +#: ui/preferences-dialog.ui:164 +msgid "_Watch for new mail when closed" +msgstr "_Verifică după emailuri noi când este închis" + +#: ui/preferences-dialog.ui:168 +msgid "Geary will keep running after all windows are closed" +msgstr "Geary va continua rularea după ce toate ferestrele sunt închise" -#: ../ui/preferences.glade.h:12 +#: ui/preferences-dialog.ui:195 msgid "Preferences" msgstr "Preferințe" -#: ../ui/remove_confirm.glade.h:1 +#. Button label for copying technical information to the clipboard +#: ui/problem-details-dialog.ui:17 +msgid "Copy to Clipboard" +msgstr "Copiază în clipboard" + +#. Button tooltip for copying technical information to the clipboard +#: ui/problem-details-dialog.ui:21 msgid "" -"Are you sure you want to remove this " -"account? " +"Copy technical details to clipboard for pasting into an email or bug report" msgstr "" -"Sigur vreți să eliminați acest cont? " +"Copiază detaliile tehnice în clipboard pentru a le lipi într-un email sau " +"raport de defect" -#: ../ui/remove_confirm.glade.h:2 +#: ui/problem-details-dialog.ui:73 msgid "" -"All email associated with this account will be removed from your computer. " -"This will not affect email on the server." +"If the problem is serious or persists, please copy and send these details to " +"the mailing list " +"or file a new " +"bug report." msgstr "" -"Toate emailurile asociate cu acest cont vor fi eliminate de pe computerul " -"dumneavoastra. Emailurile de pe server nu vor fi afectate." - -#: ../ui/remove_confirm.glade.h:3 -msgid "Nickname:" -msgstr "Pseudonim:" +"Dacă problema este gravă sau dacă persistă, copiați și trimiteți aceste " +"detalii pe lista de " +"discuții sau completați un nou raport de defecte." -#: ../ui/remove_confirm.glade.h:4 -msgid "Email address:" -msgstr "Adresă de email:" +#: ui/problem-details-dialog.ui:89 +msgid "Details:" +msgstr "Detalii:" -#: ../ui/upgrade_dialog.glade.h:1 +#: ui/upgrade_dialog.glade:60 msgid "Geary update in progress…" msgstr "În curs de actualizare Geary…" +#~ msgid "Base URL to look up contact avatars" +#~ msgstr "URL-ul de bază pentru verificarea după avatare de contact" + +#~ msgid "" +#~ "A Gravatar or Libravatar compatible URL, set to the empty string to " +#~ "disable." +#~ msgstr "" +#~ "Un URL compatibil cu Gravatar sau Libravatar, stabiliți la șir gol pentru " +#~ "a dezactiva." + +#~ msgid "Delete conversations (Shift+Delete)" +#~ msgstr "Șterge conversațiile (Shift+Delete)" + +#~ msgid "Move conversations to Trash (Delete, Backspace)" +#~ msgstr "Mută conversațiile la Gunoi (Delete, Backspace)" + +#~ msgid "Archive conversations (A)" +#~ msgstr "Arhivează conversațiile (A)" + +#~ msgid "Mark conversations" +#~ msgstr "Marchează conversațiile" + +#~ msgid "Add label to conversations" +#~ msgstr "Adaugă etichetă conversațiilor" + +#~ msgid "Move conversations" +#~ msgstr "Mută conversațiile" + +#~ msgid "Retry connecting now" +#~ msgstr "Reîncearcă conexiunea acum" + +#~ msgid "Try reconnecting now" +#~ msgstr "Încearcă reconectarea acum" + +#~ msgid "Problem with connection to incoming server for %s" +#~ msgstr "Problemă la conectarea la serverul de primire pentru %s" + +#~ msgid "Problem with connection to outgoing server for %s" +#~ msgstr "Problemă la conectarea la la serverul de trimitere pentru %s" + +#~ msgid "To: " +#~ msgstr "Către: " + +#~ msgid "Cc: " +#~ msgstr "Cc: " + +#~ msgid "Bcc: " +#~ msgstr "Bcc: " + +#~ msgid "From: %s\n" +#~ msgstr "De la: %s\n" + +#~ msgid "Subject: %s\n" +#~ msgstr "Subiect: %s\n" + +#~ msgid "Date: %s\n" +#~ msgstr "Data: %s\n" + +#~ msgid "To: %s\n" +#~ msgstr "Către: %s\n" + +#~ msgid "Cc: %s\n" +#~ msgstr "Cc: %s\n" + +#~ msgid "A_ccounts" +#~ msgstr "_Conturi" + +#~ msgid "Empty Spam or Trash folders" +#~ msgstr "Golește dosarele Spam sau Gunoi" + +#~ msgid "Email address:" +#~ msgstr "Adresă de email:" + +#~ msgid "Mail Client" +#~ msgstr "Client de email" + +#~ msgid "Geary Mail" +#~ msgstr "Geary Mail" + +#~ msgid "Additional addresses for %s" +#~ msgstr "Adrese adiționale pentru %s" + +#~ msgid "First Last" +#~ msgstr "Prenume Nume" + +#~ msgid "Enter your account information to get started." +#~ msgstr "Introduceți detaliile contului pentru a începe." + +#~ msgid "Edit" +#~ msgstr "Editare" + +#~ msgid "Preview" +#~ msgstr "Previzualizează" + +#~ msgid "Remem_ber passwords" +#~ msgstr "_Memorează parolele" + +#~ msgid "Remem_ber password" +#~ msgstr "_Memorează parola" + +#~ msgid "Unable to validate:\n" +#~ msgstr "Nu s-a putut valida:\n" + +#~ msgid " • Invalid account nickname.\n" +#~ msgstr " • Pseudonimul nu este valid.\n" + +#~ msgid " • Email address already added to Geary.\n" +#~ msgstr " • Adresa de email este deja adăugată în Geary.\n" + +#~ msgid " • IMAP connection error.\n" +#~ msgstr " • Eroare la conexiunea IMAP.\n" + +#~ msgid " • IMAP username or password incorrect.\n" +#~ msgstr " • Nume utilizator sau parolă IMAP incorecte.\n" + +#~ msgid " • SMTP connection error.\n" +#~ msgstr " • Eroare la conexiunea SMTP.\n" + +#~ msgid " • SMTP username or password incorrect.\n" +#~ msgstr "" +#~ " • Numele de utilizator sau parola SMTP sunt incorecte.\n" + +#~ msgid " • Connection error.\n" +#~ msgstr " • Eroare de conexiune.\n" + +#~ msgid " • Username or password incorrect.\n" +#~ msgstr " • Numele de utilizator sau parola sunt incorecte.\n" + +#~ msgid "_Mark as..." +#~ msgstr "_Marchează ca..." + +#~ msgid "Add label" +#~ msgstr "Adaugă etichetă" + +#~ msgid "_Label" +#~ msgstr "_Etichetă" + +#~ msgid "_Move" +#~ msgstr "_Mută" + +#~ msgid "Compose new message (Ctrl+N, N)" +#~ msgstr "Compune un mesaj nou (Ctrl+N, N)" + +#~ msgid "Reply (Ctrl+R, R)" +#~ msgstr "Răspunde (Ctrl+R, R)" + +#~ msgid "Reply all (Ctrl+Shift+R, Shift+R)" +#~ msgstr "Răspunde tuturor (Ctrl+Shift+R, Shift+R)" + +#~ msgid "Forward (Ctrl+L, F)" +#~ msgstr "Înaintează (Ctrl+L, F)" + +#~ msgid "Empty" +#~ msgstr "Golește" + +#~ msgid "Unable to store server trust exception" +#~ msgstr "Nu s-a putut stoca excepția la servere de încredere" + +#~ msgid "Your settings are insecure" +#~ msgstr "Configurările tale sunt nesigure" + +#~ msgid "" +#~ "Your IMAP and/or SMTP settings do not specify SSL or TLS. This means " +#~ "your username and password could be read by another person on the " +#~ "network. Are you sure you want to do this?" +#~ msgstr "" +#~ "Configurările IMAP și/sau SMTP nu specifică SSL sau TLS. Acest lucru " +#~ "permite unei alte persoane de pe rețea să îți citească utilizatorul și " +#~ "parola. Sunteți sigur că vreți să faceți asta?" + +#~ msgid "Co_ntinue" +#~ msgstr "Co_ntinuă" + +#~ msgid "" +#~ "Geary encountered an error sending an email. If the problem persists, " +#~ "please manually delete the email from your Outbox folder." +#~ msgstr "" +#~ "Geary a întâmpinat o eroare la trimiterea unui email. Dacă problema " +#~ "persistă, ștergeți manual emailul din directorul Outbox." + +#~ msgid "" +#~ "Geary encountered an error saving a sent message to Sent Mail. The " +#~ "message will stay in your Outbox folder until you delete it." +#~ msgstr "" +#~ "Geary a întâmpinat o eroare la salvarea unui mesaj trimis în dosarul " +#~ "„Mesaje trimise”. Mesajul va rămâne în dosarul Outbox până îl veți șterge." + +#~ msgid "Unable to open local mailbox for %s" +#~ msgstr "Nu s-a reușit deschiderea cutiei poștale locale pentru %s" + +#~ msgid "" +#~ "There was an error opening the local mail database for this account. This " +#~ "is possibly due to a file permissions problem.\n" +#~ "\n" +#~ "Please check that you have read/write permissions for all files in this " +#~ "directory:\n" +#~ "\n" +#~ "%s" +#~ msgstr "" +#~ "S-a întâmpinat o eroare la deschiderea bazei de date locală pentru email. " +#~ "Aceasta este probabil cauzată de o problemă cu permisiunile pentru " +#~ "fișiere.\n" +#~ "\n" +#~ "Verificați dacă aveți drepturi de citire/scriere pentru toate fișierele " +#~ "din director.\n" +#~ "\n" +#~ "%s" + +#~ msgid "" +#~ "The version number of the local mail database is formatted for a newer " +#~ "version of Geary. Unfortunately, the database cannot be \"rolled back\" " +#~ "to work with this version of Geary.\n" +#~ "\n" +#~ "Please install the latest version of Geary and try again." +#~ msgstr "" +#~ "Numărul versiunii bazei de date locală pentru email este formatată pentru " +#~ "o versiune mai nouă de Geary. Din nefericire, baza de date nu poate fi " +#~ "„restaurată” pentru a funcționa cu această versiune de Geary.\n" +#~ "\n" +#~ "Instalați ultima versiune de Geary și încercați din nou." + +#~ msgid "" +#~ "There was an error opening the local account. This is probably due to " +#~ "connectivity issues.\n" +#~ "\n" +#~ "Please check your network connection and restart Geary." +#~ msgstr "" +#~ "Am întâmpinat o eroare la deschiderea contului local. Aceasta este " +#~ "probabil din cauza problemelor de conectivitate.\n" +#~ "\n" +#~ "Te rog verifică-ți conexiunea la internet și repornește Geary." + +#~ msgid "%i match" +#~ msgid_plural "%i matches" +#~ msgstr[0] "%i potrivire" +#~ msgstr[1] "%i potriviri" +#~ msgstr[2] "%i de potriviri" + +#~ msgid "%i match (wrapped)" +#~ msgid_plural "%i matches (wrapped)" +#~ msgstr[0] "%i potrivire (wrapped)" +#~ msgstr[1] "%i potriviri (wrapped)" +#~ msgstr[2] "%i de potriviri (wrapped)" + +#~ msgid "not found" +#~ msgstr "nu a fost găsit" + +#~ msgid "No search results found." +#~ msgstr "Niciun rezultat la căutare." + +#~ msgid "Copy _Link" +#~ msgstr "Copiază _legătura" + +#~ msgid "Select _Message" +#~ msgstr "Selectează _mesajul" + +#~ msgid " (Invalid?)" +#~ msgstr " (Nevalid?)" + +#~ msgid "_Save As..." +#~ msgstr "_Salvează ca..." + +#~ msgid "Save A_ttachment..." +#~ msgid_plural "Save All A_ttachments..." +#~ msgstr[0] "Salvează fișierul a_tașat..." +#~ msgstr[1] "Salvează toate fișierele a_tașate..." +#~ msgstr[2] "Salvează toate fișierele a_tașate..." + +#~ msgid "none" +#~ msgstr "fără" + +#~ msgid "Geary will exit if you have no other open email accounts." +#~ msgstr "Geary se va închide dacă nu aveți alte conturi de email." + +#~ msgid "Other" +#~ msgstr "Altele" + +#~ msgid "IMAP" +#~ msgstr "IMAP" + +#~ msgid "SMTP" +#~ msgstr "SMTP" + +#~ msgid "Cannot remove account " +#~ msgstr "" +#~ "Contul nu poate fi eliminat " + +#~ msgid "" +#~ "A composer window associated with this account is currently open. Send or " +#~ "discard the message and try again." +#~ msgstr "" +#~ "O fereastră de compunere asociată cu acest cont este deschisă. Trimiteți " +#~ "sau anulați mesajul și încearcați din nou." + +#~ msgid "Please wait while Geary validates your account." +#~ msgstr "Așteptați cât timp Geary vă validează contul." + +#~ msgid "_Left" +#~ msgstr "_Stânga" + +#~ msgid "_Right" +#~ msgstr "_Dreapta" + +#~ msgid "_Center" +#~ msgstr "_Centru" + +#~ msgid "_Justify" +#~ msgstr "_Aliniază la margini" + +#~ msgid "Link (Ctrl+L)" +#~ msgstr "Legătură (Ctrl+L)" + +#~ msgid "More options" +#~ msgstr "Mai multe opțiuni" + +#~ msgctxt "Label" +#~ msgid "Close and Save" +#~ msgstr "Închide și salvează" + +#~ msgctxt "Short Label" +#~ msgid "Close and Save" +#~ msgstr "Închide și salvează" + +#~ msgctxt "Tooltip" +#~ msgid "Close and Save" +#~ msgstr "Închide și salvează" + +#~ msgctxt "Label" +#~ msgid "Close and Discard" +#~ msgstr "Închide și anulează" + +#~ msgctxt "Short Label" +#~ msgid "Close and Discard" +#~ msgstr "Închide și anulează" + +#~ msgctxt "Tooltip" +#~ msgid "Close and Discard" +#~ msgstr "Închide și anulează" + +#~ msgid "Large" +#~ msgstr "Mare" + +#~ msgid "Medium" +#~ msgstr "Mediu" + +#~ msgid "Small" +#~ msgstr "Mic" + +#~ msgid "Sans Serif" +#~ msgstr "Fără serife" + +#~ msgid "Serif" +#~ msgstr "Cu serife" + +#~ msgid "Fixed Width" +#~ msgstr "Lățime fixă" + +#~ msgid "Detach" +#~ msgstr "Detașează" + +#~ msgid "_Attach File" +#~ msgstr "_Atașează fișier" + +#~ msgid "Attach File" +#~ msgstr "Atașează fișier" + +#~ msgid "_Include Original Attachments" +#~ msgstr "_Include fișierele atașate originale" + +#~ msgid "Spelling language" +#~ msgstr "Limba pentru corectarea ortografică" + +#~ msgid "" +#~ "Some email services require additional addresses be configured on the " +#~ "server. Contact your email provider for more information." +#~ msgstr "" +#~ "Unele servicii de email necesită ca adrese adiționale să fie configurate " +#~ "pe server. Contactați furnizorul de email pentru mai multe informații." + +#~ msgid "_Update" +#~ msgstr "Act_ualizează" + +#~ msgid "E_mail address" +#~ msgstr "Adresă de e_mail" + +#~ msgid "_Password" +#~ msgstr "_Parolă" + +#~ msgid "S_ervice" +#~ msgstr "S_erviciu" + +#~ msgid "N_ame" +#~ msgstr "N_ume" + +#~ msgid "N_ickname" +#~ msgstr "Pseu_donim" + +#~ msgid "Work, Home, etc." +#~ msgstr "Muncă, Acasă, etc." + +#~ msgid "Addi_tional email addresses…" +#~ msgstr "Adrese email a_diționale…" + +#~ msgid "IMAP settings" +#~ msgstr "Configurări IMAP" + +#~ msgid "Se_rver" +#~ msgstr "Se_rver" + +#~ msgid "P_ort" +#~ msgstr "P_ort" + +#~ msgid "Ser_ver" +#~ msgstr "Ser_ver" + +#~ msgid "Por_t" +#~ msgstr "Por_t" + +#~ msgid "User_name" +#~ msgstr "_Nume utilizator" + +#~ msgid "Pass_word" +#~ msgstr "Pa_rolă" + +#~ msgid "SMTP password" +#~ msgstr "Parolă SMTP" + +#~ msgid "_Username" +#~ msgstr "Nume _utilizator" + +#~ msgid "IMAP password" +#~ msgstr "Parolă IMAP" + +#~ msgid "Encr_yption" +#~ msgstr "Crip_tare" + +#~ msgid "Encrypt_ion" +#~ msgstr "Cri_ptare" + +#~ msgid "STARTTLS" +#~ msgstr "STARTTLS" + +#~ msgid "No authentication re_quired" +#~ msgstr "Nu este necesară autentificarea" + +#~ msgid "Use IMAP cre_dentials" +#~ msgstr "Folosește cre_dențiale IMAP" + +#~ msgid "Si_gn emails (HTML allowed):" +#~ msgstr "Se_mnează emailuri (HTML permis):" + +#~ msgid "Storage" +#~ msgstr "Păstrare" + +#~ msgid "Enable _spell checking" +#~ msgstr "Activează _verificarea ortografică" + +#~ msgid "Geary will run in the background and notify of new mail" +#~ msgstr "Geary va rula în fundal și vă va notifica când sunt emailuri noi" + +#~ msgid "" +#~ "Are you sure you want to remove " +#~ "this account? " +#~ msgstr "" +#~ "Sigur vreți să eliminați acest cont?" +#~ " " + +#~ msgid "" +#~ "All email associated with this account will be removed from your " +#~ "computer. This will not affect email on the server." +#~ msgstr "" +#~ "Toate emailurile asociate cu acest cont vor fi eliminate de pe computerul " +#~ "dumneavoastra. Emailurile de pe server nu vor fi afectate." + +#~ msgid "Nickname:" +#~ msgstr "Pseudonim:" + #~ msgid "Application Menu" #~ msgstr "Meniul aplicației" #~ msgid "Copyright 2011-2014 Yorba Foundation" #~ msgstr "Drepturi de autor 2011-2014 Fundația Yorba" -#~ msgid "_Delete" -#~ msgstr "Ș_terge" - -#~ msgid "_Trash" -#~ msgstr "Mu_tă la gunoi" - #~ msgid "_Donate" #~ msgstr "_Donează" @@ -2335,24 +3829,15 @@ #~ msgid "Please enter your password" #~ msgstr "Introduceți parola" -#~ msgid "Username:" -#~ msgstr "Nume utilizator:" - #~ msgid "Password:" #~ msgstr "Parolă:" -#~ msgid "Notify of new mail at start_up" -#~ msgstr "Notifică despre emailuri noi la _pornire" - #~ msgid "C_lose" #~ msgstr "În_chide" #~ msgid "Archive conversation (Delete, Backspace, A)" #~ msgstr "Arhivează conversația (Delete, Backspace, A)" -#~ msgid "General" -#~ msgstr "General" - #~ msgid "Port:" #~ msgstr "Port:" @@ -2373,6 +3858,3 @@ #~ msgid "Unable to login to email server" #~ msgstr "Nu ma pot autentifica pe serverul de email" - -#~ msgid "_Details" -#~ msgstr "_Detalii" diff -Nru geary-0.12.4/po/ru.po geary-3.32.0/po/ru.po --- geary-0.12.4/po/ru.po 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/po/ru.po 2019-03-17 13:39:29.000000000 +0000 @@ -9,15 +9,14 @@ # Pentagrammer , 2013 # mynameisdaniil , 2012 # Aleksandr Stepanov , 2015. -# Stas Solovey , 2015, 2016. +# Stas Solovey , 2015, 2016, 2018. # msgid "" msgstr "" "Project-Id-Version: geary-0.4.1\n" -"Report-Msgid-Bugs-To: https://bugzilla.gnome.org/enter_bug.cgi?" -"product=geary&keywords=I18N+L10N&component=internationalization\n" -"POT-Creation-Date: 2017-12-15 10:48+0000\n" -"PO-Revision-Date: 2018-04-12 22:48+0300\n" +"Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/geary/issues\n" +"POT-Creation-Date: 2018-09-23 02:34+0000\n" +"PO-Revision-Date: 2018-09-25 23:14+0300\n" "Last-Translator: Stas Solovey \n" "Language-Team: Русский \n" "Language: ru\n" @@ -26,29 +25,52 @@ "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" -"X-Generator: Poedit 2.0.6\n" +"X-Generator: Poedit 2.1.1\n" + +#: desktop/geary-attach.contract.desktop.in:3 +msgid "Send by email" +msgstr "Отправить по эл. почте" + +#. Translators: Do NOT translate or transliterate this text (this is an icon file name)! +#: desktop/geary-attach.contract.desktop.in:5 +msgid "mail-send" +msgstr "mail-send" #. Translators: The application name -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:2 -#: ../desktop/org.gnome.Geary.desktop.in.h:1 -#: ../desktop/geary-autostart.desktop.in.h:1 +#: desktop/geary-autostart.desktop.in:3 +#: desktop/org.gnome.Geary.appdata.xml.in:11 +#: desktop/org.gnome.Geary.desktop.in:3 msgid "Geary" msgstr "Geary" -#. Translators: The development team's name -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:4 -msgid "Geary Development Team" -msgstr "Команда разработчиков Geary" +#: desktop/geary-autostart.desktop.in:4 desktop/org.gnome.Geary.desktop.in:4 +msgid "Email" +msgstr "Электронная почта" #. Translators: The application's summary / tagline -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:6 -#: ../desktop/org.gnome.Geary.desktop.in.h:4 -#: ../desktop/geary-autostart.desktop.in.h:4 -#: ../src/client/application/geary-application.vala:21 +#: desktop/geary-autostart.desktop.in:5 +#: desktop/org.gnome.Geary.appdata.xml.in:15 +#: desktop/org.gnome.Geary.desktop.in:5 +#: src/client/application/geary-application.vala:21 msgid "Send and receive email" msgstr "Отправка и получение электронной почты" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:7 +#. Translators: These are desktop search terms. Do not translate semicolons, end line with a semicolon. +#: desktop/geary-autostart.desktop.in:7 +msgid "Email;E-mail;Mail;" +msgstr "Электронная;Почта;Email;E-mail;Mail;" + +#. Translators: Do NOT translate or transliterate this text (this is an icon file name)! +#: desktop/geary-autostart.desktop.in:9 desktop/org.gnome.Geary.desktop.in:9 +msgid "org.gnome.Geary" +msgstr "org.gnome.Geary" + +#. Translators: The development team's name +#: desktop/org.gnome.Geary.appdata.xml.in:13 +msgid "Geary Development Team" +msgstr "Команда разработчиков Geary" + +#: desktop/org.gnome.Geary.appdata.xml.in:17 msgid "" "Geary is an email application built around conversations, for the GNOME 3 " "desktop. It allows you to read, find and send email with a straightforward, " @@ -58,7 +80,7 @@ "по принципу обмена сообщениями — беседами. Позволяет читать, находить и " "отправлять электронную почту с простым, современным интерфейсом." -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:8 +#: desktop/org.gnome.Geary.appdata.xml.in:22 msgid "" "Conversations allow you to read a complete discussion without having to find " "and click from message to message." @@ -66,99 +88,262 @@ "Беседы позволяют читать переписку целиком без необходимости искать и " "переходить от сообщения к сообщению." -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:9 +#: desktop/org.gnome.Geary.appdata.xml.in:26 msgid "Geary’s features include:" msgstr "К функциям Geary относятся:" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:10 +#: desktop/org.gnome.Geary.appdata.xml.in:28 msgid "Quick email account setup" msgstr "Быстрая настройка учётной записи электронной почты" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:11 +#: desktop/org.gnome.Geary.appdata.xml.in:29 msgid "Shows related messages together in conversations" msgstr "Отображение связанных сообщений в режиме беседы" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:12 +#: desktop/org.gnome.Geary.appdata.xml.in:30 msgid "Fast, full text and keyword search" msgstr "Быстрый полнотекстовый поиск и поиск по ключевым словам" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:13 +#: desktop/org.gnome.Geary.appdata.xml.in:31 msgid "Full-featured HTML and plain text message composer" msgstr "" "Полнофункциональный режим составления писем в виде простого текста, а также " "с HTML разметкой" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:14 +#: desktop/org.gnome.Geary.appdata.xml.in:32 msgid "Desktop notification of new mail" msgstr "Уведомления о новых сообщениях" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:15 +#: desktop/org.gnome.Geary.appdata.xml.in:33 msgid "Compatible with GMail, Yahoo! Mail, Outlook.com and other IMAP servers" msgstr "Совместим с GMail, Yahoo! Mail, Outlook.com и другими IMAP серверами" #. Translators: A screenshot description. -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:17 +#: desktop/org.gnome.Geary.appdata.xml.in:47 msgid "Geary displaying a conversation" msgstr "Интерфейс обмена сообщениями Geary" #. Translators: A screenshot description. -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:19 +#: desktop/org.gnome.Geary.appdata.xml.in:52 msgid "Geary showing the rich text composer" msgstr "Интерфейс редактирования текста Geary" -#: ../desktop/org.gnome.Geary.desktop.in.h:2 -#: ../desktop/geary-autostart.desktop.in.h:2 -msgid "Email" -msgstr "Электронная почта" - -#: ../desktop/org.gnome.Geary.desktop.in.h:3 -msgid "Geary Email" -msgstr "Электронная почта Geary" - #. Translators: These are desktop search terms. Do not translate semicolons, end line with a semicolon. -#: ../desktop/org.gnome.Geary.desktop.in.h:6 +#: desktop/org.gnome.Geary.desktop.in:7 msgid "Mail;E-mail;IMAP;GMail;Yahoo;Hotmail;Outlook;" msgstr "Mail;E-mail;IMAP;GMail;Yahoo;Hotmail;Outlook;" -#: ../desktop/org.gnome.Geary.desktop.in.h:7 ../ui/gtk/menus.ui.h:1 +#: desktop/org.gnome.Geary.desktop.in:21 ui/gtk/menus.ui:7 msgid "Compose Message" msgstr "Новое сообщение" -#: ../desktop/geary-autostart.desktop.in.h:3 -msgid "Geary Mail" -msgstr "Почтовый клиент Geary" +#: desktop/org.gnome.Geary.gschema.xml:8 +msgid "Default attachments directory" +msgstr "Каталог по умолчанию для вложений" -#: ../desktop/geary-autostart.desktop.in.h:5 -msgid "Email;E-mail;Mail;" -msgstr "Электронная;Почта;Email;E-mail;Mail;" +#: desktop/org.gnome.Geary.gschema.xml:9 +msgid "Location used when opening and saving attachments." +msgstr "Расположение используемое при открытии и сохранении вложений." -#: ../desktop/geary-attach.contract.in.h:1 -msgid "Send by email" -msgstr "Отправить по эл. почте" +#: desktop/org.gnome.Geary.gschema.xml:14 +msgid "Default print output directory" +msgstr "Каталог по умолчанию для вывода печати" + +#: desktop/org.gnome.Geary.gschema.xml:15 +msgid "Location used when printing to a file." +msgstr "Расположение используемое при печати в файл." + +#: desktop/org.gnome.Geary.gschema.xml:20 +msgid "Maximize window" +msgstr "Развернуть окно" + +#: desktop/org.gnome.Geary.gschema.xml:21 +msgid "True if the application window is maximized, false otherwise." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:26 +msgid "Width of window" +msgstr "Ширина окна" + +#: desktop/org.gnome.Geary.gschema.xml:27 +msgid "The last recorded width of the application window." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:32 +msgid "Height of window" +msgstr "Высота окна" + +#: desktop/org.gnome.Geary.gschema.xml:33 +msgid "The last recorded height of the application window." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:38 +msgid "Position of folder list pane" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:39 +msgid "Position of the folder list Paned grabber." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:44 +msgid "Position of folder list pane when horizontal" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:45 +msgid "" +"Position of the folder list Paned grabber in the horizontal orientation." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:50 +msgid "Position of folder list pane when vertical" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:51 +msgid "Position of the folder list Paned grabber in the vertical orientation." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:56 +msgid "Orientation of the folder list pane" +msgstr "" -#: ../desktop/geary-attach.contract.in.h:2 -msgid "Send files using Geary" -msgstr "Отправить файлы с помощью Geary" - -#: ../src/client/accounts/account-dialog-add-edit-pane.vala:51 -#: ../src/client/components/stock.vala:31 -#: ../ui/conversation-email-menus.ui.h:10 +#: desktop/org.gnome.Geary.gschema.xml:57 +msgid "True if the folder list Paned is in the horizontal orientation." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:62 +msgid "Position of message list pane" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:63 +msgid "Position of the message list Paned grabber." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:68 +msgid "Autoselect next message" +msgstr "Автопереход к следующему сообщению" + +#: desktop/org.gnome.Geary.gschema.xml:69 +msgid "True if we should autoselect the next available conversation." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:74 +msgid "Display message previews" +msgstr "Показывать предпросмотр сообщений" + +#: desktop/org.gnome.Geary.gschema.xml:75 +msgid "True if we should display a short preview of each message." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:80 +msgid "Languages that shall be used in the spell checker" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:81 +msgid "List of the languages to use in the spell checker." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:86 +msgid "Languages that are displayed in the spell checker popover" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:87 +msgid "" +"List of languages that are always displayed in the popover of the spell " +"checker." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:92 +msgid "Enable notification sounds" +msgstr "Включить звуковые уведомления" + +#: desktop/org.gnome.Geary.gschema.xml:93 +msgid "True to play sounds for notifications and sending." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:98 +msgid "Show notifications for new mail" +msgstr "Показывать уведомления о новых сообщениях" + +#: desktop/org.gnome.Geary.gschema.xml:99 +msgid "True to show notification bubbles." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:104 +msgid "Notify of new mail at startup" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:105 +msgid "True to notify of new mail at startup." +msgstr "Значение true для включения уведомлений о новой почте при запуске." + +#: desktop/org.gnome.Geary.gschema.xml:110 +msgid "Ask when opening an attachment" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:111 +msgid "True to ask when opening an attachment." +msgstr "Значение true чтобы спрашивать перед открытием вложения." + +#: desktop/org.gnome.Geary.gschema.xml:116 +msgid "Whether to compose emails in HTML" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:117 +msgid "True to compose emails in HTML; false for plain text." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:122 +msgid "Advisory strategy for full-text searching" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:123 +msgid "" +"Acceptable values are “exact”, “conservative”, “aggressive”, and “horizon”." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:128 +msgid "Zoom of conversation viewer" +msgstr "Масштаб окна переписки" + +#: desktop/org.gnome.Geary.gschema.xml:129 +msgid "The zoom to apply on the conservation view." +msgstr "Масштаб применяемый к виду переписки." + +#: desktop/org.gnome.Geary.gschema.xml:134 +msgid "Size of detached composer window" +msgstr "Размер отсоединённого окна композитора" + +#: desktop/org.gnome.Geary.gschema.xml:135 +msgid "The last recorded size of the detached composer window." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:140 +msgid "Whether we migrated the old settings" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:141 +msgid "" +"False to check for the old “org.yorba.geary”-schema and copy its values." +msgstr "" + +#: src/client/accounts/account-dialog-add-edit-pane.vala:54 +#: src/client/components/stock.vala:31 ui/conversation-email-menus.ui:83 msgid "_Save" msgstr "_Сохранить" -#: ../src/client/accounts/account-dialog-add-edit-pane.vala:51 -#: ../src/client/components/stock.vala:22 +#: src/client/accounts/account-dialog-add-edit-pane.vala:54 +#: src/client/components/stock.vala:22 msgid "_Add" msgstr "_Добавить" #. reset/clear widgets -#: ../src/client/accounts/account-dialog-edit-alternate-emails-pane.vala:120 +#: src/client/accounts/account-dialog-edit-alternate-emails-pane.vala:120 #, c-format msgid "Additional addresses for %s" msgstr "Дополнительные адреса для %s" #. Sets min size. -#: ../src/client/accounts/account-dialog.vala:21 +#: src/client/accounts/account-dialog.vala:28 msgid "Accounts" msgstr "Учётные записи" @@ -169,117 +354,118 @@ #. #. Page for adding or editing an account. #. / Placeholder text indicating that the user should type their first name and last name -#: ../src/client/accounts/add-edit-page.vala:10 +#: src/client/accounts/add-edit-page.vala:10 msgid "First Last" msgstr "Имя Фамилия" -#: ../src/client/accounts/add-edit-page.vala:235 +#: src/client/accounts/add-edit-page.vala:240 msgid "Welcome to Geary." msgstr "Добро пожаловать в Geary." -#: ../src/client/accounts/add-edit-page.vala:235 +#: src/client/accounts/add-edit-page.vala:240 msgid "Enter your account information to get started." msgstr "Чтобы приступить к работе, введите информацию об учётной записи." -#: ../src/client/accounts/add-edit-page.vala:255 +#: src/client/accounts/add-edit-page.vala:260 msgid "2 weeks back" msgstr "За последние 2 недели" #. IDs are # of days -#: ../src/client/accounts/add-edit-page.vala:256 +#: src/client/accounts/add-edit-page.vala:261 msgid "1 month back" msgstr "За последний месяц" -#: ../src/client/accounts/add-edit-page.vala:257 +#: src/client/accounts/add-edit-page.vala:262 msgid "3 months back" msgstr "За последние 3 месяца" -#: ../src/client/accounts/add-edit-page.vala:258 +#: src/client/accounts/add-edit-page.vala:263 msgid "6 months back" msgstr "За последние 6 месяцев" -#: ../src/client/accounts/add-edit-page.vala:259 +#: src/client/accounts/add-edit-page.vala:264 msgid "1 year back" msgstr "За последний год" -#: ../src/client/accounts/add-edit-page.vala:260 +#: src/client/accounts/add-edit-page.vala:265 msgid "2 years back" msgstr "За последние 2 года" -#: ../src/client/accounts/add-edit-page.vala:261 +#: src/client/accounts/add-edit-page.vala:266 msgid "4 years back" msgstr "За последние 4 года" #. Separator -#: ../src/client/accounts/add-edit-page.vala:263 +#: src/client/accounts/add-edit-page.vala:268 msgid "Everything" msgstr "За всё время" -#: ../src/client/accounts/add-edit-page.vala:283 +#: src/client/accounts/add-edit-page.vala:288 msgid "Edit" msgstr "Правка" -#: ../src/client/accounts/add-edit-page.vala:285 +#: src/client/accounts/add-edit-page.vala:290 msgid "Preview" msgstr "Предварительный просмотр" -#: ../src/client/accounts/add-edit-page.vala:751 +#: src/client/accounts/add-edit-page.vala:778 msgid "Remem_ber passwords" msgstr "Зап_омнить пароли" -#: ../src/client/accounts/add-edit-page.vala:758 ../ui/login.glade.h:7 +#: src/client/accounts/add-edit-page.vala:785 ui/login.glade:233 msgid "Remem_ber password" msgstr "За_помнить пароль" -#: ../src/client/accounts/add-edit-page.vala:792 +#: src/client/accounts/add-edit-page.vala:819 msgid "Unable to validate:\n" msgstr "Не удалось подтвердить:\n" -#: ../src/client/accounts/add-edit-page.vala:794 +#: src/client/accounts/add-edit-page.vala:821 msgid " • Invalid account nickname.\n" msgstr " • Неверный псевдоним учётной записи.\n" -#: ../src/client/accounts/add-edit-page.vala:797 +#: src/client/accounts/add-edit-page.vala:824 msgid " • Email address already added to Geary.\n" msgstr " • Адрес электронной почты уже добавлен в Geary.\n" -#: ../src/client/accounts/add-edit-page.vala:801 +#: src/client/accounts/add-edit-page.vala:828 msgid " • IMAP connection error.\n" msgstr " • Ошибка подключения к IMAP.\n" -#: ../src/client/accounts/add-edit-page.vala:804 +#: src/client/accounts/add-edit-page.vala:831 msgid " • IMAP username or password incorrect.\n" msgstr " • Неверные имя пользователя или пароль IMAP.\n" -#: ../src/client/accounts/add-edit-page.vala:807 +#: src/client/accounts/add-edit-page.vala:834 msgid " • SMTP connection error.\n" msgstr " • Ошибка подключения к SMTP.\n" -#: ../src/client/accounts/add-edit-page.vala:810 +#: src/client/accounts/add-edit-page.vala:837 msgid " • SMTP username or password incorrect.\n" msgstr " • Неверные имя пользователя или пароль SMTP.\n" -#: ../src/client/accounts/add-edit-page.vala:814 +#: src/client/accounts/add-edit-page.vala:841 msgid " • Connection error.\n" msgstr " • Ошибка подключения.\n" -#: ../src/client/accounts/add-edit-page.vala:818 +#: src/client/accounts/add-edit-page.vala:845 msgid " • Username or password incorrect.\n" msgstr " • Неверные имя пользователя или пароль.\n" -#: ../src/client/application/geary-application.vala:22 +#: src/client/application/geary-application.vala:22 msgid "Copyright 2016 Software Freedom Conservancy Inc." msgstr "Авторское право © 2016 Software Freedom Conservancy Inc." -#: ../src/client/application/geary-application.vala:23 -msgid "Copyright 2016-2017 Geary Development Team." -msgstr "Авторское право © 016-2017 Geary Development Team." +#: src/client/application/geary-application.vala:23 +#| msgid "Copyright 2016-2017 Geary Development Team." +msgid "Copyright 2016-2018 Geary Development Team." +msgstr "Авторское право © 2016-2018 Geary Development Team." -#: ../src/client/application/geary-application.vala:25 +#: src/client/application/geary-application.vala:25 msgid "Visit the Geary web site" msgstr "Посетить веб-сайт Geary" -#: ../src/client/application/geary-application.vala:466 +#: src/client/application/geary-application.vala:416 #, c-format msgid "About %s" msgstr "О %s" @@ -287,250 +473,102 @@ #. Translators: add your name and email address to receive #. credit in the About dialog For example: Yamada Taro #. -#: ../src/client/application/geary-application.vala:470 +#: src/client/application/geary-application.vala:420 msgid "translator-credits" -msgstr "Stas Solovey , 2015-2017." +msgstr "Stas Solovey , 2015-2018." -#: ../src/client/application/geary-args.vala:10 +#: src/client/application/geary-args.vala:10 msgid "Start Geary with hidden main window" msgstr "Запускать Geary свёрнутым" -#: ../src/client/application/geary-args.vala:11 +#: src/client/application/geary-args.vala:11 msgid "Output debugging information" msgstr "Выводить отладочную информацию" -#: ../src/client/application/geary-args.vala:12 +#: src/client/application/geary-args.vala:12 msgid "Log conversation monitoring" msgstr "Записывать мониторинг бесед" # What is "deserialization"? -#: ../src/client/application/geary-args.vala:13 +#: src/client/application/geary-args.vala:13 msgid "Log network deserialization" msgstr "Записывать десериализацию сети" -#: ../src/client/application/geary-args.vala:14 +#: src/client/application/geary-args.vala:14 msgid "Log network activity" msgstr "Записывать сетевую активность" #. / The IMAP replay queue is how changes on the server are replicated on the client. #. / It could also be called the IMAP events queue. -#: ../src/client/application/geary-args.vala:17 +#: src/client/application/geary-args.vala:17 msgid "Log IMAP replay queue" msgstr "Записывать ответ IMAP" #. / Serialization is how commands and responses are converted into a stream of bytes for #. / network transmission -#: ../src/client/application/geary-args.vala:20 +#: src/client/application/geary-args.vala:20 msgid "Log network serialization" msgstr "Записывать сериализацию сети" -#: ../src/client/application/geary-args.vala:21 +#: src/client/application/geary-args.vala:21 msgid "Log periodic activity" msgstr "Записывать периодическую активность" -#: ../src/client/application/geary-args.vala:22 +#: src/client/application/geary-args.vala:22 msgid "Log database queries (generates lots of messages)" msgstr "Записывать запросы базы данных (создаёт множество сообщений)" #. / "Normalization" can also be called "synchronization" -#: ../src/client/application/geary-args.vala:24 +#: src/client/application/geary-args.vala:24 msgid "Log folder normalization" msgstr "Записывать синхронизацию папки" -#: ../src/client/application/geary-args.vala:25 +#: src/client/application/geary-args.vala:25 msgid "Allow inspection of WebView" msgstr "Разрешить проверку WebView" -#: ../src/client/application/geary-args.vala:26 +#: src/client/application/geary-args.vala:26 msgid "Revoke all server certificates with TLS warnings" msgstr "Отозвать все сертификаты серверов с предупреждениями TLS" -#: ../src/client/application/geary-args.vala:27 +#: src/client/application/geary-args.vala:27 msgid "Perform a graceful quit" msgstr "Выполнить правильный выход" -#: ../src/client/application/geary-args.vala:28 +#: src/client/application/geary-args.vala:28 msgid "Display program version" msgstr "Показать версию приложения" #. This gives a command-line hint on how to open new composer windows with mailto: -#: ../src/client/application/geary-args.vala:53 +#: src/client/application/geary-args.vala:53 #, c-format msgid "Use %s to open a new composer window" msgstr "Используйте %s для открытия нового окна редактора" -#: ../src/client/application/geary-args.vala:56 +#: src/client/application/geary-args.vala:56 msgid "Please report comments, suggestions and bugs to:" msgstr "Комментарии, пожелания и найденные ошибки отправляйте:" #. i18n: Command line arguments are invalid -#: ../src/client/application/geary-args.vala:63 +#: src/client/application/geary-args.vala:63 #, c-format msgid "Failed to parse command line options: %s\n" msgstr "Не удалось разобрать параметры командной строки: %s\n" -#: ../src/client/application/geary-args.vala:74 +#: src/client/application/geary-args.vala:74 #, c-format msgid "Unrecognized command line option “%s”\n" msgstr "Неизвестный параметр командной строки «%s»\n" -#: ../src/client/application/geary-controller.vala:57 -msgid "Delete conversation" -msgstr "Удалить беседу" - -#: ../src/client/application/geary-controller.vala:58 -msgid "Delete conversation (Shift+Delete)" -msgstr "Удалить беседу (Shift+Delete)" - -#: ../src/client/application/geary-controller.vala:59 -msgid "Delete conversations (Shift+Delete)" -msgstr "Удалить беседы (Shift+Delete)" - -#. This refers to the action ("move email to the trash"), not the Trash folder itself -#: ../src/client/application/geary-controller.vala:63 -msgid "Move conversation to Trash (Delete, Backspace)" -msgstr "Переместить беседу в корзину (Delete, Backspace)" - -#: ../src/client/application/geary-controller.vala:64 -msgid "Move conversations to Trash (Delete, Backspace)" -msgstr "Переместить беседы в корзину (Delete, Backspace)" - -#. This refers to the action ("archive an email"), not the Archive folder itself -#: ../src/client/application/geary-controller.vala:68 -msgid "_Archive" -msgstr "_Архивировать" - -#: ../src/client/application/geary-controller.vala:69 -msgid "Archive conversation (A)" -msgstr "Архивировать беседу (A)" - -#: ../src/client/application/geary-controller.vala:70 -msgid "Archive conversations (A)" -msgstr "Архивировать беседы (A)" - -#: ../src/client/application/geary-controller.vala:73 -msgid "Mark as S_pam" -msgstr "Отметить как С_пам" - -#: ../src/client/application/geary-controller.vala:74 -msgid "Mark as not S_pam" -msgstr "Отметить как _не Спам" - -#: ../src/client/application/geary-controller.vala:76 -#: ../src/client/application/geary-controller.vala:448 -msgid "Mark conversation" -msgstr "Отметить беседу" - -#: ../src/client/application/geary-controller.vala:77 -msgid "Mark conversations" -msgstr "Отметить беседы" - -#: ../src/client/application/geary-controller.vala:78 -msgid "Add label to conversation" -msgstr "Добавить метку беседе" - -#: ../src/client/application/geary-controller.vala:79 -msgid "Add label to conversations" -msgstr "Добавить метку беседам" - -#: ../src/client/application/geary-controller.vala:80 -#: ../src/client/application/geary-controller.vala:487 -msgid "Move conversation" -msgstr "Переместить беседу" - -#: ../src/client/application/geary-controller.vala:81 -msgid "Move conversations" -msgstr "Переместить беседы" - -#: ../src/client/application/geary-controller.vala:450 -msgid "_Mark as…" -msgstr "_Отметить как…" - -#: ../src/client/application/geary-controller.vala:456 -msgid "Mark as _Read" -msgstr "Отметить как _Прочитанное" - -#: ../src/client/application/geary-controller.vala:462 -msgid "Mark as _Unread" -msgstr "Отметить как _Непрочитанное" - -#: ../src/client/application/geary-controller.vala:468 -msgid "_Star" -msgstr "_Добавить в избранное" - -#: ../src/client/application/geary-controller.vala:473 -msgid "U_nstar" -msgstr "_Убрать из избранных" - -#: ../src/client/application/geary-controller.vala:483 -msgid "Add label" -msgstr "Добавить метку" - -#: ../src/client/application/geary-controller.vala:484 -msgid "_Label" -msgstr "_Метка" - -#: ../src/client/application/geary-controller.vala:488 -msgid "_Move" -msgstr "_Переместить" - -#: ../src/client/application/geary-controller.vala:492 -msgid "Compose new message (Ctrl+N, N)" -msgstr "Создать новую беседу (Ctrl+N, N)" - -#: ../src/client/application/geary-controller.vala:496 -#: ../ui/conversation-email-menus.ui.h:1 -msgid "_Reply" -msgstr "_Ответить" - -#: ../src/client/application/geary-controller.vala:497 -msgid "Reply (Ctrl+R, R)" -msgstr "Ответить (Ctrl+R, R)" - -#: ../src/client/application/geary-controller.vala:501 -msgid "R_eply All" -msgstr "О_тветить всем" - -#: ../src/client/application/geary-controller.vala:502 -msgid "Reply all (Ctrl+Shift+R, Shift+R)" -msgstr "Ответить всем (Ctrl+Shift+R, Shift+R)" - -#: ../src/client/application/geary-controller.vala:507 -#: ../ui/conversation-email-menus.ui.h:3 -msgid "_Forward" -msgstr "_Переслать" - -#: ../src/client/application/geary-controller.vala:508 -msgid "Forward (Ctrl+L, F)" -msgstr "Переслать (Ctrl+L, F)" - -#: ../src/client/application/geary-controller.vala:538 -msgid "Empty _Spam…" -msgstr "Очистить _Спам…" - -#: ../src/client/application/geary-controller.vala:542 -msgid "Empty _Trash…" -msgstr "Очистить _Корзину…" - -#. No callback is connected, since we bind the toggle button to the search bar visibility -#: ../src/client/application/geary-controller.vala:574 -msgid "Toggle search bar" -msgstr "Включить панель поиска" - -#. No callback is connected, since we bind the toggle button to the find bar visibility -#: ../src/client/application/geary-controller.vala:579 -msgid "Toggle find bar" -msgstr "Переключить панель поиска" - -#: ../src/client/application/geary-controller.vala:757 +#: src/client/application/geary-controller.vala:719 msgid "Unable to store server trust exception" msgstr "Не удалось добавить исключение безопасности сервера" -#: ../src/client/application/geary-controller.vala:992 +#: src/client/application/geary-controller.vala:970 msgid "Your settings are insecure" msgstr "Параметры небезопасны" -#: ../src/client/application/geary-controller.vala:993 +#: src/client/application/geary-controller.vala:971 msgid "" "Your IMAP and/or SMTP settings do not specify SSL or TLS. This means your " "username and password could be read by another person on the network. Are " @@ -540,28 +578,17 @@ "что ваши имя пользователя и пароль могут быть перехвачены сторонними лицами. " "Уверены, что хотите продолжить?" -#: ../src/client/application/geary-controller.vala:994 +#: src/client/application/geary-controller.vala:972 msgid "Co_ntinue" msgstr "_Продолжить" -#: ../src/client/application/geary-controller.vala:1041 -msgid "Error connecting to the server" -msgstr "Ошибка соединения с сервером" - -#: ../src/client/application/geary-controller.vala:1042 -msgid "" -"Geary encountered an error while connecting to the server. Please try again " -"in a few moments." -msgstr "" -"Geary получил ошибку при соединении с сервером. Повторите попытку позже." - #. / Displayed in the space-limited status bar when a message fails to be sent due to error. -#: ../src/client/application/geary-controller.vala:1079 -#: ../src/client/components/status-bar.vala:29 +#: src/client/application/geary-controller.vala:1076 +#: src/client/components/status-bar.vala:29 msgid "Error sending email" msgstr "Ошибка отправки эл. почты" -#: ../src/client/application/geary-controller.vala:1080 +#: src/client/application/geary-controller.vala:1077 msgid "" "Geary encountered an error sending an email. If the problem persists, " "please manually delete the email from your Outbox folder." @@ -571,12 +598,12 @@ #. Displayed in the space-limited status bar when a message fails to be uploaded #. to Sent Mail after being sent. -#: ../src/client/application/geary-controller.vala:1084 -#: ../src/client/components/status-bar.vala:33 +#: src/client/application/geary-controller.vala:1081 +#: src/client/components/status-bar.vala:33 msgid "Error saving sent mail" msgstr "Ошибка сохранения отправленной почты" -#: ../src/client/application/geary-controller.vala:1085 +#: src/client/application/geary-controller.vala:1082 msgid "" "Geary encountered an error saving a sent message to Sent Mail. The message " "will stay in your Outbox folder until you delete it." @@ -584,19 +611,19 @@ "В Geary произошла ошибка при сохранении отправленного сообщения в папку " "«Отправленные». Сообщение будет в папке «Исходящие» пока вы не удалите его." -#: ../src/client/application/geary-controller.vala:1154 +#: src/client/application/geary-controller.vala:1149 msgid "Labels" msgstr "Метки" #. give the user two options: reset the Account local store, or exit Geary. A third #. could be done to leave the Account in an unopened state, but we don't currently #. have provisions for that. -#: ../src/client/application/geary-controller.vala:1166 +#: src/client/application/geary-controller.vala:1161 #, c-format msgid "Unable to open the database for %s" msgstr "Не удалось открыть базу данных для %s" -#: ../src/client/application/geary-controller.vala:1167 +#: src/client/application/geary-controller.vala:1162 #, c-format msgid "" "There was an error opening the local mail database for this account. This is " @@ -621,20 +648,20 @@ "Пересоздание базы данных уничтожит все локальные письма и их вложения. " "Операция не затронет почту, хранящуюся на сервере." -#: ../src/client/application/geary-controller.vala:1169 +#: src/client/application/geary-controller.vala:1164 msgid "_Rebuild" msgstr "Пересоздать" -#: ../src/client/application/geary-controller.vala:1169 +#: src/client/application/geary-controller.vala:1164 msgid "E_xit" msgstr "_Завершить" -#: ../src/client/application/geary-controller.vala:1178 +#: src/client/application/geary-controller.vala:1173 #, c-format msgid "Unable to rebuild database for “%s”" msgstr "Не удалось пересоздать базу данных для «%s»" -#: ../src/client/application/geary-controller.vala:1179 +#: src/client/application/geary-controller.vala:1174 #, c-format msgid "" "Error during rebuild:\n" @@ -647,14 +674,14 @@ #. some other problem opening the account ... as with other flow path, can't run #. Geary today with an account in unopened state, so have to exit -#: ../src/client/application/geary-controller.vala:1201 -#: ../src/client/application/geary-controller.vala:1211 -#: ../src/client/application/geary-controller.vala:1222 +#: src/client/application/geary-controller.vala:1196 +#: src/client/application/geary-controller.vala:1206 +#: src/client/application/geary-controller.vala:1217 #, c-format msgid "Unable to open local mailbox for %s" msgstr "Не удалось открыть локальный почтовый ящик для %s" -#: ../src/client/application/geary-controller.vala:1202 +#: src/client/application/geary-controller.vala:1197 #, c-format msgid "" "There was an error opening the local mail database for this account. This is " @@ -672,7 +699,7 @@ "\n" "%s" -#: ../src/client/application/geary-controller.vala:1212 +#: src/client/application/geary-controller.vala:1207 msgid "" "The version number of the local mail database is formatted for a newer " "version of Geary. Unfortunately, the database cannot be “rolled back” to " @@ -686,7 +713,7 @@ "\n" "Установите последнюю версию Geary и попробуйте снова." -#: ../src/client/application/geary-controller.vala:1223 +#: src/client/application/geary-controller.vala:1218 msgid "" "There was an error opening the local account. This is probably due to " "connectivity issues.\n" @@ -698,15 +725,15 @@ "\n" "Проверьте подключение к сети и перезапустите Geary." -#: ../src/client/application/geary-controller.vala:2010 +#: src/client/application/geary-controller.vala:2036 msgid "Undo move (Ctrl+Z)" msgstr "Отменить перемещение (Ctrl+Z)" -#: ../src/client/application/geary-controller.vala:2020 +#: src/client/application/geary-controller.vala:2046 msgid "Are you sure you want to open these attachments?" msgstr "Действительно хотите открыть эти вложения?" -#: ../src/client/application/geary-controller.vala:2021 +#: src/client/application/geary-controller.vala:2047 msgid "" "Attachments may cause damage to your system if opened. Only open files from " "trusted sources." @@ -714,16 +741,16 @@ "Вложения при открытии могут нанести ущерб системе. Открывайте вложения " "только от надёжных источников." -#: ../src/client/application/geary-controller.vala:2022 +#: src/client/application/geary-controller.vala:2048 msgid "Don’t _ask me again" msgstr "Не _спрашивать снова" -#: ../src/client/application/geary-controller.vala:2126 +#: src/client/application/geary-controller.vala:2144 #, c-format msgid "A file named “%s” already exists. Do you want to replace it?" msgstr "Файл с именем «%s» уже существует. Заменить его?" -#: ../src/client/application/geary-controller.vala:2128 +#: src/client/application/geary-controller.vala:2146 #, c-format msgid "" "The file already exists in “%s”. Replacing it will overwrite its contents." @@ -731,186 +758,430 @@ "Файл уже существует в «%s». Его замена приведёт к полной перезаписи его " "содержимого." -#: ../src/client/application/geary-controller.vala:2131 +#: src/client/application/geary-controller.vala:2149 msgid "_Replace" msgstr "_Заменить" -#. Find out what to do with the inline composers. -#. TODO: Remove this in favor of automatically saving drafts -#: ../src/client/application/geary-controller.vala:2379 -msgid "Close open draft messages?" -msgstr "Закрыть черновик?" +#: src/client/application/geary-controller.vala:2389 +msgid "Close the draft message?" +msgid_plural "Close all draft messages?" +msgstr[0] "Закрыть черновик?" +msgstr[1] "Закрыть черновики?" +msgstr[2] "Закрыть черновики?" -#: ../src/client/application/geary-controller.vala:2501 +#: src/client/application/geary-controller.vala:2515 #, c-format msgid "Empty all email from your %s folder?" msgstr "Очистить все письма папки %s?" -#: ../src/client/application/geary-controller.vala:2502 +#: src/client/application/geary-controller.vala:2516 msgid "This removes the email from Geary and your email server." msgstr "Удаляет все письма из Geary и почтового сервера." -#: ../src/client/application/geary-controller.vala:2503 +#: src/client/application/geary-controller.vala:2517 msgid "This cannot be undone." msgstr "Это не может быть отменено." -#: ../src/client/application/geary-controller.vala:2504 +#: src/client/application/geary-controller.vala:2518 #, c-format msgid "Empty %s" msgstr "Очистить %s" -#: ../src/client/application/geary-controller.vala:2521 +#: src/client/application/geary-controller.vala:2535 #, c-format msgid "Error emptying %s" msgstr "Ошибка очистки %s" -#: ../src/client/application/geary-controller.vala:2551 +#: src/client/application/geary-controller.vala:2567 msgid "Do you want to permanently delete this message?" msgid_plural "Do you want to permanently delete these messages?" msgstr[0] "Безвозвратно удалить это сообщение?" msgstr[1] "Безвозвратно удалить эти сообщения?" msgstr[2] "Безвозвратно удалить эти сообщения?" -#: ../src/client/application/geary-controller.vala:2553 +#: src/client/application/geary-controller.vala:2569 msgid "Delete" msgstr "_Удалить" -#: ../src/client/application/geary-controller.vala:2585 -msgid "Undo archive (Ctrl+Z)" -msgstr "Отменить архивирование (Ctrl+Z)" - -#: ../src/client/application/geary-controller.vala:2600 +#: src/client/application/geary-controller.vala:2583 msgid "Undo trash (Ctrl+Z)" msgstr "Отменить перемещение в корзину (Ctrl+Z)" -#: ../src/client/application/geary-controller.vala:2654 +#: src/client/application/geary-controller.vala:2633 +msgid "Undo archive (Ctrl+Z)" +msgstr "Отменить архивирование (Ctrl+Z)" + +#: src/client/application/geary-controller.vala:2678 msgid "Undo (Ctrl+Z)" msgstr "Отменить (Ctrl+Z)" -#: ../src/client/application/geary-controller.vala:2785 +#. Translators: The label for an in-app notification. The +#. string substitution is a list of recipients of the email. +#: src/client/application/geary-controller.vala:2755 +#, c-format +msgid "Successfully sent mail to %s." +msgstr "Письмо успешно отправлено %s." + +#: src/client/application/geary-controller.vala:2836 msgid "Failed to open default text editor." msgstr "Не удалось запустить текстовый редактор по-умолчанию." -#: ../src/client/components/main-window.vala:389 +#. Tooltips +#: src/client/components/main-toolbar.vala:69 +msgid "Delete conversation (Shift+Delete)" +msgstr "Удалить беседу (Shift+Delete)" + +#: src/client/components/main-toolbar.vala:70 +msgid "Delete conversations (Shift+Delete)" +msgstr "Удалить беседы (Shift+Delete)" + +#: src/client/components/main-toolbar.vala:71 +msgid "Move conversation to Trash (Delete, Backspace)" +msgstr "Переместить беседу в корзину (Delete, Backspace)" + +#: src/client/components/main-toolbar.vala:72 +msgid "Move conversations to Trash (Delete, Backspace)" +msgstr "Переместить беседы в корзину (Delete, Backspace)" + +#: src/client/components/main-toolbar.vala:73 +msgid "Archive conversation (A)" +msgstr "Архивировать беседу (A)" + +#: src/client/components/main-toolbar.vala:74 +msgid "Archive conversations (A)" +msgstr "Архивировать беседы (A)" + +#: src/client/components/main-toolbar.vala:75 +msgid "Mark conversation" +msgstr "Отметить беседу" + +#: src/client/components/main-toolbar.vala:76 +msgid "Mark conversations" +msgstr "Отметить беседы" + +#: src/client/components/main-toolbar.vala:77 +msgid "Add label to conversation" +msgstr "Добавить метку беседе" + +#: src/client/components/main-toolbar.vala:78 +msgid "Add label to conversations" +msgstr "Добавить метку беседам" + +#: src/client/components/main-toolbar.vala:79 +msgid "Move conversation" +msgstr "Переместить беседу" + +#: src/client/components/main-toolbar.vala:80 +msgid "Move conversations" +msgstr "Переместить беседы" + +#: src/client/components/main-window.vala:440 #, c-format msgid "%s (%d)" msgstr "%s (%d)" -#: ../src/client/components/search-bar.vala:8 -#: ../src/client/folder-list/folder-list-search-branch.vala:38 -#: ../src/engine/api/geary-special-folder-type.vala:51 +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:54 +#, c-format +#| msgid "Error connecting to the server" +msgid "Problem connecting to incoming server for %s" +msgstr "Проблема подключения к серверу входящих для %s" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:56 +#: src/client/components/main-window-info-bar.vala:64 +#, c-format +msgid "" +"Could not connect to %s, check your Internet access and the server name and " +"try again" +msgstr "" +"Не удалось подключиться к %s, проверьте доступ к Интернету и имя сервера, " +"затем повторить попытку" + +#: src/client/components/main-window-info-bar.vala:57 +#: src/client/components/main-window-info-bar.vala:66 +msgid "Retry connecting now" +msgstr "Повторить подключение сейчас" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:62 +#, c-format +#| msgid "Error connecting to the server" +msgid "Problem connecting to outgoing server for %s" +msgstr "Проблема подключения к серверу исходящих для %s" + +#: src/client/components/main-window-info-bar.vala:65 +msgid "Try reconnecting now" +msgstr "Попробовать переподключиться сейчас" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:71 +#, c-format +msgid "Problem with connection to incoming server for %s" +msgstr "Проблема с подключением к серверу входящих для %s" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:73 +#: src/client/components/main-window-info-bar.vala:81 +#, c-format +msgid "Network error talking to %s, check your Internet access and try again" +msgstr "" +"Ошибка сети, связанная с %s, проверьте доступ к Интернету и повторите попытку" + +#: src/client/components/main-window-info-bar.vala:74 +#: src/client/components/main-window-info-bar.vala:82 +#: src/client/components/main-window-info-bar.vala:90 +#: src/client/components/main-window-info-bar.vala:98 +#: src/client/components/main-window-info-bar.vala:119 +msgid "Try reconnecting" +msgstr "Попробовать переподключиться" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:79 +#, c-format +#| msgid "Error connecting to the server" +msgid "Problem with connection to outgoing server for %s" +msgstr "Проблема с подключением к серверу исходящих для %s" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:87 +#, c-format +msgid "Problem communicating with incoming server for %s" +msgstr "Проблема связи с сервером входящих для %s" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:89 +#, c-format +msgid "" +"Geary did not understand a message from %s or vice versa, please file a bug " +"report" +msgstr "Geary не понял сообщения от %s или наоборот, напишите отчёт об ошибке" + +#: src/client/components/main-window-info-bar.vala:94 +msgid "Problem communicating with outgoing mail server" +msgstr "Проблема связи с сервером исходящих для %s" + +#. Translators: First string substitution is the server +#. name, second is the account name +#: src/client/components/main-window-info-bar.vala:97 +#, c-format +msgid "" +"Could not communicate with %s for %s, check the server name and try again in " +"a moment" +msgstr "" +"Не удалось связаться с %s для %s, проверьте имя сервера и повторите попытку" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:103 +#, c-format +msgid "Incoming mail server password required for %s" +msgstr "Требуется пароль сервера входящих для %s" + +#: src/client/components/main-window-info-bar.vala:104 +msgid "Messages cannot be received without the correct password." +msgstr "Невозможно получить сообщения без правильного пароля." + +#: src/client/components/main-window-info-bar.vala:105 +msgid "Retry receiving email, you will be prompted for a password" +msgstr "" +"Повторить попытку получения электронной почты, вам будет предложено ввести " +"пароль" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:110 +#, c-format +msgid "Outgoing mail server password required for %s" +msgstr "Требуется пароль сервера исходящих для %s" + +#: src/client/components/main-window-info-bar.vala:111 +msgid "Messages cannot be sent without the correct password." +msgstr "Невозможно отправить сообщения без правильного пароля." + +#: src/client/components/main-window-info-bar.vala:112 +msgid "Retry sending queued messages, you will be prompted for a password" +msgstr "" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:117 +#, c-format +msgid "A problem occurred checking mail for %s" +msgstr "" + +#: src/client/components/main-window-info-bar.vala:118 +#: src/client/components/main-window-info-bar.vala:125 +msgid "Something went wrong, please file a bug report if the problem persists" +msgstr "" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:124 +#, c-format +msgid "A problem occurred sending mail for %s" +msgstr "" + +#: src/client/components/main-window-info-bar.vala:126 +msgid "Retry sending queued messages" +msgstr "" + +#: src/client/components/main-window-info-bar.vala:137 +msgid "A database problem has occurred" +msgstr "" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:139 +#, c-format +msgid "Messages for %s must be downloaded again." +msgstr "" + +#: src/client/components/main-window-info-bar.vala:152 +msgid "Geary has encountered a problem" +msgstr "Geary столкнулся с проблемой" + +#: src/client/components/main-window-info-bar.vala:153 +msgid "" +"Please check the technical details and report the problem if it persists." +msgstr "" +"Проверьте технические детали и сообщите о проблеме, если она сохраняется." + +#: src/client/components/main-window-info-bar.vala:161 +msgid "_Details" +msgstr "_Подробности" + +#: src/client/components/main-window-info-bar.vala:162 +msgid "View technical details about the error" +msgstr "Просмотреть технические сведения об ошибке" + +#: src/client/components/main-window-info-bar.vala:166 +msgid "_Retry" +msgstr "Повто_рить" + +#: src/client/components/main-window-info-bar.vala:253 +msgid "Details" +msgstr "Подробности" + +#: src/client/components/main-window-info-bar.vala:266 +#: src/client/components/stock.vala:23 +msgid "_Close" +msgstr "_Закрыть" + +#: src/client/components/main-window-info-bar.vala:270 +msgid "Copy to Clipboard" +msgstr "Скопировать в буфер обмена" + +#: src/client/components/main-window-info-bar.vala:273 +msgid "" +"Copy technical details to clipboard for pasting into an email or bug report" +msgstr "" +"Скопируйте технические данные в буфер обмена для вставки в сообщение " +"электронной почты или в сообщение об ошибке" + +#: src/client/components/search-bar.vala:8 +#: src/client/folder-list/folder-list-search-branch.vala:38 +#: src/engine/api/geary-special-folder-type.vala:51 msgid "Search" msgstr "Поиск" #. Search entry. -#: ../src/client/components/search-bar.vala:23 +#: src/client/components/search-bar.vala:23 msgid "Search all mail in account for keywords (Ctrl+S)" msgstr "Поиск по всей почте учётной записи по ключевым словам (Ctrl+S)" -#: ../src/client/components/search-bar.vala:100 +#: src/client/components/search-bar.vala:100 #, c-format msgid "Indexing %s account" msgstr "Индексируется учётная запись %s" -#: ../src/client/components/search-bar.vala:111 -#: ../src/client/folder-list/folder-list-search-branch.vala:39 +#: src/client/components/search-bar.vala:111 +#: src/client/folder-list/folder-list-search-branch.vala:39 #, c-format msgid "Search %s account" msgstr "Поиск учётной записи %s" #. / Displayed in the space-limited status bar while a message is in the process of being sent. -#: ../src/client/components/status-bar.vala:26 +#: src/client/components/status-bar.vala:26 msgid "Sending…" msgstr "Отправка…" -#: ../src/client/components/stock.vala:18 ../ui/account_cannot_remove.glade.h:3 +#: src/client/components/stock.vala:18 ui/account_cannot_remove.glade:74 msgid "_OK" msgstr "_ОК" -#: ../src/client/components/stock.vala:19 ../ui/edit_alternate_emails.glade.h:3 -#: ../ui/password-dialog.glade.h:5 ../ui/remove_confirm.glade.h:5 +#: src/client/components/stock.vala:19 ui/edit_alternate_emails.glade:160 +#: ui/password-dialog.glade:196 ui/remove_confirm.glade:155 msgid "_Cancel" msgstr "О_тмена" -#: ../src/client/components/stock.vala:21 ../ui/gtk/menus.ui.h:6 +#: src/client/components/stock.vala:21 ui/gtk/menus.ui:34 msgid "_About" msgstr "_О приложении" -#: ../src/client/components/stock.vala:23 -msgid "_Close" -msgstr "_Закрыть" - -#: ../src/client/components/stock.vala:24 +#: src/client/components/stock.vala:24 msgid "_Discard" msgstr "От_казаться" -#: ../src/client/components/stock.vala:25 ../ui/gtk/menus.ui.h:5 +#: src/client/components/stock.vala:25 ui/gtk/menus.ui:29 msgid "_Help" msgstr "_Справка" -#: ../src/client/components/stock.vala:26 ../ui/conversation-email-menus.ui.h:9 +#: src/client/components/stock.vala:26 ui/conversation-email-menus.ui:77 msgid "_Open" msgstr "Отк_рыть" -#: ../src/client/components/stock.vala:27 ../ui/gtk/menus.ui.h:3 +#: src/client/components/stock.vala:27 ui/gtk/menus.ui:17 msgid "_Preferences" -msgstr "_Параметры" +msgstr "П_араметры" -#: ../src/client/components/stock.vala:28 ../ui/conversation-email-menus.ui.h:7 +#. Translators: Menu item to print a single, specific message +#: src/client/components/stock.vala:28 ui/conversation-email-menus.ui:64 msgid "_Print…" -msgstr "_Печать…" +msgstr "П_ечать…" -#: ../src/client/components/stock.vala:29 ../ui/gtk/menus.ui.h:7 +#: src/client/components/stock.vala:29 ui/gtk/menus.ui:38 msgid "_Quit" msgstr "_Завершить" -#: ../src/client/components/stock.vala:30 ../ui/remove_confirm.glade.h:6 +#: src/client/components/stock.vala:30 ui/remove_confirm.glade:170 msgid "_Remove" msgstr "_Удалить" -#: ../src/client/components/stock.vala:32 +#: src/client/components/stock.vala:32 msgid "_Keep" msgstr "О_ставить" -#: ../src/client/composer/composer-link-popover.vala:150 +#: src/client/composer/composer-link-popover.vala:149 msgid "Link URL is not correctly formatted, e.g. http://example.com" msgstr "" "Ссылка URL неверно отформатирована, пример правильно оформленной ссылки " "http://example.com" -#: ../src/client/composer/composer-link-popover.vala:157 +#: src/client/composer/composer-link-popover.vala:156 msgid "Invalid link URL" msgstr "Некорректная ссылка URL" -#: ../src/client/composer/composer-link-popover.vala:157 +#: src/client/composer/composer-link-popover.vala:156 msgid "Invalid email address" msgstr "Некорректный адрес эл. почты" -#: ../src/client/composer/composer-widget.vala:154 +#: src/client/composer/composer-widget.vala:158 msgid "Saved" msgstr "Сохранён" -#: ../src/client/composer/composer-widget.vala:155 +#: src/client/composer/composer-widget.vala:159 msgid "Saving" msgstr "Сохраняется" -#: ../src/client/composer/composer-widget.vala:156 +#: src/client/composer/composer-widget.vala:160 msgid "Error saving" msgstr "Ошибка сохранения" -#: ../src/client/composer/composer-widget.vala:157 +#: src/client/composer/composer-widget.vala:161 msgid "Press Backspace to delete quote" msgstr "Нажмите клавишу Backspace для удаления цитаты" -#: ../src/client/composer/composer-widget.vala:158 -msgid "New Message" -msgstr "Новое сообщение" - #. Translators: This is list of keywords, separated by pipe ("|") #. characters, that suggest an attachment; since this is full-word #. checking, include all variants of each word. No spaces are #. allowed. -#: ../src/client/composer/composer-widget.vala:167 +#: src/client/composer/composer-widget.vala:170 msgid "" "attach|attaching|attaches|attachment|attachments|attached|enclose|enclosed|" "enclosing|encloses|enclosure|enclosures" @@ -918,28 +1189,37 @@ "прикрепить|прикрепление|прикрепления|прикреплён|прикреплены|вложение|" "вложенные|вложения" -#: ../src/client/composer/composer-widget.vala:1129 -#: ../src/client/composer/composer-widget.vala:1148 -msgid "Do you want to discard this message?" -msgstr "Отказаться от этого сообщения?" +#. Translators: This dialog text is displayed to the +#. user when closing a composer where the options are +#. Keep, Discard or Cancel. +#: src/client/composer/composer-widget.vala:1102 +msgid "Do you want to keep or discard this draft message?" +msgstr "Вы хотите сохранить или отказаться от этого черновика сообщения?" + +#. Translators: This dialog text is displayed to the +#. user when closing a composer where the options are +#. only Discard or Cancel. +#: src/client/composer/composer-widget.vala:1130 +msgid "Do you want to discard this draft message?" +msgstr "Вы хотите отказаться от этого черновика сообщения?" -#: ../src/client/composer/composer-widget.vala:1252 +#: src/client/composer/composer-widget.vala:1247 msgid "Send message with an empty subject and body?" msgstr "Отправить сообщение с пустой темой и без текста?" -#: ../src/client/composer/composer-widget.vala:1254 +#: src/client/composer/composer-widget.vala:1249 msgid "Send message with an empty subject?" msgstr "Отправить сообщение с пустой темой?" -#: ../src/client/composer/composer-widget.vala:1256 +#: src/client/composer/composer-widget.vala:1251 msgid "Send message with an empty body?" msgstr "Отправить сообщение без текста?" -#: ../src/client/composer/composer-widget.vala:1260 +#: src/client/composer/composer-widget.vala:1255 msgid "Send message without an attachment?" msgstr "Отправить сообщение без прикреплённых файлов?" -#: ../src/client/composer/composer-widget.vala:1522 +#: src/client/composer/composer-widget.vala:1560 #, c-format msgid "“%s” already attached for delivery." msgstr "«%s» уже вложено в письмо." @@ -949,168 +1229,214 @@ #. description of the document type, the second will #. be a human-friendly size string. For example: #. Document (100.9MB) -#: ../src/client/composer/composer-widget.vala:1530 -#: ../src/client/conversation-viewer/conversation-email.vala:138 +#: src/client/composer/composer-widget.vala:1568 +#: src/client/conversation-viewer/conversation-email.vala:136 #, c-format msgid "%s (%s)" msgstr "%s (%s)" -#: ../src/client/composer/composer-widget.vala:1567 +#: src/client/composer/composer-widget.vala:1605 #, c-format msgid "“%s” could not be found." msgstr "Невозможно найти «%s»." -#: ../src/client/composer/composer-widget.vala:1573 +#: src/client/composer/composer-widget.vala:1611 #, c-format msgid "“%s” is a folder." msgstr "«%s» является каталогом." -#: ../src/client/composer/composer-widget.vala:1579 +#: src/client/composer/composer-widget.vala:1617 #, c-format msgid "“%s” is an empty file." msgstr "«%s» — пустой файл." -#: ../src/client/composer/composer-widget.vala:1592 +#: src/client/composer/composer-widget.vala:1630 #, c-format msgid "“%s” could not be opened for reading." msgstr "Невозможно открыть для чтения «%s»." -#: ../src/client/composer/composer-widget.vala:1600 +#: src/client/composer/composer-widget.vala:1638 msgid "Cannot add attachment" msgstr "Невозможно добавить вложение" -#: ../src/client/composer/composer-widget.vala:1652 +#: src/client/composer/composer-widget.vala:1687 msgid "To: " msgstr "Кому: " -#: ../src/client/composer/composer-widget.vala:1655 +#: src/client/composer/composer-widget.vala:1690 msgid "Cc: " msgstr "Копия: " -#: ../src/client/composer/composer-widget.vala:1658 +#: src/client/composer/composer-widget.vala:1693 msgid "Bcc: " msgstr "Скрытая копия: " -#: ../src/client/composer/composer-widget.vala:1661 +#: src/client/composer/composer-widget.vala:1696 msgid "Reply-To: " msgstr "Ответить: " -#: ../src/client/composer/composer-widget.vala:1793 +#: src/client/composer/composer-widget.vala:1834 msgid "Select Color" msgstr "Выбрать цвет" #. Displayed in the From dropdown to indicate an "alternate email address" #. for an account. The first printf argument will be the alternate email #. address, and the second will be the account's primary email address. -#: ../src/client/composer/composer-widget.vala:1993 +#: src/client/composer/composer-widget.vala:2024 #, c-format msgid "%1$s via %2$s" msgstr "%1$s через %2$s" #. Composer label (with mnemonic underscore) for the account selector #. when choosing what address to send a message from. -#: ../src/client/composer/composer-widget.vala:2035 +#: src/client/composer/composer-widget.vala:2082 msgid "_From:" msgstr "_От:" #. Translators: This is the name of the file chooser filter #. when inserting an image in the composer. -#: ../src/client/composer/composer-widget.vala:2259 +#: src/client/composer/composer-widget.vala:2307 msgid "Images" msgstr "Изображения" -#: ../src/client/composer/spell-check-popover.vala:117 +#: src/client/composer/composer-window.vala:14 +msgid "New Message" +msgstr "Новое сообщение" + +#: src/client/composer/spell-check-popover.vala:117 msgid "Remove this language from the preferred list" msgstr "Удалить этот язык из списка предпочтений" -#: ../src/client/composer/spell-check-popover.vala:121 +#: src/client/composer/spell-check-popover.vala:121 msgid "Add this language to the preferred list" msgstr "Добавить этот язык в список предпочтений" -#: ../src/client/composer/spell-check-popover.vala:217 +#: src/client/composer/spell-check-popover.vala:217 msgid "Search for more languages" msgstr "Поиск дополнительных языков" -#: ../src/client/conversation-list/formatted-conversation-data.vala:11 +#: src/client/conversation-list/conversation-list-view.vala:283 +msgid "Delete conversation" +msgstr "Удалить беседу" + +#: src/client/conversation-list/conversation-list-view.vala:286 +#: ui/main-toolbar-menus.ui:16 +msgid "Mark as _Read" +msgstr "Отметить как _Прочитанное" + +#: src/client/conversation-list/conversation-list-view.vala:289 +#: ui/main-toolbar-menus.ui:20 +msgid "Mark as _Unread" +msgstr "Отметить как _Непрочитанное" + +#: src/client/conversation-list/conversation-list-view.vala:292 +#: ui/main-toolbar-menus.ui:28 +msgid "U_nstar" +msgstr "_Убрать из избранных" + +#: src/client/conversation-list/conversation-list-view.vala:294 +#: ui/main-toolbar-menus.ui:24 +msgid "_Star" +msgstr "_Добавить в избранное" + +#. Translators: Menu item to reply to a specific message. +#: src/client/conversation-list/conversation-list-view.vala:297 +#: ui/conversation-email-menus.ui:9 +msgid "_Reply" +msgstr "_Ответить" + +#: src/client/conversation-list/conversation-list-view.vala:298 +msgid "R_eply All" +msgstr "О_тветить всем" + +#. Translators: Menu item to forward a specific message. +#: src/client/conversation-list/conversation-list-view.vala:299 +#: ui/conversation-email-menus.ui:21 +msgid "_Forward" +msgstr "_Переслать" + +#: src/client/conversation-list/formatted-conversation-data.vala:11 msgid "Me" msgstr "Я" #. Translators: This is the file type displayed for #. attachments with unknown file types. -#: ../src/client/conversation-viewer/conversation-email.vala:124 +#: src/client/conversation-viewer/conversation-email.vala:122 msgid "Unknown" msgstr "Неизвестный" +#: src/client/conversation-viewer/conversation-message.vala:60 +msgid "This email address may have been forged" +msgstr "Возможно, этот адрес электронной почты был подделан" + #. Preview headers #. Translators: This is displayed in place of the from address #. when the message has no from address. -#: ../src/client/conversation-viewer/conversation-message.vala:331 +#: src/client/conversation-viewer/conversation-message.vala:330 msgid "No sender" msgstr "Отправитель не указан" #. Translators: This separates multiple 'from' #. addresses in the header preview for a message. -#: ../src/client/conversation-viewer/conversation-message.vala:585 +#: src/client/conversation-viewer/conversation-message.vala:589 msgid ", " msgstr ", " #. Translators: This string is used as the HTML IMG ALT #. attribute value when displaying an inline image in an email #. that did not specify a file name. E.g. ImageCannot remove account " msgstr "" "Невозможно удалить учётную запись " -#: ../ui/account_cannot_remove.glade.h:2 +#: ui/account_cannot_remove.glade:56 msgid "" "A composer window associated with this account is currently open. Send or " "discard the message and try again." @@ -1741,432 +2087,458 @@ "Открыто окно редактирования писем, связанное с данной учётной записью. " "Отправьте письмо или отмените его, затем попробуйте снова." -#: ../ui/account_list.glade.h:1 +#: ui/account_list.glade:69 msgid "Add account" msgstr "Добавить учётную запись" -#: ../ui/account_list.glade.h:2 +#: ui/account_list.glade:82 msgid "Edit account" msgstr "Редактировать учётную запись" -#: ../ui/account_list.glade.h:3 +#: ui/account_list.glade:95 msgid "Remove account" msgstr "Удалить учётную запись" -#: ../ui/account_spinner.glade.h:1 +#: ui/account_spinner.glade:41 msgid "Please wait while Geary validates your account." msgstr "Подождите пока Geary проверит учётную запись." -#: ../ui/certificate_warning_dialog.glade.h:1 +#: ui/certificate_warning_dialog.glade:7 msgid "Untrusted Connection" msgstr "Недоверенное соединение" -#: ../ui/certificate_warning_dialog.glade.h:2 +#: ui/certificate_warning_dialog.glade:29 msgid "_Always Trust This Server" msgstr "_Всегда доверять этому серверу" -#: ../ui/certificate_warning_dialog.glade.h:3 +#: ui/certificate_warning_dialog.glade:43 msgid "_Trust This Server" msgstr "_Доверять этому серверу" -#: ../ui/certificate_warning_dialog.glade.h:4 +#: ui/certificate_warning_dialog.glade:57 msgid "_Don’t Trust This Server" msgstr "_Не доверять этому серверу" -#: ../ui/composer-headerbar.ui.h:1 +#: ui/composer-headerbar.ui:17 ui/composer-headerbar.ui:174 msgid "Detach (Ctrl+D)" msgstr "Открепить (Ctrl+D)" -#: ../ui/composer-headerbar.ui.h:2 +#: ui/composer-headerbar.ui:57 ui/composer-headerbar.ui:83 msgid "Attach File (Ctrl+T)" msgstr "Вложить файл (Ctrl+T)" -#: ../ui/composer-headerbar.ui.h:3 +#: ui/composer-headerbar.ui:107 msgid "Include Original Attachments" msgstr "Включить исходные вложения" -#: ../ui/composer-headerbar.ui.h:4 -msgid "Send (Ctrl+Enter)" -msgstr "Отправить (Ctrl+Enter)" - -#: ../ui/composer-headerbar.ui.h:5 +#: ui/composer-headerbar.ui:202 msgid "_Send" msgstr "_Отправить" -#: ../ui/composer-headerbar.ui.h:6 +#: ui/composer-headerbar.ui:207 +msgid "Send (Ctrl+Enter)" +msgstr "Отправить (Ctrl+Enter)" + +#: ui/composer-headerbar.ui:230 msgid "Discard and Close" msgstr "Отказаться от изменений и закрыть" -#: ../ui/composer-headerbar.ui.h:7 +#: ui/composer-headerbar.ui:254 msgid "Save and Close" msgstr "Сохранить и закрыть" #. Note that this button and the Update button will never be shown at the same time to the user. -#: ../ui/composer-link-popover.ui.h:2 +#: ui/composer-link-popover.ui:41 msgid "Insert the new link with this URL" msgstr "Вставить новую ссылку с этим URL" -#: ../ui/composer-link-popover.ui.h:3 +#: ui/composer-link-popover.ui:52 msgid "Link URL" msgstr "Ссылка URL" #. Note that this button and the Insert button will never be shown at the same time to the user. -#: ../ui/composer-link-popover.ui.h:5 +#: ui/composer-link-popover.ui:66 msgid "Update this link’s URL" msgstr "Обновить URL ссылки" -#: ../ui/composer-link-popover.ui.h:6 +#: ui/composer-link-popover.ui:86 msgid "Delete this link" msgstr "Удалить эту ссылку" -#: ../ui/composer-link-popover.ui.h:7 +#: ui/composer-link-popover.ui:106 msgid "Open this link" msgstr "Открыть эту ссылку" -#: ../ui/composer-menus.ui.h:1 +#: ui/composer-menus.ui:7 msgid "S_ans Serif" msgstr "_Без засечек" -#: ../ui/composer-menus.ui.h:2 +#: ui/composer-menus.ui:12 msgid "S_erif" msgstr "_С засечками" -#: ../ui/composer-menus.ui.h:3 +#: ui/composer-menus.ui:17 msgid "_Fixed Width" msgstr "_Моноширинный" -#: ../ui/composer-menus.ui.h:4 +#: ui/composer-menus.ui:24 msgid "_Small" msgstr "_Мелкий" -#: ../ui/composer-menus.ui.h:5 +#: ui/composer-menus.ui:29 msgid "_Medium" msgstr "_Средний" -#: ../ui/composer-menus.ui.h:6 +#: ui/composer-menus.ui:34 msgid "Lar_ge" msgstr "Круп_ный" -#: ../ui/composer-menus.ui.h:7 +#: ui/composer-menus.ui:41 msgid "C_olor" msgstr "_Цвет" -#: ../ui/composer-menus.ui.h:8 +#: ui/composer-menus.ui:47 ui/composer-menus.ui:62 msgid "_Rich Text" msgstr "Текст с оформлением" -#: ../ui/composer-menus.ui.h:9 +#: ui/composer-menus.ui:53 ui/composer-menus.ui:68 msgid "Show Extended Fields" msgstr "Показывать дополнительные поля" -#: ../ui/composer-menus.ui.h:10 +#: ui/composer-menus.ui:78 msgid "_Undo" msgstr "_Отменить" -#: ../ui/composer-menus.ui.h:11 +#: ui/composer-menus.ui:82 msgid "_Redo" msgstr "В_ернуть" -#: ../ui/composer-menus.ui.h:12 +#: ui/composer-menus.ui:88 ui/composer-menus.ui:106 msgid "Cu_t" msgstr "_Вырезать" -#: ../ui/composer-menus.ui.h:13 ../ui/conversation-message-menus.ui.h:7 +#: ui/composer-menus.ui:92 ui/composer-menus.ui:110 +#: ui/conversation-message-menus.ui:37 msgid "_Copy" msgstr "_Копировать" -#: ../ui/composer-menus.ui.h:14 +#: ui/composer-menus.ui:96 ui/composer-menus.ui:114 msgid "_Paste" msgstr "В_ставить" -#: ../ui/composer-menus.ui.h:15 -msgctxt "Clipboard paste with rich text" -msgid "Paste _With Formatting" -msgstr "Вставить с _форматированием" +#: ui/composer-menus.ui:100 +msgctxt "Clipboard paste as plain text" +msgid "Paste _Without Formatting" +msgstr "Вставить бе_з форматирования" -#: ../ui/composer-menus.ui.h:16 +#: ui/composer-menus.ui:120 msgid "Select _All" msgstr "Выбрать _все" -#: ../ui/composer-menus.ui.h:17 ../ui/conversation-message-menus.ui.h:9 +#: ui/composer-menus.ui:127 ui/conversation-message-menus.ui:49 msgid "_Inspect…" msgstr "_Проверить…" #. Address(es) e-mail is to be sent to -#: ../ui/composer-widget.ui.h:2 +#: ui/composer-widget.ui:56 msgid "_To" msgstr "_Кому" -#: ../ui/composer-widget.ui.h:3 +#: ui/composer-widget.ui:75 msgid "_Cc" msgstr "Ко_пия" -#: ../ui/composer-widget.ui.h:4 +#: ui/composer-widget.ui:130 msgid "_Subject" msgstr "_Тема" -#: ../ui/composer-widget.ui.h:5 +#: ui/composer-widget.ui:149 msgid "_Bcc" msgstr "_Скрытая копия" -#: ../ui/composer-widget.ui.h:6 +#: ui/composer-widget.ui:179 msgid "_Reply-To" msgstr "_Ответить" #. Geary account mail will be sent from -#: ../ui/composer-widget.ui.h:8 +#: ui/composer-widget.ui:208 msgid "From" msgstr "От" -#: ../ui/composer-widget.ui.h:9 +#: ui/composer-widget.ui:293 msgid "Drop files here" msgstr "Перетащите файлы сюда" -#: ../ui/composer-widget.ui.h:10 +#: ui/composer-widget.ui:309 msgid "To add them as attachments" msgstr "чтобы добавить их как вложения" -#: ../ui/composer-widget.ui.h:11 +#: ui/composer-widget.ui:348 msgid "Undo last edit (Ctrl+Z)" msgstr "Отменить последние изменения (Ctrl+Z)" -#: ../ui/composer-widget.ui.h:12 +#: ui/composer-widget.ui:372 msgid "Redo last edit (Ctrl+Shift+Z)" msgstr "Вернуть последние изменения (Ctrl+Shift+Z)" -#: ../ui/composer-widget.ui.h:13 +#: ui/composer-widget.ui:410 msgid "Bold (Ctrl+B)" msgstr "Жирный (Ctrl+B)" -#: ../ui/composer-widget.ui.h:14 +#: ui/composer-widget.ui:434 msgid "Italic (Ctrl+I)" msgstr "Курсив (Ctrl+I)" -#: ../ui/composer-widget.ui.h:15 +#: ui/composer-widget.ui:458 msgid "Underline (Ctrl+U)" msgstr "Подчёркнутый (Ctrl+U)" -#: ../ui/composer-widget.ui.h:16 +#: ui/composer-widget.ui:482 msgid "Strikethrough (Ctrl+K)" msgstr "Зачёркнутый (Ctrl+K)" -#: ../ui/composer-widget.ui.h:17 +#: ui/composer-widget.ui:520 +msgid "Insert unordered list" +msgstr "Вставить неупорядоченный список" + +#: ui/composer-widget.ui:544 +msgid "Insert ordered list" +msgstr "Вставить упорядоченный список" + +#: ui/composer-widget.ui:582 msgid "Quote text (Ctrl+])" msgstr "Цитировать текст (Ctrl+])" -#: ../ui/composer-widget.ui.h:18 +#: ui/composer-widget.ui:606 msgid "Unquote text (Ctrl+[)" msgstr "Убрать цитирование текста (Ctrl+[)" -#: ../ui/composer-widget.ui.h:19 +#: ui/composer-widget.ui:644 msgid "Insert or update selection link (Ctrl+L)" msgstr "Вставить или обновить выбранную ссылку (Ctrl+L)" -#: ../ui/composer-widget.ui.h:20 +#: ui/composer-widget.ui:668 msgid "Insert an image (Ctrl+G)" msgstr "Вставить изображение (Ctrl+G)" -#: ../ui/composer-widget.ui.h:21 +#: ui/composer-widget.ui:702 msgid "Remove selection formatting (Ctrl+Space)" msgstr "Убрать форматирование выбранного (Ctrl+Space)" -#: ../ui/composer-widget.ui.h:22 +#: ui/composer-widget.ui:726 msgid "Select spell checking languages" msgstr "Выберите языки проверки орфографии" -#: ../ui/conversation-email.ui.h:1 +#: ui/conversation-email.ui:27 msgid "Save all attachments" msgstr "Сохранить все вложения" #. Note: The application will never show this button at the same time as unstar_button, one will always be hidden. -#: ../ui/conversation-email.ui.h:3 +#: ui/conversation-email.ui:50 msgid "Mark this message as starred" msgstr "Отметить это сообщение как помеченное" #. Note: The application will never show this button at the same time as star_button, one will always be hidden. -#: ../ui/conversation-email.ui.h:5 +#: ui/conversation-email.ui:72 msgid "Mark this message as not starred" msgstr "Отметить это сообщение как не помеченное" -#: ../ui/conversation-email.ui.h:6 +#: ui/conversation-email.ui:95 msgid "Display the message menu" msgstr "Показать меню сообщений" -#: ../ui/conversation-email.ui.h:7 +#: ui/conversation-email.ui:161 msgid "Open selected attachments" msgstr "Открыть выбранные вложения" -#: ../ui/conversation-email.ui.h:8 +#: ui/conversation-email.ui:178 msgid "Save selected attachments" msgstr "Сохранить выбранные вложения" -#: ../ui/conversation-email.ui.h:9 +#: ui/conversation-email.ui:195 msgid "Select all attachments" msgstr "Сохранить все вложения" -#: ../ui/conversation-email.ui.h:10 +#: ui/conversation-email.ui:240 msgid "Edit Draft" msgstr "Редактировать черновик" -#: ../ui/conversation-email.ui.h:11 +#: ui/conversation-email.ui:267 msgid "Draft message" msgstr "Черновое сообщение" -#: ../ui/conversation-email.ui.h:12 +#: ui/conversation-email.ui:283 msgid "This message has not yet been sent." msgstr "Это сообщение ещё не было отправлено." -#: ../ui/conversation-email.ui.h:13 +#: ui/conversation-email.ui:329 msgid "Message not saved" msgstr "Сообщение не сохранено" -#: ../ui/conversation-email.ui.h:14 +#: ui/conversation-email.ui:345 msgid "This message was sent, but has not been saved to your account." msgstr "Сообщение отправлено, но не сохранено в вашей учётной записи." -#: ../ui/conversation-email-menus.ui.h:2 +#. Translators: Menu item to reply to a specific message. +#: ui/conversation-email-menus.ui:15 msgid "Reply to _All" msgstr "Ответить _всем" -#: ../ui/conversation-email-menus.ui.h:4 +#. Translators: Menu item to mark a specific message as +#. read. +#: ui/conversation-email-menus.ui:30 msgid "_Mark Read" msgstr "Отметить как _прочитанное" -#: ../ui/conversation-email-menus.ui.h:5 +#: ui/conversation-email-menus.ui:36 msgid "_Mark Unread" msgstr "Отметить как _непрочитанное" -#: ../ui/conversation-email-menus.ui.h:6 +#. Translators: Menu item to mark all messages in a +#. conversation from this one as unread. +#: ui/conversation-email-menus.ui:42 msgid "Mark Unread From _Here" msgstr "Отметить как непрочитанные начиная с _этого" -#: ../ui/conversation-email-menus.ui.h:8 +#. Translators: Menu item to move a single, specific message +#. to the trash +#: ui/conversation-email-menus.ui:50 +msgid "_Trash" +msgstr "_В корзину" + +#. Translators: Menu item to delete a single, specific message +#: ui/conversation-email-menus.ui:57 +msgid "_Delete…" +msgstr "_Удалить…" + +#. Translators: Menu item to view the source for a message +#: ui/conversation-email-menus.ui:69 msgid "_View Source" msgstr "_Показать исходник" -#: ../ui/conversation-email-menus.ui.h:11 +#: ui/conversation-email-menus.ui:87 msgid "_Save All" msgstr "_Сохранить все" -#: ../ui/conversation-message-menus.ui.h:1 +#: ui/conversation-message-menus.ui:7 msgid "_Open Link" msgstr "_Открыть ссылку" -#: ../ui/conversation-message-menus.ui.h:2 +#: ui/conversation-message-menus.ui:11 msgid "Copy Link _Address" msgstr "_Копировать адрес ссылки" -#: ../ui/conversation-message-menus.ui.h:3 +#: ui/conversation-message-menus.ui:17 msgid "Send New _Message…" msgstr "Отправить новое _сообщение…" -#: ../ui/conversation-message-menus.ui.h:4 +#: ui/conversation-message-menus.ui:21 msgid "Copy Email _Address" msgstr "Копировать эл. _адрес" -#: ../ui/conversation-message-menus.ui.h:5 +#: ui/conversation-message-menus.ui:27 msgid "Save _Image As…" msgstr "_Сохранить изображение как…" -#: ../ui/conversation-message-menus.ui.h:6 +#: ui/conversation-message-menus.ui:33 msgid "_Select All" msgstr "Выбрать _все" -#: ../ui/conversation-message-menus.ui.h:8 +#: ui/conversation-message-menus.ui:43 msgid "Search for messages from" msgstr "Поиск сообщений от" -#: ../ui/conversation-message.ui.h:1 +#: ui/conversation-message.ui:64 msgid "From " msgstr "От " -#: ../ui/conversation-message.ui.h:2 +#: ui/conversation-message.ui:80 ui/conversation-message.ui:179 msgid "1/1/1970\t" msgstr "1/1/1970\t" -#: ../ui/conversation-message.ui.h:3 +#: ui/conversation-message.ui:103 msgid "Preview body text." msgstr "Предварительный просмотр основного текста." -#: ../ui/conversation-message.ui.h:4 +#: ui/conversation-message.ui:203 msgid "Sent by:" msgstr "Отправлено:" -#: ../ui/conversation-message.ui.h:5 +#: ui/conversation-message.ui:248 msgid "Reply to:" msgstr "Ответить:" -#: ../ui/conversation-message.ui.h:6 +#: ui/conversation-message.ui:292 msgid "Subject" msgstr "Тема" -#: ../ui/conversation-message.ui.h:7 +#: ui/conversation-message.ui:313 msgid "To:" msgstr "Кому:" -#: ../ui/conversation-message.ui.h:8 +#: ui/conversation-message.ui:358 msgid "Cc:" msgstr "Копия:" -#: ../ui/conversation-message.ui.h:9 +#: ui/conversation-message.ui:403 msgid "Bcc:" msgstr "Скрытая копия:" -#: ../ui/conversation-message.ui.h:10 +#: ui/conversation-message.ui:502 msgid "Show Images" msgstr "Показать изображения" -#: ../ui/conversation-message.ui.h:11 +#: ui/conversation-message.ui:515 msgid "Always Show From Sender" msgstr "Всегда показывать изображения от отправителя" -#: ../ui/conversation-message.ui.h:12 +#: ui/conversation-message.ui:543 msgid "Remote images not shown" msgstr "Удалённые изображения не показаны" -#: ../ui/conversation-message.ui.h:13 +#: ui/conversation-message.ui:560 msgid "Only show remote images from senders you trust." msgstr "Отображать удалённые изображения только от доверенных отправителей." -#: ../ui/conversation-message.ui.h:14 +#: ui/conversation-message.ui:692 msgid "But actually goes to:" msgstr "Но на самом деле ведёт на:" -#: ../ui/conversation-message.ui.h:15 +#: ui/conversation-message.ui:723 msgid "The link appears to go to:" msgstr "Кажется что эта ссылка ведёт на:" -#: ../ui/conversation-message.ui.h:16 +#: ui/conversation-message.ui:735 msgid "Deceptive link found" msgstr "Найдена ссылка с перенаправлением" -#: ../ui/conversation-message.ui.h:17 +#: ui/conversation-message.ui:750 msgid "The email sender may be leading you to the wrong web site." msgstr "Отправитель письма возможно ведёт вас на ложный веб-сайт." -#: ../ui/conversation-message.ui.h:18 +#: ui/conversation-message.ui:763 msgid "If unsure, contact the sender and ask before continuing." msgstr "" "Если вы не уверены, обратитесь к отправителю и спросите, прежде чем " "продолжить." -#: ../ui/conversation-viewer.ui.h:1 +#: ui/conversation-viewer.ui:60 msgid "Find in conversation" msgstr "Найти в беседе" -#: ../ui/conversation-viewer.ui.h:2 +#: ui/conversation-viewer.ui:74 msgid "Find the previous occurrence of the search string." msgstr "Найти предыдущее совпадение строки поиска." -#: ../ui/conversation-viewer.ui.h:3 +#: ui/conversation-viewer.ui:95 msgid "Find the next occurrence of the search string." msgstr "Найти следующее совпадение строки поиска." -#: ../ui/edit_alternate_emails.glade.h:1 +#: ui/edit_alternate_emails.glade:112 msgid "Remove email address" msgstr "Удалить адрес эл. почты" -#: ../ui/edit_alternate_emails.glade.h:2 +#: ui/edit_alternate_emails.glade:136 msgid "" "Some email services require additional addresses be configured on the " "server. Contact your email provider for more information." @@ -2175,477 +2547,532 @@ "сервере. Обратитесь к поставщику услуг электронной почты для получения " "дополнительной информации." -#: ../ui/edit_alternate_emails.glade.h:4 +#: ui/edit_alternate_emails.glade:175 msgid "_Update" msgstr "_Обновить" -#: ../ui/find_bar.glade.h:1 +#: ui/find_bar.glade:66 msgid "Find:" msgstr "Найти:" -#: ../ui/find_bar.glade.h:2 +#: ui/find_bar.glade:89 msgid "_Previous" msgstr "_Предыдущее" -#: ../ui/find_bar.glade.h:3 +#: ui/find_bar.glade:107 msgid "_Next" msgstr "_Следующее" -#: ../ui/find_bar.glade.h:4 +#: ui/find_bar.glade:125 msgid "_Case sensitive" msgstr "_Учитывать регистр" -#: ../ui/find_bar.glade.h:5 +#: ui/find_bar.glade:145 msgid "label" msgstr "метка" -#: ../ui/gtk/help-overlay.ui.h:1 +#: ui/gtk/help-overlay.ui:9 msgid "Conversation Shortcuts" msgstr "Комбинации бесед" -#: ../ui/gtk/help-overlay.ui.h:2 +#: ui/gtk/help-overlay.ui:13 ui/gtk/help-overlay.ui:254 msgctxt "shortcut window" msgid "General" msgstr "Основное" -#: ../ui/gtk/help-overlay.ui.h:3 +#: ui/gtk/help-overlay.ui:17 msgctxt "shortcut window" msgid "Move focus to the next/previous pane" msgstr "Переместить фокус на следующую/предыдущую панель" -#: ../ui/gtk/help-overlay.ui.h:4 +#: ui/gtk/help-overlay.ui:24 msgctxt "shortcut window" msgid "Move focus to conversation list" msgstr "Переместить фокус в список бесед" -#: ../ui/gtk/help-overlay.ui.h:5 +#: ui/gtk/help-overlay.ui:31 msgctxt "shortcut window" msgid "Detach composer window" msgstr "Отсоединить окно редактора" -#: ../ui/gtk/help-overlay.ui.h:6 +#: ui/gtk/help-overlay.ui:38 msgctxt "shortcut window" msgid "Close composer window" msgstr "Закрыть окно редактора" -#: ../ui/gtk/help-overlay.ui.h:7 +#: ui/gtk/help-overlay.ui:45 msgctxt "shortcut window" msgid "Show keyboard shortcuts" msgstr "Показать комбинации клавиш" -#: ../ui/gtk/help-overlay.ui.h:8 +#: ui/gtk/help-overlay.ui:52 msgctxt "shortcut window" msgid "Show help" msgstr "Показать справку" -#: ../ui/gtk/help-overlay.ui.h:9 +#: ui/gtk/help-overlay.ui:59 msgctxt "shortcut window" msgid "Quit the application" msgstr "Завершить приложение" -#: ../ui/gtk/help-overlay.ui.h:10 +#: ui/gtk/help-overlay.ui:68 msgctxt "shortcut window" msgid "Search" msgstr "Поиск" -#: ../ui/gtk/help-overlay.ui.h:11 +#: ui/gtk/help-overlay.ui:72 msgctxt "shortcut window" msgid "Jump to search box" msgstr "Перейти в строку поиска" -#: ../ui/gtk/help-overlay.ui.h:12 +#: ui/gtk/help-overlay.ui:79 msgctxt "shortcut window" msgid "Find in current conversation" msgstr "Найти в текущей беседе" -#: ../ui/gtk/help-overlay.ui.h:13 +#: ui/gtk/help-overlay.ui:86 msgctxt "shortcut window" msgid "Find next/previous in current conversation" msgstr "Найти следующее/предыдущее в текущей беседе" -#: ../ui/gtk/help-overlay.ui.h:14 +#: ui/gtk/help-overlay.ui:95 ui/gtk/help-overlay.ui:274 msgctxt "shortcut window" msgid "Actions" msgstr "Действия" -#: ../ui/gtk/help-overlay.ui.h:15 +#: ui/gtk/help-overlay.ui:99 msgctxt "shortcut window" msgid "Compose a new message" msgstr "Написать новое сообщение" -#: ../ui/gtk/help-overlay.ui.h:16 +#: ui/gtk/help-overlay.ui:106 msgctxt "shortcut window" msgid "Reply to sender " msgstr "Ответить отправителю " -#: ../ui/gtk/help-overlay.ui.h:17 +#: ui/gtk/help-overlay.ui:113 msgctxt "shortcut window" msgid "Reply to all" msgstr "Ответить всем" -#: ../ui/gtk/help-overlay.ui.h:18 +#: ui/gtk/help-overlay.ui:120 msgctxt "shortcut window" msgid "Forward" msgstr "Вперёд" -#: ../ui/gtk/help-overlay.ui.h:19 +#: ui/gtk/help-overlay.ui:127 msgctxt "shortcut window" msgid "Archive" msgstr "Архив" -#: ../ui/gtk/help-overlay.ui.h:20 +#: ui/gtk/help-overlay.ui:134 msgctxt "shortcut window" msgid "Move to trash" msgstr "Переместить в корзину" -#: ../ui/gtk/help-overlay.ui.h:21 +#: ui/gtk/help-overlay.ui:141 msgctxt "shortcut window" msgid "Toggle spam" msgstr "Переключить метку спам/не спам" -#: ../ui/gtk/help-overlay.ui.h:22 +#: ui/gtk/help-overlay.ui:148 msgctxt "shortcut window" msgid "Move the conversation" msgstr "Переместить беседу" -#: ../ui/gtk/help-overlay.ui.h:23 +#: ui/gtk/help-overlay.ui:155 msgctxt "shortcut window" msgid "Label the conversation" msgstr "Добавить метку беседе" -#: ../ui/gtk/help-overlay.ui.h:24 +#: ui/gtk/help-overlay.ui:163 msgctxt "shortcut window" msgid "Mark read" msgstr "Отметить как прочитанное" -#: ../ui/gtk/help-overlay.ui.h:25 +#: ui/gtk/help-overlay.ui:170 msgctxt "shortcut window" msgid "Mark unread" msgstr "Отметить как непрочитанное" -#: ../ui/gtk/help-overlay.ui.h:26 +#: ui/gtk/help-overlay.ui:179 msgctxt "shortcut window" msgid "View" msgstr "Вид" -#: ../ui/gtk/help-overlay.ui.h:27 +#: ui/gtk/help-overlay.ui:183 msgctxt "shortcut window" msgid "Zoom in" msgstr "Приблизить" -#: ../ui/gtk/help-overlay.ui.h:28 +#: ui/gtk/help-overlay.ui:190 msgctxt "shortcut window" msgid "Zoom out" msgstr "Отдалить" -#: ../ui/gtk/help-overlay.ui.h:29 +#: ui/gtk/help-overlay.ui:197 msgctxt "shortcut window" msgid "Reset zoom" msgstr "Сбросить масштаб" -#: ../ui/gtk/help-overlay.ui.h:30 +#: ui/gtk/help-overlay.ui:206 msgctxt "shortcut window" msgid "Additional Shortcuts" msgstr "Дополнительные комбинации" -#: ../ui/gtk/help-overlay.ui.h:31 +#: ui/gtk/help-overlay.ui:210 msgctxt "shortcut window" msgid "Star" msgstr "Добавить в избранное" -#: ../ui/gtk/help-overlay.ui.h:32 +#: ui/gtk/help-overlay.ui:217 msgctxt "shortcut window" msgid "Unstar" msgstr "Убрать из избранных" -#: ../ui/gtk/help-overlay.ui.h:33 +#: ui/gtk/help-overlay.ui:224 msgctxt "shortcut window" msgid "Delete" msgstr "Удалить" -#: ../ui/gtk/help-overlay.ui.h:34 +#: ui/gtk/help-overlay.ui:231 msgctxt "shortcut window" msgid "Jump to next (older) conversation" msgstr "Перейти к следующей (ранней) беседе" -#: ../ui/gtk/help-overlay.ui.h:35 +#: ui/gtk/help-overlay.ui:238 msgctxt "shortcut window" msgid "Jump to previous (newer) conversation" msgstr "Перейти к предыдущей (поздней) беседе" -#: ../ui/gtk/help-overlay.ui.h:36 +#: ui/gtk/help-overlay.ui:250 msgid "Composer Shortcuts" msgstr "Комбинации редактора" -#: ../ui/gtk/help-overlay.ui.h:37 +#: ui/gtk/help-overlay.ui:258 msgctxt "shortcut window" msgid "Quote text" msgstr "Цитировать текст" -#: ../ui/gtk/help-overlay.ui.h:38 +#: ui/gtk/help-overlay.ui:265 msgctxt "shortcut window" msgid "Unquote text" msgstr "Убрать цитирование текста" -#: ../ui/gtk/help-overlay.ui.h:39 +#: ui/gtk/help-overlay.ui:278 msgctxt "shortcut window" msgid "Send" msgstr "Отправить" -#: ../ui/gtk/help-overlay.ui.h:40 +#: ui/gtk/help-overlay.ui:285 msgctxt "shortcut window" msgid "Add attachment" msgstr "Добавить вложение" -#: ../ui/gtk/help-overlay.ui.h:41 +#: ui/gtk/help-overlay.ui:294 msgctxt "shortcut window" msgid "Rich text mode" msgstr "Режим форматированного текста" -#: ../ui/gtk/help-overlay.ui.h:42 +#: ui/gtk/help-overlay.ui:298 msgctxt "shortcut window" msgid "Bold text" msgstr "Жирный текст" -#: ../ui/gtk/help-overlay.ui.h:43 +#: ui/gtk/help-overlay.ui:305 msgctxt "shortcut window" msgid "Italicize text" msgstr "Курсивный текст" -#: ../ui/gtk/help-overlay.ui.h:44 +#: ui/gtk/help-overlay.ui:312 msgctxt "shortcut window" msgid "Underline text" msgstr "Подчёркнутый текст" -#: ../ui/gtk/help-overlay.ui.h:45 +#: ui/gtk/help-overlay.ui:319 msgctxt "shortcut window" msgid "Strike text" msgstr "Зачёркнутый текст" -#: ../ui/gtk/help-overlay.ui.h:46 +#: ui/gtk/help-overlay.ui:326 msgctxt "shortcut window" msgid "Insert a link" msgstr "Вставить ссылку" -#: ../ui/gtk/help-overlay.ui.h:47 +#: ui/gtk/help-overlay.ui:333 msgctxt "shortcut window" msgid "Remove formatting" msgstr "Убрать форматирование" -#: ../ui/gtk/menus.ui.h:2 +#: ui/gtk/menus.ui:13 msgid "A_ccounts" msgstr "_Учётные записи" -#: ../ui/gtk/menus.ui.h:4 +#: ui/gtk/menus.ui:23 msgid "_Keyboard Shortcuts" msgstr "_Комбинации клавиш" -#: ../ui/login.glade.h:1 +#: ui/login.glade:88 msgid "email@example.com" msgstr "email@example.com" -#: ../ui/login.glade.h:2 ../ui/password-dialog.glade.h:3 +#: ui/login.glade:107 ui/password-dialog.glade:108 msgid "Password" msgstr "Пароль" -#: ../ui/login.glade.h:3 +#: ui/login.glade:123 msgid "E_mail address" msgstr "Адрес _эл. почты" -#: ../ui/login.glade.h:4 +#: ui/login.glade:144 ui/login.glade:635 msgid "_Password" msgstr "_Пароль" -#: ../ui/login.glade.h:5 +#: ui/login.glade:178 msgid "S_ervice" msgstr "_Сервис" -#: ../ui/login.glade.h:6 +#: ui/login.glade:199 msgid "N_ame" msgstr "_Имя" -#: ../ui/login.glade.h:8 +#: ui/login.glade:256 msgid "N_ickname" msgstr "_Псевдоним" -#: ../ui/login.glade.h:9 +#: ui/login.glade:280 msgid "Work, Home, etc." msgstr "Рабочий, Домашний, и т.д." -#: ../ui/login.glade.h:10 +#: ui/login.glade:291 msgid "_Save sent mail" msgstr "_Сохранять отправленные" -#: ../ui/login.glade.h:11 +#: ui/login.glade:309 msgid "Addi_tional email addresses…" msgstr "_Дополнительные адреса эл. почты…" -#: ../ui/login.glade.h:12 +#: ui/login.glade:353 msgid "IMAP settings" msgstr "Параметры IMAP" -#: ../ui/login.glade.h:13 +#: ui/login.glade:372 msgid "Se_rver" msgstr "Се_рвер" -#: ../ui/login.glade.h:14 +#: ui/login.glade:393 msgid "imap.example.com" msgstr "imap.example.com" -#: ../ui/login.glade.h:15 +#: ui/login.glade:409 msgid "P_ort" msgstr "П_орт" -#: ../ui/login.glade.h:16 +#: ui/login.glade:448 msgid "smtp.example.com" msgstr "smtp.example.com" -#: ../ui/login.glade.h:17 +#: ui/login.glade:480 msgid "Ser_ver" msgstr "Сер_вер" -#: ../ui/login.glade.h:18 +#: ui/login.glade:501 msgid "Por_t" msgstr "По_рт" -#: ../ui/login.glade.h:19 +#: ui/login.glade:522 msgid "SMTP settings" msgstr "Параметры SMTP" -#: ../ui/login.glade.h:20 +#: ui/login.glade:541 msgid "User_name" msgstr "Имя _пользователя" -#: ../ui/login.glade.h:21 +#: ui/login.glade:562 msgid "Pass_word" msgstr "Пар_оль" -#: ../ui/login.glade.h:22 +#: ui/login.glade:582 msgid "SMTP username" msgstr "Имя пользователя SMTP" -#: ../ui/login.glade.h:23 +#: ui/login.glade:598 msgid "SMTP password" msgstr "Пароль SMTP" -#: ../ui/login.glade.h:24 +#: ui/login.glade:614 msgid "_Username" msgstr "_Имя пользователя" -#: ../ui/login.glade.h:25 +#: ui/login.glade:655 msgid "IMAP username" msgstr "Имя пользователя IMAP" -#: ../ui/login.glade.h:26 +#: ui/login.glade:671 msgid "IMAP password" msgstr "Пароль IMAP" -#: ../ui/login.glade.h:27 +#: ui/login.glade:688 msgid "Encr_yption" msgstr "Ши_фрование" -#: ../ui/login.glade.h:28 +#: ui/login.glade:711 msgid "Encrypt_ion" msgstr "Шиф_рование" -#: ../ui/login.glade.h:30 +#: ui/login.glade:733 ui/login.glade:751 msgid "SSL/TLS" msgstr "SSL/TLS" -#: ../ui/login.glade.h:31 +#: ui/login.glade:734 ui/login.glade:752 msgid "STARTTLS" msgstr "STARTTLS" -#: ../ui/login.glade.h:32 +#: ui/login.glade:764 msgid "No authentication re_quired" msgstr "Аутентификация не требуется" -#: ../ui/login.glade.h:33 +#: ui/login.glade:781 msgid "Use IMAP cre_dentials" msgstr "Использовать _учётные данные IMAP" -#: ../ui/login.glade.h:34 +#: ui/login.glade:888 msgid "Composer" msgstr "Редактирование" -#: ../ui/login.glade.h:35 +#: ui/login.glade:901 msgid "Save dra_fts on server" msgstr "Сохранять черновики на сервере" -#: ../ui/login.glade.h:36 +#: ui/login.glade:918 msgid "Si_gn emails (HTML allowed):" msgstr "Подписывать письма (доступно HTML):" -#: ../ui/login.glade.h:37 +#: ui/login.glade:976 msgid "Storage" msgstr "Хранилище" -#: ../ui/login.glade.h:38 +#: ui/login.glade:998 msgid "_Download mail" msgstr "_Загружать почту" -#: ../ui/main-toolbar.ui.h:1 +#: ui/main-toolbar.ui:51 +msgid "Toggle search bar" +msgstr "Включить панель поиска" + +#: ui/main-toolbar.ui:72 msgid "Empty Spam or Trash folders" msgstr "Очистить Корзину или Спам" -#: ../ui/password-dialog.glade.h:1 +#: ui/main-toolbar.ui:112 +msgid "Reply" +msgstr "Ответить" + +#: ui/main-toolbar.ui:135 +msgid "Reply All" +msgstr "Ответить всем" + +#: ui/main-toolbar.ui:158 +msgid "Forward" +msgstr "Вперёд" + +#: ui/main-toolbar.ui:264 +msgid "Toggle find bar" +msgstr "Переключить панель поиска" + +#: ui/main-toolbar.ui:306 +msgid "_Archive" +msgstr "_Архивировать" + +#: ui/main-toolbar-menus.ui:5 +msgid "Empty _Spam…" +msgstr "Очистить _Спам…" + +#: ui/main-toolbar-menus.ui:9 +msgid "Empty _Trash…" +msgstr "Очистить _Корзину…" + +#: ui/main-toolbar-menus.ui:32 +msgid "Mark as S_pam" +msgstr "Отметить как С_пам" + +#: ui/main-toolbar-menus.ui:36 +msgid "Mark as not S_pam" +msgstr "Отметить как _не Спам" + +#: ui/main-window-info-bar.ui:97 +msgid "" +"If the problem is serious or persists, please copy and send these details to " +"the mailing list " +"or file a new " +"bug report." +msgstr "" +"Если проблема серьёзная или сохраняется, скопируйте и отправьте эти данные в " +"список рассылки " +"или напишите новый отчёт об ошибке." + +#: ui/main-window-info-bar.ui:113 +msgid "Details:" +msgstr "Подробности:" + +#: ui/password-dialog.glade:74 msgid "SMTP Credentials" msgstr "Учётные данные SMTP" -#: ../ui/password-dialog.glade.h:2 +#: ui/password-dialog.glade:91 msgid "Username" msgstr "Имя пользователя" -#: ../ui/password-dialog.glade.h:4 +#: ui/password-dialog.glade:152 msgid "_Remember password" msgstr "_Запомнить пароль" -#: ../ui/password-dialog.glade.h:6 +#: ui/password-dialog.glade:210 msgid "_Authenticate" msgstr "_Авторизоваться" -#: ../ui/preferences-dialog.ui.h:1 +#: ui/preferences-dialog.ui:38 msgid "Reading" msgstr "Просмотр" -#: ../ui/preferences-dialog.ui.h:2 +#: ui/preferences-dialog.ui:51 msgid "_Automatically select next message" msgstr "_Автоматически переходить к следующему сообщению" -#: ../ui/preferences-dialog.ui.h:3 +#: ui/preferences-dialog.ui:70 msgid "_Display conversation preview" msgstr "_Показывать предварительный просмотр сообщений" -#: ../ui/preferences-dialog.ui.h:4 +#: ui/preferences-dialog.ui:89 msgid "Use _three pane view" msgstr "Использовать _трёхпанельный интерфейс" -#: ../ui/preferences-dialog.ui.h:5 +#: ui/preferences-dialog.ui:113 msgid "Notifications" msgstr "Уведомления" -#: ../ui/preferences-dialog.ui.h:6 +#: ui/preferences-dialog.ui:126 msgid "_Play notification sounds" msgstr "_Проигрывать звуковые уведомления" -#: ../ui/preferences-dialog.ui.h:7 +#: ui/preferences-dialog.ui:145 msgid "Show _notifications for new mail" msgstr "_Показывать уведомления о новых сообщениях" -#: ../ui/preferences-dialog.ui.h:8 -msgid "Always _watch for new mail" -msgstr "Всегда следить за поступлением новых писем" - -#: ../ui/preferences-dialog.ui.h:9 -msgid "Geary will run in the background and notify of new mail" -msgstr "" -"Geary будет работать в фоновом режиме и сообщать о поступлении новых писем" +#: ui/preferences-dialog.ui:164 +msgid "_Watch for new mail when closed" +msgstr "_Следить за поступлением новых писем при закрытом приложении" + +#: ui/preferences-dialog.ui:168 +msgid "Geary will keep running after all windows are closed" +msgstr "Geary будет продолжать работать после закрытия всех окон" -#: ../ui/preferences-dialog.ui.h:10 +#: ui/preferences-dialog.ui:195 msgid "Preferences" msgstr "Параметры" -#: ../ui/remove_confirm.glade.h:1 +#: ui/remove_confirm.glade:43 msgid "" "Are you sure you want to remove this " "account? " @@ -2653,7 +3080,7 @@ "Действительно удалить эту учётную " "запись? " -#: ../ui/remove_confirm.glade.h:2 +#: ui/remove_confirm.glade:58 msgid "" "All email associated with this account will be removed from your computer. " "This will not affect email on the server." @@ -2661,17 +3088,60 @@ "Все письма связанные с данной учётной записью будут удалены с компьютера. " "Это не повлияет на электронную почту на сервере." -#: ../ui/remove_confirm.glade.h:3 +#: ui/remove_confirm.glade:80 msgid "Nickname:" msgstr "Псевдоним:" -#: ../ui/remove_confirm.glade.h:4 +#: ui/remove_confirm.glade:94 msgid "Email address:" msgstr "Адрес эл. почты:" -#: ../ui/upgrade_dialog.glade.h:1 +#: ui/upgrade_dialog.glade:60 msgid "Geary update in progress…" -msgstr "Идёт обновление Geary…" +msgstr "Geary выполняет обновление…" + +#~ msgid "Send files using Geary" +#~ msgstr "Отправить файлы с помощью Geary" + +#~ msgid "Geary Email" +#~ msgstr "Электронная почта Geary" + +#~ msgid "Geary Mail" +#~ msgstr "Почтовый клиент Geary" + +#~ msgid "_Mark as…" +#~ msgstr "_Отметить как…" + +#~ msgid "Add label" +#~ msgstr "Добавить метку" + +#~ msgid "_Label" +#~ msgstr "_Метка" + +#~ msgid "_Move" +#~ msgstr "_Переместить" + +#~ msgid "Compose new message (Ctrl+N, N)" +#~ msgstr "Создать новую беседу (Ctrl+N, N)" + +#~ msgid "Reply (Ctrl+R, R)" +#~ msgstr "Ответить (Ctrl+R, R)" + +#~ msgid "Reply all (Ctrl+Shift+R, Shift+R)" +#~ msgstr "Ответить всем (Ctrl+Shift+R, Shift+R)" + +#~ msgid "Forward (Ctrl+L, F)" +#~ msgstr "Переслать (Ctrl+L, F)" + +#~ msgid "" +#~ "Geary encountered an error while connecting to the server. Please try " +#~ "again in a few moments." +#~ msgstr "" +#~ "Geary получил ошибку при соединении с сервером. Повторите попытку позже." + +#~ msgid "Geary will run in the background and notify of new mail" +#~ msgstr "" +#~ "Geary будет работать в фоновом режиме и сообщать о поступлении новых писем" #~ msgid "Mail Client" #~ msgstr "Почтовый клиент" @@ -2787,9 +3257,6 @@ #~ msgid "Fixed Width" #~ msgstr "Моноширинный" -#~ msgid "Detach" -#~ msgstr "Открепить" - #~ msgid "_Attach File" #~ msgstr "_Вложить файл" diff -Nru geary-0.12.4/po/sk.po geary-3.32.0/po/sk.po --- geary-0.12.4/po/sk.po 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/po/sk.po 2019-03-17 13:39:29.000000000 +0000 @@ -8,10 +8,9 @@ msgid "" msgstr "" "Project-Id-Version: geary-0.4.1\n" -"Report-Msgid-Bugs-To: https://bugzilla.gnome.org/enter_bug.cgi?" -"product=geary&keywords=I18N+L10N&component=internationalization\n" -"POT-Creation-Date: 2017-10-02 14:41+0000\n" -"PO-Revision-Date: 2017-10-05 07:42+0200\n" +"Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/geary/issues\n" +"POT-Creation-Date: 2018-10-24 11:36+0000\n" +"PO-Revision-Date: 2018-11-01 18:32+0100\n" "Last-Translator: Dušan Kazik \n" "Language-Team: Slovak (http://www.transifex.com/projects/p/geary/language/" "sk/)\n" @@ -20,29 +19,56 @@ "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" -"X-Generator: Poedit 2.0.3\n" +"X-Generator: Poedit 2.2\n" + +#: desktop/geary-attach.contract.desktop.in:3 +msgid "Send by email" +msgstr "Odoslať emailom" + +#. Translators: Do NOT translate or transliterate this text (this is an icon file name)! +#: desktop/geary-attach.contract.desktop.in:5 +msgid "mail-send" +msgstr "mail-send" + +#: desktop/geary-attach.contract.desktop.in:6 +msgid "Send files using Geary" +msgstr "Odošle súbory pomocou aplikácie Geary" #. Translators: The application name -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:2 -#: ../desktop/org.gnome.Geary.desktop.in.h:1 -#: ../desktop/geary-autostart.desktop.in.h:1 +#: desktop/geary-autostart.desktop.in:3 +#: desktop/org.gnome.Geary.appdata.xml.in:11 +#: desktop/org.gnome.Geary.desktop.in:3 msgid "Geary" msgstr "Geary" -#. Translators: The development team's name -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:4 -msgid "Geary Development Team" -msgstr "Tím vývojárov aplikácie Geary" +#: desktop/geary-autostart.desktop.in:4 desktop/org.gnome.Geary.desktop.in:4 +msgid "Email" +msgstr "Email" #. Translators: The application's summary / tagline -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:6 -#: ../desktop/org.gnome.Geary.desktop.in.h:4 -#: ../desktop/geary-autostart.desktop.in.h:4 -#: ../src/client/application/geary-application.vala:21 +#: desktop/geary-autostart.desktop.in:5 +#: desktop/org.gnome.Geary.appdata.xml.in:15 +#: desktop/org.gnome.Geary.desktop.in:5 +#: src/client/application/geary-application.vala:21 msgid "Send and receive email" msgstr "Odosiela a prijíma emaily" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:7 +#. Translators: These are desktop search terms. Do not translate semicolons, end line with a semicolon. +#: desktop/geary-autostart.desktop.in:7 +msgid "Email;E-mail;Mail;" +msgstr "email;e-mail;pošta;" + +#. Translators: Do NOT translate or transliterate this text (this is an icon file name)! +#: desktop/geary-autostart.desktop.in:9 desktop/org.gnome.Geary.desktop.in:9 +msgid "org.gnome.Geary" +msgstr "" + +#. Translators: The development team's name +#: desktop/org.gnome.Geary.appdata.xml.in:13 +msgid "Geary Development Team" +msgstr "Tím vývojárov aplikácie Geary" + +#: desktop/org.gnome.Geary.appdata.xml.in:17 msgid "" "Geary is an email application built around conversations, for the GNOME 3 " "desktop. It allows you to read, find and send email with a straightforward, " @@ -52,7 +78,7 @@ "Umožňuje vám prečítať, hľadať a odosielať emaily s jasným a moderným " "rozhraním." -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:8 +#: desktop/org.gnome.Geary.appdata.xml.in:22 msgid "" "Conversations allow you to read a complete discussion without having to find " "and click from message to message." @@ -60,99 +86,259 @@ "Rozhovory vám umožňujú prečítať kompletnú diskusiu bez nutnosti vyhľadania a " "klikania zo správy na správu." -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:9 +#: desktop/org.gnome.Geary.appdata.xml.in:26 msgid "Geary’s features include:" msgstr "Funkcie aplikácie Geary zahŕňajú:" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:10 +#: desktop/org.gnome.Geary.appdata.xml.in:28 msgid "Quick email account setup" msgstr "Rýchle nastavenie emailového účtu" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:11 +#: desktop/org.gnome.Geary.appdata.xml.in:29 msgid "Shows related messages together in conversations" msgstr "Zobrazenie spolu súvisiacich správ v rozhovoroch" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:12 +#: desktop/org.gnome.Geary.appdata.xml.in:30 msgid "Fast, full text and keyword search" msgstr "Rýchle, fulltextové vyhľadávanie a hľadanie podľa kľúčového slova" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:13 +#: desktop/org.gnome.Geary.appdata.xml.in:31 msgid "Full-featured HTML and plain text message composer" msgstr "Plnohodnotný nástroj na tvorbu správ vo formáte HTML a v čistom texte" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:14 +#: desktop/org.gnome.Geary.appdata.xml.in:32 msgid "Desktop notification of new mail" msgstr "Upozornenie pracovného prostredia na novú poštu" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:15 +#: desktop/org.gnome.Geary.appdata.xml.in:33 msgid "Compatible with GMail, Yahoo! Mail, Outlook.com and other IMAP servers" msgstr "" "Kompatibilita so servermi GMail, Yahoo! Mail, Outlook.com a inými servermi " "typu IMAP" #. Translators: A screenshot description. -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:17 +#: desktop/org.gnome.Geary.appdata.xml.in:47 msgid "Geary displaying a conversation" msgstr "Aplikácia Geary zobrazujúca rozhovor" #. Translators: A screenshot description. -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:19 +#: desktop/org.gnome.Geary.appdata.xml.in:52 msgid "Geary showing the rich text composer" msgstr "Aplikácia Geary zobrazujúca tvorcu správ s formátovaným textom" -#: ../desktop/org.gnome.Geary.desktop.in.h:2 -#: ../desktop/geary-autostart.desktop.in.h:2 -msgid "Email" -msgstr "Email" - -#: ../desktop/org.gnome.Geary.desktop.in.h:3 -msgid "Geary Email" -msgstr "Geary Email" - #. Translators: These are desktop search terms. Do not translate semicolons, end line with a semicolon. -#: ../desktop/org.gnome.Geary.desktop.in.h:6 +#: desktop/org.gnome.Geary.desktop.in:7 msgid "Mail;E-mail;IMAP;GMail;Yahoo;Hotmail;Outlook;" msgstr "Mail;pošta;E-mail;IMAP;GMail;Yahoo;Hotmail;Outlook;" -#: ../desktop/org.gnome.Geary.desktop.in.h:7 ../ui/gtk/menus.ui.h:1 +#: desktop/org.gnome.Geary.desktop.in:21 ui/gtk/menus.ui:7 msgid "Compose Message" msgstr "Napísať správu" -#: ../desktop/geary-autostart.desktop.in.h:3 -msgid "Geary Mail" -msgstr "Geary Mail" +#: desktop/org.gnome.Geary.gschema.xml:8 +msgid "Maximize window" +msgstr "" -#: ../desktop/geary-autostart.desktop.in.h:5 -msgid "Email;E-mail;Mail;" -msgstr "email;e-mail;pošta;" +#: desktop/org.gnome.Geary.gschema.xml:9 +msgid "True if the application window is maximized, false otherwise." +msgstr "" -#: ../desktop/geary-attach.contract.in.h:1 -msgid "Send by email" -msgstr "Odoslať emailom" +#: desktop/org.gnome.Geary.gschema.xml:14 +msgid "Width of window" +msgstr "Šírka okna" -#: ../desktop/geary-attach.contract.in.h:2 -msgid "Send files using Geary" -msgstr "Odošle súbory pomocou aplikácie Geary" +#: desktop/org.gnome.Geary.gschema.xml:15 +msgid "The last recorded width of the application window." +msgstr "Naposledy zaznamenaná šírka okna aplikácie." + +#: desktop/org.gnome.Geary.gschema.xml:20 +msgid "Height of window" +msgstr "Výška okna" + +#: desktop/org.gnome.Geary.gschema.xml:21 +msgid "The last recorded height of the application window." +msgstr "Naposledy zaznamenaná výška okna aplikácie." + +#: desktop/org.gnome.Geary.gschema.xml:26 +msgid "Position of folder list pane" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:27 +msgid "Position of the folder list Paned grabber." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:32 +msgid "Position of folder list pane when horizontal" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:33 +msgid "" +"Position of the folder list Paned grabber in the horizontal orientation." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:38 +msgid "Position of folder list pane when vertical" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:39 +msgid "Position of the folder list Paned grabber in the vertical orientation." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:44 +msgid "Orientation of the folder list pane" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:45 +msgid "True if the folder list Paned is in the horizontal orientation." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:50 +msgid "Position of message list pane" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:51 +msgid "Position of the message list Paned grabber." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:56 +msgid "Autoselect next message" +msgstr "Automaticky vybrať ďalšiu správu" + +#: desktop/org.gnome.Geary.gschema.xml:57 +msgid "True if we should autoselect the next available conversation." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:62 +msgid "Display message previews" +msgstr "Zobraziť náhľady správ" + +#: desktop/org.gnome.Geary.gschema.xml:63 +msgid "True if we should display a short preview of each message." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:68 +msgid "Languages that shall be used in the spell checker" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:69 +msgid "List of the languages to use in the spell checker." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:74 +msgid "Languages that are displayed in the spell checker popover" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:75 +msgid "" +"List of languages that are always displayed in the popover of the spell " +"checker." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:80 +msgid "Enable notification sounds" +msgstr "Povoliť zvuky oznámení" + +#: desktop/org.gnome.Geary.gschema.xml:81 +msgid "True to play sounds for notifications and sending." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:86 +msgid "Show notifications for new mail" +msgstr "Zobraziť upozornenia pre novú poštu" + +#: desktop/org.gnome.Geary.gschema.xml:87 +msgid "True to show notification bubbles." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:92 +msgid "Notify of new mail at startup" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:93 +#, fuzzy +#| msgid "Desktop notification of new mail" +msgid "True to notify of new mail at startup." +msgstr "Upozornenie pracovného prostredia na novú poštu" + +#: desktop/org.gnome.Geary.gschema.xml:98 +msgid "Ask when opening an attachment" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:99 +#, fuzzy +#| msgid "To add them as attachments" +msgid "True to ask when opening an attachment." +msgstr "Na ich pridanie do prílohy" + +#: desktop/org.gnome.Geary.gschema.xml:104 +msgid "Whether to compose emails in HTML" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:105 +msgid "True to compose emails in HTML; false for plain text." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:110 +msgid "Advisory strategy for full-text searching" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:111 +msgid "" +"Acceptable values are “exact”, “conservative”, “aggressive”, and “horizon”." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:116 +msgid "Zoom of conversation viewer" +msgstr "Priblíženie prehliadača konverzácií" + +#: desktop/org.gnome.Geary.gschema.xml:117 +msgid "The zoom to apply on the conservation view." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:122 +msgid "Size of detached composer window" +msgstr "Veľkosť odpojeného okna tvorcu správ" + +#: desktop/org.gnome.Geary.gschema.xml:123 +msgid "The last recorded size of the detached composer window." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:128 +msgid "Base URL to look up contact avatars" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:129 +msgid "" +"A Gravatar or Libravatar compatible URL, set to the empty string to disable." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:134 +msgid "Whether we migrated the old settings" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:135 +msgid "" +"False to check for the old “org.yorba.geary”-schema and copy its values." +msgstr "" -#: ../src/client/accounts/account-dialog-add-edit-pane.vala:51 -#: ../src/client/components/stock.vala:31 -#: ../ui/conversation-email-menus.ui.h:10 +#: src/client/accounts/account-dialog-add-edit-pane.vala:54 +#: src/client/components/stock.vala:31 ui/conversation-email-menus.ui:83 msgid "_Save" msgstr "_Uložiť" -#: ../src/client/accounts/account-dialog-add-edit-pane.vala:51 -#: ../src/client/components/stock.vala:22 +#: src/client/accounts/account-dialog-add-edit-pane.vala:54 +#: src/client/components/stock.vala:22 msgid "_Add" msgstr "_Pridať" #. reset/clear widgets -#: ../src/client/accounts/account-dialog-edit-alternate-emails-pane.vala:120 +#: src/client/accounts/account-dialog-edit-alternate-emails-pane.vala:120 #, c-format msgid "Additional addresses for %s" msgstr "Dodatočné adresy pre %s" #. Sets min size. -#: ../src/client/accounts/account-dialog.vala:21 +#: src/client/accounts/account-dialog.vala:28 msgid "Accounts" msgstr "Účty" @@ -163,118 +349,117 @@ #. #. Page for adding or editing an account. #. / Placeholder text indicating that the user should type their first name and last name -#: ../src/client/accounts/add-edit-page.vala:10 +#: src/client/accounts/add-edit-page.vala:10 msgid "First Last" msgstr "Meno a priezvisko" -#: ../src/client/accounts/add-edit-page.vala:235 +#: src/client/accounts/add-edit-page.vala:240 msgid "Welcome to Geary." msgstr "Vitajte v aplikácii Geary." -#: ../src/client/accounts/add-edit-page.vala:235 +#: src/client/accounts/add-edit-page.vala:240 msgid "Enter your account information to get started." msgstr "Zadajte informácie o vašom účte a môžete začať." -#: ../src/client/accounts/add-edit-page.vala:255 +#: src/client/accounts/add-edit-page.vala:260 msgid "2 weeks back" msgstr "starú 2 týždne" #. IDs are # of days -#: ../src/client/accounts/add-edit-page.vala:256 +#: src/client/accounts/add-edit-page.vala:261 msgid "1 month back" msgstr "starú 1 mesiac" -#: ../src/client/accounts/add-edit-page.vala:257 +#: src/client/accounts/add-edit-page.vala:262 msgid "3 months back" msgstr "starú 3 mesiace" -#: ../src/client/accounts/add-edit-page.vala:258 +#: src/client/accounts/add-edit-page.vala:263 msgid "6 months back" msgstr "starú 6 mesiacov" -#: ../src/client/accounts/add-edit-page.vala:259 +#: src/client/accounts/add-edit-page.vala:264 msgid "1 year back" msgstr "starú 1 rok" -#: ../src/client/accounts/add-edit-page.vala:260 +#: src/client/accounts/add-edit-page.vala:265 msgid "2 years back" msgstr "starú 2 roky" -#: ../src/client/accounts/add-edit-page.vala:261 +#: src/client/accounts/add-edit-page.vala:266 msgid "4 years back" msgstr "starú 4 roky" #. Separator -#: ../src/client/accounts/add-edit-page.vala:263 +#: src/client/accounts/add-edit-page.vala:268 msgid "Everything" msgstr "Všetku" -#: ../src/client/accounts/add-edit-page.vala:283 +#: src/client/accounts/add-edit-page.vala:288 msgid "Edit" msgstr "Upraví" -#: ../src/client/accounts/add-edit-page.vala:285 +#: src/client/accounts/add-edit-page.vala:290 msgid "Preview" msgstr "Zobrazí náhľad" -#: ../src/client/accounts/add-edit-page.vala:751 +#: src/client/accounts/add-edit-page.vala:778 msgid "Remem_ber passwords" msgstr "Zapa_mätať heslá" -#: ../src/client/accounts/add-edit-page.vala:758 ../ui/login.glade.h:7 +#: src/client/accounts/add-edit-page.vala:785 ui/login.glade:233 msgid "Remem_ber password" msgstr "Zapa_mätať heslo" -#: ../src/client/accounts/add-edit-page.vala:792 +#: src/client/accounts/add-edit-page.vala:819 msgid "Unable to validate:\n" msgstr "Nie je možné overiť:\n" -#: ../src/client/accounts/add-edit-page.vala:794 +#: src/client/accounts/add-edit-page.vala:821 msgid " • Invalid account nickname.\n" msgstr " • Neplatná prezývka účtu.\n" -#: ../src/client/accounts/add-edit-page.vala:797 +#: src/client/accounts/add-edit-page.vala:824 msgid " • Email address already added to Geary.\n" msgstr " • Emailová adresa už je pridaná v aplikácii Geary.\n" -#: ../src/client/accounts/add-edit-page.vala:801 +#: src/client/accounts/add-edit-page.vala:828 msgid " • IMAP connection error.\n" msgstr " • Chyba pripojenia k serveru IMAP.\n" -#: ../src/client/accounts/add-edit-page.vala:804 +#: src/client/accounts/add-edit-page.vala:831 msgid " • IMAP username or password incorrect.\n" msgstr " • Používateľské meno alebo heslo servera IMAP je nesprávne.\n" -#: ../src/client/accounts/add-edit-page.vala:807 +#: src/client/accounts/add-edit-page.vala:834 msgid " • SMTP connection error.\n" msgstr " • Chyba pripojenia k serveru SMTP.\n" -#: ../src/client/accounts/add-edit-page.vala:810 +#: src/client/accounts/add-edit-page.vala:837 msgid " • SMTP username or password incorrect.\n" msgstr " • Používateľské meno alebo heslo servera SMTP je nesprávne.\n" -#: ../src/client/accounts/add-edit-page.vala:814 +#: src/client/accounts/add-edit-page.vala:841 msgid " • Connection error.\n" msgstr " • Chyba pripojenia.\n" -#: ../src/client/accounts/add-edit-page.vala:818 +#: src/client/accounts/add-edit-page.vala:845 msgid " • Username or password incorrect.\n" msgstr " • Používateľské meno alebo heslo je nesprávne.\n" -#: ../src/client/application/geary-application.vala:22 +#: src/client/application/geary-application.vala:22 msgid "Copyright 2016 Software Freedom Conservancy Inc." msgstr "Autorské práva 2016 Software Freedom Conservancy Inc." -#: ../src/client/application/geary-application.vala:23 -#| msgid "Geary Development Team" -msgid "Copyright 2016-2017 Geary Development Team." -msgstr "Autorské práva 2016-2017 Tím vývojárov aplikácie Geary." +#: src/client/application/geary-application.vala:23 +msgid "Copyright 2016-2018 Geary Development Team." +msgstr "Autorské práva 2016-2018 Tím vývojárov aplikácie Geary." -#: ../src/client/application/geary-application.vala:25 +#: src/client/application/geary-application.vala:25 msgid "Visit the Geary web site" msgstr "Navštívte webovú stránku aplikácie Geary" -#: ../src/client/application/geary-application.vala:466 +#: src/client/application/geary-application.vala:416 #, c-format msgid "About %s" msgstr "O %s" @@ -282,252 +467,110 @@ #. Translators: add your name and email address to receive #. credit in the About dialog For example: Yamada Taro #. -#: ../src/client/application/geary-application.vala:470 +#: src/client/application/geary-application.vala:420 msgid "translator-credits" msgstr "Dušan Kazik " # cmd line desc -#: ../src/client/application/geary-args.vala:10 +#: src/client/application/geary-args.vala:10 msgid "Start Geary with hidden main window" msgstr "Spustí aplikáciu Geary so skrytým hlavným oknom" -#: ../src/client/application/geary-args.vala:11 +#: src/client/application/geary-args.vala:11 msgid "Output debugging information" msgstr "Výstupné ladiace informácie" -#: ../src/client/application/geary-args.vala:12 +#: src/client/application/geary-args.vala:12 msgid "Log conversation monitoring" msgstr "Zaznamená monitorovanie rozhovorov" # cmd line desc -#: ../src/client/application/geary-args.vala:13 +#: src/client/application/geary-args.vala:13 msgid "Log network deserialization" msgstr "Zaznamená deserializáciu siete" -#: ../src/client/application/geary-args.vala:14 +#: src/client/application/geary-args.vala:14 msgid "Log network activity" msgstr "Zaznamená aktivitu siete" #. / The IMAP replay queue is how changes on the server are replicated on the client. #. / It could also be called the IMAP events queue. -#: ../src/client/application/geary-args.vala:17 +#: src/client/application/geary-args.vala:17 msgid "Log IMAP replay queue" msgstr "Zaznamená frontu udalostí servera IMAP" #. / Serialization is how commands and responses are converted into a stream of bytes for #. / network transmission -#: ../src/client/application/geary-args.vala:20 +#: src/client/application/geary-args.vala:20 msgid "Log network serialization" msgstr "Zaznamená serializáciu siete" # cmd line desc -#: ../src/client/application/geary-args.vala:21 +#: src/client/application/geary-args.vala:21 msgid "Log periodic activity" msgstr "Periodicky zaznamená aktivitu" -#: ../src/client/application/geary-args.vala:22 +#: src/client/application/geary-args.vala:22 msgid "Log database queries (generates lots of messages)" msgstr "Zaznamená požiadavky databázy (vygeneruje množstvo správ)" #. / "Normalization" can also be called "synchronization" -#: ../src/client/application/geary-args.vala:24 +#: src/client/application/geary-args.vala:24 msgid "Log folder normalization" msgstr "Zaznamená normalizáciu priečinkov" -#: ../src/client/application/geary-args.vala:25 +#: src/client/application/geary-args.vala:25 msgid "Allow inspection of WebView" msgstr "Umožní inšpekciu WebView" -#: ../src/client/application/geary-args.vala:26 +#: src/client/application/geary-args.vala:26 msgid "Revoke all server certificates with TLS warnings" msgstr "Odvolá všetky certifikáty serverov s upozorneniami TLS" -#: ../src/client/application/geary-args.vala:27 +#: src/client/application/geary-args.vala:27 msgid "Perform a graceful quit" msgstr "Vykoná ukončenie podľa správnosti" -#: ../src/client/application/geary-args.vala:28 +#: src/client/application/geary-args.vala:28 msgid "Display program version" msgstr "Zobrazí verziu aplikácie" #. This gives a command-line hint on how to open new composer windows with mailto: -#: ../src/client/application/geary-args.vala:53 +#: src/client/application/geary-args.vala:53 #, c-format msgid "Use %s to open a new composer window" -msgstr "Použite %s na otvorenie nového okna tvorcu správy" +msgstr "Použite %s na otvorenie nového okna tvorcu správ" -#: ../src/client/application/geary-args.vala:56 +#: src/client/application/geary-args.vala:56 msgid "Please report comments, suggestions and bugs to:" msgstr "Prosím, pridajte komentáre, nápady a chyby na:" #. i18n: Command line arguments are invalid -#: ../src/client/application/geary-args.vala:63 +#: src/client/application/geary-args.vala:63 #, c-format msgid "Failed to parse command line options: %s\n" msgstr "Zlyhalo analyzovanie volieb príkazového riadka: %s\n" -#: ../src/client/application/geary-args.vala:74 +#: src/client/application/geary-args.vala:74 #, c-format msgid "Unrecognized command line option “%s”\n" msgstr "Nerozpoznaná voľba príkazového riadku „%s“\n" -#: ../src/client/application/geary-controller.vala:57 -msgid "Delete conversation" -msgstr "Odstrániť rozhovor" - -#: ../src/client/application/geary-controller.vala:58 -msgid "Delete conversation (Shift+Delete)" -msgstr "Odstráni rozhovor (Shift+Delete)" - -#: ../src/client/application/geary-controller.vala:59 -msgid "Delete conversations (Shift+Delete)" -msgstr "Odstráni rozhovory (Shift+Delete)" - -#. This refers to the action ("move email to the trash"), not the Trash folder itself -#: ../src/client/application/geary-controller.vala:63 -msgid "Move conversation to Trash (Delete, Backspace)" -msgstr "Presunie rozhovory do Koša (Delete, Backspace)" - -#: ../src/client/application/geary-controller.vala:64 -msgid "Move conversations to Trash (Delete, Backspace)" -msgstr "Presunie rozhovory do Koša (Delete, Backspace)" - -#. This refers to the action ("archive an email"), not the Archive folder itself -#: ../src/client/application/geary-controller.vala:68 -msgid "_Archive" -msgstr "_Archivovať" - -#: ../src/client/application/geary-controller.vala:69 -msgid "Archive conversation (A)" -msgstr "Archivuje rozhovor (A)" - -#: ../src/client/application/geary-controller.vala:70 -msgid "Archive conversations (A)" -msgstr "Archivuje rozhovory (A)" - -#: ../src/client/application/geary-controller.vala:73 -msgid "Mark as S_pam" -msgstr "Označiť ako nev_yžiadanú poštu" +#. Translators: File name used in save chooser when saving +#. attachments that do not otherwise have a name. +#: src/client/application/geary-controller.vala:68 +msgid "Untitled" +msgstr "Bez názvu" -#: ../src/client/application/geary-controller.vala:74 -msgid "Mark as not S_pam" -msgstr "Označiť ako _vyžiadanú poštu" - -#: ../src/client/application/geary-controller.vala:76 -#: ../src/client/application/geary-controller.vala:448 -msgid "Mark conversation" -msgstr "Označí rozhovor" - -#: ../src/client/application/geary-controller.vala:77 -msgid "Mark conversations" -msgstr "Označí rozhovory" - -#: ../src/client/application/geary-controller.vala:78 -msgid "Add label to conversation" -msgstr "Pridá menovku rozhovoru" - -#: ../src/client/application/geary-controller.vala:79 -msgid "Add label to conversations" -msgstr "Pridá menovku rozhovorom" - -#: ../src/client/application/geary-controller.vala:80 -#: ../src/client/application/geary-controller.vala:487 -msgid "Move conversation" -msgstr "Presunie rozhovor" - -#: ../src/client/application/geary-controller.vala:81 -msgid "Move conversations" -msgstr "Presunie rozhovory" - -#: ../src/client/application/geary-controller.vala:450 -msgid "_Mark as…" -msgstr "_Označiť ako…" - -#: ../src/client/application/geary-controller.vala:456 -msgid "Mark as _Read" -msgstr "Označiť ako _prečítanú" - -#: ../src/client/application/geary-controller.vala:462 -msgid "Mark as _Unread" -msgstr "Označiť ako _neprečítanú" - -#: ../src/client/application/geary-controller.vala:468 -msgid "_Star" -msgstr "Označiť _hviezdičkou" - -#: ../src/client/application/geary-controller.vala:473 -msgid "U_nstar" -msgstr "Zrušiť oz_načenie hviezdičkou" - -#: ../src/client/application/geary-controller.vala:483 -msgid "Add label" -msgstr "Pridá menovku" - -#: ../src/client/application/geary-controller.vala:484 -msgid "_Label" -msgstr "_Menovka" - -#: ../src/client/application/geary-controller.vala:488 -msgid "_Move" -msgstr "_Presunúť" - -#: ../src/client/application/geary-controller.vala:492 -msgid "Compose new message (Ctrl+N, N)" -msgstr "Vytvorí novú správu (Ctrl+N, N)" - -#: ../src/client/application/geary-controller.vala:496 -#: ../ui/conversation-email-menus.ui.h:1 -msgid "_Reply" -msgstr "_Odpovedať" - -#: ../src/client/application/geary-controller.vala:497 -msgid "Reply (Ctrl+R, R)" -msgstr "Odpovie (Ctrl+R, R)" - -#: ../src/client/application/geary-controller.vala:501 -msgid "R_eply All" -msgstr "Odpov_edať všetkým" - -#: ../src/client/application/geary-controller.vala:502 -msgid "Reply all (Ctrl+Shift+R, Shift+R)" -msgstr "Odpovie všetkým (Ctrl+Shift+R, Shift+R)" - -#: ../src/client/application/geary-controller.vala:507 -#: ../ui/conversation-email-menus.ui.h:3 -msgid "_Forward" -msgstr "_Preposlať" - -#: ../src/client/application/geary-controller.vala:508 -msgid "Forward (Ctrl+L, F)" -msgstr "Prepošle (Ctrl+L, F)" - -#: ../src/client/application/geary-controller.vala:538 -msgid "Empty _Spam…" -msgstr "Vyprázdniť _Nevyžiadanú poštu…" - -#: ../src/client/application/geary-controller.vala:542 -msgid "Empty _Trash…" -msgstr "Vyprázdniť _Kôš…" - -#. No callback is connected, since we bind the toggle button to the search bar visibility -#: ../src/client/application/geary-controller.vala:574 -msgid "Toggle search bar" -msgstr "Prepne panel vyhľadávania" - -#. No callback is connected, since we bind the toggle button to the find bar visibility -#: ../src/client/application/geary-controller.vala:579 -msgid "Toggle find bar" -msgstr "Prepne panel nájdených výrazov" - -#: ../src/client/application/geary-controller.vala:757 +#: src/client/application/geary-controller.vala:721 msgid "Unable to store server trust exception" msgstr "Nie je možné uložiť výnimku dôveryhodnosti servera" -#: ../src/client/application/geary-controller.vala:992 +#: src/client/application/geary-controller.vala:972 msgid "Your settings are insecure" msgstr "Vaše nastavenie nie sú bezpečné" -#: ../src/client/application/geary-controller.vala:993 +#: src/client/application/geary-controller.vala:973 msgid "" "Your IMAP and/or SMTP settings do not specify SSL or TLS. This means your " "username and password could be read by another person on the network. Are " @@ -537,29 +580,17 @@ "TLS. To znamená, že vaše meno používateľa a heslo môže byť prečítané inou " "osobou v sieti. Naozaj chcete toto vykonať?" -#: ../src/client/application/geary-controller.vala:994 +#: src/client/application/geary-controller.vala:974 msgid "Co_ntinue" msgstr "Po_kračovať" -#: ../src/client/application/geary-controller.vala:1041 -msgid "Error connecting to the server" -msgstr "Chyba počas pripájania k serveru" - -#: ../src/client/application/geary-controller.vala:1042 -msgid "" -"Geary encountered an error while connecting to the server. Please try again " -"in a few moments." -msgstr "" -"Aplikácia Geary narazila na chybu počas pripájania k serveru. Prosím, skúste " -"to znovu neskôr." - #. / Displayed in the space-limited status bar when a message fails to be sent due to error. -#: ../src/client/application/geary-controller.vala:1079 -#: ../src/client/components/status-bar.vala:29 +#: src/client/application/geary-controller.vala:1078 +#: src/client/components/status-bar.vala:29 msgid "Error sending email" msgstr "Chyba pri odosielaní emailu" -#: ../src/client/application/geary-controller.vala:1080 +#: src/client/application/geary-controller.vala:1079 msgid "" "Geary encountered an error sending an email. If the problem persists, " "please manually delete the email from your Outbox folder." @@ -570,12 +601,12 @@ #. Displayed in the space-limited status bar when a message fails to be uploaded #. to Sent Mail after being sent. -#: ../src/client/application/geary-controller.vala:1084 -#: ../src/client/components/status-bar.vala:33 +#: src/client/application/geary-controller.vala:1083 +#: src/client/components/status-bar.vala:33 msgid "Error saving sent mail" msgstr "Chyba pri ukladaní odoslanej pošty" -#: ../src/client/application/geary-controller.vala:1085 +#: src/client/application/geary-controller.vala:1084 msgid "" "Geary encountered an error saving a sent message to Sent Mail. The message " "will stay in your Outbox folder until you delete it." @@ -584,19 +615,19 @@ "priečinka „Odoslaná pošta“. Správa zostane vo vašom priečinku „Pošta na " "odoslanie“, pokiaľ ju neodstránite." -#: ../src/client/application/geary-controller.vala:1154 +#: src/client/application/geary-controller.vala:1151 msgid "Labels" msgstr "Menovky" #. give the user two options: reset the Account local store, or exit Geary. A third #. could be done to leave the Account in an unopened state, but we don't currently #. have provisions for that. -#: ../src/client/application/geary-controller.vala:1166 +#: src/client/application/geary-controller.vala:1163 #, c-format msgid "Unable to open the database for %s" msgstr "Nie je možné otvoriť databázu pre %s" -#: ../src/client/application/geary-controller.vala:1167 +#: src/client/application/geary-controller.vala:1164 #, c-format msgid "" "There was an error opening the local mail database for this account. This is " @@ -621,20 +652,20 @@ "Znovu zostavenie databázy zničí všetku miestnu poštu a jej prílohy. Pošta " "na serveri nebude ovplyvnená." -#: ../src/client/application/geary-controller.vala:1169 +#: src/client/application/geary-controller.vala:1166 msgid "_Rebuild" msgstr "_Znovu zostaviť" -#: ../src/client/application/geary-controller.vala:1169 +#: src/client/application/geary-controller.vala:1166 msgid "E_xit" msgstr "_Ukončiť" -#: ../src/client/application/geary-controller.vala:1178 +#: src/client/application/geary-controller.vala:1175 #, c-format msgid "Unable to rebuild database for “%s”" msgstr "Nie je možné znovu zostaviť databázu účtu „%s“" -#: ../src/client/application/geary-controller.vala:1179 +#: src/client/application/geary-controller.vala:1176 #, c-format msgid "" "Error during rebuild:\n" @@ -647,14 +678,14 @@ #. some other problem opening the account ... as with other flow path, can't run #. Geary today with an account in unopened state, so have to exit -#: ../src/client/application/geary-controller.vala:1201 -#: ../src/client/application/geary-controller.vala:1211 -#: ../src/client/application/geary-controller.vala:1222 +#: src/client/application/geary-controller.vala:1198 +#: src/client/application/geary-controller.vala:1208 +#: src/client/application/geary-controller.vala:1219 #, c-format msgid "Unable to open local mailbox for %s" msgstr "Nie je možné otvoriť miestnu poštovú schránku účtu %s" -#: ../src/client/application/geary-controller.vala:1202 +#: src/client/application/geary-controller.vala:1199 #, c-format msgid "" "There was an error opening the local mail database for this account. This is " @@ -673,7 +704,7 @@ "\n" "%s" -#: ../src/client/application/geary-controller.vala:1212 +#: src/client/application/geary-controller.vala:1209 msgid "" "The version number of the local mail database is formatted for a newer " "version of Geary. Unfortunately, the database cannot be “rolled back” to " @@ -686,7 +717,7 @@ "s touto verziou aplikácie Geary.\n" "ynProsím, nainštalujte najnovšiu verziu programu Geary a skúste to znovu." -#: ../src/client/application/geary-controller.vala:1223 +#: src/client/application/geary-controller.vala:1220 msgid "" "There was an error opening the local account. This is probably due to " "connectivity issues.\n" @@ -699,15 +730,15 @@ "Prosím, skontrolujte vaše sieťové pripojenie a znovu spustite aplikáciu " "Geary." -#: ../src/client/application/geary-controller.vala:1999 +#: src/client/application/geary-controller.vala:2038 msgid "Undo move (Ctrl+Z)" msgstr "Vráti späť presunutie (Ctrl+Z)" -#: ../src/client/application/geary-controller.vala:2009 +#: src/client/application/geary-controller.vala:2048 msgid "Are you sure you want to open these attachments?" msgstr "Naozaj chcete otvoriť tieto prílohy?" -#: ../src/client/application/geary-controller.vala:2010 +#: src/client/application/geary-controller.vala:2049 msgid "" "Attachments may cause damage to your system if opened. Only open files from " "trusted sources." @@ -715,201 +746,453 @@ "Prílohy môžu po otvorení poškodiť váš systém. Otvárajte iba súbory z " "dôveryhodných zdrojov." -#: ../src/client/application/geary-controller.vala:2011 +#: src/client/application/geary-controller.vala:2050 msgid "Don’t _ask me again" msgstr "Nepýtať s_a znovu" -#: ../src/client/application/geary-controller.vala:2121 +#. Translators: Dialog primary label when prompting to +#. overwrite a file. The string substitution is the file'sx +#. name. +#: src/client/application/geary-controller.vala:2179 #, c-format msgid "A file named “%s” already exists. Do you want to replace it?" msgstr "Súbor s názvom „%s“ už existuje. Chcete ho nahradiť?" -#: ../src/client/application/geary-controller.vala:2123 +#. Translators: Dialog secondary label when prompting to +#. overwrite a file. The string substitution is the parent +#. folder's name. +#: src/client/application/geary-controller.vala:2186 #, c-format msgid "" "The file already exists in “%s”. Replacing it will overwrite its contents." msgstr "Súbor v „%s“ už existuje. Nahradením sa prepíše jeho obsah." -#: ../src/client/application/geary-controller.vala:2126 +#: src/client/application/geary-controller.vala:2190 msgid "_Replace" msgstr "_Nahradiť" -#. Find out what to do with the inline composers. -#. TODO: Remove this in favor of automatically saving drafts -#: ../src/client/application/geary-controller.vala:2374 -msgid "Close open draft messages?" -msgstr "Zavrieť otvorené koncepty správ?" +#: src/client/application/geary-controller.vala:2460 +msgid "Close the draft message?" +msgid_plural "Close all draft messages?" +msgstr[0] "Zavrieť koncept?" +msgstr[1] "Zavrieť koncepty?" +msgstr[2] "Zavrieť koncepty?" -#: ../src/client/application/geary-controller.vala:2496 +#: src/client/application/geary-controller.vala:2586 #, c-format msgid "Empty all email from your %s folder?" msgstr "Vyprázdniť všetky emaily z vášho priečinka %s?" -#: ../src/client/application/geary-controller.vala:2497 +#: src/client/application/geary-controller.vala:2587 msgid "This removes the email from Geary and your email server." msgstr "" "Týmto sa odstránia všetky emaily z aplikácie Geary a vášho emailového " "servera." -#: ../src/client/application/geary-controller.vala:2498 +#: src/client/application/geary-controller.vala:2588 msgid "This cannot be undone." msgstr "Táto akcia sa nebude dať vrátiť späť." -#: ../src/client/application/geary-controller.vala:2499 +#: src/client/application/geary-controller.vala:2589 #, c-format msgid "Empty %s" msgstr "Vyprázdniť priečinok %s" -#: ../src/client/application/geary-controller.vala:2516 +#: src/client/application/geary-controller.vala:2606 #, c-format msgid "Error emptying %s" msgstr "Chyba pri vyprázdňovaní priečinka %s" -#: ../src/client/application/geary-controller.vala:2546 +#: src/client/application/geary-controller.vala:2638 msgid "Do you want to permanently delete this message?" msgid_plural "Do you want to permanently delete these messages?" msgstr[0] "Chcete natrvalo odstrániť túto správu?" msgstr[1] "Chcete natrvalo odstrániť tieto správy?" msgstr[2] "Chcete natrvalo odstrániť tieto správy?" -#: ../src/client/application/geary-controller.vala:2548 +#: src/client/application/geary-controller.vala:2640 msgid "Delete" msgstr "Odstrániť" -#: ../src/client/application/geary-controller.vala:2580 -msgid "Undo archive (Ctrl+Z)" -msgstr "Vráti späť archiváciu (Ctrl+Z)" - -#: ../src/client/application/geary-controller.vala:2595 +#: src/client/application/geary-controller.vala:2654 msgid "Undo trash (Ctrl+Z)" msgstr "Vráti späť kôš (Ctrl+Z)" -#: ../src/client/application/geary-controller.vala:2649 +#: src/client/application/geary-controller.vala:2704 +msgid "Undo archive (Ctrl+Z)" +msgstr "Vráti späť archiváciu (Ctrl+Z)" + +#: src/client/application/geary-controller.vala:2749 msgid "Undo (Ctrl+Z)" msgstr "Vráti späť (Ctrl+Z)" -#: ../src/client/application/geary-controller.vala:2780 +#. Translators: The label for an in-app notification. The +#. string substitution is a list of recipients of the email. +#: src/client/application/geary-controller.vala:2826 +#, c-format +msgid "Successfully sent mail to %s." +msgstr "Úspešne odoslaný email príjemcovi %s." + +#: src/client/application/geary-controller.vala:2907 msgid "Failed to open default text editor." msgstr "Zlyhalo otvorenie predvoleného textového editora." -#: ../src/client/components/main-window.vala:389 +#. Tooltips +#: src/client/components/main-toolbar.vala:69 +msgid "Delete conversation (Shift+Delete)" +msgstr "Odstráni rozhovor (Shift+Delete)" + +#: src/client/components/main-toolbar.vala:70 +msgid "Delete conversations (Shift+Delete)" +msgstr "Odstráni rozhovory (Shift+Delete)" + +#: src/client/components/main-toolbar.vala:71 +msgid "Move conversation to Trash (Delete, Backspace)" +msgstr "Presunie rozhovory do Koša (Delete, Backspace)" + +#: src/client/components/main-toolbar.vala:72 +msgid "Move conversations to Trash (Delete, Backspace)" +msgstr "Presunie rozhovory do Koša (Delete, Backspace)" + +#: src/client/components/main-toolbar.vala:73 +msgid "Archive conversation (A)" +msgstr "Archivuje rozhovor (A)" + +#: src/client/components/main-toolbar.vala:74 +msgid "Archive conversations (A)" +msgstr "Archivuje rozhovory (A)" + +#: src/client/components/main-toolbar.vala:75 +msgid "Mark conversation" +msgstr "Označí rozhovor" + +#: src/client/components/main-toolbar.vala:76 +msgid "Mark conversations" +msgstr "Označí rozhovory" + +#: src/client/components/main-toolbar.vala:77 +msgid "Add label to conversation" +msgstr "Pridá menovku rozhovoru" + +#: src/client/components/main-toolbar.vala:78 +msgid "Add label to conversations" +msgstr "Pridá menovku rozhovorom" + +#: src/client/components/main-toolbar.vala:79 +msgid "Move conversation" +msgstr "Presunie rozhovor" + +#: src/client/components/main-toolbar.vala:80 +msgid "Move conversations" +msgstr "Presunie rozhovory" + +#: src/client/components/main-window.vala:440 #, c-format msgid "%s (%d)" msgstr "%s (%d)" -#: ../src/client/components/search-bar.vala:8 -#: ../src/client/folder-list/folder-list-search-branch.vala:38 -#: ../src/engine/api/geary-special-folder-type.vala:51 +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:54 +#, c-format +msgid "Problem connecting to incoming server for %s" +msgstr "Problém s pripájaním k prichádzajúcemu serveru pre účet %s" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:56 +#: src/client/components/main-window-info-bar.vala:64 +#, c-format +msgid "" +"Could not connect to %s, check your Internet access and the server name and " +"try again" +msgstr "" +"Nepodarilo sa pripojiť k serveru %s. Skontrolujte vaše pripojenie k " +"internetu a názov servera a skúste to znovu." + +#: src/client/components/main-window-info-bar.vala:57 +#: src/client/components/main-window-info-bar.vala:66 +msgid "Retry connecting now" +msgstr "Znovu skúsiť pripojenie teraz" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:62 +#, c-format +msgid "Problem connecting to outgoing server for %s" +msgstr "Problém s pripájaním k odchádzajúcemu serveru pre účet %s" + +#: src/client/components/main-window-info-bar.vala:65 +msgid "Try reconnecting now" +msgstr "Skúsiť znovu pripojenie teraz" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:71 +#, c-format +msgid "Problem with connection to incoming server for %s" +msgstr "Problém s pripojením k serveru prichádzajúcich správ účtu %s" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:73 +#: src/client/components/main-window-info-bar.vala:81 +#, c-format +msgid "Network error talking to %s, check your Internet access and try again" +msgstr "" +"Chyba sieťovej komunikácie so serverom %s. Skontrolujte vaše internetové " +"pripojenie a skúste to znovu" + +#: src/client/components/main-window-info-bar.vala:74 +#: src/client/components/main-window-info-bar.vala:82 +#: src/client/components/main-window-info-bar.vala:90 +#: src/client/components/main-window-info-bar.vala:98 +#: src/client/components/main-window-info-bar.vala:119 +msgid "Try reconnecting" +msgstr "Skúsiť znovu pripojenie" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:79 +#, c-format +msgid "Problem with connection to outgoing server for %s" +msgstr "Problém s pripojením k odchádzajúcemu serveru pre účet %s" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:87 +#, c-format +msgid "Problem communicating with incoming server for %s" +msgstr "Komunikačný problém so serverom prichádzajúcich správ účtu %s" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:89 +#, c-format +msgid "" +"Geary did not understand a message from %s or vice versa, please file a bug " +"report" +msgstr "" +"Aplikácia Geary nerozumie správe zo servera %s alebo naopak. Prosím, " +"nahláste chybu." + +#: src/client/components/main-window-info-bar.vala:94 +msgid "Problem communicating with outgoing mail server" +msgstr "Komunikačný problém s poštovým serverom odchádzajúcich správ" + +#. Translators: First string substitution is the server +#. name, second is the account name +#: src/client/components/main-window-info-bar.vala:97 +#, c-format +msgid "" +"Could not communicate with %s for %s, check the server name and try again in " +"a moment" +msgstr "" +"Nepodarilo sa komunikovať so serverom %s účtu %s. Skontrolujte názov servera " +"a skúste to znovu neskôr." + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:103 +#, c-format +msgid "Incoming mail server password required for %s" +msgstr "Vyžaduje sa heslo pre server prichádzajúcich správ účtu %s" + +#: src/client/components/main-window-info-bar.vala:104 +msgid "Messages cannot be received without the correct password." +msgstr "Správy nemôžu byť prijaté bez správneho hesla." + +#: src/client/components/main-window-info-bar.vala:105 +msgid "Retry receiving email, you will be prompted for a password" +msgstr "Skúsiť prijatie emailu, budete vyzvaný na zadanie hesla" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:110 +#, c-format +msgid "Outgoing mail server password required for %s" +msgstr "Vyžaduje sa heslo pre server odchádzajúcich správ účtu %s" + +#: src/client/components/main-window-info-bar.vala:111 +msgid "Messages cannot be sent without the correct password." +msgstr "Správy nemôžu byť odoslané bez správneho hesla." + +#: src/client/components/main-window-info-bar.vala:112 +msgid "Retry sending queued messages, you will be prompted for a password" +msgstr "Skúsiť znovu odoslať správy vo fronte, budete vyzvaný zadať heslo" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:117 +#, c-format +msgid "A problem occurred checking mail for %s" +msgstr "Vyskytol sa problém s kontrolou pošty v účte %s" + +#: src/client/components/main-window-info-bar.vala:118 +#: src/client/components/main-window-info-bar.vala:125 +msgid "Something went wrong, please file a bug report if the problem persists" +msgstr "Niekde nastala chyba. Ak problém pretrváva, prosím, nahláste chybu." + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:124 +#, c-format +msgid "A problem occurred sending mail for %s" +msgstr "Vyskytol sa problém s odosielaním pošty z účtu %s" + +#: src/client/components/main-window-info-bar.vala:126 +msgid "Retry sending queued messages" +msgstr "Skúsiť znovu odoslať správy vo fronte" + +#: src/client/components/main-window-info-bar.vala:137 +msgid "A database problem has occurred" +msgstr "Vyskytol sa problém s databázou" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:139 +#, c-format +msgid "Messages for %s must be downloaded again." +msgstr "Správy účtu %s musia byť znovu prevzaté." + +#: src/client/components/main-window-info-bar.vala:152 +msgid "Geary has encountered a problem" +msgstr "Aplikácia Geary narazila na problém" + +#: src/client/components/main-window-info-bar.vala:153 +msgid "" +"Please check the technical details and report the problem if it persists." +msgstr "" +"Prosím, skontrolujte technické podrobnosti a ak problém pretrváva, nahláste " +"ho." + +#: src/client/components/main-window-info-bar.vala:161 +msgid "_Details" +msgstr "Po_drobnosti" + +# tooltip +#: src/client/components/main-window-info-bar.vala:162 +msgid "View technical details about the error" +msgstr "Zobrazí technické podrobnosti o chybe" + +#: src/client/components/main-window-info-bar.vala:166 +msgid "_Retry" +msgstr "_Skúsiť znova" + +#: src/client/components/main-window-info-bar.vala:253 +msgid "Details" +msgstr "Podrobnosti" + +#: src/client/components/main-window-info-bar.vala:266 +#: src/client/components/stock.vala:23 +msgid "_Close" +msgstr "_Zavrieť" + +#: src/client/components/main-window-info-bar.vala:270 +msgid "Copy to Clipboard" +msgstr "Skopírovať do schránky" + +# tooltip +#: src/client/components/main-window-info-bar.vala:273 +msgid "" +"Copy technical details to clipboard for pasting into an email or bug report" +msgstr "" +"Skopíruje technické podrobnosti do schránky pre vloženie do emailu alebo " +"chybového hlásenia" + +#: src/client/components/search-bar.vala:8 +#: src/client/folder-list/folder-list-search-branch.vala:38 +#: src/engine/api/geary-special-folder-type.vala:51 msgid "Search" msgstr "Vyhľadať" #. Search entry. -#: ../src/client/components/search-bar.vala:23 +#: src/client/components/search-bar.vala:23 msgid "Search all mail in account for keywords (Ctrl+S)" msgstr "Cyhľadá kľúčové slová vo všetkých správach poštového účtu (Ctrl+S)" -#: ../src/client/components/search-bar.vala:100 +#: src/client/components/search-bar.vala:100 #, c-format msgid "Indexing %s account" msgstr "Indexuje sa účet %s" -#: ../src/client/components/search-bar.vala:111 -#: ../src/client/folder-list/folder-list-search-branch.vala:39 +#: src/client/components/search-bar.vala:111 +#: src/client/folder-list/folder-list-search-branch.vala:39 #, c-format msgid "Search %s account" msgstr "Prehľadať účet %s" #. / Displayed in the space-limited status bar while a message is in the process of being sent. -#: ../src/client/components/status-bar.vala:26 +#: src/client/components/status-bar.vala:26 msgid "Sending…" msgstr "Odosiela sa…" -#: ../src/client/components/stock.vala:18 ../ui/account_cannot_remove.glade.h:3 +#: src/client/components/stock.vala:18 ui/account_cannot_remove.glade:74 msgid "_OK" msgstr "_OK" -#: ../src/client/components/stock.vala:19 ../ui/edit_alternate_emails.glade.h:3 -#: ../ui/password-dialog.glade.h:5 ../ui/remove_confirm.glade.h:5 +#: src/client/components/stock.vala:19 ui/edit_alternate_emails.glade:160 +#: ui/password-dialog.glade:196 ui/remove_confirm.glade:155 msgid "_Cancel" msgstr "_Zrušiť" -#: ../src/client/components/stock.vala:21 ../ui/gtk/menus.ui.h:6 +#: src/client/components/stock.vala:21 ui/gtk/menus.ui:34 msgid "_About" msgstr "_O aplikácii" -#: ../src/client/components/stock.vala:23 -msgid "_Close" -msgstr "_Zavrieť" - -#: ../src/client/components/stock.vala:24 +#: src/client/components/stock.vala:24 msgid "_Discard" msgstr "Za_hodiť" -#: ../src/client/components/stock.vala:25 ../ui/gtk/menus.ui.h:5 +#: src/client/components/stock.vala:25 ui/gtk/menus.ui:29 msgid "_Help" msgstr "Po_mocník" -#: ../src/client/components/stock.vala:26 ../ui/conversation-email-menus.ui.h:9 +#: src/client/components/stock.vala:26 ui/conversation-email-menus.ui:77 msgid "_Open" msgstr "_Otvoriť" -#: ../src/client/components/stock.vala:27 ../ui/gtk/menus.ui.h:3 +#: src/client/components/stock.vala:27 ui/gtk/menus.ui:17 msgid "_Preferences" msgstr "N_astavenia" -#: ../src/client/components/stock.vala:28 ../ui/conversation-email-menus.ui.h:7 +#. Translators: Menu item to print a single, specific message +#: src/client/components/stock.vala:28 ui/conversation-email-menus.ui:64 msgid "_Print…" msgstr "_Tlačiť…" -#: ../src/client/components/stock.vala:29 ../ui/gtk/menus.ui.h:7 +#: src/client/components/stock.vala:29 ui/gtk/menus.ui:38 msgid "_Quit" msgstr "_Ukončiť" -#: ../src/client/components/stock.vala:30 ../ui/remove_confirm.glade.h:6 +#: src/client/components/stock.vala:30 ui/remove_confirm.glade:170 msgid "_Remove" msgstr "O_dstrániť" -#: ../src/client/components/stock.vala:32 +#: src/client/components/stock.vala:32 msgid "_Keep" msgstr "_Ponechať" -#: ../src/client/composer/composer-link-popover.vala:150 +#: src/client/composer/composer-link-popover.vala:149 msgid "Link URL is not correctly formatted, e.g. http://example.com" msgstr "URL odkazu nie je v správnom formáte, napr. http://priklad.sk" -#: ../src/client/composer/composer-link-popover.vala:157 +#: src/client/composer/composer-link-popover.vala:156 msgid "Invalid link URL" msgstr "Neplatný URL odkazu" -#: ../src/client/composer/composer-link-popover.vala:157 +#: src/client/composer/composer-link-popover.vala:156 msgid "Invalid email address" msgstr "Neplatná emailová adresa" -#: ../src/client/composer/composer-widget.vala:154 +#: src/client/composer/composer-widget.vala:158 msgid "Saved" msgstr "Uložené" -#: ../src/client/composer/composer-widget.vala:155 +#: src/client/composer/composer-widget.vala:159 msgid "Saving" msgstr "Ukladá sa" -#: ../src/client/composer/composer-widget.vala:156 +#: src/client/composer/composer-widget.vala:160 msgid "Error saving" msgstr "Chyba pri ukladaní" -#: ../src/client/composer/composer-widget.vala:157 +#: src/client/composer/composer-widget.vala:161 msgid "Press Backspace to delete quote" msgstr "Stlačte kláves Backspace na odstránenie citácie" -#: ../src/client/composer/composer-widget.vala:158 -msgid "New Message" -msgstr "Nová správa" - #. Translators: This is list of keywords, separated by pipe ("|") #. characters, that suggest an attachment; since this is full-word #. checking, include all variants of each word. No spaces are #. allowed. -#: ../src/client/composer/composer-widget.vala:167 +#: src/client/composer/composer-widget.vala:170 msgid "" "attach|attaching|attaches|attachment|attachments|attached|enclose|enclosed|" "enclosing|encloses|enclosure|enclosures" @@ -918,28 +1201,37 @@ "priloženým|priložených|priloženého|prikladám|prikladanú|prikladané|" "prikladaný|prikladaným|prikladanému" -#: ../src/client/composer/composer-widget.vala:1122 -#: ../src/client/composer/composer-widget.vala:1141 -msgid "Do you want to discard this message?" -msgstr "Chcete zahodiť túto správu?" +#. Translators: This dialog text is displayed to the +#. user when closing a composer where the options are +#. Keep, Discard or Cancel. +#: src/client/composer/composer-widget.vala:1102 +msgid "Do you want to keep or discard this draft message?" +msgstr "Chcete ponechať alebo zahodiť tento koncept?" + +#. Translators: This dialog text is displayed to the +#. user when closing a composer where the options are +#. only Discard or Cancel. +#: src/client/composer/composer-widget.vala:1130 +msgid "Do you want to discard this draft message?" +msgstr "Chcete zahodiť tento koncept?" -#: ../src/client/composer/composer-widget.vala:1245 +#: src/client/composer/composer-widget.vala:1247 msgid "Send message with an empty subject and body?" msgstr "Odoslať správu bez predmetu a s prázdnym telom?" -#: ../src/client/composer/composer-widget.vala:1247 +#: src/client/composer/composer-widget.vala:1249 msgid "Send message with an empty subject?" msgstr "Odoslať správu bez predmetu?" -#: ../src/client/composer/composer-widget.vala:1249 +#: src/client/composer/composer-widget.vala:1251 msgid "Send message with an empty body?" msgstr "Odoslať správu s prázdnym telom?" -#: ../src/client/composer/composer-widget.vala:1253 +#: src/client/composer/composer-widget.vala:1255 msgid "Send message without an attachment?" msgstr "Odoslať správu bez prílohy?" -#: ../src/client/composer/composer-widget.vala:1515 +#: src/client/composer/composer-widget.vala:1560 #, c-format msgid "“%s” already attached for delivery." msgstr "„%s“ je už priložený na doručenie." @@ -949,172 +1241,245 @@ #. description of the document type, the second will #. be a human-friendly size string. For example: #. Document (100.9MB) -#: ../src/client/composer/composer-widget.vala:1523 -#: ../src/client/conversation-viewer/conversation-email.vala:138 +#: src/client/composer/composer-widget.vala:1568 +#: src/client/conversation-viewer/conversation-email.vala:136 #, c-format msgid "%s (%s)" msgstr "%s (%s)" -#: ../src/client/composer/composer-widget.vala:1560 +#: src/client/composer/composer-widget.vala:1605 #, c-format msgid "“%s” could not be found." msgstr "„%s“ sa nepodarilo nájsť." -#: ../src/client/composer/composer-widget.vala:1566 +#: src/client/composer/composer-widget.vala:1611 #, c-format msgid "“%s” is a folder." msgstr "„%s“ je priečinok." -#: ../src/client/composer/composer-widget.vala:1572 +#: src/client/composer/composer-widget.vala:1617 #, c-format msgid "“%s” is an empty file." msgstr "„%s“ je prázdny súbor." -#: ../src/client/composer/composer-widget.vala:1585 +#: src/client/composer/composer-widget.vala:1630 #, c-format msgid "“%s” could not be opened for reading." msgstr "„%s“ sa nepodarilo otvoriť na čítanie." -#: ../src/client/composer/composer-widget.vala:1593 +#: src/client/composer/composer-widget.vala:1638 msgid "Cannot add attachment" msgstr "Nedá sa pridať príloha" -#: ../src/client/composer/composer-widget.vala:1645 +#: src/client/composer/composer-widget.vala:1687 msgid "To: " msgstr "Pre: " -#: ../src/client/composer/composer-widget.vala:1648 +#: src/client/composer/composer-widget.vala:1690 msgid "Cc: " msgstr "Kópia: " -#: ../src/client/composer/composer-widget.vala:1651 +#: src/client/composer/composer-widget.vala:1693 msgid "Bcc: " msgstr "Skrytá kópia: " -#: ../src/client/composer/composer-widget.vala:1654 +#: src/client/composer/composer-widget.vala:1696 msgid "Reply-To: " msgstr "Komu odpovedať: " -#: ../src/client/composer/composer-widget.vala:1786 +#: src/client/composer/composer-widget.vala:1834 msgid "Select Color" msgstr "Výber farby" #. Displayed in the From dropdown to indicate an "alternate email address" #. for an account. The first printf argument will be the alternate email #. address, and the second will be the account's primary email address. -#: ../src/client/composer/composer-widget.vala:1986 +#: src/client/composer/composer-widget.vala:2024 #, c-format msgid "%1$s via %2$s" msgstr "%1$s prostredníctvom účtu %2$s" #. Composer label (with mnemonic underscore) for the account selector #. when choosing what address to send a message from. -#: ../src/client/composer/composer-widget.vala:2028 +#: src/client/composer/composer-widget.vala:2082 msgid "_From:" msgstr "Od:" #. Translators: This is the name of the file chooser filter #. when inserting an image in the composer. -#: ../src/client/composer/composer-widget.vala:2252 +#: src/client/composer/composer-widget.vala:2307 msgid "Images" msgstr "Obrázky" +#: src/client/composer/composer-window.vala:14 +msgid "New Message" +msgstr "Nová správa" + # tooltip -#: ../src/client/composer/spell-check-popover.vala:117 +#: src/client/composer/spell-check-popover.vala:117 msgid "Remove this language from the preferred list" msgstr "Odstráni tento jazyk zo zoznamu uprednostňovaných" # tooltip -#: ../src/client/composer/spell-check-popover.vala:121 +#: src/client/composer/spell-check-popover.vala:121 msgid "Add this language to the preferred list" msgstr "Pridá tento jazyk do zoznamu uprednostňovaných" # placeholder -#: ../src/client/composer/spell-check-popover.vala:217 +#: src/client/composer/spell-check-popover.vala:217 msgid "Search for more languages" msgstr "Vyhľadajte viac jazykov" -#: ../src/client/conversation-list/formatted-conversation-data.vala:11 +#: src/client/conversation-list/conversation-list-view.vala:283 +msgid "Delete conversation" +msgstr "Odstrániť rozhovor" + +#: src/client/conversation-list/conversation-list-view.vala:286 +#: ui/main-toolbar-menus.ui:16 +msgid "Mark as _Read" +msgstr "Označiť ako _prečítanú" + +#: src/client/conversation-list/conversation-list-view.vala:289 +#: ui/main-toolbar-menus.ui:20 +msgid "Mark as _Unread" +msgstr "Označiť ako _neprečítanú" + +#: src/client/conversation-list/conversation-list-view.vala:292 +#: ui/main-toolbar-menus.ui:28 +msgid "U_nstar" +msgstr "Zrušiť oz_načenie hviezdičkou" + +#: src/client/conversation-list/conversation-list-view.vala:294 +#: ui/main-toolbar-menus.ui:24 +msgid "_Star" +msgstr "Označiť _hviezdičkou" + +#. Translators: Menu item to reply to a specific message. +#: src/client/conversation-list/conversation-list-view.vala:297 +#: ui/conversation-email-menus.ui:9 +msgid "_Reply" +msgstr "_Odpovedať" + +#: src/client/conversation-list/conversation-list-view.vala:298 +msgid "R_eply All" +msgstr "Odpov_edať všetkým" + +#. Translators: Menu item to forward a specific message. +#: src/client/conversation-list/conversation-list-view.vala:299 +#: ui/conversation-email-menus.ui:21 +msgid "_Forward" +msgstr "_Preposlať" + +#: src/client/conversation-list/formatted-conversation-data.vala:11 msgid "Me" msgstr "Ja" #. Translators: This is the file type displayed for #. attachments with unknown file types. -#: ../src/client/conversation-viewer/conversation-email.vala:124 +#: src/client/conversation-viewer/conversation-email.vala:122 msgid "Unknown" msgstr "Neznámy" +#: src/client/conversation-viewer/conversation-email.vala:811 +msgid "From:" +msgstr "Od:" + +#: src/client/conversation-viewer/conversation-email.vala:815 +#: ui/conversation-message.ui:313 +msgid "To:" +msgstr "Pre:" + +#: src/client/conversation-viewer/conversation-email.vala:819 +#: ui/conversation-message.ui:358 +msgid "Cc:" +msgstr "Kópia:" + +#: src/client/conversation-viewer/conversation-email.vala:823 +#: ui/conversation-message.ui:403 +msgid "Bcc:" +msgstr "Skrytá kópia:" + +#: src/client/conversation-viewer/conversation-email.vala:827 +msgid "Date:" +msgstr "Dátum:" + +#: src/client/conversation-viewer/conversation-email.vala:831 +msgid "Subject:" +msgstr "Predmet:" + +#: src/client/conversation-viewer/conversation-message.vala:60 +msgid "This email address may have been forged" +msgstr "Táto emailová adresa mohla byť zabudnutá" + #. Preview headers #. Translators: This is displayed in place of the from address #. when the message has no from address. -#: ../src/client/conversation-viewer/conversation-message.vala:331 +#: src/client/conversation-viewer/conversation-message.vala:330 msgid "No sender" msgstr "Žiadny odosielateľ" #. Translators: This separates multiple 'from' #. addresses in the header preview for a message. -#: ../src/client/conversation-viewer/conversation-message.vala:586 +#: src/client/conversation-viewer/conversation-message.vala:589 msgid ", " msgstr ", " #. Translators: This string is used as the HTML IMG ALT #. attribute value when displaying an inline image in an email #. that did not specify a file name. E.g. ImageCannot remove account " msgstr "Nedá sa odstrániť účet " -#: ../ui/account_cannot_remove.glade.h:2 +#: ui/account_cannot_remove.glade:56 msgid "" "A composer window associated with this account is currently open. Send or " "discard the message and try again." @@ -1743,458 +2128,471 @@ "Okno tvorcu správy priradeného k tomuto účtu je momentálne otvorené. " "Odošlite alebo zahoďte správu a skúste to znovu." -#: ../ui/account_list.glade.h:1 +#: ui/account_list.glade:69 msgid "Add account" msgstr "Pridá účet" -#: ../ui/account_list.glade.h:2 +#: ui/account_list.glade:82 msgid "Edit account" msgstr "Upraví účet" -#: ../ui/account_list.glade.h:3 +#: ui/account_list.glade:95 msgid "Remove account" msgstr "Odstráni účet" -#: ../ui/account_spinner.glade.h:1 +#: ui/account_spinner.glade:41 msgid "Please wait while Geary validates your account." msgstr "Prosím, čakajte, kým aplikácia Geary overí váš účet." -#: ../ui/certificate_warning_dialog.glade.h:1 +#: ui/certificate_warning_dialog.glade:7 msgid "Untrusted Connection" msgstr "Nedôveryhodné pripojenie" -#: ../ui/certificate_warning_dialog.glade.h:2 +#: ui/certificate_warning_dialog.glade:29 msgid "_Always Trust This Server" msgstr "_Vždy dôverovať tomuto serveru" -#: ../ui/certificate_warning_dialog.glade.h:3 +#: ui/certificate_warning_dialog.glade:43 msgid "_Trust This Server" msgstr "_Dôverovať tomuto serveru" -#: ../ui/certificate_warning_dialog.glade.h:4 +#: ui/certificate_warning_dialog.glade:57 msgid "_Don’t Trust This Server" msgstr "_Nedôverovať tomuto serveru" -#: ../ui/composer-headerbar.ui.h:1 +#: ui/composer-headerbar.ui:17 ui/composer-headerbar.ui:174 msgid "Detach (Ctrl+D)" msgstr "Odpojí (Ctrl+D)" # tooltip -#: ../ui/composer-headerbar.ui.h:2 +#: ui/composer-headerbar.ui:57 ui/composer-headerbar.ui:83 msgid "Attach File (Ctrl+T)" msgstr "Priloží súbor (Ctrl+T)" -#: ../ui/composer-headerbar.ui.h:3 +#: ui/composer-headerbar.ui:107 msgid "Include Original Attachments" -msgstr "zahrnie pôvodné prílohy" - -#: ../ui/composer-headerbar.ui.h:4 -msgid "Send (Ctrl+Enter)" -msgstr "Odošle (Ctrl+Enter)" +msgstr "Zahrnie pôvodné prílohy" -#: ../ui/composer-headerbar.ui.h:5 +#: ui/composer-headerbar.ui:202 msgid "_Send" msgstr "_Odoslať" +#: ui/composer-headerbar.ui:207 +msgid "Send (Ctrl+Enter)" +msgstr "Odošle (Ctrl+Enter)" + # tooltip -#: ../ui/composer-headerbar.ui.h:6 +#: ui/composer-headerbar.ui:230 msgid "Discard and Close" msgstr "Zahodí a zavrie" # tooltip -#: ../ui/composer-headerbar.ui.h:7 +#: ui/composer-headerbar.ui:254 msgid "Save and Close" msgstr "Uloží a zavrie" # tooltip #. Note that this button and the Update button will never be shown at the same time to the user. -#: ../ui/composer-link-popover.ui.h:2 +#: ui/composer-link-popover.ui:41 msgid "Insert the new link with this URL" msgstr "Vloží nový odkaz s týmto URL" -#: ../ui/composer-link-popover.ui.h:3 +#: ui/composer-link-popover.ui:52 msgid "Link URL" msgstr "URL odkazu" # tooltip #. Note that this button and the Insert button will never be shown at the same time to the user. -#: ../ui/composer-link-popover.ui.h:5 +#: ui/composer-link-popover.ui:66 msgid "Update this link’s URL" msgstr "Aktualizuje URL tohto odkazu" # tooltip -#: ../ui/composer-link-popover.ui.h:6 +#: ui/composer-link-popover.ui:86 msgid "Delete this link" msgstr "Odstráni tento odkaz" # tooltip -#: ../ui/composer-link-popover.ui.h:7 +#: ui/composer-link-popover.ui:106 msgid "Open this link" msgstr "Otvorí tento odkaz" -#: ../ui/composer-menus.ui.h:1 +#: ui/composer-menus.ui:7 msgid "S_ans Serif" msgstr "S_ans Serif" -#: ../ui/composer-menus.ui.h:2 +#: ui/composer-menus.ui:12 msgid "S_erif" msgstr "S_erif" -#: ../ui/composer-menus.ui.h:3 +#: ui/composer-menus.ui:17 msgid "_Fixed Width" msgstr "_Pevná šírka" -#: ../ui/composer-menus.ui.h:4 +#: ui/composer-menus.ui:24 msgid "_Small" msgstr "_Malé" -#: ../ui/composer-menus.ui.h:5 +#: ui/composer-menus.ui:29 msgid "_Medium" msgstr "_Stredné" -#: ../ui/composer-menus.ui.h:6 +#: ui/composer-menus.ui:34 msgid "Lar_ge" msgstr "_Veľké" -#: ../ui/composer-menus.ui.h:7 +#: ui/composer-menus.ui:41 msgid "C_olor" msgstr "_Farba" -#: ../ui/composer-menus.ui.h:8 +#: ui/composer-menus.ui:47 ui/composer-menus.ui:62 msgid "_Rich Text" msgstr "_Formátovaný text" -#: ../ui/composer-menus.ui.h:9 +#: ui/composer-menus.ui:53 ui/composer-menus.ui:68 msgid "Show Extended Fields" msgstr "Zobrazí rozšírené polia" -#: ../ui/composer-menus.ui.h:10 +#: ui/composer-menus.ui:78 msgid "_Undo" msgstr "Vrátiť _späť" -#: ../ui/composer-menus.ui.h:11 +#: ui/composer-menus.ui:82 msgid "_Redo" msgstr "_Znovu vykonať" -#: ../ui/composer-menus.ui.h:12 +#: ui/composer-menus.ui:88 ui/composer-menus.ui:106 msgid "Cu_t" msgstr "Vys_trihnúť" -#: ../ui/composer-menus.ui.h:13 ../ui/conversation-message-menus.ui.h:7 +#: ui/composer-menus.ui:92 ui/composer-menus.ui:110 +#: ui/conversation-message-menus.ui:37 msgid "_Copy" msgstr "_Skopírovať" -#: ../ui/composer-menus.ui.h:14 +#: ui/composer-menus.ui:96 ui/composer-menus.ui:114 msgid "_Paste" msgstr "_Vložiť" -#: ../ui/composer-menus.ui.h:15 -msgctxt "Clipboard paste with rich text" -msgid "Paste _With Formatting" -msgstr "Vložiť s _formátovaním" +#: ui/composer-menus.ui:100 +msgctxt "Clipboard paste as plain text" +msgid "Paste _Without Formatting" +msgstr "Vložiť _bez formátovania" -#: ../ui/composer-menus.ui.h:16 +#: ui/composer-menus.ui:120 msgid "Select _All" msgstr "Vybr_ať všetko" -#: ../ui/composer-menus.ui.h:17 ../ui/conversation-message-menus.ui.h:9 +#: ui/composer-menus.ui:127 ui/conversation-message-menus.ui:49 msgid "_Inspect…" msgstr "_Preskúmať…" #. Address(es) e-mail is to be sent to -#: ../ui/composer-widget.ui.h:2 +#: ui/composer-widget.ui:56 msgid "_To" msgstr "_Pre" -#: ../ui/composer-widget.ui.h:3 +#: ui/composer-widget.ui:75 msgid "_Cc" msgstr "_Kópia" -#: ../ui/composer-widget.ui.h:4 +#: ui/composer-widget.ui:130 msgid "_Subject" msgstr "P_redmet" -#: ../ui/composer-widget.ui.h:5 +#: ui/composer-widget.ui:149 msgid "_Bcc" msgstr "_Skrytá kópia" -#: ../ui/composer-widget.ui.h:6 +#: ui/composer-widget.ui:179 msgid "_Reply-To" msgstr "_Komu odpovedať" #. Geary account mail will be sent from -#: ../ui/composer-widget.ui.h:8 +#: ui/composer-widget.ui:208 msgid "From" msgstr "Od" -#: ../ui/composer-widget.ui.h:9 +#: ui/composer-widget.ui:293 msgid "Drop files here" msgstr "Sem pustite súbory" -#: ../ui/composer-widget.ui.h:10 +#: ui/composer-widget.ui:309 msgid "To add them as attachments" msgstr "Na ich pridanie do prílohy" # tooltip -#: ../ui/composer-widget.ui.h:11 +#: ui/composer-widget.ui:348 msgid "Undo last edit (Ctrl+Z)" msgstr "Vráti späť poslednú úpravu (Ctrl+Z)" # tooltip -#: ../ui/composer-widget.ui.h:12 +#: ui/composer-widget.ui:372 msgid "Redo last edit (Ctrl+Shift+Z)" msgstr "Znovu vykoná poslednú úpravu (Ctrl+Shift+Z)" -#: ../ui/composer-widget.ui.h:13 +#: ui/composer-widget.ui:410 msgid "Bold (Ctrl+B)" msgstr "Tučné (Ctrl+B)" -#: ../ui/composer-widget.ui.h:14 +#: ui/composer-widget.ui:434 msgid "Italic (Ctrl+I)" msgstr "Kurzíva (Ctrl+I)" -#: ../ui/composer-widget.ui.h:15 +#: ui/composer-widget.ui:458 msgid "Underline (Ctrl+U)" msgstr "Podčiarknuté (Ctrl+U)" -#: ../ui/composer-widget.ui.h:16 +#: ui/composer-widget.ui:482 msgid "Strikethrough (Ctrl+K)" msgstr "Preškrtnuté (Ctrl+K)" -#: ../ui/composer-widget.ui.h:17 +# tooltip +#: ui/composer-widget.ui:520 +msgid "Insert unordered list" +msgstr "Vloží neusporiadaný zoznam" + +# tooltip +#: ui/composer-widget.ui:544 +msgid "Insert ordered list" +msgstr "Vloží usporiadaný zoznam" + +#: ui/composer-widget.ui:582 msgid "Quote text (Ctrl+])" msgstr "Cituje text (Ctrl+])" # tooltip -#: ../ui/composer-widget.ui.h:18 +#: ui/composer-widget.ui:606 msgid "Unquote text (Ctrl+[)" msgstr "Zruší citáciu textu (Ctrl+[)" # tooltip -#: ../ui/composer-widget.ui.h:19 +#: ui/composer-widget.ui:644 msgid "Insert or update selection link (Ctrl+L)" msgstr "Vloží alebo aktualizuje odkaz výberu (Ctrl+L)" # tooltip -#: ../ui/composer-widget.ui.h:20 +#: ui/composer-widget.ui:668 msgid "Insert an image (Ctrl+G)" msgstr "Vloží obrázok (Ctrl+G)" # tooltip -#: ../ui/composer-widget.ui.h:21 +#: ui/composer-widget.ui:702 msgid "Remove selection formatting (Ctrl+Space)" msgstr "Odstráni formátovanie výberu (Ctrl+Space)" # tooltip -#: ../ui/composer-widget.ui.h:22 +#: ui/composer-widget.ui:726 msgid "Select spell checking languages" msgstr "Vyberie jazyky kontroly pravopisu" # tooltip -#: ../ui/conversation-email.ui.h:1 +#: ui/conversation-email.ui:27 msgid "Save all attachments" msgstr "Uloží všetky prílohy" # tooltip #. Note: The application will never show this button at the same time as unstar_button, one will always be hidden. -#: ../ui/conversation-email.ui.h:3 +#: ui/conversation-email.ui:50 msgid "Mark this message as starred" msgstr "Označí túto správu hviezdičkou" # tooltip #. Note: The application will never show this button at the same time as star_button, one will always be hidden. -#: ../ui/conversation-email.ui.h:5 +#: ui/conversation-email.ui:72 msgid "Mark this message as not starred" msgstr "Zruší označenie tejto správy hviezdičkou" # tooltip -#: ../ui/conversation-email.ui.h:6 +#: ui/conversation-email.ui:95 msgid "Display the message menu" msgstr "Zobrazí ponuku správy" # tooltip -#: ../ui/conversation-email.ui.h:7 +#: ui/conversation-email.ui:161 msgid "Open selected attachments" msgstr "Otvorí vybrané prílohy" # tooltip -#: ../ui/conversation-email.ui.h:8 +#: ui/conversation-email.ui:178 msgid "Save selected attachments" msgstr "Uloží všetky prílohy" # tooltip -#: ../ui/conversation-email.ui.h:9 +#: ui/conversation-email.ui:195 msgid "Select all attachments" msgstr "Vyberie všetky prílohy" -#: ../ui/conversation-email.ui.h:10 +#: ui/conversation-email.ui:240 msgid "Edit Draft" msgstr "Upraviť koncept" -#: ../ui/conversation-email.ui.h:11 +#: ui/conversation-email.ui:267 msgid "Draft message" msgstr "Koncept" -#: ../ui/conversation-email.ui.h:12 +#: ui/conversation-email.ui:283 msgid "This message has not yet been sent." msgstr "Táto správa ešte nebola odoslaná." -#: ../ui/conversation-email.ui.h:13 -msgid "Try Again" -msgstr "Skúsiť znovu" - -#: ../ui/conversation-email.ui.h:14 +#: ui/conversation-email.ui:329 msgid "Message not saved" msgstr "Správa nebola uložená" -#: ../ui/conversation-email.ui.h:15 +#: ui/conversation-email.ui:345 msgid "This message was sent, but has not been saved to your account." msgstr "Táto správa bola odoslaná, ale nebola uložená do vášho účtu." -#: ../ui/conversation-email-menus.ui.h:2 +#. Translators: Menu item to reply to a specific message. +#: ui/conversation-email-menus.ui:15 msgid "Reply to _All" msgstr "Odpoved_ať všetkým" -#: ../ui/conversation-email-menus.ui.h:4 +#. Translators: Menu item to mark a specific message as +#. read. +#: ui/conversation-email-menus.ui:30 msgid "_Mark Read" msgstr "_Označiť ako prečítané" -#: ../ui/conversation-email-menus.ui.h:5 +#: ui/conversation-email-menus.ui:36 msgid "_Mark Unread" msgstr "_Označiť ako neprečítané" -#: ../ui/conversation-email-menus.ui.h:6 +#. Translators: Menu item to mark all messages in a +#. conversation from this one as unread. +#: ui/conversation-email-menus.ui:42 msgid "Mark Unread From _Here" msgstr "Označiť ako neprečítané _odtiaľto" -#: ../ui/conversation-email-menus.ui.h:8 +#. Translators: Menu item to move a single, specific message +#. to the trash +#: ui/conversation-email-menus.ui:50 +msgid "_Trash" +msgstr "_Kôš" + +#. Translators: Menu item to delete a single, specific message +#: ui/conversation-email-menus.ui:57 +#| msgid "Delete" +msgid "_Delete…" +msgstr "O_dstrániť…" + +#. Translators: Menu item to view the source for a message +#: ui/conversation-email-menus.ui:69 msgid "_View Source" msgstr "Zobraziť z_droj" -#: ../ui/conversation-email-menus.ui.h:11 +#: ui/conversation-email-menus.ui:87 msgid "_Save All" msgstr "_Uložiť všetko" -#: ../ui/conversation-message-menus.ui.h:1 +#: ui/conversation-message-menus.ui:7 msgid "_Open Link" msgstr "_Otvoriť odkaz" -#: ../ui/conversation-message-menus.ui.h:2 +#: ui/conversation-message-menus.ui:11 msgid "Copy Link _Address" msgstr "Skopírovať _adresu odkazu" -#: ../ui/conversation-message-menus.ui.h:3 +#: ui/conversation-message-menus.ui:17 msgid "Send New _Message…" msgstr "Odoslať _novú správu…" -#: ../ui/conversation-message-menus.ui.h:4 +#: ui/conversation-message-menus.ui:21 msgid "Copy Email _Address" msgstr "Skopírovať _emailovú adresu" -#: ../ui/conversation-message-menus.ui.h:5 +#: ui/conversation-message-menus.ui:27 msgid "Save _Image As…" msgstr "_Uložiť obrázok ako…" -#: ../ui/conversation-message-menus.ui.h:6 +#: ui/conversation-message-menus.ui:33 msgid "_Select All" msgstr "Vybr_ať všetko" # placeholder -#: ../ui/conversation-message-menus.ui.h:8 +#: ui/conversation-message-menus.ui:43 msgid "Search for messages from" msgstr "Vyhľadať správy od" -#: ../ui/conversation-message.ui.h:1 +#: ui/conversation-message.ui:64 msgid "From " msgstr "Od " -#: ../ui/conversation-message.ui.h:2 +#: ui/conversation-message.ui:80 ui/conversation-message.ui:179 msgid "1/1/1970\t" msgstr "1.1.1970\t" -#: ../ui/conversation-message.ui.h:3 +#: ui/conversation-message.ui:103 msgid "Preview body text." msgstr "Náhľad textu v tele správy." -#: ../ui/conversation-message.ui.h:4 +#: ui/conversation-message.ui:203 msgid "Sent by:" msgstr "Odoslané kým:" -#: ../ui/conversation-message.ui.h:5 +#: ui/conversation-message.ui:248 msgid "Reply to:" msgstr "Komu odpovedať:" -#: ../ui/conversation-message.ui.h:6 +#: ui/conversation-message.ui:292 msgid "Subject" msgstr "Predmet" -#: ../ui/conversation-message.ui.h:7 -msgid "To:" -msgstr "Pre:" - -#: ../ui/conversation-message.ui.h:8 -msgid "Cc:" -msgstr "Kópia:" - -#: ../ui/conversation-message.ui.h:9 -msgid "Bcc:" -msgstr "Skrytá kópia:" - -#: ../ui/conversation-message.ui.h:10 +#: ui/conversation-message.ui:502 msgid "Show Images" msgstr "Zobraziť obrázky" -#: ../ui/conversation-message.ui.h:11 +#: ui/conversation-message.ui:515 msgid "Always Show From Sender" msgstr "Vždy zobraziť od odosielateľa" -#: ../ui/conversation-message.ui.h:12 +#: ui/conversation-message.ui:543 msgid "Remote images not shown" msgstr "Vzdialené obrázky neboli zobrazené" -#: ../ui/conversation-message.ui.h:13 +#: ui/conversation-message.ui:560 msgid "Only show remote images from senders you trust." msgstr "Vzdialené obrázky zobrazujte od odosielateľov, ktorým dôverujete." -#: ../ui/conversation-message.ui.h:14 +#: ui/conversation-message.ui:692 msgid "But actually goes to:" msgstr "Ale v skutočnosti odkazuje na:" -#: ../ui/conversation-message.ui.h:15 +#: ui/conversation-message.ui:723 msgid "The link appears to go to:" msgstr "Zdá sa, že tento odkaz odkazuje na:" -#: ../ui/conversation-message.ui.h:16 +#: ui/conversation-message.ui:735 msgid "Deceptive link found" msgstr "Našiel sa klamlivý odkaz" -#: ../ui/conversation-message.ui.h:17 +#: ui/conversation-message.ui:750 msgid "The email sender may be leading you to the wrong web site." msgstr "Odosielateľ emailu vás môže smerovať na nesprávnu webovú stránku." -#: ../ui/conversation-message.ui.h:18 +#: ui/conversation-message.ui:763 msgid "If unsure, contact the sender and ask before continuing." msgstr "Ak si nie ste istý, pred pokračovaním kontaktujte odosielateľa." # placeholder -#: ../ui/conversation-viewer.ui.h:1 +#: ui/conversation-viewer.ui:60 msgid "Find in conversation" msgstr "Nájdite v rozhovore" -#: ../ui/conversation-viewer.ui.h:2 +#: ui/conversation-viewer.ui:74 msgid "Find the previous occurrence of the search string." msgstr "Nájde predošlý výskyt hľadaného reťazca." # tooltip -#: ../ui/conversation-viewer.ui.h:3 +#: ui/conversation-viewer.ui:95 msgid "Find the next occurrence of the search string." msgstr "Nájde nasledovný výskyt hľadaného reťazca." -#: ../ui/edit_alternate_emails.glade.h:1 +#: ui/edit_alternate_emails.glade:112 msgid "Remove email address" msgstr "Odstráni emailové adresy" -#: ../ui/edit_alternate_emails.glade.h:2 +#: ui/edit_alternate_emails.glade:136 msgid "" "Some email services require additional addresses be configured on the " "server. Contact your email provider for more information." @@ -2202,478 +2600,534 @@ "Niektoré emailové služby vyžadujú nastavenie dodatočných adries na serveri. " "Pre viac informácií kontaktuje vášho poskytovateľa emailovej služby." -#: ../ui/edit_alternate_emails.glade.h:4 +#: ui/edit_alternate_emails.glade:175 msgid "_Update" msgstr "_Aktualizovať" -#: ../ui/find_bar.glade.h:1 +#: ui/find_bar.glade:66 msgid "Find:" msgstr "Nájsť:" -#: ../ui/find_bar.glade.h:2 +#: ui/find_bar.glade:89 msgid "_Previous" msgstr "_Predchádzajúca" -#: ../ui/find_bar.glade.h:3 +#: ui/find_bar.glade:107 msgid "_Next" msgstr "_Nasledujúca" -#: ../ui/find_bar.glade.h:4 +#: ui/find_bar.glade:125 msgid "_Case sensitive" msgstr "_Rozlišovať veľkosť písmen" -#: ../ui/find_bar.glade.h:5 +#: ui/find_bar.glade:145 msgid "label" msgstr "menovka" -#: ../ui/gtk/help-overlay.ui.h:1 +#: ui/gtk/help-overlay.ui:9 msgid "Conversation Shortcuts" msgstr "Skratky rozhovorov" -#: ../ui/gtk/help-overlay.ui.h:2 +#: ui/gtk/help-overlay.ui:13 ui/gtk/help-overlay.ui:254 msgctxt "shortcut window" msgid "General" msgstr "Všeobecné" -#: ../ui/gtk/help-overlay.ui.h:3 +#: ui/gtk/help-overlay.ui:17 msgctxt "shortcut window" msgid "Move focus to the next/previous pane" msgstr "Zmena zamerania na nasledovný/predošlý panel" -#: ../ui/gtk/help-overlay.ui.h:4 +#: ui/gtk/help-overlay.ui:24 msgctxt "shortcut window" msgid "Move focus to conversation list" msgstr "Zmena zamerania na zoznam rozhovorov" -#: ../ui/gtk/help-overlay.ui.h:5 +#: ui/gtk/help-overlay.ui:31 msgctxt "shortcut window" msgid "Detach composer window" msgstr "Odpojenie okna tvorcu správy" -#: ../ui/gtk/help-overlay.ui.h:6 +#: ui/gtk/help-overlay.ui:38 msgctxt "shortcut window" msgid "Close composer window" msgstr "Zavretie okna tvorcu správy" -#: ../ui/gtk/help-overlay.ui.h:7 +#: ui/gtk/help-overlay.ui:45 msgctxt "shortcut window" msgid "Show keyboard shortcuts" msgstr "Zobrazenie klávesových skratiek" -#: ../ui/gtk/help-overlay.ui.h:8 +#: ui/gtk/help-overlay.ui:52 msgctxt "shortcut window" msgid "Show help" msgstr "Zobrazenie pomocníka" -#: ../ui/gtk/help-overlay.ui.h:9 +#: ui/gtk/help-overlay.ui:59 msgctxt "shortcut window" msgid "Quit the application" msgstr "Ukončenie aplikácie" -#: ../ui/gtk/help-overlay.ui.h:10 +#: ui/gtk/help-overlay.ui:68 msgctxt "shortcut window" msgid "Search" msgstr "Vyhľadávanie" -#: ../ui/gtk/help-overlay.ui.h:11 +#: ui/gtk/help-overlay.ui:72 msgctxt "shortcut window" msgid "Jump to search box" msgstr "Prejdenie do poľa vyhľadávania" -#: ../ui/gtk/help-overlay.ui.h:12 +#: ui/gtk/help-overlay.ui:79 msgctxt "shortcut window" msgid "Find in current conversation" msgstr "Nájdenie v aktuálnom rozhovore" -#: ../ui/gtk/help-overlay.ui.h:13 +#: ui/gtk/help-overlay.ui:86 msgctxt "shortcut window" msgid "Find next/previous in current conversation" msgstr "Nájdenie ďalšieho/predošlého výrazu v aktuálnom rozhovore" -#: ../ui/gtk/help-overlay.ui.h:14 +#: ui/gtk/help-overlay.ui:95 ui/gtk/help-overlay.ui:274 msgctxt "shortcut window" msgid "Actions" msgstr "Akcie" -#: ../ui/gtk/help-overlay.ui.h:15 +#: ui/gtk/help-overlay.ui:99 msgctxt "shortcut window" msgid "Compose a new message" msgstr "Napísanie novej správy" -#: ../ui/gtk/help-overlay.ui.h:16 +#: ui/gtk/help-overlay.ui:106 msgctxt "shortcut window" msgid "Reply to sender " msgstr "Odpovedanie odosielateľovi" -#: ../ui/gtk/help-overlay.ui.h:17 +#: ui/gtk/help-overlay.ui:113 msgctxt "shortcut window" msgid "Reply to all" msgstr "Odpovedanie všetkým" -#: ../ui/gtk/help-overlay.ui.h:18 +#: ui/gtk/help-overlay.ui:120 msgctxt "shortcut window" msgid "Forward" msgstr "Preposlanie" -#: ../ui/gtk/help-overlay.ui.h:19 +#: ui/gtk/help-overlay.ui:127 msgctxt "shortcut window" msgid "Archive" msgstr "Archivácia" -#: ../ui/gtk/help-overlay.ui.h:20 +#: ui/gtk/help-overlay.ui:134 msgctxt "shortcut window" msgid "Move to trash" msgstr "Presunutie do koša" -#: ../ui/gtk/help-overlay.ui.h:21 +#: ui/gtk/help-overlay.ui:141 msgctxt "shortcut window" msgid "Toggle spam" msgstr "Prepnutie nevyžiadanej pošty" -#: ../ui/gtk/help-overlay.ui.h:22 +#: ui/gtk/help-overlay.ui:148 msgctxt "shortcut window" msgid "Move the conversation" msgstr "Presun rozhovoru" -#: ../ui/gtk/help-overlay.ui.h:23 +#: ui/gtk/help-overlay.ui:155 msgctxt "shortcut window" msgid "Label the conversation" msgstr "Pridanie menovky rozhovoru" -#: ../ui/gtk/help-overlay.ui.h:24 +#: ui/gtk/help-overlay.ui:163 msgctxt "shortcut window" msgid "Mark read" msgstr "Označenie ako prečítané" -#: ../ui/gtk/help-overlay.ui.h:25 +#: ui/gtk/help-overlay.ui:170 msgctxt "shortcut window" msgid "Mark unread" msgstr "Označenie ako neprečítané" -#: ../ui/gtk/help-overlay.ui.h:26 +#: ui/gtk/help-overlay.ui:179 msgctxt "shortcut window" msgid "View" msgstr "Zobrazenie" -#: ../ui/gtk/help-overlay.ui.h:27 +#: ui/gtk/help-overlay.ui:183 msgctxt "shortcut window" msgid "Zoom in" msgstr "Zväčšenie" -#: ../ui/gtk/help-overlay.ui.h:28 +#: ui/gtk/help-overlay.ui:190 msgctxt "shortcut window" msgid "Zoom out" msgstr "Zmenšenie" -#: ../ui/gtk/help-overlay.ui.h:29 +#: ui/gtk/help-overlay.ui:197 msgctxt "shortcut window" msgid "Reset zoom" msgstr "Obnovenie priblíženia" -#: ../ui/gtk/help-overlay.ui.h:30 +#: ui/gtk/help-overlay.ui:206 msgctxt "shortcut window" msgid "Additional Shortcuts" msgstr "Dodatočné skratky" -#: ../ui/gtk/help-overlay.ui.h:31 +#: ui/gtk/help-overlay.ui:210 msgctxt "shortcut window" msgid "Star" msgstr "Označenie hviezdičkou" -#: ../ui/gtk/help-overlay.ui.h:32 +#: ui/gtk/help-overlay.ui:217 msgctxt "shortcut window" msgid "Unstar" msgstr "Zrušiť označenie hviezdičkou" -#: ../ui/gtk/help-overlay.ui.h:33 +#: ui/gtk/help-overlay.ui:224 msgctxt "shortcut window" msgid "Delete" msgstr "Odstránenie" -#: ../ui/gtk/help-overlay.ui.h:34 +#: ui/gtk/help-overlay.ui:231 msgctxt "shortcut window" msgid "Jump to next (older) conversation" msgstr "Prejdenie na nasledovný (starší) rozhovor" -#: ../ui/gtk/help-overlay.ui.h:35 +#: ui/gtk/help-overlay.ui:238 msgctxt "shortcut window" msgid "Jump to previous (newer) conversation" msgstr "Prejdenie na predošlý (novší) rozhovor" -#: ../ui/gtk/help-overlay.ui.h:36 +#: ui/gtk/help-overlay.ui:250 msgid "Composer Shortcuts" msgstr "Skratky tvorcu správ" -#: ../ui/gtk/help-overlay.ui.h:37 +#: ui/gtk/help-overlay.ui:258 msgctxt "shortcut window" msgid "Quote text" msgstr "Citácia textu" # tooltip -#: ../ui/gtk/help-overlay.ui.h:38 +#: ui/gtk/help-overlay.ui:265 msgctxt "shortcut window" msgid "Unquote text" msgstr "Zrušenie citácie textu" -#: ../ui/gtk/help-overlay.ui.h:39 +#: ui/gtk/help-overlay.ui:278 msgctxt "shortcut window" msgid "Send" msgstr "Odoslanie" -#: ../ui/gtk/help-overlay.ui.h:40 +#: ui/gtk/help-overlay.ui:285 msgctxt "shortcut window" msgid "Add attachment" msgstr "Pridanie prílohy" -#: ../ui/gtk/help-overlay.ui.h:41 +#: ui/gtk/help-overlay.ui:294 msgctxt "shortcut window" msgid "Rich text mode" msgstr "Formátovanie textu" -#: ../ui/gtk/help-overlay.ui.h:42 +#: ui/gtk/help-overlay.ui:298 msgctxt "shortcut window" msgid "Bold text" msgstr "Tučný text" -#: ../ui/gtk/help-overlay.ui.h:43 +#: ui/gtk/help-overlay.ui:305 msgctxt "shortcut window" msgid "Italicize text" msgstr "Kurzíva textu" -#: ../ui/gtk/help-overlay.ui.h:44 +#: ui/gtk/help-overlay.ui:312 msgctxt "shortcut window" msgid "Underline text" msgstr "Podčiarknutie textu" -#: ../ui/gtk/help-overlay.ui.h:45 +#: ui/gtk/help-overlay.ui:319 msgctxt "shortcut window" msgid "Strike text" msgstr "Preškrtnutie textu" -#: ../ui/gtk/help-overlay.ui.h:46 +#: ui/gtk/help-overlay.ui:326 msgctxt "shortcut window" msgid "Insert a link" msgstr "Vloženie odkazu" -#: ../ui/gtk/help-overlay.ui.h:47 +#: ui/gtk/help-overlay.ui:333 msgctxt "shortcut window" msgid "Remove formatting" msgstr "Odstránenie formátovania" -#: ../ui/gtk/menus.ui.h:2 +#: ui/gtk/menus.ui:13 msgid "A_ccounts" msgstr "Úč_ty" -#: ../ui/gtk/menus.ui.h:4 +#: ui/gtk/menus.ui:23 msgid "_Keyboard Shortcuts" msgstr "Klávesové _skratky" -#: ../ui/login.glade.h:1 +#: ui/login.glade:88 msgid "email@example.com" msgstr "email@priklad.sk" -#: ../ui/login.glade.h:2 ../ui/password-dialog.glade.h:3 +#: ui/login.glade:107 ui/password-dialog.glade:108 msgid "Password" msgstr "Heslo" -#: ../ui/login.glade.h:3 +#: ui/login.glade:123 msgid "E_mail address" msgstr "E_mailová adresa" -#: ../ui/login.glade.h:4 +#: ui/login.glade:144 ui/login.glade:635 msgid "_Password" msgstr "_Heslo" -#: ../ui/login.glade.h:5 +#: ui/login.glade:178 msgid "S_ervice" msgstr "S_lužba" -#: ../ui/login.glade.h:6 +#: ui/login.glade:199 msgid "N_ame" msgstr "_Meno" -#: ../ui/login.glade.h:8 +#: ui/login.glade:256 msgid "N_ickname" msgstr "Pre_zývka" -#: ../ui/login.glade.h:9 +#: ui/login.glade:280 msgid "Work, Home, etc." msgstr "Práca, domov, atď." -#: ../ui/login.glade.h:10 +#: ui/login.glade:291 msgid "_Save sent mail" msgstr "_Uložiť odoslanú poštu" -#: ../ui/login.glade.h:11 +#: ui/login.glade:309 msgid "Addi_tional email addresses…" msgstr "Doda_točné emailové adresy…" -#: ../ui/login.glade.h:12 +#: ui/login.glade:353 msgid "IMAP settings" msgstr "Nastavenia IMAP" -#: ../ui/login.glade.h:13 +#: ui/login.glade:372 msgid "Se_rver" msgstr "Se_rver" -#: ../ui/login.glade.h:14 +#: ui/login.glade:393 msgid "imap.example.com" msgstr "imap.priklad.sk" -#: ../ui/login.glade.h:15 +#: ui/login.glade:409 msgid "P_ort" msgstr "P_ort" -#: ../ui/login.glade.h:16 +#: ui/login.glade:448 msgid "smtp.example.com" msgstr "smtp.priklad.sk" -#: ../ui/login.glade.h:17 +#: ui/login.glade:480 msgid "Ser_ver" msgstr "Ser_ver" -#: ../ui/login.glade.h:18 +#: ui/login.glade:501 msgid "Por_t" msgstr "Por_t" -#: ../ui/login.glade.h:19 +#: ui/login.glade:522 msgid "SMTP settings" msgstr "Nastavenia SMTP" -#: ../ui/login.glade.h:20 +#: ui/login.glade:541 msgid "User_name" msgstr "Používateľské _meno" -#: ../ui/login.glade.h:21 +#: ui/login.glade:562 msgid "Pass_word" msgstr "He_slo" -#: ../ui/login.glade.h:22 +#: ui/login.glade:582 msgid "SMTP username" msgstr "Používateľské meno pre SMTP" -#: ../ui/login.glade.h:23 +#: ui/login.glade:598 msgid "SMTP password" msgstr "Heslo pre SMTP" -#: ../ui/login.glade.h:24 +#: ui/login.glade:614 msgid "_Username" msgstr "_Používateľské meno" -#: ../ui/login.glade.h:25 +#: ui/login.glade:655 msgid "IMAP username" msgstr "Používateľské meno pre IMAP" -#: ../ui/login.glade.h:26 +#: ui/login.glade:671 msgid "IMAP password" msgstr "Heslo pre IMAP" -#: ../ui/login.glade.h:27 +#: ui/login.glade:688 msgid "Encr_yption" msgstr "Š_ifrovanie" -#: ../ui/login.glade.h:28 +#: ui/login.glade:711 msgid "Encrypt_ion" msgstr "Ši_frovanie" -#: ../ui/login.glade.h:30 +#: ui/login.glade:733 ui/login.glade:751 msgid "SSL/TLS" msgstr "SSL/TLS" -#: ../ui/login.glade.h:31 +#: ui/login.glade:734 ui/login.glade:752 msgid "STARTTLS" msgstr "STARTTLS" -#: ../ui/login.glade.h:32 +#: ui/login.glade:764 msgid "No authentication re_quired" msgstr "Ne_požaduje sa overenie totožnosti" -#: ../ui/login.glade.h:33 +#: ui/login.glade:781 msgid "Use IMAP cre_dentials" msgstr "Použiť p_overenia pre IMAP" -#: ../ui/login.glade.h:34 +#: ui/login.glade:888 msgid "Composer" msgstr "Tvorca správy" -#: ../ui/login.glade.h:35 +#: ui/login.glade:901 msgid "Save dra_fts on server" msgstr "Uložiť _koncepty na serveri" -#: ../ui/login.glade.h:36 +#: ui/login.glade:918 msgid "Si_gn emails (HTML allowed):" msgstr "_Podpísať emaily (s povoleným HTML):" -#: ../ui/login.glade.h:37 +#: ui/login.glade:976 msgid "Storage" msgstr "Úložisko" -#: ../ui/login.glade.h:38 +#: ui/login.glade:998 msgid "_Download mail" msgstr "_Prevziať poštu" -#: ../ui/main-toolbar.ui.h:1 +#: ui/main-toolbar.ui:51 +msgid "Toggle search bar" +msgstr "Prepne panel vyhľadávania" + +#: ui/main-toolbar.ui:72 msgid "Empty Spam or Trash folders" msgstr "Vyprázdni priečinky Nevyžiadaná pošta alebo Kôš" -#: ../ui/password-dialog.glade.h:1 +#: ui/main-toolbar.ui:112 +msgid "Reply" +msgstr "Odpovie" + +#: ui/main-toolbar.ui:135 +msgid "Reply All" +msgstr "Odpovie všetkým" + +# tooltip +#: ui/main-toolbar.ui:158 +msgid "Forward" +msgstr "Prepošle" + +#: ui/main-toolbar.ui:264 +msgid "Toggle find bar" +msgstr "Prepne panel nájdených výrazov" + +#: ui/main-toolbar.ui:306 +msgid "_Archive" +msgstr "_Archivovať" + +#: ui/main-toolbar-menus.ui:5 +msgid "Empty _Spam…" +msgstr "Vyprázdniť _Nevyžiadanú poštu…" + +#: ui/main-toolbar-menus.ui:9 +msgid "Empty _Trash…" +msgstr "Vyprázdniť _Kôš…" + +#: ui/main-toolbar-menus.ui:32 +msgid "Mark as S_pam" +msgstr "Označiť ako nev_yžiadanú poštu" + +#: ui/main-toolbar-menus.ui:36 +msgid "Mark as not S_pam" +msgstr "Označiť ako _vyžiadanú poštu" + +#: ui/main-window-info-bar.ui:97 +msgid "" +"If the problem is serious or persists, please copy and send these details to " +"the mailing list " +"or file a new " +"bug report." +msgstr "" +"Ak je problém vážny alebo pretrváva, prosím, skopírujte a odošlite tieto " +"podrobnosti do poštovej konferencie alebo nahláste nové chybové hlásenie." + +#: ui/main-window-info-bar.ui:113 +msgid "Details:" +msgstr "Podrobnosti:" + +#: ui/password-dialog.glade:74 msgid "SMTP Credentials" msgstr "Poverenia pre SMTP" -#: ../ui/password-dialog.glade.h:2 +#: ui/password-dialog.glade:91 msgid "Username" msgstr "Používateľské meno" -#: ../ui/password-dialog.glade.h:4 +#: ui/password-dialog.glade:152 msgid "_Remember password" msgstr "_Zapamätať heslo" -#: ../ui/password-dialog.glade.h:6 +#: ui/password-dialog.glade:210 msgid "_Authenticate" msgstr "_Overiť totožnosť" -#: ../ui/preferences-dialog.ui.h:1 +#: ui/preferences-dialog.ui:38 msgid "Reading" msgstr "Čítanie" -#: ../ui/preferences-dialog.ui.h:2 +#: ui/preferences-dialog.ui:51 msgid "_Automatically select next message" msgstr "_Automaticky vybrať ďalšiu správu" -#: ../ui/preferences-dialog.ui.h:3 +#: ui/preferences-dialog.ui:70 msgid "_Display conversation preview" msgstr "_Zobraziť náhľad rozhovoru" -#: ../ui/preferences-dialog.ui.h:4 +#: ui/preferences-dialog.ui:89 msgid "Use _three pane view" msgstr "Použiť zobrazenie s _troma panelmi" -#: ../ui/preferences-dialog.ui.h:5 +#: ui/preferences-dialog.ui:113 msgid "Notifications" msgstr "Oznámenia" -#: ../ui/preferences-dialog.ui.h:6 +#: ui/preferences-dialog.ui:126 msgid "_Play notification sounds" msgstr "_Prehrať zvuky upozornení" -#: ../ui/preferences-dialog.ui.h:7 +#: ui/preferences-dialog.ui:145 msgid "Show _notifications for new mail" msgstr "Zobraziť _upozornenia pre novú poštu" -#: ../ui/preferences-dialog.ui.h:8 -msgid "Always _watch for new mail" -msgstr "Vždy _sledovať novú poštu" - -#: ../ui/preferences-dialog.ui.h:9 -msgid "Geary will run in the background and notify of new mail" -msgstr "" -"Aplikácia Geary bude spustená na pozadí a bude upozorňovať na novú poštu" +#: ui/preferences-dialog.ui:164 +msgid "_Watch for new mail when closed" +msgstr "_Sledovať novú poštu, keď je aplikácia zavretá" + +#: ui/preferences-dialog.ui:168 +msgid "Geary will keep running after all windows are closed" +msgstr "Aplikácia Geary bude naďalej spustená aj po uzavretí všetkých okien" -#: ../ui/preferences-dialog.ui.h:10 +#: ui/preferences-dialog.ui:195 msgid "Preferences" msgstr "Nastavenia" -#: ../ui/remove_confirm.glade.h:1 +#: ui/remove_confirm.glade:43 msgid "" "Are you sure you want to remove this " "account? " @@ -2681,7 +3135,7 @@ "Naozaj chcete odstrániť tento účet? " -#: ../ui/remove_confirm.glade.h:2 +#: ui/remove_confirm.glade:58 msgid "" "All email associated with this account will be removed from your computer. " "This will not affect email on the server." @@ -2689,18 +3143,70 @@ "Všetky emaily priradené k tomuto účtu budú odstránené z vášho počítača. " "Emaily na serveri nebudú ovplyvnené." -#: ../ui/remove_confirm.glade.h:3 +#: ui/remove_confirm.glade:80 msgid "Nickname:" msgstr "Prezývka:" -#: ../ui/remove_confirm.glade.h:4 +#: ui/remove_confirm.glade:94 msgid "Email address:" msgstr "Emailová adresa:" -#: ../ui/upgrade_dialog.glade.h:1 +#: ui/upgrade_dialog.glade:60 msgid "Geary update in progress…" msgstr "Prebieha aktualizácia aplikácie Geary…" +# tooltip +#~ msgid "Default attachments directory" +#~ msgstr "Predvolený adresár pre prílohy" + +#~ msgid "Geary will run in the background and notify of new mail" +#~ msgstr "" +#~ "Aplikácia Geary bude spustená na pozadí a bude upozorňovať na novú poštu" + +#~| msgid "Geary" +#~ msgid "geary" +#~ msgstr "geary" + +#~ msgid "" +#~ "Geary encountered an error while connecting to the server. Please try " +#~ "again in a few moments." +#~ msgstr "" +#~ "Aplikácia Geary narazila na chybu počas pripájania k serveru. Prosím, " +#~ "skúste to znovu neskôr." + +#~ msgid "Geary Email" +#~ msgstr "Geary Email" + +#~ msgid "Geary Mail" +#~ msgstr "Geary Mail" + +#~ msgid "_Mark as…" +#~ msgstr "_Označiť ako…" + +#~ msgid "Add label" +#~ msgstr "Pridá menovku" + +#~ msgid "_Label" +#~ msgstr "_Menovka" + +#~ msgid "_Move" +#~ msgstr "_Presunúť" + +#~ msgid "Compose new message (Ctrl+N, N)" +#~ msgstr "Vytvorí novú správu (Ctrl+N, N)" + +#~ msgid "Reply (Ctrl+R, R)" +#~ msgstr "Odpovie (Ctrl+R, R)" + +#~ msgid "Reply all (Ctrl+Shift+R, Shift+R)" +#~ msgstr "Odpovie všetkým (Ctrl+Shift+R, Shift+R)" + +#~ msgid "Forward (Ctrl+L, F)" +#~ msgstr "Prepošle (Ctrl+L, F)" + +#~ msgid "Try Again" +#~ msgstr "Skúsiť znovu" + #~ msgid "Mail Client" #~ msgstr "Poštový klient" @@ -2769,12 +3275,6 @@ #~ msgid "No search results found." #~ msgstr "Žiadne výsledky vyhľadávania." -#~ msgid "From:" -#~ msgstr "Od:" - -#~ msgid "Date:" -#~ msgstr "Dátum:" - #~ msgid "Select _Message" #~ msgstr "Vybrať _správu" @@ -2877,6 +3377,3 @@ #~ msgid "Unable to login to email server" #~ msgstr "Nepodarilo sa prihlásiť k poštovému serveru" - -#~ msgid "_Details" -#~ msgstr "Po_drobnosti" diff -Nru geary-0.12.4/po/sl.po geary-3.32.0/po/sl.po --- geary-0.12.4/po/sl.po 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/po/sl.po 2019-03-17 13:39:29.000000000 +0000 @@ -3,15 +3,14 @@ # This file is distributed under the same license as the geary package. # # anthonmanix , 2012–2013. -# Matej Urbančič , 2014–2017. +# Matej Urbančič , 2014–2018. # msgid "" msgstr "" "Project-Id-Version: geary master\n" -"Report-Msgid-Bugs-To: https://bugzilla.gnome.org/enter_bug.cgi?" -"product=geary&keywords=I18N+L10N&component=internationalization\n" -"POT-Creation-Date: 2017-09-26 17:00+0200\n" -"PO-Revision-Date: 2017-09-27 16:30+0200\n" +"Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/geary/issues\n" +"POT-Creation-Date: 2018-10-25 02:47+0000\n" +"PO-Revision-Date: 2018-10-25 18:24+0200\n" "Last-Translator: Matej Urbančič \n" "Language-Team: Slovenian GNOME Translation Team \n" "Language: sl_SI\n" @@ -21,28 +20,56 @@ "Plural-Forms: nplurals=4; plural=(n%100==1 ? 1 : n%100==2 ? 2 : n%100==3 || n" "%100==4 ? 3 : 0);\n" "X-Poedit-SourceCharset: utf-8\n" -"X-Generator: Poedit 2.0.1\n" +"X-Generator: Poedit 2.1.1\n" + +#: desktop/geary-attach.contract.desktop.in:3 +msgid "Send by email" +msgstr "Pošlji z elektronsko pošto" + +#. Translators: Do NOT translate or transliterate this text (this is an icon file name)! +#: desktop/geary-attach.contract.desktop.in:5 +msgid "mail-send" +msgstr "mail-send" + +#: desktop/geary-attach.contract.desktop.in:6 +msgid "Send files using Geary" +msgstr "Pošlji datoteke s programom Geary" #. Translators: The application name -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:2 -#: ../desktop/org.gnome.Geary.desktop.in.h:1 -#: ../desktop/geary-autostart.desktop.in.h:1 +#: desktop/geary-autostart.desktop.in:3 +#: desktop/org.gnome.Geary.appdata.xml.in:11 +#: desktop/org.gnome.Geary.desktop.in:3 msgid "Geary" msgstr "Geary" -#. Translators: The development team's name -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:4 -msgid "Geary Development Team" -msgstr "Razvojna skupina Geary" +#: desktop/geary-autostart.desktop.in:4 desktop/org.gnome.Geary.desktop.in:4 +msgid "Email" +msgstr "Elektronska pošta" #. Translators: The application's summary / tagline -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:6 -#: ../desktop/org.gnome.Geary.desktop.in.h:4 -#: ../desktop/geary-autostart.desktop.in.h:4 +#: desktop/geary-autostart.desktop.in:5 +#: desktop/org.gnome.Geary.appdata.xml.in:15 +#: desktop/org.gnome.Geary.desktop.in:5 +#: src/client/application/geary-application.vala:21 msgid "Send and receive email" msgstr "Pošlji in prejmi sporočila" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:7 +#. Translators: These are desktop search terms. Do not translate semicolons, end line with a semicolon. +#: desktop/geary-autostart.desktop.in:7 +msgid "Email;E-mail;Mail;" +msgstr "elektronska pošta;epošta;e-pošta;pošta;sporočila;email;mail;" + +#. Translators: Do NOT translate or transliterate this text (this is an icon file name)! +#: desktop/geary-autostart.desktop.in:9 desktop/org.gnome.Geary.desktop.in:9 +msgid "org.gnome.Geary" +msgstr "org.gnome.Geary" + +#. Translators: The development team's name +#: desktop/org.gnome.Geary.appdata.xml.in:13 +msgid "Geary Development Team" +msgstr "Razvojna skupina Geary" + +#: desktop/org.gnome.Geary.appdata.xml.in:17 msgid "" "Geary is an email application built around conversations, for the GNOME 3 " "desktop. It allows you to read, find and send email with a straightforward, " @@ -52,7 +79,7 @@ "pogovorov. Omogoča branje, iskanje in pošiljanje sporočil prek preglednega " "modernega vmesnika." -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:8 +#: desktop/org.gnome.Geary.appdata.xml.in:22 msgid "" "Conversations allow you to read a complete discussion without having to find " "and click from message to message." @@ -60,104 +87,266 @@ "Sistem pogovorov omogoča branje nizov sporočil brez nepotrebnega klikanja " "med sporočilo." -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:9 +#: desktop/org.gnome.Geary.appdata.xml.in:26 msgid "Geary’s features include:" msgstr "Program Geary omogoča:" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:10 +#: desktop/org.gnome.Geary.appdata.xml.in:28 msgid "Quick email account setup" msgstr "Hitro nastavitev računa elektronske pošte" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:11 +#: desktop/org.gnome.Geary.appdata.xml.in:29 msgid "Shows related messages together in conversations" msgstr "Povezana sporočila so zbrana skupaj v obliki pogovora" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:12 +#: desktop/org.gnome.Geary.appdata.xml.in:30 msgid "Fast, full text and keyword search" msgstr "Hitro iskanje po celotnem besedilu s ključnimi besedami" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:13 +#: desktop/org.gnome.Geary.appdata.xml.in:31 msgid "Full-featured HTML and plain text message composer" msgstr "" "Zmogljiv sestavljalnik sporočil v besedilnem ali oblikovanem zapisu HTML" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:14 +#: desktop/org.gnome.Geary.appdata.xml.in:32 msgid "Desktop notification of new mail" msgstr "Obvestila o prispeli pošti" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:15 +#: desktop/org.gnome.Geary.appdata.xml.in:33 msgid "Compatible with GMail, Yahoo! Mail, Outlook.com and other IMAP servers" msgstr "" "Podprti so sistemi spletne pošte, kot so Gmail, Yahoo!, Outlook.com in drugi " "strežniki IMAP" #. Translators: A screenshot description. -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:17 +#: desktop/org.gnome.Geary.appdata.xml.in:47 msgid "Geary displaying a conversation" msgstr "Prikaz sporočil zbranih v pogovor" #. Translators: A screenshot description. -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:19 +#: desktop/org.gnome.Geary.appdata.xml.in:52 msgid "Geary showing the rich text composer" msgstr "Geary z odprtim oknom sestavljalnika" -#: ../desktop/org.gnome.Geary.desktop.in.h:2 -msgid "Email" -msgstr "Elektronska pošta" - -#: ../desktop/org.gnome.Geary.desktop.in.h:3 -msgid "Geary Email" -msgstr "Pošta Geary" - #. Translators: These are desktop search terms. Do not translate semicolons, end line with a semicolon. -#: ../desktop/org.gnome.Geary.desktop.in.h:6 +#: desktop/org.gnome.Geary.desktop.in:7 msgid "Mail;E-mail;IMAP;GMail;Yahoo;Hotmail;Outlook;" msgstr "Mail;E-mail;IMAP;GMail;Yahoo;Hotmail;Outlook;Elektronska pošta;" -#: ../desktop/org.gnome.Geary.desktop.in.h:7 ../ui/gtk/menus.ui.h:1 +#: desktop/org.gnome.Geary.desktop.in:21 ui/gtk/menus.ui:7 msgid "Compose Message" msgstr "Sestavi sporočilo" -#: ../desktop/geary-autostart.desktop.in.h:2 -#: ../src/client/application/geary-application.vala:21 -msgid "Mail Client" -msgstr "Poštni odjemalec" - -#: ../desktop/geary-autostart.desktop.in.h:3 -msgid "Geary Mail" -msgstr "Pošta Geary" +#: desktop/org.gnome.Geary.gschema.xml:8 +msgid "Maximize window" +msgstr "Razpni okno" -#: ../desktop/geary-autostart.desktop.in.h:5 -msgid "Email;E-mail;Mail;" -msgstr "elektronska pošta;epošta;e-pošta;pošta;sporočila;email;mail;" +#: desktop/org.gnome.Geary.gschema.xml:9 +msgid "True if the application window is maximized, false otherwise." +msgstr "" -#: ../desktop/geary-attach.contract.in.h:1 -msgid "Send by email" -msgstr "Pošlji z elektronsko pošto" +#: desktop/org.gnome.Geary.gschema.xml:14 +msgid "Width of window" +msgstr "Širina okna" -#: ../desktop/geary-attach.contract.in.h:2 -msgid "Send files using Geary" -msgstr "Pošlji datoteke s programom Geary" +#: desktop/org.gnome.Geary.gschema.xml:15 +msgid "The last recorded width of the application window." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:20 +msgid "Height of window" +msgstr "Višina okna" + +#: desktop/org.gnome.Geary.gschema.xml:21 +msgid "The last recorded height of the application window." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:26 +msgid "Position of folder list pane" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:27 +msgid "Position of the folder list Paned grabber." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:32 +msgid "Position of folder list pane when horizontal" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:33 +msgid "" +"Position of the folder list Paned grabber in the horizontal orientation." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:38 +msgid "Position of folder list pane when vertical" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:39 +msgid "Position of the folder list Paned grabber in the vertical orientation." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:44 +msgid "Orientation of the folder list pane" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:45 +msgid "True if the folder list Paned is in the horizontal orientation." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:50 +msgid "Position of message list pane" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:51 +msgid "Position of the message list Paned grabber." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:56 +msgid "Autoselect next message" +msgstr "Samodejno izberi naslednje sporočilo" + +#: desktop/org.gnome.Geary.gschema.xml:57 +msgid "True if we should autoselect the next available conversation." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:62 +msgid "Display message previews" +msgstr "Pokaži predogled sporočila" + +#: desktop/org.gnome.Geary.gschema.xml:63 +msgid "True if we should display a short preview of each message." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:68 +msgid "Languages that shall be used in the spell checker" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:69 +msgid "List of the languages to use in the spell checker." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:74 +msgid "Languages that are displayed in the spell checker popover" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:75 +msgid "" +"List of languages that are always displayed in the popover of the spell " +"checker." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:80 +msgid "Enable notification sounds" +msgstr "Omogoči zvok obvestil" + +#: desktop/org.gnome.Geary.gschema.xml:81 +msgid "True to play sounds for notifications and sending." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:86 +msgid "Show notifications for new mail" +msgstr "Prikaži obvestila o novo prispeli pošti" + +#: desktop/org.gnome.Geary.gschema.xml:87 +msgid "True to show notification bubbles." +msgstr "" -#: ../src/client/accounts/account-dialog-add-edit-pane.vala:51 -#: ../src/client/components/stock.vala:31 -#: ../ui/conversation-email-menus.ui.h:10 +#: desktop/org.gnome.Geary.gschema.xml:92 +msgid "Notify of new mail at startup" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:93 +#, fuzzy +#| msgid "Desktop notification of new mail" +msgid "True to notify of new mail at startup." +msgstr "Obvestila o prispeli pošti" + +#: desktop/org.gnome.Geary.gschema.xml:98 +msgid "Ask when opening an attachment" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:99 +#, fuzzy +#| msgid "To add them as attachments" +msgid "True to ask when opening an attachment." +msgstr "Da jih dodate kot priponke." + +#: desktop/org.gnome.Geary.gschema.xml:104 +msgid "Whether to compose emails in HTML" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:105 +msgid "True to compose emails in HTML; false for plain text." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:110 +msgid "Advisory strategy for full-text searching" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:111 +msgid "" +"Acceptable values are “exact”, “conservative”, “aggressive”, and “horizon”." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:116 +#, fuzzy +#| msgctxt "shortcut window" +#| msgid "Move focus to conversation list" +msgid "Zoom of conversation viewer" +msgstr "Premakni žarišče na seznam pogovorov" + +#: desktop/org.gnome.Geary.gschema.xml:117 +msgid "The zoom to apply on the conservation view." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:122 +#, fuzzy +#| msgctxt "shortcut window" +#| msgid "Detach composer window" +msgid "Size of detached composer window" +msgstr "Odpni okno skladalnika" + +#: desktop/org.gnome.Geary.gschema.xml:123 +msgid "The last recorded size of the detached composer window." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:128 +msgid "Base URL to look up contact avatars" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:129 +msgid "" +"A Gravatar or Libravatar compatible URL, set to the empty string to disable." +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:134 +msgid "Whether we migrated the old settings" +msgstr "" + +#: desktop/org.gnome.Geary.gschema.xml:135 +msgid "" +"False to check for the old “org.yorba.geary”-schema and copy its values." +msgstr "" + +#: src/client/accounts/account-dialog-add-edit-pane.vala:54 +#: src/client/components/stock.vala:31 ui/conversation-email-menus.ui:83 msgid "_Save" msgstr "_Shrani" -#: ../src/client/accounts/account-dialog-add-edit-pane.vala:51 -#: ../src/client/components/stock.vala:22 +#: src/client/accounts/account-dialog-add-edit-pane.vala:54 +#: src/client/components/stock.vala:22 msgid "_Add" msgstr "_Dodaj" #. reset/clear widgets -#: ../src/client/accounts/account-dialog-edit-alternate-emails-pane.vala:120 +#: src/client/accounts/account-dialog-edit-alternate-emails-pane.vala:120 #, c-format msgid "Additional addresses for %s" msgstr "Dodatni naslovi za %s" #. Sets min size. -#: ../src/client/accounts/account-dialog.vala:21 +#: src/client/accounts/account-dialog.vala:28 msgid "Accounts" msgstr "Računi" @@ -168,123 +357,123 @@ #. #. Page for adding or editing an account. #. / Placeholder text indicating that the user should type their first name and last name -#: ../src/client/accounts/add-edit-page.vala:10 +#: src/client/accounts/add-edit-page.vala:10 msgid "First Last" msgstr "Ime Priimek" -#: ../src/client/accounts/add-edit-page.vala:235 +#: src/client/accounts/add-edit-page.vala:240 msgid "Welcome to Geary." msgstr "Dobrodošli v programu Geary." -#: ../src/client/accounts/add-edit-page.vala:235 +#: src/client/accounts/add-edit-page.vala:240 msgid "Enter your account information to get started." msgstr "Vpišite podatke o računa za začetek uporabe." -#: ../src/client/accounts/add-edit-page.vala:255 +#: src/client/accounts/add-edit-page.vala:260 msgid "2 weeks back" msgstr "2 tedna nazaj" #. IDs are # of days -#: ../src/client/accounts/add-edit-page.vala:256 +#: src/client/accounts/add-edit-page.vala:261 msgid "1 month back" msgstr "1 mesec nazaj" -#: ../src/client/accounts/add-edit-page.vala:257 +#: src/client/accounts/add-edit-page.vala:262 msgid "3 months back" msgstr "3 mesece nazaj" -#: ../src/client/accounts/add-edit-page.vala:258 +#: src/client/accounts/add-edit-page.vala:263 msgid "6 months back" msgstr "6 mesecev nazaj" -#: ../src/client/accounts/add-edit-page.vala:259 +#: src/client/accounts/add-edit-page.vala:264 msgid "1 year back" msgstr "1 leto nazaj" -#: ../src/client/accounts/add-edit-page.vala:260 +#: src/client/accounts/add-edit-page.vala:265 msgid "2 years back" msgstr "2 leti nazaj" -#: ../src/client/accounts/add-edit-page.vala:261 +#: src/client/accounts/add-edit-page.vala:266 msgid "4 years back" msgstr "4 leta nazaj" #. Separator -#: ../src/client/accounts/add-edit-page.vala:263 +#: src/client/accounts/add-edit-page.vala:268 msgid "Everything" msgstr "Vse" -#: ../src/client/accounts/add-edit-page.vala:283 +#: src/client/accounts/add-edit-page.vala:288 msgid "Edit" msgstr "Uredi" -#: ../src/client/accounts/add-edit-page.vala:285 +#: src/client/accounts/add-edit-page.vala:290 msgid "Preview" msgstr "Predogled" -#: ../src/client/accounts/add-edit-page.vala:751 +#: src/client/accounts/add-edit-page.vala:778 msgid "Remem_ber passwords" msgstr "Zap_omni si gesla" -#: ../src/client/accounts/add-edit-page.vala:758 ../ui/login.glade.h:7 +#: src/client/accounts/add-edit-page.vala:785 ui/login.glade:233 msgid "Remem_ber password" msgstr "Zap_omni si geslo" -#: ../src/client/accounts/add-edit-page.vala:792 +#: src/client/accounts/add-edit-page.vala:819 msgid "Unable to validate:\n" msgstr "Overitev ni mogoča:\n" -#: ../src/client/accounts/add-edit-page.vala:794 -#| msgid " • Invalid account nickname.\n" +#: src/client/accounts/add-edit-page.vala:821 msgid " • Invalid account nickname.\n" msgstr " • Neveljaven naziv računa.\n" -#: ../src/client/accounts/add-edit-page.vala:797 -#| msgid " • Email address already added to Geary.\n" +#: src/client/accounts/add-edit-page.vala:824 msgid " • Email address already added to Geary.\n" msgstr " • Elektronski naslov je že dodan v program.\n" -#: ../src/client/accounts/add-edit-page.vala:801 -#| msgid " • IMAP connection error.\n" +#: src/client/accounts/add-edit-page.vala:828 msgid " • IMAP connection error.\n" msgstr " • Napaka povezave s strežnikom IMAP.\n" -#: ../src/client/accounts/add-edit-page.vala:804 -#| msgid " • IMAP username or password incorrect.\n" +#: src/client/accounts/add-edit-page.vala:831 msgid " • IMAP username or password incorrect.\n" msgstr "" " • Uporabniško ime ali geslo za povezavo s strežnikom IMAP je " "napačno.\n" -#: ../src/client/accounts/add-edit-page.vala:807 -#| msgid " • SMTP connection error.\n" +#: src/client/accounts/add-edit-page.vala:834 msgid " • SMTP connection error.\n" msgstr " • Napaka povezovanja s strežnikom SMTP.\n" -#: ../src/client/accounts/add-edit-page.vala:810 +#: src/client/accounts/add-edit-page.vala:837 msgid " • SMTP username or password incorrect.\n" msgstr "" " • Uporabniško ime ali geslo za povezavo s strežnikom SMTP je " "napačno.\n" -#: ../src/client/accounts/add-edit-page.vala:814 -#| msgid " • Connection error.\n" +#: src/client/accounts/add-edit-page.vala:841 msgid " • Connection error.\n" msgstr " • Napaka povezovanja.\n" -#: ../src/client/accounts/add-edit-page.vala:818 +#: src/client/accounts/add-edit-page.vala:845 msgid " • Username or password incorrect.\n" msgstr " • Uporabniško ime ali geslo za povezavo je napačno.\n" -#: ../src/client/application/geary-application.vala:22 +#: src/client/application/geary-application.vala:22 msgid "Copyright 2016 Software Freedom Conservancy Inc." msgstr "Avtorske pravice 2017 Software Freedom Conservancy Inc." -#: ../src/client/application/geary-application.vala:24 +#: src/client/application/geary-application.vala:23 +#, fuzzy +#| msgid "Copyright 2016-2017 Geary Development Team." +msgid "Copyright 2016-2018 Geary Development Team." +msgstr "Avtorske pravice 2016–2017 Razvojna skupina Geary" + +#: src/client/application/geary-application.vala:25 msgid "Visit the Geary web site" msgstr "Obišči spletišče Geary" -#: ../src/client/application/geary-application.vala:464 +#: src/client/application/geary-application.vala:416 #, c-format msgid "About %s" msgstr "O %s" @@ -292,249 +481,107 @@ #. Translators: add your name and email address to receive #. credit in the About dialog For example: Yamada Taro #. -#: ../src/client/application/geary-application.vala:468 +#: src/client/application/geary-application.vala:420 msgid "translator-credits" msgstr "Matej Urbančič " -#: ../src/client/application/geary-args.vala:10 +#: src/client/application/geary-args.vala:10 msgid "Start Geary with hidden main window" msgstr "Zaženi program s skritim glavnim oknom" -#: ../src/client/application/geary-args.vala:11 +#: src/client/application/geary-args.vala:11 msgid "Output debugging information" msgstr "Izpiši podrobnosti o razhroščevanju" -#: ../src/client/application/geary-args.vala:12 +#: src/client/application/geary-args.vala:12 msgid "Log conversation monitoring" msgstr "Beleži nadzorovanje pogovorov" -#: ../src/client/application/geary-args.vala:13 +#: src/client/application/geary-args.vala:13 msgid "Log network deserialization" msgstr "Beleži razporejanje omrežja" -#: ../src/client/application/geary-args.vala:14 +#: src/client/application/geary-args.vala:14 msgid "Log network activity" msgstr "Beleži dejavnost omrežja" #. / The IMAP replay queue is how changes on the server are replicated on the client. #. / It could also be called the IMAP events queue. -#: ../src/client/application/geary-args.vala:17 +#: src/client/application/geary-args.vala:17 msgid "Log IMAP replay queue" msgstr "Beleži vrsto odzivov IMAP" #. / Serialization is how commands and responses are converted into a stream of bytes for #. / network transmission -#: ../src/client/application/geary-args.vala:20 +#: src/client/application/geary-args.vala:20 msgid "Log network serialization" msgstr "Beleži zaporejanje omrežja" -#: ../src/client/application/geary-args.vala:21 +#: src/client/application/geary-args.vala:21 msgid "Log periodic activity" msgstr "Beleži dejavnost" -#: ../src/client/application/geary-args.vala:22 +#: src/client/application/geary-args.vala:22 msgid "Log database queries (generates lots of messages)" msgstr "Beleži poizvedbe podatkovne zbirke (ustvari mnogo sporočil)" #. / "Normalization" can also be called "synchronization" -#: ../src/client/application/geary-args.vala:24 +#: src/client/application/geary-args.vala:24 msgid "Log folder normalization" msgstr "Beleži usklajevanje map" -#: ../src/client/application/geary-args.vala:25 +#: src/client/application/geary-args.vala:25 msgid "Allow inspection of WebView" msgstr "Dovoli nadzor WebView" -#: ../src/client/application/geary-args.vala:26 +#: src/client/application/geary-args.vala:26 msgid "Revoke all server certificates with TLS warnings" msgstr "Prekliči vsa potrdila strežnika z varnostnimi opozorili TLS" -#: ../src/client/application/geary-args.vala:27 +#: src/client/application/geary-args.vala:27 msgid "Perform a graceful quit" msgstr "Izvedi končanje" -#: ../src/client/application/geary-args.vala:28 +#: src/client/application/geary-args.vala:28 msgid "Display program version" msgstr "Pokaži različico programa" #. This gives a command-line hint on how to open new composer windows with mailto: -#: ../src/client/application/geary-args.vala:53 +#: src/client/application/geary-args.vala:53 #, c-format msgid "Use %s to open a new composer window" msgstr "Uporabi %s za odpiranje novega okna sestavljalnika" -#: ../src/client/application/geary-args.vala:54 +#: src/client/application/geary-args.vala:56 msgid "Please report comments, suggestions and bugs to:" msgstr "Opombe, predloge in poročila o hroščih, pošljite na:" #. i18n: Command line arguments are invalid -#: ../src/client/application/geary-args.vala:61 +#: src/client/application/geary-args.vala:63 #, c-format msgid "Failed to parse command line options: %s\n" msgstr "Razčlenjevanje možnosti ukazne vrstice je spodletelo: %s\n" -#: ../src/client/application/geary-args.vala:72 +#: src/client/application/geary-args.vala:74 #, c-format msgid "Unrecognized command line option “%s”\n" msgstr "Neprepoznana možnost ukazne vrstice »%s«\n" -#: ../src/client/application/geary-controller.vala:57 -msgid "Delete conversation" -msgstr "Izbriši pogovor" - -#: ../src/client/application/geary-controller.vala:58 -msgid "Delete conversation (Shift+Delete)" -msgstr "Izbriši pogovor (SHIFT+Del)" - -#: ../src/client/application/geary-controller.vala:59 -msgid "Delete conversations (Shift+Delete)" -msgstr "Izbriši pogovore (SHIFT+Del)" - -#. This refers to the action ("move email to the trash"), not the Trash folder itself -#: ../src/client/application/geary-controller.vala:63 -msgid "Move conversation to Trash (Delete, Backspace)" -msgstr "Premakni pogovor v smeti (Del, Povratna tipka)" - -#: ../src/client/application/geary-controller.vala:64 -msgid "Move conversations to Trash (Delete, Backspace)" -msgstr "Premakni pogovore v smeti (Del, Povratna tipka)" - -#. This refers to the action ("archive an email"), not the Archive folder itself -#: ../src/client/application/geary-controller.vala:68 -msgid "_Archive" -msgstr "_Arhiv" - -#: ../src/client/application/geary-controller.vala:69 -msgid "Archive conversation (A)" -msgstr "Arhiviraj pogovor (A)" - -#: ../src/client/application/geary-controller.vala:70 -msgid "Archive conversations (A)" -msgstr "Arhiviraj pogovore (A)" - -#: ../src/client/application/geary-controller.vala:73 -msgid "Mark as S_pam" -msgstr "Označi kot _neželeno pošto" - -#: ../src/client/application/geary-controller.vala:74 -msgid "Mark as not S_pam" -msgstr "Označi kot _želeno pošto" - -#: ../src/client/application/geary-controller.vala:76 -#: ../src/client/application/geary-controller.vala:448 -msgid "Mark conversation" -msgstr "Označi pogovor" - -#: ../src/client/application/geary-controller.vala:77 -msgid "Mark conversations" -msgstr "Označi pogovore" - -#: ../src/client/application/geary-controller.vala:78 -msgid "Add label to conversation" -msgstr "Dodaj oznako pogovoru" - -#: ../src/client/application/geary-controller.vala:79 -msgid "Add label to conversations" -msgstr "Dodaj oznako pogovorom" - -#: ../src/client/application/geary-controller.vala:80 -#: ../src/client/application/geary-controller.vala:487 -msgid "Move conversation" -msgstr "Premakni pogovor" - -#: ../src/client/application/geary-controller.vala:81 -msgid "Move conversations" -msgstr "Premakni pogovore" - -#: ../src/client/application/geary-controller.vala:450 -msgid "_Mark as…" -msgstr "_Označi kot ..." - -#: ../src/client/application/geary-controller.vala:456 -msgid "Mark as _Read" -msgstr "Označi kot _prebrano" - -#: ../src/client/application/geary-controller.vala:462 -msgid "Mark as _Unread" -msgstr "Označi kot _neprebrano" - -#: ../src/client/application/geary-controller.vala:468 -msgid "_Star" -msgstr "Dodaj _zvezdico" - -#: ../src/client/application/geary-controller.vala:473 -msgid "U_nstar" -msgstr "_Odstrani zvezdico" - -#: ../src/client/application/geary-controller.vala:483 -msgid "Add label" -msgstr "Dodaj oznako" - -#: ../src/client/application/geary-controller.vala:484 -msgid "_Label" -msgstr "_Oznaka" - -#: ../src/client/application/geary-controller.vala:488 -msgid "_Move" -msgstr "_Premakni" - -#: ../src/client/application/geary-controller.vala:492 -msgid "Compose new message (Ctrl+N, N)" -msgstr "Sestavi novo sporočilo (CTRL+N, N)" - -#: ../src/client/application/geary-controller.vala:496 -#: ../ui/conversation-email-menus.ui.h:1 -msgid "_Reply" -msgstr "_Odgovori" - -#: ../src/client/application/geary-controller.vala:497 -msgid "Reply (Ctrl+R, R)" -msgstr "Odgovori (CTRL+R, R)" - -#: ../src/client/application/geary-controller.vala:501 -msgid "R_eply All" -msgstr "O_dgovori vsem" - -#: ../src/client/application/geary-controller.vala:502 -msgid "Reply all (Ctrl+Shift+R, Shift+R)" -msgstr "Odgovori vsem (CTRL+SHIFT+R, SHIFT+R)" - -#: ../src/client/application/geary-controller.vala:507 -#: ../ui/conversation-email-menus.ui.h:3 -msgid "_Forward" -msgstr "_Posreduj" - -#: ../src/client/application/geary-controller.vala:508 -msgid "Forward (Ctrl+L, F)" -msgstr "Posreduj (CTRL+L, F)" - -#: ../src/client/application/geary-controller.vala:538 -msgid "Empty _Spam…" -msgstr "Izprazni mapo _neželene pošte" - -#: ../src/client/application/geary-controller.vala:542 -msgid "Empty _Trash…" -msgstr "Izprazni _smeti" - -#. No callback is connected, since we bind the toggle button to the search bar visibility -#: ../src/client/application/geary-controller.vala:574 -msgid "Toggle search bar" -msgstr "Preklopi prikaz iskalne vrstice" - -#. No callback is connected, since we bind the toggle button to the find bar visibility -#: ../src/client/application/geary-controller.vala:579 -msgid "Toggle find bar" -msgstr "Preklopi vrstico iskanja" +#. Translators: File name used in save chooser when saving +#. attachments that do not otherwise have a name. +#: src/client/application/geary-controller.vala:68 +msgid "Untitled" +msgstr "" -#: ../src/client/application/geary-controller.vala:757 +#: src/client/application/geary-controller.vala:721 msgid "Unable to store server trust exception" msgstr "Ni mogoče shraniti potrditve izjeme varnosti strežnika" -#: ../src/client/application/geary-controller.vala:992 +#: src/client/application/geary-controller.vala:972 msgid "Your settings are insecure" msgstr "Nastavitve niso varne" -#: ../src/client/application/geary-controller.vala:993 +#: src/client/application/geary-controller.vala:973 msgid "" "Your IMAP and/or SMTP settings do not specify SSL or TLS. This means your " "username and password could be read by another person on the network. Are " @@ -544,29 +591,17 @@ "da lahko tretje osebe neovirano pridobijo uporabniško ime in geslo. Ali ste " "prepričani, da želite to storiti?" -#: ../src/client/application/geary-controller.vala:994 +#: src/client/application/geary-controller.vala:974 msgid "Co_ntinue" msgstr "_Nadaljuj" -#: ../src/client/application/geary-controller.vala:1041 -msgid "Error connecting to the server" -msgstr "Napaka med povezovanjem s strežnikom" - -#: ../src/client/application/geary-controller.vala:1042 -msgid "" -"Geary encountered an error while connecting to the server. Please try again " -"in a few moments." -msgstr "" -"Med povezovanjem s strežnikom je prišlo do napake. Poskusite znova čez nekaj " -"trenutkov." - #. / Displayed in the space-limited status bar when a message fails to be sent due to error. -#: ../src/client/application/geary-controller.vala:1079 -#: ../src/client/components/status-bar.vala:29 +#: src/client/application/geary-controller.vala:1078 +#: src/client/components/status-bar.vala:29 msgid "Error sending email" msgstr "Napaka med pošiljanjem sporočila" -#: ../src/client/application/geary-controller.vala:1080 +#: src/client/application/geary-controller.vala:1079 msgid "" "Geary encountered an error sending an email. If the problem persists, " "please manually delete the email from your Outbox folder." @@ -577,12 +612,12 @@ #. Displayed in the space-limited status bar when a message fails to be uploaded #. to Sent Mail after being sent. -#: ../src/client/application/geary-controller.vala:1084 -#: ../src/client/components/status-bar.vala:33 +#: src/client/application/geary-controller.vala:1083 +#: src/client/components/status-bar.vala:33 msgid "Error saving sent mail" msgstr "Napaka shranjevanja poslanega sporočila" -#: ../src/client/application/geary-controller.vala:1085 +#: src/client/application/geary-controller.vala:1084 msgid "" "Geary encountered an error saving a sent message to Sent Mail. The message " "will stay in your Outbox folder until you delete it." @@ -590,19 +625,19 @@ "Prišlo je do napake med shranjevanjem sporočila v mapo poslane pošte. " "Sporočilo bo ostalo v mapi, dokler ga ročno ne izbrišete." -#: ../src/client/application/geary-controller.vala:1154 +#: src/client/application/geary-controller.vala:1151 msgid "Labels" msgstr "Oznake" #. give the user two options: reset the Account local store, or exit Geary. A third #. could be done to leave the Account in an unopened state, but we don't currently #. have provisions for that. -#: ../src/client/application/geary-controller.vala:1166 +#: src/client/application/geary-controller.vala:1163 #, c-format msgid "Unable to open the database for %s" msgstr "Podatkovne zbirke za %s ni mogoče odpreti" -#: ../src/client/application/geary-controller.vala:1167 +#: src/client/application/geary-controller.vala:1164 #, c-format msgid "" "There was an error opening the local mail database for this account. This is " @@ -626,20 +661,20 @@ "S ponovno izgradnjo podatkovne zbirke so uničeni vsi krajevni podatki " "sporočil in prilog. Sporočila na strežniku ostanejo nespremenjena." -#: ../src/client/application/geary-controller.vala:1169 +#: src/client/application/geary-controller.vala:1166 msgid "_Rebuild" msgstr "Ponovno _izgradi" -#: ../src/client/application/geary-controller.vala:1169 +#: src/client/application/geary-controller.vala:1166 msgid "E_xit" msgstr "_Končaj" -#: ../src/client/application/geary-controller.vala:1178 +#: src/client/application/geary-controller.vala:1175 #, c-format msgid "Unable to rebuild database for “%s”" msgstr "Ponovna izgradnja mape za »%s« ni mogoča." -#: ../src/client/application/geary-controller.vala:1179 +#: src/client/application/geary-controller.vala:1176 #, c-format msgid "" "Error during rebuild:\n" @@ -652,14 +687,14 @@ #. some other problem opening the account ... as with other flow path, can't run #. Geary today with an account in unopened state, so have to exit -#: ../src/client/application/geary-controller.vala:1201 -#: ../src/client/application/geary-controller.vala:1211 -#: ../src/client/application/geary-controller.vala:1222 +#: src/client/application/geary-controller.vala:1198 +#: src/client/application/geary-controller.vala:1208 +#: src/client/application/geary-controller.vala:1219 #, c-format msgid "Unable to open local mailbox for %s" msgstr "Ni mogoče odpreti krajevnega poštnega predala za %s" -#: ../src/client/application/geary-controller.vala:1202 +#: src/client/application/geary-controller.vala:1199 #, c-format msgid "" "There was an error opening the local mail database for this account. This is " @@ -677,7 +712,7 @@ "\n" "%s" -#: ../src/client/application/geary-controller.vala:1212 +#: src/client/application/geary-controller.vala:1209 msgid "" "The version number of the local mail database is formatted for a newer " "version of Geary. Unfortunately, the database cannot be “rolled back” to " @@ -691,7 +726,7 @@ "\n" "Namestite najnovejšo različico programa in poskusite znova." -#: ../src/client/application/geary-controller.vala:1223 +#: src/client/application/geary-controller.vala:1220 msgid "" "There was an error opening the local account. This is probably due to " "connectivity issues.\n" @@ -703,15 +738,15 @@ "\n" "Preverite povezave in ponovno zaženite program." -#: ../src/client/application/geary-controller.vala:1999 +#: src/client/application/geary-controller.vala:2038 msgid "Undo move (Ctrl+Z)" msgstr "Razveljavi premik (CTRL+Z)" -#: ../src/client/application/geary-controller.vala:2009 +#: src/client/application/geary-controller.vala:2048 msgid "Are you sure you want to open these attachments?" msgstr "Ali ste prepričani, da želite odpreti vse priloge?" -#: ../src/client/application/geary-controller.vala:2010 +#: src/client/application/geary-controller.vala:2049 msgid "" "Attachments may cause damage to your system if opened. Only open files from " "trusted sources." @@ -719,16 +754,22 @@ "Priloge lahko vsebujejo zlonamerno kodo, ki lahko škoduje sistemu. Odpirajte " "samo datoteke iz zaupljivih virov." -#: ../src/client/application/geary-controller.vala:2011 +#: src/client/application/geary-controller.vala:2050 msgid "Don’t _ask me again" msgstr "_Ne vprašaj več" -#: ../src/client/application/geary-controller.vala:2121 +#. Translators: Dialog primary label when prompting to +#. overwrite a file. The string substitution is the file'sx +#. name. +#: src/client/application/geary-controller.vala:2179 #, c-format msgid "A file named “%s” already exists. Do you want to replace it?" msgstr "Datoteka z imenom »%s« že obstaja. Ali jo želite zamenjati?" -#: ../src/client/application/geary-controller.vala:2123 +#. Translators: Dialog secondary label when prompting to +#. overwrite a file. The string substitution is the parent +#. folder's name. +#: src/client/application/geary-controller.vala:2186 #, c-format msgid "" "The file already exists in “%s”. Replacing it will overwrite its contents." @@ -736,40 +777,44 @@ "Datoteka že obstaja v mapi »%s«. S prepisom bo izgubljena vsebina ciljne " "datoteke." -#: ../src/client/application/geary-controller.vala:2126 +#: src/client/application/geary-controller.vala:2190 msgid "_Replace" msgstr "_Zamenjaj" -#. Find out what to do with the inline composers. -#. TODO: Remove this in favor of automatically saving drafts -#: ../src/client/application/geary-controller.vala:2374 -msgid "Close open draft messages?" -msgstr "Ali želite zapreti odprt osnutek sporočila?" +#: src/client/application/geary-controller.vala:2460 +#, fuzzy +#| msgid "Close open draft messages?" +msgid "Close the draft message?" +msgid_plural "Close all draft messages?" +msgstr[0] "Ali želite zapreti odprt osnutek sporočila?" +msgstr[1] "Ali želite zapreti odprt osnutek sporočila?" +msgstr[2] "Ali želite zapreti odprt osnutek sporočila?" +msgstr[3] "Ali želite zapreti odprt osnutek sporočila?" -#: ../src/client/application/geary-controller.vala:2496 +#: src/client/application/geary-controller.vala:2586 #, c-format msgid "Empty all email from your %s folder?" msgstr "Ali želite odstraniti vsa elektronska sporočila iz mape %s?" -#: ../src/client/application/geary-controller.vala:2497 +#: src/client/application/geary-controller.vala:2587 msgid "This removes the email from Geary and your email server." msgstr "S tem bo sporočilo odstranjeno krajevno in iz poštnega strežnika." -#: ../src/client/application/geary-controller.vala:2498 +#: src/client/application/geary-controller.vala:2588 msgid "This cannot be undone." msgstr "Dejanja ni mogoče povrniti." -#: ../src/client/application/geary-controller.vala:2499 +#: src/client/application/geary-controller.vala:2589 #, c-format msgid "Empty %s" msgstr "Izprazni %s" -#: ../src/client/application/geary-controller.vala:2516 +#: src/client/application/geary-controller.vala:2606 #, c-format msgid "Error emptying %s" msgstr "Napaka praznjenja %s" -#: ../src/client/application/geary-controller.vala:2546 +#: src/client/application/geary-controller.vala:2638 msgid "Do you want to permanently delete this message?" msgid_plural "Do you want to permanently delete these messages?" msgstr[0] "Ali res želite trajno izbrisati ta sporočila?" @@ -777,147 +822,380 @@ msgstr[2] "Ali res želite trajno izbrisati ti sporočili?" msgstr[3] "Ali res želite trajno izbrisati ta sporočila?" -#: ../src/client/application/geary-controller.vala:2548 +#: src/client/application/geary-controller.vala:2640 msgid "Delete" msgstr "Izbriši" -#: ../src/client/application/geary-controller.vala:2580 -msgid "Undo archive (Ctrl+Z)" -msgstr "Razveljavi arhiviranje (CTRL+Z)" - -#: ../src/client/application/geary-controller.vala:2595 +#: src/client/application/geary-controller.vala:2654 msgid "Undo trash (Ctrl+Z)" msgstr "Razveljavi smeti (CTRL+Z)" -#: ../src/client/application/geary-controller.vala:2649 +#: src/client/application/geary-controller.vala:2704 +msgid "Undo archive (Ctrl+Z)" +msgstr "Razveljavi arhiviranje (CTRL+Z)" + +#: src/client/application/geary-controller.vala:2749 msgid "Undo (Ctrl+Z)" msgstr "Razveljavi (CTRL+Z)" -#: ../src/client/application/geary-controller.vala:2780 +#. Translators: The label for an in-app notification. The +#. string substitution is a list of recipients of the email. +#: src/client/application/geary-controller.vala:2826 +#, c-format +msgid "Successfully sent mail to %s." +msgstr "" + +#: src/client/application/geary-controller.vala:2907 msgid "Failed to open default text editor." msgstr "Odpiranje privzetega urejevalnika besedila je spodletelo." -#: ../src/client/components/main-window.vala:389 +#. Tooltips +#: src/client/components/main-toolbar.vala:69 +msgid "Delete conversation (Shift+Delete)" +msgstr "Izbriši pogovor (SHIFT+Del)" + +#: src/client/components/main-toolbar.vala:70 +msgid "Delete conversations (Shift+Delete)" +msgstr "Izbriši pogovore (SHIFT+Del)" + +#: src/client/components/main-toolbar.vala:71 +msgid "Move conversation to Trash (Delete, Backspace)" +msgstr "Premakni pogovor v smeti (Del, Povratna tipka)" + +#: src/client/components/main-toolbar.vala:72 +msgid "Move conversations to Trash (Delete, Backspace)" +msgstr "Premakni pogovore v smeti (Del, Povratna tipka)" + +#: src/client/components/main-toolbar.vala:73 +msgid "Archive conversation (A)" +msgstr "Arhiviraj pogovor (A)" + +#: src/client/components/main-toolbar.vala:74 +msgid "Archive conversations (A)" +msgstr "Arhiviraj pogovore (A)" + +#: src/client/components/main-toolbar.vala:75 +msgid "Mark conversation" +msgstr "Označi pogovor" + +#: src/client/components/main-toolbar.vala:76 +msgid "Mark conversations" +msgstr "Označi pogovore" + +#: src/client/components/main-toolbar.vala:77 +msgid "Add label to conversation" +msgstr "Dodaj oznako pogovoru" + +#: src/client/components/main-toolbar.vala:78 +msgid "Add label to conversations" +msgstr "Dodaj oznako pogovorom" + +#: src/client/components/main-toolbar.vala:79 +msgid "Move conversation" +msgstr "Premakni pogovor" + +#: src/client/components/main-toolbar.vala:80 +msgid "Move conversations" +msgstr "Premakni pogovore" + +#: src/client/components/main-window.vala:440 #, c-format -#| msgid "%s (%s)" msgid "%s (%d)" msgstr "%s (%d)" -#: ../src/client/components/search-bar.vala:8 -#: ../src/client/folder-list/folder-list-search-branch.vala:38 -#: ../src/engine/api/geary-special-folder-type.vala:51 +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:54 +#, fuzzy, c-format +#| msgid "Error connecting to the server" +msgid "Problem connecting to incoming server for %s" +msgstr "Napaka med povezovanjem s strežnikom" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:56 +#: src/client/components/main-window-info-bar.vala:64 +#, c-format +msgid "" +"Could not connect to %s, check your Internet access and the server name and " +"try again" +msgstr "" + +#: src/client/components/main-window-info-bar.vala:57 +#: src/client/components/main-window-info-bar.vala:66 +msgid "Retry connecting now" +msgstr "" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:62 +#, fuzzy, c-format +#| msgid "Error connecting to the server" +msgid "Problem connecting to outgoing server for %s" +msgstr "Napaka med povezovanjem s strežnikom" + +#: src/client/components/main-window-info-bar.vala:65 +msgid "Try reconnecting now" +msgstr "" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:71 +#, c-format +msgid "Problem with connection to incoming server for %s" +msgstr "" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:73 +#: src/client/components/main-window-info-bar.vala:81 +#, c-format +msgid "Network error talking to %s, check your Internet access and try again" +msgstr "" + +#: src/client/components/main-window-info-bar.vala:74 +#: src/client/components/main-window-info-bar.vala:82 +#: src/client/components/main-window-info-bar.vala:90 +#: src/client/components/main-window-info-bar.vala:98 +#: src/client/components/main-window-info-bar.vala:119 +msgid "Try reconnecting" +msgstr "" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:79 +#, fuzzy, c-format +#| msgid "Error connecting to the server" +msgid "Problem with connection to outgoing server for %s" +msgstr "Napaka med povezovanjem s strežnikom" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:87 +#, c-format +msgid "Problem communicating with incoming server for %s" +msgstr "" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:89 +#, c-format +msgid "" +"Geary did not understand a message from %s or vice versa, please file a bug " +"report" +msgstr "" + +#: src/client/components/main-window-info-bar.vala:94 +msgid "Problem communicating with outgoing mail server" +msgstr "" + +#. Translators: First string substitution is the server +#. name, second is the account name +#: src/client/components/main-window-info-bar.vala:97 +#, c-format +msgid "" +"Could not communicate with %s for %s, check the server name and try again in " +"a moment" +msgstr "" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:103 +#, c-format +msgid "Incoming mail server password required for %s" +msgstr "" + +#: src/client/components/main-window-info-bar.vala:104 +msgid "Messages cannot be received without the correct password." +msgstr "" + +#: src/client/components/main-window-info-bar.vala:105 +msgid "Retry receiving email, you will be prompted for a password" +msgstr "" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:110 +#, c-format +msgid "Outgoing mail server password required for %s" +msgstr "" + +#: src/client/components/main-window-info-bar.vala:111 +msgid "Messages cannot be sent without the correct password." +msgstr "" + +#: src/client/components/main-window-info-bar.vala:112 +msgid "Retry sending queued messages, you will be prompted for a password" +msgstr "" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:117 +#, c-format +msgid "A problem occurred checking mail for %s" +msgstr "" + +#: src/client/components/main-window-info-bar.vala:118 +#: src/client/components/main-window-info-bar.vala:125 +msgid "Something went wrong, please file a bug report if the problem persists" +msgstr "" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:124 +#, c-format +msgid "A problem occurred sending mail for %s" +msgstr "" + +#: src/client/components/main-window-info-bar.vala:126 +msgid "Retry sending queued messages" +msgstr "" + +#: src/client/components/main-window-info-bar.vala:137 +msgid "A database problem has occurred" +msgstr "" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:139 +#, c-format +msgid "Messages for %s must be downloaded again." +msgstr "" + +#: src/client/components/main-window-info-bar.vala:152 +msgid "Geary has encountered a problem" +msgstr "" + +#: src/client/components/main-window-info-bar.vala:153 +msgid "" +"Please check the technical details and report the problem if it persists." +msgstr "" + +#: src/client/components/main-window-info-bar.vala:161 +msgid "_Details" +msgstr "_Podrobnosti" + +#: src/client/components/main-window-info-bar.vala:162 +msgid "View technical details about the error" +msgstr "" + +#: src/client/components/main-window-info-bar.vala:166 +msgid "_Retry" +msgstr "_Poskusi znova" + +#: src/client/components/main-window-info-bar.vala:253 +msgid "Details" +msgstr "Podrobnosti" + +#: src/client/components/main-window-info-bar.vala:266 +#: src/client/components/stock.vala:23 +msgid "_Close" +msgstr "_Zapri" + +#: src/client/components/main-window-info-bar.vala:270 +msgid "Copy to Clipboard" +msgstr "Kopiraj v odložišče" + +#: src/client/components/main-window-info-bar.vala:273 +msgid "" +"Copy technical details to clipboard for pasting into an email or bug report" +msgstr "" + +#: src/client/components/search-bar.vala:8 +#: src/client/folder-list/folder-list-search-branch.vala:38 +#: src/engine/api/geary-special-folder-type.vala:51 msgid "Search" msgstr "Poišči" #. Search entry. -#: ../src/client/components/search-bar.vala:23 +#: src/client/components/search-bar.vala:23 msgid "Search all mail in account for keywords (Ctrl+S)" msgstr "Preišči vsa sporočila računa za ključne besede (CTRL+S)" -#: ../src/client/components/search-bar.vala:100 +#: src/client/components/search-bar.vala:100 #, c-format msgid "Indexing %s account" msgstr "Poteka ustvarjanje kazala računa %s" -#: ../src/client/components/search-bar.vala:111 -#: ../src/client/folder-list/folder-list-search-branch.vala:39 +#: src/client/components/search-bar.vala:111 +#: src/client/folder-list/folder-list-search-branch.vala:39 #, c-format msgid "Search %s account" msgstr "Preišči račun %s" #. / Displayed in the space-limited status bar while a message is in the process of being sent. -#: ../src/client/components/status-bar.vala:26 +#: src/client/components/status-bar.vala:26 msgid "Sending…" msgstr "Poteka pošiljanje ..." -#: ../src/client/components/stock.vala:18 ../ui/account_cannot_remove.glade.h:3 +#: src/client/components/stock.vala:18 ui/account_cannot_remove.glade:74 msgid "_OK" msgstr "_V redu" -#: ../src/client/components/stock.vala:19 ../ui/edit_alternate_emails.glade.h:3 -#: ../ui/password-dialog.glade.h:5 ../ui/remove_confirm.glade.h:5 +#: src/client/components/stock.vala:19 ui/edit_alternate_emails.glade:160 +#: ui/password-dialog.glade:196 ui/remove_confirm.glade:155 msgid "_Cancel" msgstr "_Prekliči" -#: ../src/client/components/stock.vala:21 ../ui/gtk/menus.ui.h:6 +#: src/client/components/stock.vala:21 ui/gtk/menus.ui:34 msgid "_About" msgstr "_O programu" -#: ../src/client/components/stock.vala:23 -msgid "_Close" -msgstr "_Zapri" - -#: ../src/client/components/stock.vala:24 +#: src/client/components/stock.vala:24 msgid "_Discard" msgstr "_Zavrzi" -#: ../src/client/components/stock.vala:25 ../ui/gtk/menus.ui.h:5 +#: src/client/components/stock.vala:25 ui/gtk/menus.ui:29 msgid "_Help" msgstr "Pomo_č" -#: ../src/client/components/stock.vala:26 ../ui/conversation-email-menus.ui.h:9 +#: src/client/components/stock.vala:26 ui/conversation-email-menus.ui:77 msgid "_Open" msgstr "_Odpri" -#: ../src/client/components/stock.vala:27 ../ui/gtk/menus.ui.h:3 +#: src/client/components/stock.vala:27 ui/gtk/menus.ui:17 msgid "_Preferences" msgstr "_Možnosti" -#: ../src/client/components/stock.vala:28 ../ui/conversation-email-menus.ui.h:7 +#. Translators: Menu item to print a single, specific message +#: src/client/components/stock.vala:28 ui/conversation-email-menus.ui:64 msgid "_Print…" msgstr "_Natisni ..." -#: ../src/client/components/stock.vala:29 ../ui/gtk/menus.ui.h:7 +#: src/client/components/stock.vala:29 ui/gtk/menus.ui:38 msgid "_Quit" msgstr "_Končaj" -#: ../src/client/components/stock.vala:30 ../ui/remove_confirm.glade.h:6 +#: src/client/components/stock.vala:30 ui/remove_confirm.glade:170 msgid "_Remove" msgstr "_Odstrani" -#: ../src/client/components/stock.vala:32 +#: src/client/components/stock.vala:32 msgid "_Keep" msgstr "_Obdrži" -#: ../src/client/composer/composer-link-popover.vala:150 +#: src/client/composer/composer-link-popover.vala:149 msgid "Link URL is not correctly formatted, e.g. http://example.com" msgstr "" "Naslov URL povezave ni pravilno oblikovan; biti mora v zapisu http://primer." "si" -#: ../src/client/composer/composer-link-popover.vala:157 +#: src/client/composer/composer-link-popover.vala:156 msgid "Invalid link URL" msgstr "Neveljaven naslov URL povezave" -#: ../src/client/composer/composer-link-popover.vala:157 +#: src/client/composer/composer-link-popover.vala:156 msgid "Invalid email address" msgstr "Neveljaven elektronski naslov" -#: ../src/client/composer/composer-widget.vala:154 +#: src/client/composer/composer-widget.vala:158 msgid "Saved" msgstr "Shranjeno" -#: ../src/client/composer/composer-widget.vala:155 +#: src/client/composer/composer-widget.vala:159 msgid "Saving" msgstr "Poteka shranjevanje" -#: ../src/client/composer/composer-widget.vala:156 +#: src/client/composer/composer-widget.vala:160 msgid "Error saving" msgstr "Napaka med shranjevanjem" -#: ../src/client/composer/composer-widget.vala:157 +#: src/client/composer/composer-widget.vala:161 msgid "Press Backspace to delete quote" msgstr "Pritisnite povratno tipko za brisanje izvornega besedila" -#: ../src/client/composer/composer-widget.vala:158 -msgid "New Message" -msgstr "Novo sporočilo" - #. Translators: This is list of keywords, separated by pipe ("|") #. characters, that suggest an attachment; since this is full-word #. checking, include all variants of each word. No spaces are #. allowed. -#: ../src/client/composer/composer-widget.vala:167 +#: src/client/composer/composer-widget.vala:170 msgid "" "attach|attaching|attaches|attachment|attachments|attached|enclose|enclosed|" "enclosing|encloses|enclosure|enclosures" @@ -925,28 +1203,41 @@ "pripni|pripenjanje|priloži|priloga|priloge|priloženo|pripeto|vključeno|" "vstavljeno|dodano|priponka|vstavek" -#: ../src/client/composer/composer-widget.vala:1122 -#: ../src/client/composer/composer-widget.vala:1141 -msgid "Do you want to discard this message?" +#. Translators: This dialog text is displayed to the +#. user when closing a composer where the options are +#. Keep, Discard or Cancel. +#: src/client/composer/composer-widget.vala:1102 +#, fuzzy +#| msgid "Do you want to discard this message?" +msgid "Do you want to keep or discard this draft message?" +msgstr "Ali želite zavreči to sporočilo?" + +#. Translators: This dialog text is displayed to the +#. user when closing a composer where the options are +#. only Discard or Cancel. +#: src/client/composer/composer-widget.vala:1130 +#, fuzzy +#| msgid "Do you want to discard this message?" +msgid "Do you want to discard this draft message?" msgstr "Ali želite zavreči to sporočilo?" -#: ../src/client/composer/composer-widget.vala:1245 +#: src/client/composer/composer-widget.vala:1247 msgid "Send message with an empty subject and body?" msgstr "Ali želite poslati sporočilo brez zadeve in vsebine?" -#: ../src/client/composer/composer-widget.vala:1247 +#: src/client/composer/composer-widget.vala:1249 msgid "Send message with an empty subject?" msgstr "Ali želite poslati sporočilo brez vpisane zadeve?" -#: ../src/client/composer/composer-widget.vala:1249 +#: src/client/composer/composer-widget.vala:1251 msgid "Send message with an empty body?" msgstr "Ali želite poslati sporočilo brez vsebine?" -#: ../src/client/composer/composer-widget.vala:1253 +#: src/client/composer/composer-widget.vala:1255 msgid "Send message without an attachment?" msgstr "Ali želite poslati sporočilo brez priloge?" -#: ../src/client/composer/composer-widget.vala:1515 +#: src/client/composer/composer-widget.vala:1560 #, c-format msgid "“%s” already attached for delivery." msgstr "Datoteka »%s« je že priložena za pošiljanje." @@ -956,168 +1247,241 @@ #. description of the document type, the second will #. be a human-friendly size string. For example: #. Document (100.9MB) -#: ../src/client/composer/composer-widget.vala:1523 -#: ../src/client/conversation-viewer/conversation-email.vala:138 +#: src/client/composer/composer-widget.vala:1568 +#: src/client/conversation-viewer/conversation-email.vala:136 #, c-format msgid "%s (%s)" msgstr "%s (%s)" -#: ../src/client/composer/composer-widget.vala:1560 +#: src/client/composer/composer-widget.vala:1605 #, c-format msgid "“%s” could not be found." msgstr "Predmeta »%s« ni mogoče najti." -#: ../src/client/composer/composer-widget.vala:1566 +#: src/client/composer/composer-widget.vala:1611 #, c-format msgid "“%s” is a folder." msgstr "Predmet »%s« je mapa." -#: ../src/client/composer/composer-widget.vala:1572 +#: src/client/composer/composer-widget.vala:1617 #, c-format msgid "“%s” is an empty file." msgstr "Datoteka »%s« je prazna datoteka." -#: ../src/client/composer/composer-widget.vala:1585 +#: src/client/composer/composer-widget.vala:1630 #, c-format msgid "“%s” could not be opened for reading." msgstr "Datoteke »%s« ni mogoče odpreti za branje." -#: ../src/client/composer/composer-widget.vala:1593 +#: src/client/composer/composer-widget.vala:1638 msgid "Cannot add attachment" msgstr "Priloge ni mogoče dodati" -#: ../src/client/composer/composer-widget.vala:1645 +#: src/client/composer/composer-widget.vala:1687 msgid "To: " msgstr "Za: " -#: ../src/client/composer/composer-widget.vala:1648 +#: src/client/composer/composer-widget.vala:1690 msgid "Cc: " msgstr "Kp: " -#: ../src/client/composer/composer-widget.vala:1651 +#: src/client/composer/composer-widget.vala:1693 msgid "Bcc: " msgstr "Skp: " -#: ../src/client/composer/composer-widget.vala:1654 +#: src/client/composer/composer-widget.vala:1696 msgid "Reply-To: " msgstr "Odgovori na:" -#: ../src/client/composer/composer-widget.vala:1786 +#: src/client/composer/composer-widget.vala:1834 msgid "Select Color" msgstr "Izbor barve" #. Displayed in the From dropdown to indicate an "alternate email address" #. for an account. The first printf argument will be the alternate email #. address, and the second will be the account's primary email address. -#: ../src/client/composer/composer-widget.vala:1986 +#: src/client/composer/composer-widget.vala:2024 #, c-format msgid "%1$s via %2$s" msgstr "%1$s prek %2$s" #. Composer label (with mnemonic underscore) for the account selector #. when choosing what address to send a message from. -#: ../src/client/composer/composer-widget.vala:2028 +#: src/client/composer/composer-widget.vala:2082 msgid "_From:" msgstr "_Od:" #. Translators: This is the name of the file chooser filter #. when inserting an image in the composer. -#: ../src/client/composer/composer-widget.vala:2252 +#: src/client/composer/composer-widget.vala:2307 msgid "Images" msgstr "Slike" -#: ../src/client/composer/spell-check-popover.vala:117 +#: src/client/composer/composer-window.vala:14 +msgid "New Message" +msgstr "Novo sporočilo" + +#: src/client/composer/spell-check-popover.vala:117 msgid "Remove this language from the preferred list" msgstr "Odstrani jezik s seznama prednostnih jezikov" -#: ../src/client/composer/spell-check-popover.vala:121 +#: src/client/composer/spell-check-popover.vala:121 msgid "Add this language to the preferred list" msgstr "Dodaj jezik na seznam prednostnih jezikov" -#: ../src/client/composer/spell-check-popover.vala:217 +#: src/client/composer/spell-check-popover.vala:217 msgid "Search for more languages" msgstr "Poišči dodatne jezike" -#: ../src/client/conversation-list/formatted-conversation-data.vala:11 +#: src/client/conversation-list/conversation-list-view.vala:283 +msgid "Delete conversation" +msgstr "Izbriši pogovor" + +#: src/client/conversation-list/conversation-list-view.vala:286 +#: ui/main-toolbar-menus.ui:16 +msgid "Mark as _Read" +msgstr "Označi kot _prebrano" + +#: src/client/conversation-list/conversation-list-view.vala:289 +#: ui/main-toolbar-menus.ui:20 +msgid "Mark as _Unread" +msgstr "Označi kot _neprebrano" + +#: src/client/conversation-list/conversation-list-view.vala:292 +#: ui/main-toolbar-menus.ui:28 +msgid "U_nstar" +msgstr "_Odstrani zvezdico" + +#: src/client/conversation-list/conversation-list-view.vala:294 +#: ui/main-toolbar-menus.ui:24 +msgid "_Star" +msgstr "Dodaj _zvezdico" + +#. Translators: Menu item to reply to a specific message. +#: src/client/conversation-list/conversation-list-view.vala:297 +#: ui/conversation-email-menus.ui:9 +msgid "_Reply" +msgstr "_Odgovori" + +#: src/client/conversation-list/conversation-list-view.vala:298 +msgid "R_eply All" +msgstr "O_dgovori vsem" + +#. Translators: Menu item to forward a specific message. +#: src/client/conversation-list/conversation-list-view.vala:299 +#: ui/conversation-email-menus.ui:21 +msgid "_Forward" +msgstr "_Posreduj" + +#: src/client/conversation-list/formatted-conversation-data.vala:11 msgid "Me" msgstr "Jaz" #. Translators: This is the file type displayed for #. attachments with unknown file types. -#: ../src/client/conversation-viewer/conversation-email.vala:124 +#: src/client/conversation-viewer/conversation-email.vala:122 msgid "Unknown" msgstr "Neznano" +#: src/client/conversation-viewer/conversation-email.vala:811 +msgid "From:" +msgstr "Od:" + +#: src/client/conversation-viewer/conversation-email.vala:815 +#: ui/conversation-message.ui:313 +msgid "To:" +msgstr "Za:" + +#: src/client/conversation-viewer/conversation-email.vala:819 +#: ui/conversation-message.ui:358 +msgid "Cc:" +msgstr "Kp:" + +#: src/client/conversation-viewer/conversation-email.vala:823 +#: ui/conversation-message.ui:403 +msgid "Bcc:" +msgstr "Skp:" + +#: src/client/conversation-viewer/conversation-email.vala:827 +msgid "Date:" +msgstr "Datum:" + +#: src/client/conversation-viewer/conversation-email.vala:831 +msgid "Subject:" +msgstr "Zadeva:" + +#: src/client/conversation-viewer/conversation-message.vala:60 +msgid "This email address may have been forged" +msgstr "" + #. Preview headers #. Translators: This is displayed in place of the from address #. when the message has no from address. -#: ../src/client/conversation-viewer/conversation-message.vala:331 +#: src/client/conversation-viewer/conversation-message.vala:330 msgid "No sender" msgstr "Ni pošiljatelja" #. Translators: This separates multiple 'from' #. addresses in the header preview for a message. -#: ../src/client/conversation-viewer/conversation-message.vala:586 +#: src/client/conversation-viewer/conversation-message.vala:589 msgid ", " msgstr ", " #. Translators: This string is used as the HTML IMG ALT #. attribute value when displaying an inline image in an email #. that did not specify a file name. E.g. ImageCannot remove account " msgstr "" "Računa ni mogoče odstraniti " -#: ../ui/account_cannot_remove.glade.h:2 +#: ui/account_cannot_remove.glade:56 msgid "" "A composer window associated with this account is currently open. Send or " "discard the message and try again." @@ -1752,440 +2136,452 @@ "Ta račun je trenutno odprto okno za sestavljalnika sporoči. Pošljite ali " "zavrzite sporočilo in poskusite znova." -#: ../ui/account_list.glade.h:1 +#: ui/account_list.glade:69 msgid "Add account" msgstr "Dodaj račun" -#: ../ui/account_list.glade.h:2 +#: ui/account_list.glade:82 msgid "Edit account" msgstr "Uredi račun" -#: ../ui/account_list.glade.h:3 +#: ui/account_list.glade:95 msgid "Remove account" msgstr "Odstrani račun" -#: ../ui/account_spinner.glade.h:1 +#: ui/account_spinner.glade:41 msgid "Please wait while Geary validates your account." msgstr "Prosimo počakajte, da Geary potrdi vaš račun." -#: ../ui/certificate_warning_dialog.glade.h:1 +#: ui/certificate_warning_dialog.glade:7 msgid "Untrusted Connection" msgstr "Ne-varna povezava" -#: ../ui/certificate_warning_dialog.glade.h:2 +#: ui/certificate_warning_dialog.glade:29 msgid "_Always Trust This Server" msgstr "_Vedno zaupaj strežniku" -#: ../ui/certificate_warning_dialog.glade.h:3 +#: ui/certificate_warning_dialog.glade:43 msgid "_Trust This Server" msgstr "_Zaupaj strežniku" -#: ../ui/certificate_warning_dialog.glade.h:4 +#: ui/certificate_warning_dialog.glade:57 msgid "_Don’t Trust This Server" msgstr "_Ne zaupaj strežniku" -#: ../ui/composer-headerbar.ui.h:1 +#: ui/composer-headerbar.ui:17 ui/composer-headerbar.ui:174 msgid "Detach (Ctrl+D)" msgstr "Odpni (CTRL+D)" -#: ../ui/composer-headerbar.ui.h:2 +#: ui/composer-headerbar.ui:57 ui/composer-headerbar.ui:83 msgid "Attach File (Ctrl+T)" msgstr "Priloži datoteko (CTRL+T)" -#: ../ui/composer-headerbar.ui.h:3 +#: ui/composer-headerbar.ui:107 msgid "Include Original Attachments" msgstr "Vključi izvorne priloge" -#: ../ui/composer-headerbar.ui.h:4 -#| msgid "Bold (Ctrl+B)" -msgid "Send (Ctrl+Enter)" -msgstr "Pošlji (CTRL+Enter)" - -#: ../ui/composer-headerbar.ui.h:5 +#: ui/composer-headerbar.ui:202 msgid "_Send" msgstr "_Pošlji" -#: ../ui/composer-headerbar.ui.h:6 +#: ui/composer-headerbar.ui:207 +msgid "Send (Ctrl+Enter)" +msgstr "Pošlji (CTRL+Enter)" + +#: ui/composer-headerbar.ui:230 msgid "Discard and Close" msgstr "Zavrzi in zapri" -#: ../ui/composer-headerbar.ui.h:7 +#: ui/composer-headerbar.ui:254 msgid "Save and Close" msgstr "Shrani in zapri" #. Note that this button and the Update button will never be shown at the same time to the user. -#: ../ui/composer-link-popover.ui.h:2 +#: ui/composer-link-popover.ui:41 msgid "Insert the new link with this URL" msgstr "Ustavi novo povezavo s tem naslovom URL" -#: ../ui/composer-link-popover.ui.h:3 +#: ui/composer-link-popover.ui:52 msgid "Link URL" msgstr "Naslov URL povezave" #. Note that this button and the Insert button will never be shown at the same time to the user. -#: ../ui/composer-link-popover.ui.h:5 +#: ui/composer-link-popover.ui:66 msgid "Update this link’s URL" msgstr "Posodobi naslov URL povezave" -#: ../ui/composer-link-popover.ui.h:6 +#: ui/composer-link-popover.ui:86 msgid "Delete this link" msgstr "Izbriši povezavo" -#: ../ui/composer-link-popover.ui.h:7 +#: ui/composer-link-popover.ui:106 msgid "Open this link" msgstr "Odpri povezavo" -#: ../ui/composer-menus.ui.h:1 +#: ui/composer-menus.ui:7 msgid "S_ans Serif" msgstr "S_ans Serif" -#: ../ui/composer-menus.ui.h:2 +#: ui/composer-menus.ui:12 msgid "S_erif" msgstr "S_erif" -#: ../ui/composer-menus.ui.h:3 +#: ui/composer-menus.ui:17 msgid "_Fixed Width" msgstr "_Nespremenljiva širina" -#: ../ui/composer-menus.ui.h:4 +#: ui/composer-menus.ui:24 msgid "_Small" msgstr "_Majhna" -#: ../ui/composer-menus.ui.h:5 +#: ui/composer-menus.ui:29 msgid "_Medium" msgstr "_Srednja" -#: ../ui/composer-menus.ui.h:6 +#: ui/composer-menus.ui:34 msgid "Lar_ge" msgstr "_Velika" -#: ../ui/composer-menus.ui.h:7 +#: ui/composer-menus.ui:41 msgid "C_olor" msgstr "_Barva" -#: ../ui/composer-menus.ui.h:8 +#: ui/composer-menus.ui:47 ui/composer-menus.ui:62 msgid "_Rich Text" msgstr "_Oblikovano besedilo" -#: ../ui/composer-menus.ui.h:9 +#: ui/composer-menus.ui:53 ui/composer-menus.ui:68 msgid "Show Extended Fields" msgstr "Pokaži razširjena polja" -#: ../ui/composer-menus.ui.h:10 +#: ui/composer-menus.ui:78 msgid "_Undo" msgstr "_Razveljavi" -#: ../ui/composer-menus.ui.h:11 +#: ui/composer-menus.ui:82 msgid "_Redo" msgstr "_Ponovi" -#: ../ui/composer-menus.ui.h:12 +#: ui/composer-menus.ui:88 ui/composer-menus.ui:106 msgid "Cu_t" msgstr "_Izreži" -#: ../ui/composer-menus.ui.h:13 ../ui/conversation-message-menus.ui.h:7 +#: ui/composer-menus.ui:92 ui/composer-menus.ui:110 +#: ui/conversation-message-menus.ui:37 msgid "_Copy" msgstr "_Kopiraj" -#: ../ui/composer-menus.ui.h:14 +#: ui/composer-menus.ui:96 ui/composer-menus.ui:114 msgid "_Paste" msgstr "_Prilepi" -#: ../ui/composer-menus.ui.h:15 -msgctxt "Clipboard paste with rich text" -msgid "Paste _With Formatting" +#: ui/composer-menus.ui:100 +#, fuzzy +#| msgctxt "Clipboard paste with rich text" +#| msgid "Paste _With Formatting" +msgctxt "Clipboard paste as plain text" +msgid "Paste _Without Formatting" msgstr "Prilepi z _oblikovanjem" -#: ../ui/composer-menus.ui.h:16 +#: ui/composer-menus.ui:120 msgid "Select _All" msgstr "Izberi _vse" -#: ../ui/composer-menus.ui.h:17 ../ui/conversation-message-menus.ui.h:9 +#: ui/composer-menus.ui:127 ui/conversation-message-menus.ui:49 msgid "_Inspect…" msgstr "_Preveri ..." #. Address(es) e-mail is to be sent to -#: ../ui/composer-widget.ui.h:2 +#: ui/composer-widget.ui:56 msgid "_To" msgstr "_Za" -#: ../ui/composer-widget.ui.h:3 +#: ui/composer-widget.ui:75 msgid "_Cc" msgstr "_Kp" -#: ../ui/composer-widget.ui.h:4 +#: ui/composer-widget.ui:130 msgid "_Subject" msgstr "Z_adeva" -#: ../ui/composer-widget.ui.h:5 +#: ui/composer-widget.ui:149 msgid "_Bcc" msgstr "_Skp" -#: ../ui/composer-widget.ui.h:6 +#: ui/composer-widget.ui:179 msgid "_Reply-To" msgstr "_Odgovori na:" #. Geary account mail will be sent from -#: ../ui/composer-widget.ui.h:8 +#: ui/composer-widget.ui:208 msgid "From" msgstr "Od" -#: ../ui/composer-widget.ui.h:9 +#: ui/composer-widget.ui:293 msgid "Drop files here" msgstr "Povlecite datoteke sem" -#: ../ui/composer-widget.ui.h:10 +#: ui/composer-widget.ui:309 msgid "To add them as attachments" msgstr "Da jih dodate kot priponke." -#: ../ui/composer-widget.ui.h:11 +#: ui/composer-widget.ui:348 msgid "Undo last edit (Ctrl+Z)" msgstr "Razveljavi zadnjo spremembo [CTRL+Z]" -#: ../ui/composer-widget.ui.h:12 +#: ui/composer-widget.ui:372 msgid "Redo last edit (Ctrl+Shift+Z)" msgstr "Ponovno uveljavi zadnje urejanje (CTRL+SHIFT+Z)" -#: ../ui/composer-widget.ui.h:13 +#: ui/composer-widget.ui:410 msgid "Bold (Ctrl+B)" msgstr "Krepko (CTRL+B)" -#: ../ui/composer-widget.ui.h:14 +#: ui/composer-widget.ui:434 msgid "Italic (Ctrl+I)" msgstr "Ležeče (CTRL+I)" -#: ../ui/composer-widget.ui.h:15 +#: ui/composer-widget.ui:458 msgid "Underline (Ctrl+U)" msgstr "Podčrtano (CTRL+U)" -#: ../ui/composer-widget.ui.h:16 +#: ui/composer-widget.ui:482 msgid "Strikethrough (Ctrl+K)" msgstr "Prečrtano (CTRL+K)" -#: ../ui/composer-widget.ui.h:17 +#: ui/composer-widget.ui:520 +msgid "Insert unordered list" +msgstr "" + +#: ui/composer-widget.ui:544 +msgid "Insert ordered list" +msgstr "Vstavi oštevilčen vrstični seznam" + +#: ui/composer-widget.ui:582 msgid "Quote text (Ctrl+])" msgstr "Dodaj narekovaje (CTRL+])" -#: ../ui/composer-widget.ui.h:18 +#: ui/composer-widget.ui:606 msgid "Unquote text (Ctrl+[)" msgstr "Odstrani izvorno besedilo" -#: ../ui/composer-widget.ui.h:19 +#: ui/composer-widget.ui:644 msgid "Insert or update selection link (Ctrl+L)" msgstr "Vstavi ali posodobi povezavo izbire (CTRL+L)" -#: ../ui/composer-widget.ui.h:20 +#: ui/composer-widget.ui:668 msgid "Insert an image (Ctrl+G)" msgstr "Vstavi sliko (CTRL+G)" -#: ../ui/composer-widget.ui.h:21 +#: ui/composer-widget.ui:702 msgid "Remove selection formatting (Ctrl+Space)" msgstr "Odstrani oblikovanje izbranega besedila (CTRL+Presledek)" -#: ../ui/composer-widget.ui.h:22 +#: ui/composer-widget.ui:726 msgid "Select spell checking languages" msgstr "Izbor jezikov za preverjanje črkovanja" -#: ../ui/conversation-email.ui.h:1 +#: ui/conversation-email.ui:27 msgid "Save all attachments" msgstr "Shrani vse priloge" #. Note: The application will never show this button at the same time as unstar_button, one will always be hidden. -#: ../ui/conversation-email.ui.h:3 +#: ui/conversation-email.ui:50 msgid "Mark this message as starred" msgstr "Označi sporočilo z zvezdico" #. Note: The application will never show this button at the same time as star_button, one will always be hidden. -#: ../ui/conversation-email.ui.h:5 +#: ui/conversation-email.ui:72 msgid "Mark this message as not starred" msgstr "Odstrani oznako z zvezdico s sporočila" -#: ../ui/conversation-email.ui.h:6 +#: ui/conversation-email.ui:95 msgid "Display the message menu" msgstr "Prikaži meni sporočila" -#: ../ui/conversation-email.ui.h:7 +#: ui/conversation-email.ui:161 msgid "Open selected attachments" msgstr "Odpri izbrane priloge" -#: ../ui/conversation-email.ui.h:8 +#: ui/conversation-email.ui:178 msgid "Save selected attachments" msgstr "Shrani izbrane priloge" -#: ../ui/conversation-email.ui.h:9 +#: ui/conversation-email.ui:195 msgid "Select all attachments" msgstr "Izberi vse priloge" -#: ../ui/conversation-email.ui.h:10 +#: ui/conversation-email.ui:240 msgid "Edit Draft" msgstr "Uredi osnutek" -#: ../ui/conversation-email.ui.h:11 +#: ui/conversation-email.ui:267 msgid "Draft message" msgstr "Osnutek" -#: ../ui/conversation-email.ui.h:12 +#: ui/conversation-email.ui:283 msgid "This message has not yet been sent." msgstr "Sporočilo še ni bilo poslano." -#: ../ui/conversation-email.ui.h:13 -msgid "Try Again" -msgstr "Poskusite ponovno" - -#: ../ui/conversation-email.ui.h:14 +#: ui/conversation-email.ui:329 msgid "Message not saved" msgstr "Sporočilo ni shranjeno." -#: ../ui/conversation-email.ui.h:15 +#: ui/conversation-email.ui:345 msgid "This message was sent, but has not been saved to your account." msgstr "" "Sporočilo je poslano, vendar pa kopija ni shranjena v mapo poslanih sporočil." -#: ../ui/conversation-email-menus.ui.h:2 +#. Translators: Menu item to reply to a specific message. +#: ui/conversation-email-menus.ui:15 msgid "Reply to _All" msgstr "Odgovori _vsem" -#: ../ui/conversation-email-menus.ui.h:4 +#. Translators: Menu item to mark a specific message as +#. read. +#: ui/conversation-email-menus.ui:30 msgid "_Mark Read" msgstr "Označi kot _prebrano" -#: ../ui/conversation-email-menus.ui.h:5 +#: ui/conversation-email-menus.ui:36 msgid "_Mark Unread" msgstr "Označi kot _neprebrano" -#: ../ui/conversation-email-menus.ui.h:6 +#. Translators: Menu item to mark all messages in a +#. conversation from this one as unread. +#: ui/conversation-email-menus.ui:42 msgid "Mark Unread From _Here" msgstr "Označi kot neprebrano od _tukaj" -#: ../ui/conversation-email-menus.ui.h:8 +#. Translators: Menu item to move a single, specific message +#. to the trash +#: ui/conversation-email-menus.ui:50 +msgid "_Trash" +msgstr "_Smeti" + +#. Translators: Menu item to delete a single, specific message +#: ui/conversation-email-menus.ui:57 +msgid "_Delete…" +msgstr "_Izbriši …" + +#. Translators: Menu item to view the source for a message +#: ui/conversation-email-menus.ui:69 msgid "_View Source" msgstr "_Pokaži izvorno kodo" -#: ../ui/conversation-email-menus.ui.h:11 +#: ui/conversation-email-menus.ui:87 msgid "_Save All" msgstr "Shrani _vse" -#: ../ui/conversation-message-menus.ui.h:1 +#: ui/conversation-message-menus.ui:7 msgid "_Open Link" msgstr "_Odpri povezavo" -#: ../ui/conversation-message-menus.ui.h:2 +#: ui/conversation-message-menus.ui:11 msgid "Copy Link _Address" msgstr "Kopiraj _naslov povezave" -#: ../ui/conversation-message-menus.ui.h:3 +#: ui/conversation-message-menus.ui:17 msgid "Send New _Message…" msgstr "Pošlji novo _sporočilo ..." -#: ../ui/conversation-message-menus.ui.h:4 +#: ui/conversation-message-menus.ui:21 msgid "Copy Email _Address" msgstr "Kopiraj _elektronski naslov" -#: ../ui/conversation-message-menus.ui.h:5 +#: ui/conversation-message-menus.ui:27 msgid "Save _Image As…" msgstr "Shrani sliko _kot ..." -#: ../ui/conversation-message-menus.ui.h:6 +#: ui/conversation-message-menus.ui:33 msgid "_Select All" msgstr "_Izberi vse" -#: ../ui/conversation-message-menus.ui.h:8 +#: ui/conversation-message-menus.ui:43 msgid "Search for messages from" msgstr "Išči sporočila iz" -#: ../ui/conversation-message.ui.h:1 +#: ui/conversation-message.ui:64 msgid "From " msgstr "Od " -#: ../ui/conversation-message.ui.h:2 +#: ui/conversation-message.ui:80 ui/conversation-message.ui:179 msgid "1/1/1970\t" msgstr "1/1/1970\t" -#: ../ui/conversation-message.ui.h:3 +#: ui/conversation-message.ui:103 msgid "Preview body text." msgstr "Predogled besedila vsebine." -#: ../ui/conversation-message.ui.h:4 +#: ui/conversation-message.ui:203 msgid "Sent by:" msgstr "Pošiljatelj:" -#: ../ui/conversation-message.ui.h:5 +#: ui/conversation-message.ui:248 msgid "Reply to:" msgstr "Odgovori na:" -#: ../ui/conversation-message.ui.h:6 +#: ui/conversation-message.ui:292 msgid "Subject" msgstr "Zadeva" -#: ../ui/conversation-message.ui.h:7 -msgid "To:" -msgstr "Za:" - -#: ../ui/conversation-message.ui.h:8 -msgid "Cc:" -msgstr "Kp:" - -#: ../ui/conversation-message.ui.h:9 -msgid "Bcc:" -msgstr "Skp:" - -#: ../ui/conversation-message.ui.h:10 +#: ui/conversation-message.ui:502 msgid "Show Images" msgstr "Prikaži slike" -#: ../ui/conversation-message.ui.h:11 +#: ui/conversation-message.ui:515 msgid "Always Show From Sender" msgstr "Vedno pokaži naslov pošiljatelja" -#: ../ui/conversation-message.ui.h:12 +#: ui/conversation-message.ui:543 msgid "Remote images not shown" msgstr "Slike na oddaljenih strežnikih niso prikazane" -#: ../ui/conversation-message.ui.h:13 +#: ui/conversation-message.ui:560 msgid "Only show remote images from senders you trust." msgstr "" "Slike z oddaljenih mest pokaži le, če je pošiljatelj na seznamu zaupljivih " "oseb." -#: ../ui/conversation-message.ui.h:14 +#: ui/conversation-message.ui:692 msgid "But actually goes to:" msgstr "Vendar bo narejena preusmeritev na:" -#: ../ui/conversation-message.ui.h:15 +#: ui/conversation-message.ui:723 msgid "The link appears to go to:" msgstr "Videti je, da kaže povezava na:" -#: ../ui/conversation-message.ui.h:16 +#: ui/conversation-message.ui:735 msgid "Deceptive link found" msgstr "Zaznan je zavajajoča povezava" -#: ../ui/conversation-message.ui.h:17 +#: ui/conversation-message.ui:750 msgid "The email sender may be leading you to the wrong web site." msgstr "Pošiljatelj sporočila vas morda pošilja na napačno spletno mesto." -#: ../ui/conversation-message.ui.h:18 +#: ui/conversation-message.ui:763 msgid "If unsure, contact the sender and ask before continuing." msgstr "" "V kolikor niste prepričani, stopite v stik s pošiljateljem in vprašajte pred " "nadaljevanjem." -#: ../ui/conversation-viewer.ui.h:1 +#: ui/conversation-viewer.ui:60 msgid "Find in conversation" msgstr "Najdi v pogovoru" -#: ../ui/conversation-viewer.ui.h:2 +#: ui/conversation-viewer.ui:74 msgid "Find the previous occurrence of the search string." msgstr "Najde predhodno pojavitev iskanega niza." -#: ../ui/conversation-viewer.ui.h:3 +#: ui/conversation-viewer.ui:95 msgid "Find the next occurrence of the search string." msgstr "Najde naslednjo pojavitev iskanega niza." -#: ../ui/edit_alternate_emails.glade.h:1 +#: ui/edit_alternate_emails.glade:112 msgid "Remove email address" msgstr "Odstrani elektronski naslov" -#: ../ui/edit_alternate_emails.glade.h:2 +#: ui/edit_alternate_emails.glade:136 msgid "" "Some email services require additional addresses be configured on the " "server. Contact your email provider for more information." @@ -2193,476 +2589,530 @@ "Nekateri strežniki poštnih storitev zahtevajo dodatne nastavitve naslovov. " "Za več podrobnosti stopite v stik s ponudnikom poštnih storitev." -#: ../ui/edit_alternate_emails.glade.h:4 +#: ui/edit_alternate_emails.glade:175 msgid "_Update" msgstr "_Posodobi" -#: ../ui/find_bar.glade.h:1 +#: ui/find_bar.glade:66 msgid "Find:" msgstr "Najdi:" -#: ../ui/find_bar.glade.h:2 +#: ui/find_bar.glade:89 msgid "_Previous" msgstr "_Predhodno" -#: ../ui/find_bar.glade.h:3 +#: ui/find_bar.glade:107 msgid "_Next" msgstr "_Naslednje" -#: ../ui/find_bar.glade.h:4 +#: ui/find_bar.glade:125 msgid "_Case sensitive" msgstr "_Razlikuj velikosti črk" -#: ../ui/find_bar.glade.h:5 +#: ui/find_bar.glade:145 msgid "label" msgstr "oznaka" -#: ../ui/gtk/help-overlay.ui.h:1 +#: ui/gtk/help-overlay.ui:9 msgid "Conversation Shortcuts" msgstr "Tipkovna bližnjica pogovora" -#: ../ui/gtk/help-overlay.ui.h:2 +#: ui/gtk/help-overlay.ui:13 ui/gtk/help-overlay.ui:254 msgctxt "shortcut window" msgid "General" msgstr "Splošno" -#: ../ui/gtk/help-overlay.ui.h:3 +#: ui/gtk/help-overlay.ui:17 msgctxt "shortcut window" msgid "Move focus to the next/previous pane" msgstr "Premakni žarišče na naslednje/predhodno okno" -#: ../ui/gtk/help-overlay.ui.h:4 +#: ui/gtk/help-overlay.ui:24 msgctxt "shortcut window" msgid "Move focus to conversation list" msgstr "Premakni žarišče na seznam pogovorov" -#: ../ui/gtk/help-overlay.ui.h:5 +#: ui/gtk/help-overlay.ui:31 msgctxt "shortcut window" msgid "Detach composer window" msgstr "Odpni okno skladalnika" -#: ../ui/gtk/help-overlay.ui.h:6 +#: ui/gtk/help-overlay.ui:38 msgctxt "shortcut window" msgid "Close composer window" msgstr "Zapri okno skladalnika" -#: ../ui/gtk/help-overlay.ui.h:7 +#: ui/gtk/help-overlay.ui:45 msgctxt "shortcut window" msgid "Show keyboard shortcuts" msgstr "Pokaži tipkovne bližnjice‫" -#: ../ui/gtk/help-overlay.ui.h:8 +#: ui/gtk/help-overlay.ui:52 msgctxt "shortcut window" msgid "Show help" msgstr "Pokaži pomoč" -#: ../ui/gtk/help-overlay.ui.h:9 +#: ui/gtk/help-overlay.ui:59 msgctxt "shortcut window" msgid "Quit the application" msgstr "Končaj program" -#: ../ui/gtk/help-overlay.ui.h:10 +#: ui/gtk/help-overlay.ui:68 msgctxt "shortcut window" msgid "Search" msgstr "Iskanje" -#: ../ui/gtk/help-overlay.ui.h:11 +#: ui/gtk/help-overlay.ui:72 msgctxt "shortcut window" msgid "Jump to search box" msgstr "Skoči na iskalno polje" -#: ../ui/gtk/help-overlay.ui.h:12 +#: ui/gtk/help-overlay.ui:79 msgctxt "shortcut window" msgid "Find in current conversation" msgstr "Najdi v pogovoru" -#: ../ui/gtk/help-overlay.ui.h:13 +#: ui/gtk/help-overlay.ui:86 msgctxt "shortcut window" msgid "Find next/previous in current conversation" msgstr "Najdi naslednjo/predhodno pojavitev iskalnega niza v pogovoru" -#: ../ui/gtk/help-overlay.ui.h:14 +#: ui/gtk/help-overlay.ui:95 ui/gtk/help-overlay.ui:274 msgctxt "shortcut window" msgid "Actions" msgstr "Dejanja" -#: ../ui/gtk/help-overlay.ui.h:15 +#: ui/gtk/help-overlay.ui:99 msgctxt "shortcut window" msgid "Compose a new message" msgstr "Sestavi novo sporočilo" -#: ../ui/gtk/help-overlay.ui.h:16 +#: ui/gtk/help-overlay.ui:106 msgctxt "shortcut window" msgid "Reply to sender " msgstr "Odgovori pošiljatelju" -#: ../ui/gtk/help-overlay.ui.h:17 +#: ui/gtk/help-overlay.ui:113 msgctxt "shortcut window" msgid "Reply to all" msgstr "Odgovori vsem" -#: ../ui/gtk/help-overlay.ui.h:18 +#: ui/gtk/help-overlay.ui:120 msgctxt "shortcut window" msgid "Forward" msgstr "Posreduj" -#: ../ui/gtk/help-overlay.ui.h:19 +#: ui/gtk/help-overlay.ui:127 msgctxt "shortcut window" msgid "Archive" msgstr "Arhiv" -#: ../ui/gtk/help-overlay.ui.h:20 +#: ui/gtk/help-overlay.ui:134 msgctxt "shortcut window" msgid "Move to trash" msgstr "Premakni v smeti" -#: ../ui/gtk/help-overlay.ui.h:21 +#: ui/gtk/help-overlay.ui:141 msgctxt "shortcut window" msgid "Toggle spam" msgstr "Preklopi sporočilo kot neželeno" -#: ../ui/gtk/help-overlay.ui.h:22 +#: ui/gtk/help-overlay.ui:148 msgctxt "shortcut window" msgid "Move the conversation" msgstr "Premakni pogovor" -#: ../ui/gtk/help-overlay.ui.h:23 +#: ui/gtk/help-overlay.ui:155 msgctxt "shortcut window" msgid "Label the conversation" msgstr "Označi pogovor" -#: ../ui/gtk/help-overlay.ui.h:24 +#: ui/gtk/help-overlay.ui:163 msgctxt "shortcut window" msgid "Mark read" msgstr "Označi kot prebrano" -#: ../ui/gtk/help-overlay.ui.h:25 +#: ui/gtk/help-overlay.ui:170 msgctxt "shortcut window" msgid "Mark unread" msgstr "Označi kot neprebrano" -#: ../ui/gtk/help-overlay.ui.h:26 +#: ui/gtk/help-overlay.ui:179 msgctxt "shortcut window" msgid "View" msgstr "Pogled" -#: ../ui/gtk/help-overlay.ui.h:27 +#: ui/gtk/help-overlay.ui:183 msgctxt "shortcut window" msgid "Zoom in" msgstr "Približaj" -#: ../ui/gtk/help-overlay.ui.h:28 +#: ui/gtk/help-overlay.ui:190 msgctxt "shortcut window" msgid "Zoom out" msgstr "Oddalji" -#: ../ui/gtk/help-overlay.ui.h:29 +#: ui/gtk/help-overlay.ui:197 msgctxt "shortcut window" msgid "Reset zoom" msgstr "Ponastavi približanje" -#: ../ui/gtk/help-overlay.ui.h:30 +#: ui/gtk/help-overlay.ui:206 msgctxt "shortcut window" msgid "Additional Shortcuts" msgstr "Dodatne tipkovne bližnjice" -#: ../ui/gtk/help-overlay.ui.h:31 +#: ui/gtk/help-overlay.ui:210 msgctxt "shortcut window" msgid "Star" msgstr "Dodaj zvezdico" -#: ../ui/gtk/help-overlay.ui.h:32 +#: ui/gtk/help-overlay.ui:217 msgctxt "shortcut window" msgid "Unstar" msgstr "Odstrani zvezdico" -#: ../ui/gtk/help-overlay.ui.h:33 +#: ui/gtk/help-overlay.ui:224 msgctxt "shortcut window" msgid "Delete" msgstr "Izbriši" -#: ../ui/gtk/help-overlay.ui.h:34 +#: ui/gtk/help-overlay.ui:231 msgctxt "shortcut window" msgid "Jump to next (older) conversation" msgstr "Skoči na naslednji (starejši) pogovor" -#: ../ui/gtk/help-overlay.ui.h:35 +#: ui/gtk/help-overlay.ui:238 msgctxt "shortcut window" msgid "Jump to previous (newer) conversation" msgstr "Skoči na predhodni (novejši) pogovor" -#: ../ui/gtk/help-overlay.ui.h:36 +#: ui/gtk/help-overlay.ui:250 msgid "Composer Shortcuts" msgstr "Tipkovne bližnjice skladalnika" -#: ../ui/gtk/help-overlay.ui.h:37 +#: ui/gtk/help-overlay.ui:258 msgctxt "shortcut window" msgid "Quote text" msgstr "Navedi izvorno besedilo" -#: ../ui/gtk/help-overlay.ui.h:38 +#: ui/gtk/help-overlay.ui:265 msgctxt "shortcut window" msgid "Unquote text" msgstr "Odstrani izvorno besedilo" -#: ../ui/gtk/help-overlay.ui.h:39 +#: ui/gtk/help-overlay.ui:278 msgctxt "shortcut window" msgid "Send" msgstr "Pošlji" -#: ../ui/gtk/help-overlay.ui.h:40 +#: ui/gtk/help-overlay.ui:285 msgctxt "shortcut window" msgid "Add attachment" msgstr "Dodaj prilogo" -#: ../ui/gtk/help-overlay.ui.h:41 +#: ui/gtk/help-overlay.ui:294 msgctxt "shortcut window" msgid "Rich text mode" msgstr "Oblikovano sporočilo" -#: ../ui/gtk/help-overlay.ui.h:42 +#: ui/gtk/help-overlay.ui:298 msgctxt "shortcut window" msgid "Bold text" msgstr "Krepko besedilo" -#: ../ui/gtk/help-overlay.ui.h:43 +#: ui/gtk/help-overlay.ui:305 msgctxt "shortcut window" msgid "Italicize text" msgstr "Ležeče besedilo" -#: ../ui/gtk/help-overlay.ui.h:44 +#: ui/gtk/help-overlay.ui:312 msgctxt "shortcut window" msgid "Underline text" msgstr "Podčrtano besedilo" -#: ../ui/gtk/help-overlay.ui.h:45 +#: ui/gtk/help-overlay.ui:319 msgctxt "shortcut window" msgid "Strike text" msgstr "Prečrtano besedilo" -#: ../ui/gtk/help-overlay.ui.h:46 +#: ui/gtk/help-overlay.ui:326 msgctxt "shortcut window" msgid "Insert a link" msgstr "Vstavi povezavo" -#: ../ui/gtk/help-overlay.ui.h:47 +#: ui/gtk/help-overlay.ui:333 msgctxt "shortcut window" msgid "Remove formatting" msgstr "Odstrani oblikovanje" -#: ../ui/gtk/menus.ui.h:2 +#: ui/gtk/menus.ui:13 msgid "A_ccounts" msgstr "_Računi" -#: ../ui/gtk/menus.ui.h:4 +#: ui/gtk/menus.ui:23 msgid "_Keyboard Shortcuts" msgstr "_Tipkovne bližnjice‫" -#: ../ui/login.glade.h:1 +#: ui/login.glade:88 msgid "email@example.com" msgstr "elektronski.naslov@primer.si" -#: ../ui/login.glade.h:2 ../ui/password-dialog.glade.h:3 +#: ui/login.glade:107 ui/password-dialog.glade:108 msgid "Password" msgstr "Geslo" -#: ../ui/login.glade.h:3 +#: ui/login.glade:123 msgid "E_mail address" msgstr "_Elektronski naslov" -#: ../ui/login.glade.h:4 +#: ui/login.glade:144 ui/login.glade:635 msgid "_Password" msgstr "_Geslo" -#: ../ui/login.glade.h:5 +#: ui/login.glade:178 msgid "S_ervice" msgstr "_Storitev" -#: ../ui/login.glade.h:6 +#: ui/login.glade:199 msgid "N_ame" msgstr "_Ime" -#: ../ui/login.glade.h:8 +#: ui/login.glade:256 msgid "N_ickname" msgstr "_Vzdevek" -#: ../ui/login.glade.h:9 +#: ui/login.glade:280 msgid "Work, Home, etc." msgstr "Delovno mesto, domači naslov, itd." -#: ../ui/login.glade.h:10 +#: ui/login.glade:291 msgid "_Save sent mail" msgstr "_Shrani poslana sporočila" -#: ../ui/login.glade.h:11 +#: ui/login.glade:309 msgid "Addi_tional email addresses…" msgstr "Dodatni _elektronski naslovi ..." -#: ../ui/login.glade.h:12 +#: ui/login.glade:353 msgid "IMAP settings" msgstr "Nastavitve IMAP" -#: ../ui/login.glade.h:13 +#: ui/login.glade:372 msgid "Se_rver" msgstr "_Strežnik" -#: ../ui/login.glade.h:14 +#: ui/login.glade:393 msgid "imap.example.com" msgstr "imap@primer.si" -#: ../ui/login.glade.h:15 +#: ui/login.glade:409 msgid "P_ort" msgstr "_Vrata" -#: ../ui/login.glade.h:16 +#: ui/login.glade:448 msgid "smtp.example.com" msgstr "smtp@primer.si" -#: ../ui/login.glade.h:17 +#: ui/login.glade:480 msgid "Ser_ver" msgstr "St_režnik" -#: ../ui/login.glade.h:18 +#: ui/login.glade:501 msgid "Por_t" msgstr "_Vrata" -#: ../ui/login.glade.h:19 +#: ui/login.glade:522 msgid "SMTP settings" msgstr "Nastavitve SMTP" -#: ../ui/login.glade.h:20 +#: ui/login.glade:541 msgid "User_name" msgstr "_Uporabniško ime" -#: ../ui/login.glade.h:21 +#: ui/login.glade:562 msgid "Pass_word" msgstr "_Geslo" -#: ../ui/login.glade.h:22 +#: ui/login.glade:582 msgid "SMTP username" msgstr "Uporabniško ime SMTP" -#: ../ui/login.glade.h:23 +#: ui/login.glade:598 msgid "SMTP password" msgstr "Geslo SMTP" -#: ../ui/login.glade.h:24 +#: ui/login.glade:614 msgid "_Username" msgstr "_Uporabniško ime" -#: ../ui/login.glade.h:25 +#: ui/login.glade:655 msgid "IMAP username" msgstr "Uporabniško ime IMAP" -#: ../ui/login.glade.h:26 +#: ui/login.glade:671 msgid "IMAP password" msgstr "Geslo IMAP" -#: ../ui/login.glade.h:27 +#: ui/login.glade:688 msgid "Encr_yption" msgstr "_Šifriranje" -#: ../ui/login.glade.h:28 +#: ui/login.glade:711 msgid "Encrypt_ion" msgstr "Ši_friranje" -#: ../ui/login.glade.h:30 +#: ui/login.glade:733 ui/login.glade:751 msgid "SSL/TLS" msgstr "SSL/TLS" -#: ../ui/login.glade.h:31 +#: ui/login.glade:734 ui/login.glade:752 msgid "STARTTLS" msgstr "STARTTLS" -#: ../ui/login.glade.h:32 +#: ui/login.glade:764 msgid "No authentication re_quired" msgstr "_Overitev ni potrebna" -#: ../ui/login.glade.h:33 +#: ui/login.glade:781 msgid "Use IMAP cre_dentials" msgstr "Uporabi poverilnice _IMAP" -#: ../ui/login.glade.h:34 +#: ui/login.glade:888 msgid "Composer" msgstr "Sestavljalnik" -#: ../ui/login.glade.h:35 +#: ui/login.glade:901 msgid "Save dra_fts on server" msgstr "Shrani osnutke na strežnik" -#: ../ui/login.glade.h:36 +#: ui/login.glade:918 msgid "Si_gn emails (HTML allowed):" msgstr "_Podpiši sporočila (s podporo za HTML):" -#: ../ui/login.glade.h:37 +#: ui/login.glade:976 msgid "Storage" msgstr "Shramba" -#: ../ui/login.glade.h:38 +#: ui/login.glade:998 msgid "_Download mail" msgstr "_Prejmi sporočila" -#: ../ui/main-toolbar.ui.h:1 +#: ui/main-toolbar.ui:51 +msgid "Toggle search bar" +msgstr "Preklopi prikaz iskalne vrstice" + +#: ui/main-toolbar.ui:72 msgid "Empty Spam or Trash folders" msgstr "Izprazni mapi smeti in neželene pošte" -#: ../ui/password-dialog.glade.h:1 +#: ui/main-toolbar.ui:112 +msgid "Reply" +msgstr "Odgovori" + +#: ui/main-toolbar.ui:135 +msgid "Reply All" +msgstr "Odgovori vsem" + +#: ui/main-toolbar.ui:158 +msgid "Forward" +msgstr "Posreduj" + +#: ui/main-toolbar.ui:264 +msgid "Toggle find bar" +msgstr "Preklopi vrstico iskanja" + +#: ui/main-toolbar.ui:306 +msgid "_Archive" +msgstr "_Arhiv" + +#: ui/main-toolbar-menus.ui:5 +msgid "Empty _Spam…" +msgstr "Izprazni mapo _neželene pošte" + +#: ui/main-toolbar-menus.ui:9 +msgid "Empty _Trash…" +msgstr "Izprazni _smeti" + +#: ui/main-toolbar-menus.ui:32 +msgid "Mark as S_pam" +msgstr "Označi kot _neželeno pošto" + +#: ui/main-toolbar-menus.ui:36 +msgid "Mark as not S_pam" +msgstr "Označi kot _želeno pošto" + +#: ui/main-window-info-bar.ui:97 +msgid "" +"If the problem is serious or persists, please copy and send these details to " +"the mailing list " +"or file a new " +"bug report." +msgstr "" + +#: ui/main-window-info-bar.ui:113 +msgid "Details:" +msgstr "Podrobnosti:" + +#: ui/password-dialog.glade:74 msgid "SMTP Credentials" msgstr "Poverila SMTP" -#: ../ui/password-dialog.glade.h:2 +#: ui/password-dialog.glade:91 msgid "Username" msgstr "Uporabniško ime" -#: ../ui/password-dialog.glade.h:4 +#: ui/password-dialog.glade:152 msgid "_Remember password" msgstr "_Zapomni si geslo" -#: ../ui/password-dialog.glade.h:6 +#: ui/password-dialog.glade:210 msgid "_Authenticate" msgstr "_Overi" -#: ../ui/preferences-dialog.ui.h:1 +#: ui/preferences-dialog.ui:38 msgid "Reading" msgstr "Branje" -#: ../ui/preferences-dialog.ui.h:2 +#: ui/preferences-dialog.ui:51 msgid "_Automatically select next message" msgstr "_Samodejno izberi novo sporočilo" -#: ../ui/preferences-dialog.ui.h:3 +#: ui/preferences-dialog.ui:70 msgid "_Display conversation preview" msgstr "_Prikaži predogled pogovora" -#: ../ui/preferences-dialog.ui.h:4 +#: ui/preferences-dialog.ui:89 msgid "Use _three pane view" msgstr "Uporabi pogled _drevesne zgradbe" -#: ../ui/preferences-dialog.ui.h:5 +#: ui/preferences-dialog.ui:113 msgid "Notifications" msgstr "Obvestila" -#: ../ui/preferences-dialog.ui.h:6 +#: ui/preferences-dialog.ui:126 msgid "_Play notification sounds" msgstr "_Predvajaj zvočna obvestila" -#: ../ui/preferences-dialog.ui.h:7 +#: ui/preferences-dialog.ui:145 msgid "Show _notifications for new mail" msgstr "Prikaži _obvestila o novo prispeli pošti" -#: ../ui/preferences-dialog.ui.h:8 -msgid "Always _watch for new mail" +#: ui/preferences-dialog.ui:164 +#, fuzzy +#| msgid "Always _watch for new mail" +msgid "_Watch for new mail when closed" msgstr "Vedno preveri za _nova sporočila" -#: ../ui/preferences-dialog.ui.h:9 -msgid "Geary will run in the background and notify of new mail" -msgstr "Program Geary bo zagnan v ozadju za javljanje o novo prispeli pošti" +#: ui/preferences-dialog.ui:168 +msgid "Geary will keep running after all windows are closed" +msgstr "" -#: ../ui/preferences-dialog.ui.h:10 +#: ui/preferences-dialog.ui:195 msgid "Preferences" msgstr "Možnosti" -#: ../ui/remove_confirm.glade.h:1 +#: ui/remove_confirm.glade:43 msgid "" "Are you sure you want to remove this " "account? " @@ -2670,7 +3120,7 @@ "Ali ste prepričani, da želite " "odstraniti ta račun? " -#: ../ui/remove_confirm.glade.h:2 +#: ui/remove_confirm.glade:58 msgid "" "All email associated with this account will be removed from your computer. " "This will not affect email on the server." @@ -2678,26 +3128,66 @@ "Vsa elektronska pošta, povezana s tem računom, bo odstranjena z računalnika. " "To ne bo vplivalo na sporočila, shranjena na strežniku." -#: ../ui/remove_confirm.glade.h:3 +#: ui/remove_confirm.glade:80 msgid "Nickname:" msgstr "Vzdevek:" -#: ../ui/remove_confirm.glade.h:4 +#: ui/remove_confirm.glade:94 msgid "Email address:" msgstr "Elektronski naslov:" -#: ../ui/upgrade_dialog.glade.h:1 +#: ui/upgrade_dialog.glade:60 msgid "Geary update in progress…" msgstr "Posodobitev programa Geary je v teku ..." -#~ msgid "Copyright 2011-2014 Yorba Foundation" -#~ msgstr "Avtorske pravice © 2004-2014 Yorba Foundation" +#~ msgid "Geary Email" +#~ msgstr "Pošta Geary" + +#~ msgid "Geary Mail" +#~ msgstr "Pošta Geary" + +#~ msgid "_Mark as…" +#~ msgstr "_Označi kot ..." -#~ msgid "_Delete" -#~ msgstr "_Briši" +#~ msgid "Add label" +#~ msgstr "Dodaj oznako" -#~ msgid "_Trash" -#~ msgstr "_Smeti" +#~ msgid "_Label" +#~ msgstr "_Oznaka" + +#~ msgid "_Move" +#~ msgstr "_Premakni" + +#~ msgid "Compose new message (Ctrl+N, N)" +#~ msgstr "Sestavi novo sporočilo (CTRL+N, N)" + +#~ msgid "Reply (Ctrl+R, R)" +#~ msgstr "Odgovori (CTRL+R, R)" + +#~ msgid "Reply all (Ctrl+Shift+R, Shift+R)" +#~ msgstr "Odgovori vsem (CTRL+SHIFT+R, SHIFT+R)" + +#~ msgid "Forward (Ctrl+L, F)" +#~ msgstr "Posreduj (CTRL+L, F)" + +#~ msgid "" +#~ "Geary encountered an error while connecting to the server. Please try " +#~ "again in a few moments." +#~ msgstr "" +#~ "Med povezovanjem s strežnikom je prišlo do napake. Poskusite znova čez " +#~ "nekaj trenutkov." + +#~ msgid "Geary will run in the background and notify of new mail" +#~ msgstr "Program Geary bo zagnan v ozadju za javljanje o novo prispeli pošti" + +#~ msgid "Try Again" +#~ msgstr "Poskusite ponovno" + +#~ msgid "Mail Client" +#~ msgstr "Poštni odjemalec" + +#~ msgid "Copyright 2011-2014 Yorba Foundation" +#~ msgstr "Avtorske pravice © 2004-2014 Yorba Foundation" #~ msgid "_Donate" #~ msgstr "_Prispevaj" @@ -2727,9 +3217,6 @@ #~ msgid "No search results found." #~ msgstr "Ni najdenih rezultatov iskanja." -#~ msgid "Date:" -#~ msgstr "Datum:" - #~ msgid "Copy _Link" #~ msgstr "Kopiraj _povezavo" @@ -2822,6 +3309,3 @@ #~ msgid "Unable to login to email server" #~ msgstr "Povezava z epoštnim strežnikom ni možna" - -#~ msgid "_Details" -#~ msgstr "_Podrobnosti" diff -Nru geary-0.12.4/po/sr@latin.po geary-3.32.0/po/sr@latin.po --- geary-0.12.4/po/sr@latin.po 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/po/sr@latin.po 2019-03-17 13:39:29.000000000 +0000 @@ -1,48 +1,40 @@ # Serbian translation of Geary email client -# Courtesy of Prevod.org team (http://prevod.org/) -- 2012—2018. +# Courtesy of Prevod.org team (http://prevod.org/) -- 2012—2017. # Copyright 2016 Software Freedom Conservancy Inc. # This file is distributed under the GNU LGPL, version 2.1. # igorpan , 2012 -# Miroslav Nikolić , 2014—2018. +# Miroslav Nikolić , 2014—2017. # Miloš Popović , 2015. msgid "" msgstr "" "Project-Id-Version: geary\n" -"Report-Msgid-Bugs-To: https://bugzilla.gnome.org/enter_bug.cgi?product=geary&" -"keywords=I18N+L10N&component=internationalization\n" -"POT-Creation-Date: 2017-11-16 05:25+0000\n" -"PO-Revision-Date: 2018-03-03 08:12+0200\n" +"Report-Msgid-Bugs-To: https://bugzilla.gnome.org/enter_bug.cgi?" +"product=geary&keywords=I18N+L10N&component=internationalization\n" +"POT-Creation-Date: 2017-02-26 06:52+0000\n" +"PO-Revision-Date: 2017-02-26 09:25+0200\n" "Last-Translator: Miroslav Nikolić \n" -"Language-Team: srpski \n" +"Language-Team: Serbian <(nothing)>\n" "Language: sr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=4; plural=n==1? 3 : n%10==1 && n%100!=11 ? 0 : " -"n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" +"Plural-Forms: nplurals=4; plural=n==1? 3 : n%10==1 && n%100!=11 ? 0 : n" +"%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" "X-Project-Style: gnome\n" -#. Translators: The application name -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:2 +#: ../desktop/org.gnome.Geary.appdata.xml.in.h:1 #: ../desktop/org.gnome.Geary.desktop.in.h:1 #: ../desktop/geary-autostart.desktop.in.h:1 msgid "Geary" msgstr "Geri" -#. Translators: The development team's name -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:4 -msgid "Geary Development Team" -msgstr "Razvojni tim Gerija" - -#. Translators: The application's summary / tagline -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:6 +#: ../desktop/org.gnome.Geary.appdata.xml.in.h:2 #: ../desktop/org.gnome.Geary.desktop.in.h:4 #: ../desktop/geary-autostart.desktop.in.h:4 -#: ../src/client/application/geary-application.vala:21 msgid "Send and receive email" msgstr "Šalje i prima e-poštu" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:7 +#: ../desktop/org.gnome.Geary.appdata.xml.in.h:3 msgid "" "Geary is an email application built around conversations, for the GNOME 3 " "desktop. It allows you to read, find and send email with a straightforward, " @@ -52,7 +44,7 @@ "Omogućuje vam da čitate, nađete i šaljete e-poštu sa jasnim, savremenim " "sučeljem." -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:8 +#: ../desktop/org.gnome.Geary.appdata.xml.in.h:4 msgid "" "Conversations allow you to read a complete discussion without having to find " "and click from message to message." @@ -60,48 +52,36 @@ "Razgovori vam omogućavaju da pročitate čitav razgovor a da ne morate da " "tražite i klikate od poruke do poruke." -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:9 +#: ../desktop/org.gnome.Geary.appdata.xml.in.h:5 msgid "Geary’s features include:" msgstr "U Gerijeve funkcije spadaju:" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:10 +#: ../desktop/org.gnome.Geary.appdata.xml.in.h:6 msgid "Quick email account setup" msgstr "Brzo podešavanje naloga e-pošte" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:11 +#: ../desktop/org.gnome.Geary.appdata.xml.in.h:7 msgid "Shows related messages together in conversations" msgstr "Zajednički prikaz povezanih poruka u razgovoru" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:12 +#: ../desktop/org.gnome.Geary.appdata.xml.in.h:8 msgid "Fast, full text and keyword search" msgstr "Brza pretraga po čitavom tekstu i ključnoj reči" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:13 +#: ../desktop/org.gnome.Geary.appdata.xml.in.h:9 msgid "Full-featured HTML and plain text message composer" msgstr "Potpuno funkcionalan sastavljač poruke u HTML-u i običnom tekstu" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:14 +#: ../desktop/org.gnome.Geary.appdata.xml.in.h:10 msgid "Desktop notification of new mail" msgstr "Obaveštenje o novoj pošti na radnoj površi" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:15 +#: ../desktop/org.gnome.Geary.appdata.xml.in.h:11 msgid "Compatible with GMail, Yahoo! Mail, Outlook.com and other IMAP servers" msgstr "" "Saglasan je sa G-poštom, Jahu! poštom, Autlukom i drugim IMAP serverima" -#. Translators: A screenshot description. -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:17 -#| msgid "_Display conversation preview" -msgid "Geary displaying a conversation" -msgstr "Geri prikazuje razgovor" - -#. Translators: A screenshot description. -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:19 -msgid "Geary showing the rich text composer" -msgstr "Geri prikazuje sastavljač opširnog teksta" - #: ../desktop/org.gnome.Geary.desktop.in.h:2 -#: ../desktop/geary-autostart.desktop.in.h:2 msgid "Email" msgstr "E-pošta" @@ -118,6 +98,11 @@ msgid "Compose Message" msgstr "Nova poruka" +#: ../desktop/geary-autostart.desktop.in.h:2 +#: ../src/client/application/geary-application.vala:21 +msgid "Mail Client" +msgstr "Program za e-poštu" + #: ../desktop/geary-autostart.desktop.in.h:3 msgid "Geary Mail" msgstr "Gerijeva pošta" @@ -265,15 +250,11 @@ msgid "Copyright 2016 Software Freedom Conservancy Inc." msgstr "Autorska prava 2016 Software Freedom Conservancy Inc." -#: ../src/client/application/geary-application.vala:23 -msgid "Copyright 2016-2017 Geary Development Team." -msgstr "Autorska prava 2016-2017 razvojni tim Gerija." - -#: ../src/client/application/geary-application.vala:25 +#: ../src/client/application/geary-application.vala:24 msgid "Visit the Geary web site" msgstr "Posetite veb stranicu Gerija" -#: ../src/client/application/geary-application.vala:466 +#: ../src/client/application/geary-application.vala:445 #, c-format msgid "About %s" msgstr "O programu „%s“" @@ -281,7 +262,7 @@ #. Translators: add your name and email address to receive #. credit in the About dialog For example: Yamada Taro #. -#: ../src/client/application/geary-application.vala:470 +#: ../src/client/application/geary-application.vala:449 msgid "translator-credits" msgstr "" "Miroslav Nikolić \n" @@ -357,17 +338,17 @@ msgid "Use %s to open a new composer window" msgstr "Koristite „%s“ za otvaranje novog prozora sastavljača" -#: ../src/client/application/geary-args.vala:56 +#: ../src/client/application/geary-args.vala:54 msgid "Please report comments, suggestions and bugs to:" msgstr "Napomene, predloge i greške šaljite na:" #. i18n: Command line arguments are invalid -#: ../src/client/application/geary-args.vala:63 +#: ../src/client/application/geary-args.vala:61 #, c-format msgid "Failed to parse command line options: %s\n" msgstr "Ne mogu da obradim opciju linije naredbi: %s\n" -#: ../src/client/application/geary-args.vala:74 +#: ../src/client/application/geary-args.vala:72 #, c-format msgid "Unrecognized command line option “%s”\n" msgstr "Nepoznata opcija linije naredbi „%s“\n" @@ -697,15 +678,15 @@ "\n" "Proverite vašu mrežu i ponovo pokrenite Gerija." -#: ../src/client/application/geary-controller.vala:2008 +#: ../src/client/application/geary-controller.vala:1998 msgid "Undo move (Ctrl+Z)" msgstr "Opozovi (Ktrl+Z)" -#: ../src/client/application/geary-controller.vala:2018 +#: ../src/client/application/geary-controller.vala:2008 msgid "Are you sure you want to open these attachments?" msgstr "Da li sigurno želite da otvorite ove priloge?" -#: ../src/client/application/geary-controller.vala:2019 +#: ../src/client/application/geary-controller.vala:2009 msgid "" "Attachments may cause damage to your system if opened. Only open files from " "trusted sources." @@ -713,56 +694,56 @@ "Priložene datoteke mogu da oštete sistem ako se otvore. Otvorite datoteke " "samo sa poverljivih izvora." -#: ../src/client/application/geary-controller.vala:2020 +#: ../src/client/application/geary-controller.vala:2010 msgid "Don’t _ask me again" msgstr "Ne pitaj me _ponovo" -#: ../src/client/application/geary-controller.vala:2130 +#: ../src/client/application/geary-controller.vala:2145 #, c-format msgid "A file named “%s” already exists. Do you want to replace it?" msgstr "Datoteka pod nazivom „%s“ već postoji. Da li želite da je zamenite?" -#: ../src/client/application/geary-controller.vala:2132 +#: ../src/client/application/geary-controller.vala:2147 #, c-format msgid "" "The file already exists in “%s”. Replacing it will overwrite its contents." msgstr "" "Datoteka već postoji u „%s“. Ukoliko je zamenite prepisaćete njen sadržaj." -#: ../src/client/application/geary-controller.vala:2135 +#: ../src/client/application/geary-controller.vala:2150 msgid "_Replace" msgstr "_Zameni" #. Find out what to do with the inline composers. #. TODO: Remove this in favor of automatically saving drafts -#: ../src/client/application/geary-controller.vala:2383 +#: ../src/client/application/geary-controller.vala:2398 msgid "Close open draft messages?" msgstr "Da zatvorim otvorene poruke s nacrtima?" -#: ../src/client/application/geary-controller.vala:2505 +#: ../src/client/application/geary-controller.vala:2520 #, c-format msgid "Empty all email from your %s folder?" msgstr "Da ispraznim e-poštu iz fascikle „%s“?" -#: ../src/client/application/geary-controller.vala:2506 +#: ../src/client/application/geary-controller.vala:2521 msgid "This removes the email from Geary and your email server." msgstr "Ovo će ukloniti poštu iz Gerija i na serverima vaše e-pošte." -#: ../src/client/application/geary-controller.vala:2507 +#: ../src/client/application/geary-controller.vala:2522 msgid "This cannot be undone." msgstr "Ne možete da opozovete ovu radnju." -#: ../src/client/application/geary-controller.vala:2508 +#: ../src/client/application/geary-controller.vala:2523 #, c-format msgid "Empty %s" msgstr "Isprazni „%s“" -#: ../src/client/application/geary-controller.vala:2525 +#: ../src/client/application/geary-controller.vala:2540 #, c-format msgid "Error emptying %s" msgstr "Greška prilikom pražnjenja „%s“" -#: ../src/client/application/geary-controller.vala:2555 +#: ../src/client/application/geary-controller.vala:2570 msgid "Do you want to permanently delete this message?" msgid_plural "Do you want to permanently delete these messages?" msgstr[0] "Da li želite trajno da obrišete ovu poruku?" @@ -770,23 +751,23 @@ msgstr[2] "Da li želite trajno da obrišete ove poruke?" msgstr[3] "Da li želite trajno da obrišete ovu poruku?" -#: ../src/client/application/geary-controller.vala:2557 +#: ../src/client/application/geary-controller.vala:2572 msgid "Delete" msgstr "Obriši" -#: ../src/client/application/geary-controller.vala:2589 +#: ../src/client/application/geary-controller.vala:2604 msgid "Undo archive (Ctrl+Z)" msgstr "Opozovi arhiviranje (Ktrl+Z)" -#: ../src/client/application/geary-controller.vala:2604 +#: ../src/client/application/geary-controller.vala:2619 msgid "Undo trash (Ctrl+Z)" msgstr "Opozovi brisanje (Ktrl+Z)" -#: ../src/client/application/geary-controller.vala:2658 +#: ../src/client/application/geary-controller.vala:2673 msgid "Undo (Ctrl+Z)" msgstr "Opozovi (Ktrl+Z)" -#: ../src/client/application/geary-controller.vala:2789 +#: ../src/client/application/geary-controller.vala:2804 msgid "Failed to open default text editor." msgstr "Ne mogu da otvorim podrazumevani urednik teksta." @@ -913,28 +894,28 @@ "enclosing|encloses|enclosure|enclosures" msgstr "prilog|priloži|prilogu|prilažem|prilaganja|prilozi|priloženo" -#: ../src/client/composer/composer-widget.vala:1122 -#: ../src/client/composer/composer-widget.vala:1141 +#: ../src/client/composer/composer-widget.vala:1121 +#: ../src/client/composer/composer-widget.vala:1140 msgid "Do you want to discard this message?" msgstr "Da li želite da odbacite ovu poruku?" -#: ../src/client/composer/composer-widget.vala:1245 +#: ../src/client/composer/composer-widget.vala:1237 msgid "Send message with an empty subject and body?" msgstr "Da pošaljem poruku sa praznim naslovom i tekstom?" -#: ../src/client/composer/composer-widget.vala:1247 +#: ../src/client/composer/composer-widget.vala:1239 msgid "Send message with an empty subject?" msgstr "Da pošaljem poruku sa praznim naslovom?" -#: ../src/client/composer/composer-widget.vala:1249 +#: ../src/client/composer/composer-widget.vala:1241 msgid "Send message with an empty body?" msgstr "Da pošaljem poruku bez ikakvog teksta?" -#: ../src/client/composer/composer-widget.vala:1253 +#: ../src/client/composer/composer-widget.vala:1245 msgid "Send message without an attachment?" msgstr "Da pošaljem poruku bez priloga?" -#: ../src/client/composer/composer-widget.vala:1515 +#: ../src/client/composer/composer-widget.vala:1498 #, c-format msgid "“%s” already attached for delivery." msgstr "„%s“ je već priloženo za isporuku." @@ -944,73 +925,73 @@ #. description of the document type, the second will #. be a human-friendly size string. For example: #. Document (100.9MB) -#: ../src/client/composer/composer-widget.vala:1523 +#: ../src/client/composer/composer-widget.vala:1506 #: ../src/client/conversation-viewer/conversation-email.vala:138 #, c-format msgid "%s (%s)" msgstr "%s (%s)" -#: ../src/client/composer/composer-widget.vala:1560 +#: ../src/client/composer/composer-widget.vala:1543 #, c-format msgid "“%s” could not be found." msgstr "Ne mogu da pronađem „%s“." -#: ../src/client/composer/composer-widget.vala:1566 +#: ../src/client/composer/composer-widget.vala:1549 #, c-format msgid "“%s” is a folder." msgstr "„%s“ je fascikla." -#: ../src/client/composer/composer-widget.vala:1572 +#: ../src/client/composer/composer-widget.vala:1555 #, c-format msgid "“%s” is an empty file." msgstr "„%s“ je prazna datoteka." -#: ../src/client/composer/composer-widget.vala:1585 +#: ../src/client/composer/composer-widget.vala:1568 #, c-format msgid "“%s” could not be opened for reading." msgstr "Ne mogu da otvorim „%s“ za čitanje." -#: ../src/client/composer/composer-widget.vala:1593 +#: ../src/client/composer/composer-widget.vala:1576 msgid "Cannot add attachment" msgstr "Ne mogu da dodam prilog" -#: ../src/client/composer/composer-widget.vala:1645 +#: ../src/client/composer/composer-widget.vala:1638 msgid "To: " msgstr "Prima: " -#: ../src/client/composer/composer-widget.vala:1648 +#: ../src/client/composer/composer-widget.vala:1641 msgid "Cc: " msgstr "Cc: " -#: ../src/client/composer/composer-widget.vala:1651 +#: ../src/client/composer/composer-widget.vala:1644 msgid "Bcc: " msgstr "Bcc: " -#: ../src/client/composer/composer-widget.vala:1654 +#: ../src/client/composer/composer-widget.vala:1647 msgid "Reply-To: " msgstr "Odgovor za: " -#: ../src/client/composer/composer-widget.vala:1786 +#: ../src/client/composer/composer-widget.vala:1778 msgid "Select Color" msgstr "Izaberite boju" #. Displayed in the From dropdown to indicate an "alternate email address" #. for an account. The first printf argument will be the alternate email #. address, and the second will be the account's primary email address. -#: ../src/client/composer/composer-widget.vala:1986 +#: ../src/client/composer/composer-widget.vala:1978 #, c-format msgid "%1$s via %2$s" msgstr "%1$s preko %2$s" #. Composer label (with mnemonic underscore) for the account selector #. when choosing what address to send a message from. -#: ../src/client/composer/composer-widget.vala:2028 +#: ../src/client/composer/composer-widget.vala:2020 msgid "_From:" msgstr "_Šalje:" #. Translators: This is the name of the file chooser filter #. when inserting an image in the composer. -#: ../src/client/composer/composer-widget.vala:2252 +#: ../src/client/composer/composer-widget.vala:2244 msgid "Images" msgstr "Slike" @@ -1053,35 +1034,36 @@ #. attribute value when displaying an inline image in an email #. that did not specify a file name. E.g. Image, 2012 -# Мирослав Николић , 2014—2018. +# Мирослав Николић , 2014—2017. # Милош Поповић , 2015. msgid "" msgstr "" "Project-Id-Version: geary\n" -"Report-Msgid-Bugs-To: https://bugzilla.gnome.org/enter_bug.cgi?product=geary&" -"keywords=I18N+L10N&component=internationalization\n" -"POT-Creation-Date: 2017-11-16 05:25+0000\n" -"PO-Revision-Date: 2018-03-03 08:12+0200\n" +"Report-Msgid-Bugs-To: https://bugzilla.gnome.org/enter_bug.cgi?" +"product=geary&keywords=I18N+L10N&component=internationalization\n" +"POT-Creation-Date: 2017-02-26 06:52+0000\n" +"PO-Revision-Date: 2017-02-26 09:25+0200\n" "Last-Translator: Мирослав Николић \n" -"Language-Team: српски \n" +"Language-Team: Serbian <(nothing)>\n" "Language: sr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=4; plural=n==1? 3 : n%10==1 && n%100!=11 ? 0 : " -"n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" +"Plural-Forms: nplurals=4; plural=n==1? 3 : n%10==1 && n%100!=11 ? 0 : n" +"%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" "X-Project-Style: gnome\n" -#. Translators: The application name -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:2 +#: ../desktop/org.gnome.Geary.appdata.xml.in.h:1 #: ../desktop/org.gnome.Geary.desktop.in.h:1 #: ../desktop/geary-autostart.desktop.in.h:1 msgid "Geary" msgstr "Гери" -#. Translators: The development team's name -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:4 -msgid "Geary Development Team" -msgstr "Развојни тим Герија" - -#. Translators: The application's summary / tagline -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:6 +#: ../desktop/org.gnome.Geary.appdata.xml.in.h:2 #: ../desktop/org.gnome.Geary.desktop.in.h:4 #: ../desktop/geary-autostart.desktop.in.h:4 -#: ../src/client/application/geary-application.vala:21 msgid "Send and receive email" msgstr "Шаље и прима е-пошту" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:7 +#: ../desktop/org.gnome.Geary.appdata.xml.in.h:3 msgid "" "Geary is an email application built around conversations, for the GNOME 3 " "desktop. It allows you to read, find and send email with a straightforward, " @@ -52,7 +44,7 @@ "Омогућује вам да читате, нађете и шаљете е-пошту са јасним, савременим " "сучељем." -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:8 +#: ../desktop/org.gnome.Geary.appdata.xml.in.h:4 msgid "" "Conversations allow you to read a complete discussion without having to find " "and click from message to message." @@ -60,48 +52,36 @@ "Разговори вам омогућавају да прочитате читав разговор а да не морате да " "тражите и кликате од поруке до поруке." -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:9 +#: ../desktop/org.gnome.Geary.appdata.xml.in.h:5 msgid "Geary’s features include:" msgstr "У Геријеве функције спадају:" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:10 +#: ../desktop/org.gnome.Geary.appdata.xml.in.h:6 msgid "Quick email account setup" msgstr "Брзо подешавање налога е-поште" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:11 +#: ../desktop/org.gnome.Geary.appdata.xml.in.h:7 msgid "Shows related messages together in conversations" msgstr "Заједнички приказ повезаних порука у разговору" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:12 +#: ../desktop/org.gnome.Geary.appdata.xml.in.h:8 msgid "Fast, full text and keyword search" msgstr "Брза претрага по читавом тексту и кључној речи" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:13 +#: ../desktop/org.gnome.Geary.appdata.xml.in.h:9 msgid "Full-featured HTML and plain text message composer" msgstr "Потпуно функционалан састављач поруке у ХТМЛ-у и обичном тексту" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:14 +#: ../desktop/org.gnome.Geary.appdata.xml.in.h:10 msgid "Desktop notification of new mail" msgstr "Обавештење о новој пошти на радној површи" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:15 +#: ../desktop/org.gnome.Geary.appdata.xml.in.h:11 msgid "Compatible with GMail, Yahoo! Mail, Outlook.com and other IMAP servers" msgstr "" "Сагласан је са Г-поштом, Јаху! поштом, Аутлуком и другим ИМАП серверима" -#. Translators: A screenshot description. -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:17 -#| msgid "_Display conversation preview" -msgid "Geary displaying a conversation" -msgstr "Гери приказује разговор" - -#. Translators: A screenshot description. -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:19 -msgid "Geary showing the rich text composer" -msgstr "Гери приказује састављач опширног текста" - #: ../desktop/org.gnome.Geary.desktop.in.h:2 -#: ../desktop/geary-autostart.desktop.in.h:2 msgid "Email" msgstr "Е-пошта" @@ -118,6 +98,11 @@ msgid "Compose Message" msgstr "Нова порука" +#: ../desktop/geary-autostart.desktop.in.h:2 +#: ../src/client/application/geary-application.vala:21 +msgid "Mail Client" +msgstr "Програм за е-пошту" + #: ../desktop/geary-autostart.desktop.in.h:3 msgid "Geary Mail" msgstr "Геријева пошта" @@ -265,15 +250,11 @@ msgid "Copyright 2016 Software Freedom Conservancy Inc." msgstr "Ауторска права 2016 Software Freedom Conservancy Inc." -#: ../src/client/application/geary-application.vala:23 -msgid "Copyright 2016-2017 Geary Development Team." -msgstr "Ауторска права 2016-2017 развојни тим Герија." - -#: ../src/client/application/geary-application.vala:25 +#: ../src/client/application/geary-application.vala:24 msgid "Visit the Geary web site" msgstr "Посетите веб страницу Герија" -#: ../src/client/application/geary-application.vala:466 +#: ../src/client/application/geary-application.vala:445 #, c-format msgid "About %s" msgstr "О програму „%s“" @@ -281,7 +262,7 @@ #. Translators: add your name and email address to receive #. credit in the About dialog For example: Yamada Taro #. -#: ../src/client/application/geary-application.vala:470 +#: ../src/client/application/geary-application.vala:449 msgid "translator-credits" msgstr "" "Мирослав Николић \n" @@ -357,17 +338,17 @@ msgid "Use %s to open a new composer window" msgstr "Користите „%s“ за отварање новог прозора састављача" -#: ../src/client/application/geary-args.vala:56 +#: ../src/client/application/geary-args.vala:54 msgid "Please report comments, suggestions and bugs to:" msgstr "Напомене, предлоге и грешке шаљите на:" #. i18n: Command line arguments are invalid -#: ../src/client/application/geary-args.vala:63 +#: ../src/client/application/geary-args.vala:61 #, c-format msgid "Failed to parse command line options: %s\n" msgstr "Не могу да обрадим опцију линије наредби: %s\n" -#: ../src/client/application/geary-args.vala:74 +#: ../src/client/application/geary-args.vala:72 #, c-format msgid "Unrecognized command line option “%s”\n" msgstr "Непозната опција линије наредби „%s“\n" @@ -697,15 +678,15 @@ "\n" "Проверите вашу мрежу и поново покрените Герија." -#: ../src/client/application/geary-controller.vala:2008 +#: ../src/client/application/geary-controller.vala:1998 msgid "Undo move (Ctrl+Z)" msgstr "Опозови (Ктрл+Z)" -#: ../src/client/application/geary-controller.vala:2018 +#: ../src/client/application/geary-controller.vala:2008 msgid "Are you sure you want to open these attachments?" msgstr "Да ли сигурнo желите да отворите ове прилоге?" -#: ../src/client/application/geary-controller.vala:2019 +#: ../src/client/application/geary-controller.vala:2009 msgid "" "Attachments may cause damage to your system if opened. Only open files from " "trusted sources." @@ -713,56 +694,56 @@ "Приложене датотеке могу да оштете систем ако се отворе. Отворите датотеке " "само са поверљивих извора." -#: ../src/client/application/geary-controller.vala:2020 +#: ../src/client/application/geary-controller.vala:2010 msgid "Don’t _ask me again" msgstr "Не питај ме _поново" -#: ../src/client/application/geary-controller.vala:2130 +#: ../src/client/application/geary-controller.vala:2145 #, c-format msgid "A file named “%s” already exists. Do you want to replace it?" msgstr "Датотека под називом „%s“ већ постоји. Да ли желите да је замените?" -#: ../src/client/application/geary-controller.vala:2132 +#: ../src/client/application/geary-controller.vala:2147 #, c-format msgid "" "The file already exists in “%s”. Replacing it will overwrite its contents." msgstr "" "Датотека већ постоји у „%s“. Уколико је замените преписаћете њен садржај." -#: ../src/client/application/geary-controller.vala:2135 +#: ../src/client/application/geary-controller.vala:2150 msgid "_Replace" msgstr "_Замени" #. Find out what to do with the inline composers. #. TODO: Remove this in favor of automatically saving drafts -#: ../src/client/application/geary-controller.vala:2383 +#: ../src/client/application/geary-controller.vala:2398 msgid "Close open draft messages?" msgstr "Да затворим отворене поруке с нацртима?" -#: ../src/client/application/geary-controller.vala:2505 +#: ../src/client/application/geary-controller.vala:2520 #, c-format msgid "Empty all email from your %s folder?" msgstr "Да испразним е-пошту из фасцикле „%s“?" -#: ../src/client/application/geary-controller.vala:2506 +#: ../src/client/application/geary-controller.vala:2521 msgid "This removes the email from Geary and your email server." msgstr "Ово ће уклонити пошту из Герија и на серверима ваше е-поште." -#: ../src/client/application/geary-controller.vala:2507 +#: ../src/client/application/geary-controller.vala:2522 msgid "This cannot be undone." msgstr "Не можете да опозовете ову радњу." -#: ../src/client/application/geary-controller.vala:2508 +#: ../src/client/application/geary-controller.vala:2523 #, c-format msgid "Empty %s" msgstr "Испразни „%s“" -#: ../src/client/application/geary-controller.vala:2525 +#: ../src/client/application/geary-controller.vala:2540 #, c-format msgid "Error emptying %s" msgstr "Грешка приликом пражњења „%s“" -#: ../src/client/application/geary-controller.vala:2555 +#: ../src/client/application/geary-controller.vala:2570 msgid "Do you want to permanently delete this message?" msgid_plural "Do you want to permanently delete these messages?" msgstr[0] "Да ли желите трајно да обришете ову поруку?" @@ -770,23 +751,23 @@ msgstr[2] "Да ли желите трајно да обришете ове поруке?" msgstr[3] "Да ли желите трајно да обришете ову поруку?" -#: ../src/client/application/geary-controller.vala:2557 +#: ../src/client/application/geary-controller.vala:2572 msgid "Delete" msgstr "Обриши" -#: ../src/client/application/geary-controller.vala:2589 +#: ../src/client/application/geary-controller.vala:2604 msgid "Undo archive (Ctrl+Z)" msgstr "Опозови архивирање (Ктрл+Z)" -#: ../src/client/application/geary-controller.vala:2604 +#: ../src/client/application/geary-controller.vala:2619 msgid "Undo trash (Ctrl+Z)" msgstr "Опозови брисање (Ктрл+Z)" -#: ../src/client/application/geary-controller.vala:2658 +#: ../src/client/application/geary-controller.vala:2673 msgid "Undo (Ctrl+Z)" msgstr "Опозови (Ктрл+Z)" -#: ../src/client/application/geary-controller.vala:2789 +#: ../src/client/application/geary-controller.vala:2804 msgid "Failed to open default text editor." msgstr "Не могу да отворим подразумевани уредник текста." @@ -913,28 +894,28 @@ "enclosing|encloses|enclosure|enclosures" msgstr "прилог|приложи|прилогу|прилажем|прилагања|прилози|приложено" -#: ../src/client/composer/composer-widget.vala:1122 -#: ../src/client/composer/composer-widget.vala:1141 +#: ../src/client/composer/composer-widget.vala:1121 +#: ../src/client/composer/composer-widget.vala:1140 msgid "Do you want to discard this message?" msgstr "Да ли желите да одбаците ову поруку?" -#: ../src/client/composer/composer-widget.vala:1245 +#: ../src/client/composer/composer-widget.vala:1237 msgid "Send message with an empty subject and body?" msgstr "Да пошаљем поруку са празним насловом и текстом?" -#: ../src/client/composer/composer-widget.vala:1247 +#: ../src/client/composer/composer-widget.vala:1239 msgid "Send message with an empty subject?" msgstr "Да пошаљем поруку са празним насловом?" -#: ../src/client/composer/composer-widget.vala:1249 +#: ../src/client/composer/composer-widget.vala:1241 msgid "Send message with an empty body?" msgstr "Да пошаљем поруку без икаквог текста?" -#: ../src/client/composer/composer-widget.vala:1253 +#: ../src/client/composer/composer-widget.vala:1245 msgid "Send message without an attachment?" msgstr "Да пошаљем поруку без прилога?" -#: ../src/client/composer/composer-widget.vala:1515 +#: ../src/client/composer/composer-widget.vala:1498 #, c-format msgid "“%s” already attached for delivery." msgstr "„%s“ је већ приложено за испоруку." @@ -944,73 +925,73 @@ #. description of the document type, the second will #. be a human-friendly size string. For example: #. Document (100.9MB) -#: ../src/client/composer/composer-widget.vala:1523 +#: ../src/client/composer/composer-widget.vala:1506 #: ../src/client/conversation-viewer/conversation-email.vala:138 #, c-format msgid "%s (%s)" msgstr "%s (%s)" -#: ../src/client/composer/composer-widget.vala:1560 +#: ../src/client/composer/composer-widget.vala:1543 #, c-format msgid "“%s” could not be found." msgstr "Не могу да пронађем „%s“." -#: ../src/client/composer/composer-widget.vala:1566 +#: ../src/client/composer/composer-widget.vala:1549 #, c-format msgid "“%s” is a folder." msgstr "„%s“ је фасцикла." -#: ../src/client/composer/composer-widget.vala:1572 +#: ../src/client/composer/composer-widget.vala:1555 #, c-format msgid "“%s” is an empty file." msgstr "„%s“ је празна датотека." -#: ../src/client/composer/composer-widget.vala:1585 +#: ../src/client/composer/composer-widget.vala:1568 #, c-format msgid "“%s” could not be opened for reading." msgstr "Не могу да отворим „%s“ за читање." -#: ../src/client/composer/composer-widget.vala:1593 +#: ../src/client/composer/composer-widget.vala:1576 msgid "Cannot add attachment" msgstr "Не могу да додам прилог" -#: ../src/client/composer/composer-widget.vala:1645 +#: ../src/client/composer/composer-widget.vala:1638 msgid "To: " msgstr "Прима: " -#: ../src/client/composer/composer-widget.vala:1648 +#: ../src/client/composer/composer-widget.vala:1641 msgid "Cc: " msgstr "Цц: " -#: ../src/client/composer/composer-widget.vala:1651 +#: ../src/client/composer/composer-widget.vala:1644 msgid "Bcc: " msgstr "Бцц: " -#: ../src/client/composer/composer-widget.vala:1654 +#: ../src/client/composer/composer-widget.vala:1647 msgid "Reply-To: " msgstr "Одговор за: " -#: ../src/client/composer/composer-widget.vala:1786 +#: ../src/client/composer/composer-widget.vala:1778 msgid "Select Color" msgstr "Изаберите боју" #. Displayed in the From dropdown to indicate an "alternate email address" #. for an account. The first printf argument will be the alternate email #. address, and the second will be the account's primary email address. -#: ../src/client/composer/composer-widget.vala:1986 +#: ../src/client/composer/composer-widget.vala:1978 #, c-format msgid "%1$s via %2$s" msgstr "%1$s преко %2$s" #. Composer label (with mnemonic underscore) for the account selector #. when choosing what address to send a message from. -#: ../src/client/composer/composer-widget.vala:2028 +#: ../src/client/composer/composer-widget.vala:2020 msgid "_From:" msgstr "_Шаље:" #. Translators: This is the name of the file chooser filter #. when inserting an image in the composer. -#: ../src/client/composer/composer-widget.vala:2252 +#: ../src/client/composer/composer-widget.vala:2244 msgid "Images" msgstr "Слике" @@ -1053,35 +1034,36 @@ #. attribute value when displaying an inline image in an email #. that did not specify a file name. E.g. Image, 2013 # TommyBrunn , 2012 # Mattias Eriksson , 2014, 2015. -# Anders Jonsson , 2016. -# Josef Andersson , 2016, 2017. +# Anders Jonsson , 2016, 2018, 2019. +# Josef Andersson , 2016, 2017, 2018. # msgid "" msgstr "" -"Project-Id-Version: geary-0.5.2\n" -"Report-Msgid-Bugs-To: https://bugzilla.gnome.org/enter_bug.cgi?" -"product=geary&keywords=I18N+L10N&component=internationalization\n" -"POT-Creation-Date: 2017-10-02 14:41+0000\n" -"PO-Revision-Date: 2017-10-03 13:21+0200\n" +"Project-Id-Version: geary\n" +"Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/geary/issues\n" +"POT-Creation-Date: 2019-03-10 16:43+0000\n" +"PO-Revision-Date: 2019-03-13 10:53+0100\n" "Last-Translator: Anders Jonsson \n" "Language-Team: Swedish \n" "Language: sv\n" @@ -28,29 +27,57 @@ "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Poedit 2.0.4\n" +"X-Generator: Poedit 2.2.1\n" + +#: desktop/geary-attach.contract.desktop.in:3 +msgid "Send by email" +msgstr "Skicka som e-post" + +#. Translators: Do NOT translate or transliterate this text (this is an icon file name)! +#: desktop/geary-attach.contract.desktop.in:5 +msgid "mail-send" +msgstr "mail-send" + +#: desktop/geary-attach.contract.desktop.in:6 +msgid "Send files using Geary" +msgstr "Skicka filer med Geary" #. Translators: The application name -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:2 -#: ../desktop/org.gnome.Geary.desktop.in.h:1 -#: ../desktop/geary-autostart.desktop.in.h:1 +#: desktop/geary-autostart.desktop.in:3 +#: desktop/org.gnome.Geary.appdata.xml.in:11 +#: desktop/org.gnome.Geary.desktop.in:3 +#: src/client/accounts/accounts-editor-servers-pane.vala:555 msgid "Geary" msgstr "Geary" -#. Translators: The development team's name -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:4 -msgid "Geary Development Team" -msgstr "Gearys utvecklingsgrupp" +#: desktop/geary-autostart.desktop.in:4 desktop/org.gnome.Geary.desktop.in:4 +msgid "Email" +msgstr "E-post" #. Translators: The application's summary / tagline -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:6 -#: ../desktop/org.gnome.Geary.desktop.in.h:4 -#: ../desktop/geary-autostart.desktop.in.h:4 -#: ../src/client/application/geary-application.vala:21 +#: desktop/geary-autostart.desktop.in:5 +#: desktop/org.gnome.Geary.appdata.xml.in:15 +#: desktop/org.gnome.Geary.desktop.in:5 +#: src/client/application/geary-application.vala:23 msgid "Send and receive email" msgstr "Skicka och ta emot e-post" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:7 +#. Translators: These are desktop search terms. Do not translate semicolons, end line with a semicolon. +#: desktop/geary-autostart.desktop.in:7 +msgid "Email;E-mail;Mail;" +msgstr "Email;E-mail;Mail;Epost;E-Post;" + +#. Translators: Do NOT translate or transliterate this text (this is an icon file name)! +#: desktop/geary-autostart.desktop.in:9 desktop/org.gnome.Geary.desktop.in:9 +msgid "org.gnome.Geary" +msgstr "org.gnome.Geary" + +#. Translators: The development team's name +#: desktop/org.gnome.Geary.appdata.xml.in:13 +msgid "Geary Development Team" +msgstr "Gearys utvecklingsgrupp" + +#: desktop/org.gnome.Geary.appdata.xml.in:17 msgid "" "Geary is an email application built around conversations, for the GNOME 3 " "desktop. It allows you to read, find and send email with a straightforward, " @@ -60,7 +87,7 @@ "skrivbordet. Det låter dig läsa, söka och sända e-post med ett enkelt " "modernt gränssnitt." -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:8 +#: desktop/org.gnome.Geary.appdata.xml.in:22 msgid "" "Conversations allow you to read a complete discussion without having to find " "and click from message to message." @@ -68,218 +95,682 @@ "Konversationer låter dig läsa en fullständig diskussion utan att behöva " "hitta och klicka från meddelande till meddelande." -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:9 +#: desktop/org.gnome.Geary.appdata.xml.in:26 msgid "Geary’s features include:" msgstr "Gearys egenskaper omfattar:" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:10 +#: desktop/org.gnome.Geary.appdata.xml.in:28 msgid "Quick email account setup" msgstr "Snabb inställning av e-postkonto" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:11 +#: desktop/org.gnome.Geary.appdata.xml.in:29 msgid "Shows related messages together in conversations" msgstr "Visa relaterade meddelanden tillsammans i konversationer" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:12 +#: desktop/org.gnome.Geary.appdata.xml.in:30 msgid "Fast, full text and keyword search" msgstr "Snabb heltext- och nyckelordssökning" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:13 +#: desktop/org.gnome.Geary.appdata.xml.in:31 msgid "Full-featured HTML and plain text message composer" msgstr "Fullständig HTML- och textbehandlare" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:14 +#: desktop/org.gnome.Geary.appdata.xml.in:32 msgid "Desktop notification of new mail" msgstr "Skrivbordasaviseringar för ny e-post" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:15 +#: desktop/org.gnome.Geary.appdata.xml.in:33 msgid "Compatible with GMail, Yahoo! Mail, Outlook.com and other IMAP servers" msgstr "Kompatibel med GMail, Yahoo! Mail, Outlook.com och andra IMAP-servrar" #. Translators: A screenshot description. -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:17 +#: desktop/org.gnome.Geary.appdata.xml.in:47 msgid "Geary displaying a conversation" msgstr "Geary visande en konversation" #. Translators: A screenshot description. -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:19 +#: desktop/org.gnome.Geary.appdata.xml.in:52 msgid "Geary showing the rich text composer" msgstr "Geary visande rich text-redigeraren" -#: ../desktop/org.gnome.Geary.desktop.in.h:2 -#: ../desktop/geary-autostart.desktop.in.h:2 -msgid "Email" -msgstr "E-post" - -#: ../desktop/org.gnome.Geary.desktop.in.h:3 -msgid "Geary Email" -msgstr "Geary E-post" - #. Translators: These are desktop search terms. Do not translate semicolons, end line with a semicolon. -#: ../desktop/org.gnome.Geary.desktop.in.h:6 +#: desktop/org.gnome.Geary.desktop.in:7 msgid "Mail;E-mail;IMAP;GMail;Yahoo;Hotmail;Outlook;" msgstr "E-post;Epost;IMAP;GMail;Yahoo;Hotmail;Outlook;" -#: ../desktop/org.gnome.Geary.desktop.in.h:7 ../ui/gtk/menus.ui.h:1 +#: desktop/org.gnome.Geary.desktop.in:21 msgid "Compose Message" msgstr "Skriv meddelande" -#: ../desktop/geary-autostart.desktop.in.h:3 -msgid "Geary Mail" -msgstr "Geary Mail" - -#: ../desktop/geary-autostart.desktop.in.h:5 -msgid "Email;E-mail;Mail;" -msgstr "Email;E-mail;Mail;Epost;E-Post;" +#: desktop/org.gnome.Geary.gschema.xml:8 +msgid "Maximize window" +msgstr "Maximera fönster" + +#: desktop/org.gnome.Geary.gschema.xml:9 +msgid "True if the application window is maximized, false otherwise." +msgstr "True om programfönstret är maximerat, annars false." + +#: desktop/org.gnome.Geary.gschema.xml:14 +msgid "Width of window" +msgstr "Bredd på fönstret" + +#: desktop/org.gnome.Geary.gschema.xml:15 +msgid "The last recorded width of the application window." +msgstr "Den senast lagrade bredden för programmets fönster." + +#: desktop/org.gnome.Geary.gschema.xml:20 +msgid "Height of window" +msgstr "Höjd på fönstret" + +#: desktop/org.gnome.Geary.gschema.xml:21 +msgid "The last recorded height of the application window." +msgstr "Den senast lagrade höjden på programmets fönster." + +#: desktop/org.gnome.Geary.gschema.xml:26 +msgid "Position of folder list pane" +msgstr "Position för listpanelen för mappar" + +#: desktop/org.gnome.Geary.gschema.xml:27 +msgid "Position of the folder list Paned grabber." +msgstr "Position för handtag till listpanelen för mappar." + +#: desktop/org.gnome.Geary.gschema.xml:32 +msgid "Position of folder list pane when horizontal" +msgstr "Position för listpanelen för mappar då horisontell" + +#: desktop/org.gnome.Geary.gschema.xml:33 +msgid "" +"Position of the folder list Paned grabber in the horizontal orientation." +msgstr "Position för handtag till listpanelen för mappar då horisontell." + +#: desktop/org.gnome.Geary.gschema.xml:38 +msgid "Position of folder list pane when vertical" +msgstr "Position för listpanelen för mappar då vertikal" + +#: desktop/org.gnome.Geary.gschema.xml:39 +msgid "Position of the folder list Paned grabber in the vertical orientation." +msgstr "Position för handtag till listpanelen för mappar då vertikal." + +#: desktop/org.gnome.Geary.gschema.xml:44 +msgid "Orientation of the folder list pane" +msgstr "Rikting på listpanelen för mappar" + +#: desktop/org.gnome.Geary.gschema.xml:45 +msgid "True if the folder list Paned is in the horizontal orientation." +msgstr "True om listpanelen för mappar är i horisontell riktning." + +#: desktop/org.gnome.Geary.gschema.xml:50 +msgid "Position of message list pane" +msgstr "Position för listpanelen för meddelanden" + +#: desktop/org.gnome.Geary.gschema.xml:51 +msgid "Position of the message list Paned grabber." +msgstr "Position för handtag till listpanelen för meddelanden." + +#: desktop/org.gnome.Geary.gschema.xml:56 +msgid "Autoselect next message" +msgstr "Välj nästa meddelande automatiskt" + +#: desktop/org.gnome.Geary.gschema.xml:57 +msgid "True if we should autoselect the next available conversation." +msgstr "True om nästa tillgänglig konversation ska väljas automatiskt." + +#: desktop/org.gnome.Geary.gschema.xml:62 +msgid "Display message previews" +msgstr "Förhandsvisning av meddelanden" + +#: desktop/org.gnome.Geary.gschema.xml:63 +msgid "True if we should display a short preview of each message." +msgstr "True om en kort förhandsvisning ska visas för varje meddelande." + +#: desktop/org.gnome.Geary.gschema.xml:68 +msgid "Languages that shall be used in the spell checker" +msgstr "Språk som ska användas i stavningskontrollen" + +#: desktop/org.gnome.Geary.gschema.xml:69 +msgid "List of the languages to use in the spell checker." +msgstr "Lista på språk som ska användas i stavningskontrollen." + +#: desktop/org.gnome.Geary.gschema.xml:74 +msgid "Languages that are displayed in the spell checker popover" +msgstr "Språk som visas i stavningskontrollens kontextfönster" + +#: desktop/org.gnome.Geary.gschema.xml:75 +msgid "" +"List of languages that are always displayed in the popover of the spell " +"checker." +msgstr "" +"Lista över språk som alltid visas i stavningskontrollens kontextfönster." + +#: desktop/org.gnome.Geary.gschema.xml:80 +msgid "Enable notification sounds" +msgstr "Aktivera aviseringsljud" + +#: desktop/org.gnome.Geary.gschema.xml:81 +msgid "True to play sounds for notifications and sending." +msgstr "True för att spela ljud för aviseringar och för att skicka." + +#: desktop/org.gnome.Geary.gschema.xml:86 +msgid "Show notifications for new mail" +msgstr "Visa aviseringar för ny e-post" + +#: desktop/org.gnome.Geary.gschema.xml:87 +msgid "True to show notification bubbles." +msgstr "True för att visa aviseringsbubblor." + +#: desktop/org.gnome.Geary.gschema.xml:92 +msgid "Notify of new mail at startup" +msgstr "Avisera om ny e-post vid uppstart" + +#: desktop/org.gnome.Geary.gschema.xml:93 +msgid "True to notify of new mail at startup." +msgstr "True för att avisera om ny e-post vid uppstart." + +#: desktop/org.gnome.Geary.gschema.xml:98 +msgid "Ask when opening an attachment" +msgstr "Fråga vid öppnandet av en bilaga" + +#: desktop/org.gnome.Geary.gschema.xml:99 +msgid "True to ask when opening an attachment." +msgstr "True för att fråga vid öppnandet av en bilaga." + +#: desktop/org.gnome.Geary.gschema.xml:104 +msgid "Whether to compose emails in HTML" +msgstr "Huruvida e-post ska skrivas i html" + +#: desktop/org.gnome.Geary.gschema.xml:105 +msgid "True to compose emails in HTML; false for plain text." +msgstr "True för att skriva e-post i html; false för ren text." + +#: desktop/org.gnome.Geary.gschema.xml:110 +msgid "Advisory strategy for full-text searching" +msgstr "Rekommenderad strategi för fulltextsökning" + +#: desktop/org.gnome.Geary.gschema.xml:111 +msgid "" +"Acceptable values are “exact”, “conservative”, “aggressive”, and “horizon”." +msgstr "" +"Tillåtna värden är ”exact”, ”conservative”, ”aggressive”, och ”horizon”." + +#: desktop/org.gnome.Geary.gschema.xml:116 +msgid "Zoom of conversation viewer" +msgstr "Zooma till konversationsvisaren" + +#: desktop/org.gnome.Geary.gschema.xml:117 +msgid "The zoom to apply on the conservation view." +msgstr "Zoom att tillämpa på konversationsvyn." + +#: desktop/org.gnome.Geary.gschema.xml:122 +msgid "Size of detached composer window" +msgstr "Storlek på frånkopplat textinmatningsfönster" + +#: desktop/org.gnome.Geary.gschema.xml:123 +msgid "The last recorded size of the detached composer window." +msgstr "" +"Den senast lagrade storleken för det frikopplade textinmatningsfönstret." + +#: desktop/org.gnome.Geary.gschema.xml:128 +msgid "Whether we migrated the old settings" +msgstr "Huruvida de gamla inställningarna migrerades" + +#: desktop/org.gnome.Geary.gschema.xml:129 +msgid "" +"False to check for the old “org.yorba.geary”-schema and copy its values." +msgstr "" +"False för att leta efter gamla ”org.yorba.geary”-schemat och kopiera dess " +"värden." + +#. Translators: In-app notification label, when +#. the app had a problem pinning an otherwise +#. untrusted TLS certificate +#: src/client/accounts/accounts-editor.vala:203 +msgid "Failed to store certificate" +msgstr "Misslyckades med att lagra certifikat" + +#. Translators: Label for adding an email account +#. account for a generic IMAP service provider. +#: src/client/accounts/accounts-editor-add-pane.vala:109 +msgid "All others" +msgstr "Alla andra" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:196 +#: src/client/accounts/accounts-editor-servers-pane.vala:316 +msgid "Check your receiving login and password" +msgstr "Kontrollera din inloggning och lösenord för mottagning" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:211 +#: src/client/accounts/accounts-editor-servers-pane.vala:329 +msgid "Check your receiving server details" +msgstr "Kontrollera dina detaljer för mottagande server" + +#. Translators: In-app notification label +#. There was an SMTP auth error, but IMAP already +#. succeeded, so the user probably needs to +#. specify custom creds here +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:233 +#: src/client/accounts/accounts-editor-servers-pane.vala:350 +msgid "Check your sending login and password" +msgstr "Kontrollera din inloggning och lösenord för sändning" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:247 +#: src/client/accounts/accounts-editor-servers-pane.vala:363 +msgid "Check your sending server details" +msgstr "Kontrollera dina detaljer för sändande server" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:262 +msgid "Check your email address and password" +msgstr "Kontrollera din e-postadress och lösenord" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:273 +msgid "Could not connect, check your network" +msgstr "Det gick inte att ansluta, kontrollera ditt nätverk" + +#. Translators: In-app notification label for a +#. generic error creating an account +#: src/client/accounts/accounts-editor-add-pane.vala:286 +msgid "An unexpected problem occurred" +msgstr "Ett oväntat problem uppstod" + +#. Translators: In-app notification label, the +#. string substitution is a more detailed reason. +#: src/client/accounts/accounts-editor-add-pane.vala:304 +#, c-format +msgid "Account not created: %s" +msgstr "Konto inte skapat: %s" + +#. Translators: Label for the person's actual name when adding +#. an account +#: src/client/accounts/accounts-editor-add-pane.vala:551 +msgid "Your name" +msgstr "Ditt namn" + +#. Translators: Label used for the address part of an +#. email address when editing a user's sender address +#. preferences for an account. +#: src/client/accounts/accounts-editor-add-pane.vala:568 +#: src/client/accounts/accounts-editor-edit-pane.vala:501 +msgid "Email address" +msgstr "E-postadress" + +#. Translators: Placeholder for the default sender address +#. when adding an account +#. Translators: This is used as a placeholder for the +#. address part of an email address when editing a user's +#. sender address preferences for an account. +#: src/client/accounts/accounts-editor-add-pane.vala:571 +#: src/client/accounts/accounts-editor-edit-pane.vala:469 +msgid "person@example.com" +msgstr "person@example.com" + +#. Translators: Label for an IMAP/SMTP service login/user name +#. when adding an account +#. Translators: Label for the user's login name for an +#. IMAP, SMTP, etc service +#: src/client/accounts/accounts-editor-add-pane.vala:585 +#: src/client/accounts/accounts-editor-servers-pane.vala:880 +msgid "Login name" +msgstr "Inloggningsnamn" + +#. Translators: Label for the user's password for an IMAP, +#. SMTP, etc service +#: src/client/accounts/accounts-editor-add-pane.vala:599 +#: src/client/accounts/accounts-editor-servers-pane.vala:999 +#: ui/password-dialog.glade:108 +msgid "Password" +msgstr "Lösenord" -#: ../desktop/geary-attach.contract.in.h:1 -msgid "Send by email" -msgstr "Skicka som e-post" +#. Translators: Label for the IMAP server hostname when +#. adding an account. +#. Translators: This label describes the host name or IP +#. address and port used by an account's IMAP service. +#: src/client/accounts/accounts-editor-add-pane.vala:621 +#: src/client/accounts/accounts-editor-servers-pane.vala:727 +msgid "IMAP server" +msgstr "IMAP-server" + +#. Translators: Placeholder for the IMAP server hostname +#. when adding an account. +#: src/client/accounts/accounts-editor-add-pane.vala:624 +msgid "imap.example.com" +msgstr "email@exempel.se" -#: ../desktop/geary-attach.contract.in.h:2 -msgid "Send files using Geary" -msgstr "Skicka filer med Geary" +#. Translators: Label for the SMTP server hostname when +#. adding an account. +#. Translators: This label describes the host name or IP +#. address and port used by an account's SMTP service. +#: src/client/accounts/accounts-editor-add-pane.vala:630 +#: src/client/accounts/accounts-editor-servers-pane.vala:733 +msgid "SMTP server" +msgstr "SMTP-server" + +#. Translators: Placeholder for the SMTP server hostname +#. when adding an account. +#: src/client/accounts/accounts-editor-add-pane.vala:633 +msgid "smtp.example.com" +msgstr "smtp.exempel.se" -#: ../src/client/accounts/account-dialog-add-edit-pane.vala:51 -#: ../src/client/components/stock.vala:31 -#: ../ui/conversation-email-menus.ui.h:10 -msgid "_Save" -msgstr "_Spara" +#. Translators: Label in the account editor for the user's +#. custom name for an account. +#: src/client/accounts/accounts-editor-edit-pane.vala:278 +#: ui/accounts_editor_remove_pane.ui:123 +msgid "Account name" +msgstr "Kontonamn" + +#. Translators: Tooltip used to undo changing +#. the name of an account. The string +#. substitution is the old name of the +#. account. +#: src/client/accounts/accounts-editor-edit-pane.vala:312 +#, c-format +msgid "Change account name back to “%s”" +msgstr "Ändra tillbaka kontonamn till ”%s”" + +#. Translators: Tooltip for adding a new email sender/from +#. address's address to an account +#: src/client/accounts/accounts-editor-edit-pane.vala:336 +msgid "Add a new sender email address" +msgstr "Lägg till en ny e-postadress för sändning" + +#. Translators: Label used to indicate the user has +#. provided no display name for one of their sender +#. email addresses in their account settings. +#: src/client/accounts/accounts-editor-edit-pane.vala:417 +msgid "Name not set" +msgstr "Namn har inte satts" + +#. Translators: This is used as a placeholder for the +#. display name for an email address when editing a user's +#. sender address preferences for an account. +#: src/client/accounts/accounts-editor-edit-pane.vala:456 +msgid "Sender Name" +msgstr "Avsändarnamn" -#: ../src/client/accounts/account-dialog-add-edit-pane.vala:51 -#: ../src/client/components/stock.vala:22 -msgid "_Add" -msgstr "_Lägg till" +#: src/client/accounts/accounts-editor-edit-pane.vala:479 +msgid "Remove" +msgstr "Ta bort" -#. reset/clear widgets -#: ../src/client/accounts/account-dialog-edit-alternate-emails-pane.vala:120 +#. Translators: Label used for the display name part of an +#. email address when editing a user's sender address +#. preferences for an account. +#: src/client/accounts/accounts-editor-edit-pane.vala:494 +msgid "Sender name" +msgstr "Avsändarnamn" + +#. Translators: Label used as the undo tooltip after adding an +#. new sender email address to an account. The string +#. substitution is the email address added. +#: src/client/accounts/accounts-editor-edit-pane.vala:561 +#, c-format +msgid "Remove “%s”" +msgstr "Ta bort ”%s”" + +#. Translators: Label used as the undo tooltip after editing a +#. sender address for an account. The string substitution is +#. the email address edited. +#: src/client/accounts/accounts-editor-edit-pane.vala:601 +#, c-format +msgid "Undo changes to “%s”" +msgstr "Ångra ändringar till ”%s”" + +#. Translators: Label used as the undo tooltip after removing +#. a sender address from an account. The string substitution +#. is the email address edited. +#: src/client/accounts/accounts-editor-edit-pane.vala:688 +#, c-format +msgid "Add “%s” back" +msgstr "Lägg till ”%s” igen" + +#. Translators: Label used as the undo tooltip after removing +#. a sender address from an account. The string substitution +#. is the email address edited. +#: src/client/accounts/accounts-editor-edit-pane.vala:730 +msgid "Undo signature changes" +msgstr "Ångra signaturändringar" + +#. Translators: This label describes the account +#. preference for the length of time (weeks, months or +#. years) that past email should be downloaded. +#: src/client/accounts/accounts-editor-edit-pane.vala:778 +msgid "Download mail" +msgstr "Hämta e-post" + +#. Translators: Tooltip for undoing a change +#. to the length of time that past email +#. should be downloaded for an account. The +#. string substitution is the duration, +#. e.g. "1 month back". +#: src/client/accounts/accounts-editor-edit-pane.vala:810 #, c-format -msgid "Additional addresses for %s" -msgstr "Ytterligare adresser för %s" - -#. Sets min size. -#: ../src/client/accounts/account-dialog.vala:21 -msgid "Accounts" -msgstr "Konton" +msgid "Change download period back to: %s" +msgstr "Ändra hämtningsperiod tillbaka till: %s" -#. Copyright 2016 Software Freedom Conservancy Inc. -#. * -#. * This software is licensed under the GNU Lesser General Public License -#. * (version 2.1 or later). See the COPYING file in this distribution. -#. -#. Page for adding or editing an account. -#. / Placeholder text indicating that the user should type their first name and last name -#: ../src/client/accounts/add-edit-page.vala:10 -msgid "First Last" -msgstr "Förnamn Efternamn" - -#: ../src/client/accounts/add-edit-page.vala:235 -msgid "Welcome to Geary." -msgstr "Välkommen till Geary." - -#: ../src/client/accounts/add-edit-page.vala:235 -msgid "Enter your account information to get started." -msgstr "Fyll i din kontoinformation för att komma igång." +#: src/client/accounts/accounts-editor-edit-pane.vala:831 +msgid "Everything" +msgstr "Allt" -#: ../src/client/accounts/add-edit-page.vala:255 +#: src/client/accounts/accounts-editor-edit-pane.vala:835 msgid "2 weeks back" msgstr "två veckor tillbaka" -#. IDs are # of days -#: ../src/client/accounts/add-edit-page.vala:256 +#: src/client/accounts/accounts-editor-edit-pane.vala:839 msgid "1 month back" msgstr "en månad tillbaka" -#: ../src/client/accounts/add-edit-page.vala:257 +#: src/client/accounts/accounts-editor-edit-pane.vala:843 msgid "3 months back" msgstr "tre månader tillbaka" -#: ../src/client/accounts/add-edit-page.vala:258 +#: src/client/accounts/accounts-editor-edit-pane.vala:847 msgid "6 months back" msgstr "ett halvår tillbaka" -#: ../src/client/accounts/add-edit-page.vala:259 +#: src/client/accounts/accounts-editor-edit-pane.vala:851 msgid "1 year back" msgstr "ett år tillbaka" -#: ../src/client/accounts/add-edit-page.vala:260 +#: src/client/accounts/accounts-editor-edit-pane.vala:855 msgid "2 years back" msgstr "2 år tillbaka" -#: ../src/client/accounts/add-edit-page.vala:261 +#: src/client/accounts/accounts-editor-edit-pane.vala:859 msgid "4 years back" msgstr "4 år tillbaka" -#. Separator -#: ../src/client/accounts/add-edit-page.vala:263 -msgid "Everything" -msgstr "Allt" - -#: ../src/client/accounts/add-edit-page.vala:283 -msgid "Edit" -msgstr "Redigera" - -#: ../src/client/accounts/add-edit-page.vala:285 -msgid "Preview" -msgstr "Förhandsgranska" +#: src/client/accounts/accounts-editor-edit-pane.vala:865 +#, c-format +msgid "%d day back" +msgid_plural "%d days back" +msgstr[0] "%d dag tillbaka" +msgstr[1] "%d dagar tillbaka" + +#: src/client/accounts/accounts-editor-list-pane.vala:243 +msgid "Undo" +msgstr "Ångra" + +#: src/client/accounts/accounts-editor-list-pane.vala:251 +msgid "Redo" +msgstr "Gör om" + +#: src/client/accounts/accounts-editor-list-pane.vala:345 +#: src/client/accounts/accounts-editor-list-pane.vala:433 +#: src/client/accounts/accounts-editor-row.vala:278 +msgid "Gmail" +msgstr "Gmail" -#: ../src/client/accounts/add-edit-page.vala:751 -msgid "Remem_ber passwords" -msgstr "_Kom ihåg lösenord" +#: src/client/accounts/accounts-editor-list-pane.vala:349 +#: src/client/accounts/accounts-editor-list-pane.vala:437 +#: src/client/accounts/accounts-editor-row.vala:282 +msgid "Outlook.com" +msgstr "Outlook.com" -#: ../src/client/accounts/add-edit-page.vala:758 ../ui/login.glade.h:7 -msgid "Remem_ber password" -msgstr "_Kom ihåg lösenord" +#: src/client/accounts/accounts-editor-list-pane.vala:353 +#: src/client/accounts/accounts-editor-list-pane.vala:441 +#: src/client/accounts/accounts-editor-row.vala:286 +msgid "Yahoo" +msgstr "Yahoo" + +#. Translators: Tooltip for accounts that have been +#. loaded but disabled by the user. +#: src/client/accounts/accounts-editor-list-pane.vala:371 +msgid "This account has been disabled" +msgstr "Detta konto har inaktiverats" + +#. Translators: Tooltip for accounts that have been +#. loaded but because of some error are not able to be +#. used. +#: src/client/accounts/accounts-editor-list-pane.vala:380 +msgid "This account has encountered a problem and is unavailable" +msgstr "Detta konto har stött på ett problem och är ej tillgängligt" + +#. Translators: Label for adding a generic email account +#: src/client/accounts/accounts-editor-list-pane.vala:430 +msgid "Other email providers" +msgstr "Andra e-postleverantörer" + +#. Translators: Notification shown after removing an +#. account. The string substitution is the name of the +#. account. +#: src/client/accounts/accounts-editor-list-pane.vala:547 +#, c-format +msgid "Account “%s” removed" +msgstr "Kontot ”%s” togs bort" + +#. Translators: Notification shown after removing an account +#. is undone. The string substitution is the name of the +#. account. +#: src/client/accounts/accounts-editor-list-pane.vala:554 +#, c-format +msgid "Account “%s” restored" +msgstr "Kontot ”%s” återställdes" + +#. Translators: Tooltip for dragging list items +#: src/client/accounts/accounts-editor-row.vala:49 +msgid "Drag to move this item" +msgstr "Dra för att flytta detta objekt" + +#. Translators: Label describes the service provider +#. hosting the email account, e.g. Gmail, Yahoo, or some +#. other generic IMAP service. +#: src/client/accounts/accounts-editor-row.vala:294 +msgid "Service provider" +msgstr "Tjänsteleverantör" + +#. Translators: This label describes what form of transport +#. security (TLS, StartTLS, etc) used by an account's IMAP or SMTP +#. service. +#: src/client/accounts/accounts-editor-row.vala:467 +msgid "Connection security" +msgstr "Anslutningssäkerhet" + +#. Translators: Label used when no auth scheme is used +#. by an account's IMAP or SMTP service. +#: src/client/accounts/accounts-editor-row.vala:478 +#: src/client/accounts/accounts-editor-servers-pane.vala:752 +#: src/client/accounts/accounts-editor-servers-pane.vala:964 +#: src/engine/api/geary-special-folder-type.vala:58 +msgid "None" +msgstr "Ingen" -#: ../src/client/accounts/add-edit-page.vala:792 -msgid "Unable to validate:\n" -msgstr "Kan inte bekräfta:\n" - -#: ../src/client/accounts/add-edit-page.vala:794 -msgid " • Invalid account nickname.\n" -msgstr " • Ogiltigt konto-alias.\n" - -#: ../src/client/accounts/add-edit-page.vala:797 -msgid " • Email address already added to Geary.\n" -msgstr " • E-postadress redan tillagd i Geary.\n" - -#: ../src/client/accounts/add-edit-page.vala:801 -msgid " • IMAP connection error.\n" -msgstr " • IMAP-anslutningsfel.\n" - -#: ../src/client/accounts/add-edit-page.vala:804 -msgid " • IMAP username or password incorrect.\n" -msgstr " • Felaktigt IMAP-användarnamn eller -lösenord.\n" - -#: ../src/client/accounts/add-edit-page.vala:807 -msgid " • SMTP connection error.\n" -msgstr " • SMTP-anslutningsfel.\n" - -#: ../src/client/accounts/add-edit-page.vala:810 -msgid " • SMTP username or password incorrect.\n" -msgstr " • Felaktigt SMTP-användarnamn eller -lösenord.\n" - -#: ../src/client/accounts/add-edit-page.vala:814 -msgid " • Connection error.\n" -msgstr " • Anslutningsfel.\n" - -#: ../src/client/accounts/add-edit-page.vala:818 -msgid " • Username or password incorrect.\n" -msgstr " • Felaktigt användarnamn eller lösenord.\n" +#: src/client/accounts/accounts-editor-row.vala:485 +msgid "StartTLS" +msgstr "StartTLS" + +#: src/client/accounts/accounts-editor-row.vala:492 +msgid "TLS" +msgstr "TLS" + +#. Translators: Label for source of SMTP authentication +#. credentials (none, use IMAP, custom) when adding a new +#. account +#. Button label for retrying when a login error has occurred +#: src/client/accounts/accounts-editor-row.vala:533 ui/main-window.ui:455 +msgid "Login" +msgstr "Inloggning" + +#. Translators: ComboBox value for source of SMTP +#. authentication credentials (none) when adding a new +#. account +#: src/client/accounts/accounts-editor-row.vala:540 +msgid "No login needed" +msgstr "Ingen inloggning krävs" + +#. Translators: ComboBox value for source of SMTP +#. authentication credentials (use IMAP) when adding a new +#. account +#: src/client/accounts/accounts-editor-row.vala:548 +msgid "Use same login as receiving" +msgstr "Använd samma inloggning som för mottagning" + +#. Translators: ComboBox value for source of SMTP +#. authentication credentials (custom) when adding a new +#. account +#: src/client/accounts/accounts-editor-row.vala:556 +msgid "Use a different login" +msgstr "Använd en annan inloggning" + +#. Translators: In-app notification label, the +#. string substitution is a more detailed reason. +#: src/client/accounts/accounts-editor-servers-pane.vala:377 +#, c-format +msgid "Account not updated: %s" +msgstr "Konto inte uppdaterat: %s" + +#. Translators: This label describes the program that +#. created the account, e.g. an SSO service like GOA, or +#. locally by Geary. +#: src/client/accounts/accounts-editor-servers-pane.vala:540 +msgid "Account source" +msgstr "Kontokälla" + +#: src/client/accounts/accounts-editor-servers-pane.vala:552 +msgid "GNOME Online Accounts" +msgstr "GNOME Nätkonton" + +#. Translators: This label describes an account +#. preference. +#: src/client/accounts/accounts-editor-servers-pane.vala:611 +msgid "Save drafts on server" +msgstr "Spara utkast på servern" + +#. Translators: This label describes an account +#. preference. +#: src/client/accounts/accounts-editor-servers-pane.vala:666 +msgid "Save sent email on server" +msgstr "Spara skickad e-post på server" + +#. Add a suffix for OAuth2 auth so people know they +#. shouldn't expect to be prompted for a password +#. Translators: Label used when an account's IMAP or +#. SMTP service uses OAuth2. The string replacement is +#. the service's login name. +#: src/client/accounts/accounts-editor-servers-pane.vala:950 +#, c-format +msgid "%s using OAuth2" +msgstr "%s använder OAuth2" + +#: src/client/accounts/accounts-editor-servers-pane.vala:960 +msgid "Use receiving server login" +msgstr "Använd inloggning för mottagningsserver" -#: ../src/client/application/geary-application.vala:22 +#: src/client/application/geary-application.vala:24 msgid "Copyright 2016 Software Freedom Conservancy Inc." msgstr "Copyright 2016 Software Freedom Conservancy Inc." -#: ../src/client/application/geary-application.vala:23 -msgid "Copyright 2016-2017 Geary Development Team." -msgstr "Copyright 2016-2017 Gearys utvecklingsgrupp." +#: src/client/application/geary-application.vala:25 +msgid "Copyright 2016-2019 Geary Development Team." +msgstr "Copyright 2016-2019 Gearys utvecklingsgrupp." -#: ../src/client/application/geary-application.vala:25 +#: src/client/application/geary-application.vala:27 msgid "Visit the Geary web site" msgstr "Besök Gearys webbplats" -#: ../src/client/application/geary-application.vala:466 +#: src/client/application/geary-application.vala:454 #, c-format msgid "About %s" msgstr "Om %s" @@ -287,322 +778,118 @@ #. Translators: add your name and email address to receive #. credit in the About dialog For example: Yamada Taro #. -#: ../src/client/application/geary-application.vala:470 +#: src/client/application/geary-application.vala:458 msgid "translator-credits" msgstr "" "Joachim Johansson \n" "Mattias Eriksson \n" +"Josef Andersson \n" +"Anders Jonsson \n" "\n" "Skicka synpunkter på översättningen till\n" -"." +"" -#: ../src/client/application/geary-args.vala:10 +#: src/client/application/geary-args.vala:10 msgid "Start Geary with hidden main window" msgstr "Starta Geary med huvudfönstret dolt" -#: ../src/client/application/geary-args.vala:11 +#: src/client/application/geary-args.vala:11 msgid "Output debugging information" msgstr "Skriv ut felsökningsinformation" -#: ../src/client/application/geary-args.vala:12 +#: src/client/application/geary-args.vala:12 msgid "Log conversation monitoring" msgstr "Logga konversationsövervakning" -#: ../src/client/application/geary-args.vala:13 +#: src/client/application/geary-args.vala:13 msgid "Log network deserialization" msgstr "Logga avserialisering av nätverksdata" -#: ../src/client/application/geary-args.vala:14 +#: src/client/application/geary-args.vala:14 msgid "Log network activity" msgstr "Logga nätverksaktivitet" #. / The IMAP replay queue is how changes on the server are replicated on the client. #. / It could also be called the IMAP events queue. -#: ../src/client/application/geary-args.vala:17 +#: src/client/application/geary-args.vala:17 msgid "Log IMAP replay queue" msgstr "Logga IMAP-händelser" #. / Serialization is how commands and responses are converted into a stream of bytes for #. / network transmission -#: ../src/client/application/geary-args.vala:20 +#: src/client/application/geary-args.vala:20 msgid "Log network serialization" msgstr "Logga nätverksserialisering" -#: ../src/client/application/geary-args.vala:21 +#: src/client/application/geary-args.vala:21 msgid "Log periodic activity" msgstr "Logga periodisk aktivitet" -#: ../src/client/application/geary-args.vala:22 +#: src/client/application/geary-args.vala:22 msgid "Log database queries (generates lots of messages)" msgstr "Logga databasförfrågningar (detta genererar många meddelanden)" #. / "Normalization" can also be called "synchronization" -#: ../src/client/application/geary-args.vala:24 +#: src/client/application/geary-args.vala:24 msgid "Log folder normalization" msgstr "Logga mappsynkronisering" -#: ../src/client/application/geary-args.vala:25 +#: src/client/application/geary-args.vala:25 msgid "Allow inspection of WebView" msgstr "Tillåt inspektion av WebView" -#: ../src/client/application/geary-args.vala:26 +#: src/client/application/geary-args.vala:26 msgid "Revoke all server certificates with TLS warnings" msgstr "Återkalla alla servercertifikat med TLS-varningar" -#: ../src/client/application/geary-args.vala:27 +#: src/client/application/geary-args.vala:27 msgid "Perform a graceful quit" msgstr "Avsluta" -#: ../src/client/application/geary-args.vala:28 +#: src/client/application/geary-args.vala:28 msgid "Display program version" msgstr "Visa programversion" #. This gives a command-line hint on how to open new composer windows with mailto: -#: ../src/client/application/geary-args.vala:53 +#: src/client/application/geary-args.vala:53 #, c-format msgid "Use %s to open a new composer window" msgstr "Använd %s för att öppna ett nytt redigerarfönster" -#: ../src/client/application/geary-args.vala:56 +#: src/client/application/geary-args.vala:56 msgid "Please report comments, suggestions and bugs to:" msgstr "Rapportera fel, eller kom med förslag och kommentarer, på:" #. i18n: Command line arguments are invalid -#: ../src/client/application/geary-args.vala:63 +#: src/client/application/geary-args.vala:63 #, c-format msgid "Failed to parse command line options: %s\n" msgstr "Kunde ej tyda kommandoradsalternativ: %s\n" -#: ../src/client/application/geary-args.vala:74 +#: src/client/application/geary-args.vala:74 #, c-format msgid "Unrecognized command line option “%s”\n" msgstr "Felaktigt kommandoradsalternativ: ”%s”\n" -#: ../src/client/application/geary-controller.vala:57 -msgid "Delete conversation" -msgstr "Radera konversation" - -#: ../src/client/application/geary-controller.vala:58 -msgid "Delete conversation (Shift+Delete)" -msgstr "Radera konversationen (Skift+Delete)" - -#: ../src/client/application/geary-controller.vala:59 -msgid "Delete conversations (Shift+Delete)" -msgstr "Radera konversationerna (Skift+Delete)" - -#. This refers to the action ("move email to the trash"), not the Trash folder itself -#: ../src/client/application/geary-controller.vala:63 -msgid "Move conversation to Trash (Delete, Backspace)" -msgstr "Flytta konversationen till papperskorgen (Delete, Backsteg)" - -#: ../src/client/application/geary-controller.vala:64 -msgid "Move conversations to Trash (Delete, Backspace)" -msgstr "Flytta konversationerna till papperskorgen (Delete, Backsteg)" - -#. This refers to the action ("archive an email"), not the Archive folder itself -#: ../src/client/application/geary-controller.vala:68 -msgid "_Archive" -msgstr "_Arkivera" - -#: ../src/client/application/geary-controller.vala:69 -msgid "Archive conversation (A)" -msgstr "Arkivera konversationen (A)" - -#: ../src/client/application/geary-controller.vala:70 -msgid "Archive conversations (A)" -msgstr "Arkivera konversationerna (A)" - -#: ../src/client/application/geary-controller.vala:73 -msgid "Mark as S_pam" -msgstr "Markera som skrä_ppost" - -#: ../src/client/application/geary-controller.vala:74 -msgid "Mark as not S_pam" -msgstr "Markera som icke skrä_ppost" - -#: ../src/client/application/geary-controller.vala:76 -#: ../src/client/application/geary-controller.vala:448 -msgid "Mark conversation" -msgstr "Markera konversation" - -#: ../src/client/application/geary-controller.vala:77 -msgid "Mark conversations" -msgstr "Markera konversationer" - -#: ../src/client/application/geary-controller.vala:78 -msgid "Add label to conversation" -msgstr "Lägg till en etikett till konversationen" - -#: ../src/client/application/geary-controller.vala:79 -msgid "Add label to conversations" -msgstr "Lägg till etiketter till konversationerna" - -#: ../src/client/application/geary-controller.vala:80 -#: ../src/client/application/geary-controller.vala:487 -msgid "Move conversation" -msgstr "Flytta konversationen" - -#: ../src/client/application/geary-controller.vala:81 -msgid "Move conversations" -msgstr "Flytta konversationerna" - -#: ../src/client/application/geary-controller.vala:450 -msgid "_Mark as…" -msgstr "_Markera som…" - -#: ../src/client/application/geary-controller.vala:456 -msgid "Mark as _Read" -msgstr "Markera som _läst" - -#: ../src/client/application/geary-controller.vala:462 -msgid "Mark as _Unread" -msgstr "Markera som _oläst" - -#: ../src/client/application/geary-controller.vala:468 -msgid "_Star" -msgstr "Markera som _viktigt" - -#: ../src/client/application/geary-controller.vala:473 -msgid "U_nstar" -msgstr "Markera som ov_iktigt" - -#: ../src/client/application/geary-controller.vala:483 -msgid "Add label" -msgstr "Lägg till etikett" - -#: ../src/client/application/geary-controller.vala:484 -msgid "_Label" -msgstr "Tilldela _etikett" - -#: ../src/client/application/geary-controller.vala:488 -msgid "_Move" -msgstr "_Flytta" - -#: ../src/client/application/geary-controller.vala:492 -msgid "Compose new message (Ctrl+N, N)" -msgstr "Skriv nytt meddelande (Ctrl+N, N)" - -#: ../src/client/application/geary-controller.vala:496 -#: ../ui/conversation-email-menus.ui.h:1 -msgid "_Reply" -msgstr "Sva_ra" - -#: ../src/client/application/geary-controller.vala:497 -msgid "Reply (Ctrl+R, R)" -msgstr "Svara (Ctrl+R, R)" - -#: ../src/client/application/geary-controller.vala:501 -msgid "R_eply All" -msgstr "Svara _alla" - -#: ../src/client/application/geary-controller.vala:502 -msgid "Reply all (Ctrl+Shift+R, Shift+R)" -msgstr "Svara alla (Ctrl+Skift+R, Skift+R)" - -#: ../src/client/application/geary-controller.vala:507 -#: ../ui/conversation-email-menus.ui.h:3 -msgid "_Forward" -msgstr "Vidare_befordra" - -#: ../src/client/application/geary-controller.vala:508 -msgid "Forward (Ctrl+L, F)" -msgstr "Vidarebefordra (Ctrl+L, F)" - -#: ../src/client/application/geary-controller.vala:538 -msgid "Empty _Spam…" -msgstr "Töm _skräpkorg…" - -#: ../src/client/application/geary-controller.vala:542 -msgid "Empty _Trash…" -msgstr "_Töm papperskorg…" - -#. No callback is connected, since we bind the toggle button to the search bar visibility -#: ../src/client/application/geary-controller.vala:574 -msgid "Toggle search bar" -msgstr "Växla visning av sökrad" +#. Translators: File name used in save chooser when saving +#. attachments that do not otherwise have a name. +#: src/client/application/geary-controller.vala:58 +msgid "Untitled" +msgstr "Namnlös" -#. No callback is connected, since we bind the toggle button to the find bar visibility -#: ../src/client/application/geary-controller.vala:579 -msgid "Toggle find bar" -msgstr "Växla visning av sökrad" - -#: ../src/client/application/geary-controller.vala:757 -msgid "Unable to store server trust exception" -msgstr "Kan inte lagra undantag för tillit till server" - -#: ../src/client/application/geary-controller.vala:992 -msgid "Your settings are insecure" -msgstr "Dina inställningar är osäkra" - -#: ../src/client/application/geary-controller.vala:993 -msgid "" -"Your IMAP and/or SMTP settings do not specify SSL or TLS. This means your " -"username and password could be read by another person on the network. Are " -"you sure you want to do this?" -msgstr "" -"Dina IMAP- och/eller SMTP-inställningar specificerar inte SSL eller TLS. " -"Detta innebär att ditt användarnamn och lösenord kan avläsas av någon annan " -"på nätverket. Är du säker på att du vill göra detta?" - -#: ../src/client/application/geary-controller.vala:994 -msgid "Co_ntinue" -msgstr "_Fortsätt" - -#: ../src/client/application/geary-controller.vala:1041 -msgid "Error connecting to the server" -msgstr "Fel vid anslutning till server" - -#: ../src/client/application/geary-controller.vala:1042 -msgid "" -"Geary encountered an error while connecting to the server. Please try again " -"in a few moments." -msgstr "" -"Geary påträffade ett fel vid anslutning till server. Prova igen efter en " -"stund." - -#. / Displayed in the space-limited status bar when a message fails to be sent due to error. -#: ../src/client/application/geary-controller.vala:1079 -#: ../src/client/components/status-bar.vala:29 -msgid "Error sending email" -msgstr "Misslyckades med att skicka e-post" - -#: ../src/client/application/geary-controller.vala:1080 -msgid "" -"Geary encountered an error sending an email. If the problem persists, " -"please manually delete the email from your Outbox folder." -msgstr "" -"Geary råkade ut för ett fel vid skickande av e-post. Om problemen kvarstår " -"kan du manuellt ta bort e-postmeddelandet från din utkorg." - -#. Displayed in the space-limited status bar when a message fails to be uploaded -#. to Sent Mail after being sent. -#: ../src/client/application/geary-controller.vala:1084 -#: ../src/client/components/status-bar.vala:33 -msgid "Error saving sent mail" -msgstr "Misslyckades med att spara skickat e-postmeddelande" - -#: ../src/client/application/geary-controller.vala:1085 -msgid "" -"Geary encountered an error saving a sent message to Sent Mail. The message " -"will stay in your Outbox folder until you delete it." -msgstr "" -"Geary råkade ut för ett fel vid sparande av ett skickat e-postmeddelande " -"till \"Skickad e-post\". Meddelandet kommer att ligga kvar i utkorgen tills " -"du tar bort det." - -#: ../src/client/application/geary-controller.vala:1154 +#: src/client/application/geary-controller.vala:954 msgid "Labels" msgstr "Etiketter" #. give the user two options: reset the Account local store, or exit Geary. A third #. could be done to leave the Account in an unopened state, but we don't currently #. have provisions for that. -#: ../src/client/application/geary-controller.vala:1166 +#: src/client/application/geary-controller.vala:967 #, c-format msgid "Unable to open the database for %s" msgstr "Kunde inte öppna databasen för %s" -#: ../src/client/application/geary-controller.vala:1167 +#: src/client/application/geary-controller.vala:968 #, c-format msgid "" "There was an error opening the local mail database for this account. This is " @@ -625,20 +912,20 @@ "Att bygga om databasen medför att alla lokala e-postmeddelanden och bifogade " "filer förstörs. E-post på din server är inte berörda av felet." -#: ../src/client/application/geary-controller.vala:1169 +#: src/client/application/geary-controller.vala:970 msgid "_Rebuild" msgstr "_Bygg om" -#: ../src/client/application/geary-controller.vala:1169 +#: src/client/application/geary-controller.vala:970 msgid "E_xit" msgstr "_Avsluta" -#: ../src/client/application/geary-controller.vala:1178 +#: src/client/application/geary-controller.vala:979 #, c-format msgid "Unable to rebuild database for “%s”" msgstr "Kunde inte bygga om databasen för ”%s”" -#: ../src/client/application/geary-controller.vala:1179 +#: src/client/application/geary-controller.vala:980 #, c-format msgid "" "Error during rebuild:\n" @@ -649,69 +936,15 @@ "\n" "%s" -#. some other problem opening the account ... as with other flow path, can't run -#. Geary today with an account in unopened state, so have to exit -#: ../src/client/application/geary-controller.vala:1201 -#: ../src/client/application/geary-controller.vala:1211 -#: ../src/client/application/geary-controller.vala:1222 -#, c-format -msgid "Unable to open local mailbox for %s" -msgstr "Kunde inte öppna lokal brevlåda för %s" - -#: ../src/client/application/geary-controller.vala:1202 -#, c-format -msgid "" -"There was an error opening the local mail database for this account. This is " -"possibly due to a file permissions problem.\n" -"\n" -"Please check that you have read/write permissions for all files in this " -"directory:\n" -"\n" -"%s" -msgstr "" -"Ett fel inträffade när den lokala e-postdatabasen för detta konto öppnades. " -"En möjlig orsak till detta kan vara problem med felaktiga filrättigheter. \n" -"\n" -"Vänligen kontrollera att du har läs- och skrivrättigheter för alla filer i " -"följande katalog:\n" -"\n" -"%s" - -#: ../src/client/application/geary-controller.vala:1212 -msgid "" -"The version number of the local mail database is formatted for a newer " -"version of Geary. Unfortunately, the database cannot be “rolled back” to " -"work with this version of Geary.\n" -"\n" -"Please install the latest version of Geary and try again." -msgstr "" -"Versionsnumret för den lokala e-postdatabasen är formaterad för en nyare " -"version av Geary. Tyvärr kan databasen inte återställas att fungera med " -"denna version av Geary.\n" -"\n" -"Vänligen installera den senaste versionen av Geary och försök igen." - -#: ../src/client/application/geary-controller.vala:1223 -msgid "" -"There was an error opening the local account. This is probably due to " -"connectivity issues.\n" -"\n" -"Please check your network connection and restart Geary." -msgstr "" -"Det uppstod ett fel när det lokala kontot öppnades. Detta beror förmodligen " -"på problem med anslutningen. \n" -"\n" -"Vänligen kontrollera din nätverksanslutning och starta om Geary." - -#: ../src/client/application/geary-controller.vala:1999 +#: src/client/application/geary-controller.vala:1835 msgid "Undo move (Ctrl+Z)" msgstr "Ångra förflyttning (Ctrl+Z)" -#: ../src/client/application/geary-controller.vala:2009 +#: src/client/application/geary-controller.vala:1845 msgid "Are you sure you want to open these attachments?" msgstr "Är du säker på att du vill öppna dessa bilagor?" -#: ../src/client/application/geary-controller.vala:2010 +#: src/client/application/geary-controller.vala:1846 msgid "" "Attachments may cause damage to your system if opened. Only open files from " "trusted sources." @@ -719,16 +952,22 @@ "Om du öppnar bifogade filer kan du skada ditt system. Öppna enbart filer " "från kontakter du litar på." -#: ../src/client/application/geary-controller.vala:2011 +#: src/client/application/geary-controller.vala:1847 msgid "Don’t _ask me again" msgstr "Fråga _inte igen" -#: ../src/client/application/geary-controller.vala:2121 +#. Translators: Dialog primary label when prompting to +#. overwrite a file. The string substitution is the file'sx +#. name. +#: src/client/application/geary-controller.vala:1976 #, c-format msgid "A file named “%s” already exists. Do you want to replace it?" msgstr "En fil med namnet ”%s” finns redan. Vill du ersätta den?" -#: ../src/client/application/geary-controller.vala:2123 +#. Translators: Dialog secondary label when prompting to +#. overwrite a file. The string substitution is the parent +#. folder's name. +#: src/client/application/geary-controller.vala:1983 #, c-format msgid "" "The file already exists in “%s”. Replacing it will overwrite its contents." @@ -736,184 +975,461 @@ "Filen finns redan i ”%s”. Att ersätta den kommer att skriva över dess " "innehåll." -#: ../src/client/application/geary-controller.vala:2126 +#: src/client/application/geary-controller.vala:1987 msgid "_Replace" msgstr "E_rsätt" -#. Find out what to do with the inline composers. -#. TODO: Remove this in favor of automatically saving drafts -#: ../src/client/application/geary-controller.vala:2374 -msgid "Close open draft messages?" -msgstr "Stänga öppna utkast?" +#: src/client/application/geary-controller.vala:2263 +msgid "Close the draft message?" +msgid_plural "Close all draft messages?" +msgstr[0] "Stänga utkastet?" +msgstr[1] "Stänga alla utkast?" -#: ../src/client/application/geary-controller.vala:2496 +#: src/client/application/geary-controller.vala:2389 #, c-format msgid "Empty all email from your %s folder?" msgstr "Ta bort alla e-postmeddelanden från din mapp %s?" -#: ../src/client/application/geary-controller.vala:2497 +#: src/client/application/geary-controller.vala:2390 msgid "This removes the email from Geary and your email server." msgstr "Detta tar bort e-postmeddelanden från Geary och din e-postserver." -#: ../src/client/application/geary-controller.vala:2498 +#: src/client/application/geary-controller.vala:2391 msgid "This cannot be undone." msgstr "Detta kan inte ångras." -#: ../src/client/application/geary-controller.vala:2499 +#: src/client/application/geary-controller.vala:2392 #, c-format msgid "Empty %s" msgstr "Töm %s" -#: ../src/client/application/geary-controller.vala:2516 +#: src/client/application/geary-controller.vala:2409 #, c-format msgid "Error emptying %s" msgstr "Fel vid tömning av %s" -#: ../src/client/application/geary-controller.vala:2546 +#: src/client/application/geary-controller.vala:2441 msgid "Do you want to permanently delete this message?" msgid_plural "Do you want to permanently delete these messages?" msgstr[0] "Vill du radera detta meddelande permanent?" msgstr[1] "Vill du radera dessa meddelanden permanent?" -#: ../src/client/application/geary-controller.vala:2548 +#: src/client/application/geary-controller.vala:2443 msgid "Delete" msgstr "Radera" -#: ../src/client/application/geary-controller.vala:2580 -msgid "Undo archive (Ctrl+Z)" -msgstr "Ångra arkivering (Ctrl+Z)" - -#: ../src/client/application/geary-controller.vala:2595 +#: src/client/application/geary-controller.vala:2457 msgid "Undo trash (Ctrl+Z)" msgstr "Ångra flytt till papperskorgen (Ctrl+Z)" -#: ../src/client/application/geary-controller.vala:2649 +#: src/client/application/geary-controller.vala:2507 +msgid "Undo archive (Ctrl+Z)" +msgstr "Ångra arkivering (Ctrl+Z)" + +#: src/client/application/geary-controller.vala:2552 msgid "Undo (Ctrl+Z)" msgstr "Ångra (Ctrl+Z)" -#: ../src/client/application/geary-controller.vala:2780 +#. Translators: The label for an in-app notification. The +#. string substitution is a list of recipients of the email. +#: src/client/application/geary-controller.vala:2633 +#, c-format +msgid "Successfully sent mail to %s." +msgstr "Skickade e-post till %s." + +#: src/client/application/geary-controller.vala:2715 msgid "Failed to open default text editor." msgstr "Det gick inte att öppna standardtextredigeraren." -#: ../src/client/components/main-window.vala:389 +#. Translators: Tooltip used when an entry requires a valid +#. email address to be entered, but one is not provided. +#: src/client/components/components-validator.vala:378 +msgid "An email address is required" +msgstr "En e-postadress krävs" + +#. Translators: Tooltip used when an entry requires a valid +#. email address to be entered, but the address is invalid. +#: src/client/components/components-validator.vala:382 +msgid "Not a valid email address" +msgstr "Inte en giltig e-postadress" + +#. Translators: Tooltip used when an entry requires a valid, +#. resolvable server name to be entered, but one is not +#. provided. +#: src/client/components/components-validator.vala:428 +msgid "A server name is required" +msgstr "Ett servernamn krävs" + +#. Translators: Tooltip used when an entry requires a valid +#. server name to be entered, but it was unable to be +#. looked-up in the DNS. +#: src/client/components/components-validator.vala:433 +msgid "Could not look up server name" +msgstr "Det gick inte att slå upp servernamn" + +#: src/client/components/main-toolbar.vala:151 +msgid "Mark conversation" +msgid_plural "Mark conversations" +msgstr[0] "Markera konversation" +msgstr[1] "Markera konversationer" + +#: src/client/components/main-toolbar.vala:156 +msgid "Add label to conversation" +msgid_plural "Add label to conversations" +msgstr[0] "Lägg till etikett till konversation" +msgstr[1] "Lägg till etikett till konversationer" + +#: src/client/components/main-toolbar.vala:161 +msgid "Move conversation" +msgid_plural "Move conversations" +msgstr[0] "Flytta konversation" +msgstr[1] "Flytta konversationer" + +#: src/client/components/main-toolbar.vala:166 +msgid "Archive conversation (A)" +msgid_plural "Archive conversations (A)" +msgstr[0] "Arkivera konversation (A)" +msgstr[1] "Arkivera konversationer (A)" + +#: src/client/components/main-toolbar.vala:175 +msgid "Move conversation to Trash (Delete, Backspace)" +msgid_plural "Move conversations to Trash (Delete, Backspace)" +msgstr[0] "Flytta konversation till papperskorgen (Delete, Backsteg)" +msgstr[1] "Flytta konversationer till papperskorgen (Delete, Backsteg)" + +#: src/client/components/main-toolbar.vala:183 +msgid "Delete conversation (Shift+Delete)" +msgid_plural "Delete conversations (Shift+Delete)" +msgstr[0] "Radera konversation (Skift+Delete)" +msgstr[1] "Radera konversationer (Skift+Delete)" + +#: src/client/components/main-window.vala:503 #, c-format msgid "%s (%d)" msgstr "%s (%d)" -#: ../src/client/components/search-bar.vala:8 -#: ../src/client/folder-list/folder-list-search-branch.vala:38 -#: ../src/engine/api/geary-special-folder-type.vala:51 +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:47 +#, c-format +msgid "Problem connecting to incoming server for %s" +msgstr "Fel vid anslutning till inkommande server för %s" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:49 +#: src/client/components/main-window-info-bar.vala:58 +#, c-format +msgid "" +"Could not connect to %s, check your Internet access and the server name and " +"try again" +msgstr "" +"Det gick att inte ansluta till %s, kontrollera din internetåtkomst och " +"servernamn och prova igen" + +#. Translators: Tooltip label for Retry button +#. Button tooltip for retrying an account problem +#: src/client/components/main-window-info-bar.vala:51 +#: src/client/components/main-window-info-bar.vala:60 +#: src/client/components/main-window-info-bar.vala:69 +#: src/client/components/main-window-info-bar.vala:78 +#: src/client/components/main-window-info-bar.vala:87 +#: src/client/components/main-window-info-bar.vala:96 +#: src/client/components/main-window-info-bar.vala:136 ui/main-window.ui:265 +msgid "Try reconnecting" +msgstr "Försök att ansluta igen" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:56 +#, c-format +msgid "Problem connecting to outgoing server for %s" +msgstr "Fel vid anslutning till utgående server för %s" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:65 +#: src/client/components/main-window-info-bar.vala:83 +#, c-format +msgid "Problem communicating with incoming server for %s" +msgstr "Problem med kommunikationen till inkommande server för %s" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:67 +#: src/client/components/main-window-info-bar.vala:76 +#, c-format +msgid "Network error talking to %s, check your Internet access and try again" +msgstr "" +"Nätverksfel vid kontakt med %s, kontrollera din internetåtkomst och försök " +"igen" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:74 +#: src/client/components/main-window-info-bar.vala:91 +msgid "Problem communicating with outgoing mail server" +msgstr "Problem med kommunikationen till utgående e-postserver" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:85 +#, c-format +msgid "" +"Geary did not understand a message from %s or vice versa, please file a bug " +"report" +msgstr "" +"Geary förstår inte ett meddelande från %s eller tvärtom, vänligen skicka in " +"en felrapport" + +#. Translators: First string substitution is the server +#. name, second is the account name +#: src/client/components/main-window-info-bar.vala:94 +#, c-format +msgid "" +"Could not communicate with %s for %s, check the server name and try again in " +"a moment" +msgstr "" +"Det gick att inte kommunicera med %s för %s, kontrollera ditt servernamn och " +"prova igen" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:101 +#, c-format +msgid "Incoming mail server password required for %s" +msgstr "Kräver lösenord för inkommande e-postserver för %s" + +#: src/client/components/main-window-info-bar.vala:102 +msgid "Messages cannot be received without the correct password." +msgstr "Meddelanden kan inte hämtas utan korrekt lösenord." + +#. Translators: Tooltip label for Retry button +#: src/client/components/main-window-info-bar.vala:104 +msgid "Retry receiving email, you will be prompted for a password" +msgstr "" +"Försök igen att hämta e-post, du kommer att efterfrågas om ett lösenord" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:109 +#, c-format +msgid "Outgoing mail server password required for %s" +msgstr "Kräver lösenord för utgående e-postserver för %s" + +#: src/client/components/main-window-info-bar.vala:110 +msgid "Messages cannot be sent without the correct password." +msgstr "Meddelanden kan inte skickas utan korrekt lösenord." + +#. Translators: Tooltip label for Retry button +#: src/client/components/main-window-info-bar.vala:112 +msgid "Retry sending queued messages, you will be prompted for a password" +msgstr "" +"Försök igen att skicka köade meddelanden, du kommer att efterfrågas om ett " +"lösenord" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:117 +#, c-format +msgid "Incoming mail server security is not trusted for %s" +msgstr "Säkerhet för inkommande e-postserver är inte betrodd för %s" + +#: src/client/components/main-window-info-bar.vala:118 +msgid "Messages will not be received until checked." +msgstr "Meddelanden kommer inte att hämtas innan den kontrollerats." + +#. Translators: Tooltip label for Retry button +#: src/client/components/main-window-info-bar.vala:120 +#: src/client/components/main-window-info-bar.vala:128 +msgid "Check security details" +msgstr "Kontrollera säkerhetsdetaljer" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:125 +#, c-format +msgid "Outgoing mail server security is not trusted for %s" +msgstr "Säkerhet för utkommande e-postserver är inte betrodd för %s" + +#: src/client/components/main-window-info-bar.vala:126 +msgid "Messages cannot be sent until checked." +msgstr "Meddelanden kan inte skickas innan den kontrollerats." + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:133 +#, c-format +msgid "A problem occurred checking mail for %s" +msgstr "Ett problem uppstod vid kontroll av e-post för %s" + +#: src/client/components/main-window-info-bar.vala:134 +#: src/client/components/main-window-info-bar.vala:142 +msgid "Something went wrong, please file a bug report if the problem persists" +msgstr "Något gick fel, vänligen skicka in en felrapport om problemet kvarstår" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:141 +#, c-format +msgid "A problem occurred sending mail for %s" +msgstr "Ett problem uppstod då e-post skickades för %s" + +#. Translators: Tooltip label for Retry button +#: src/client/components/main-window-info-bar.vala:144 +msgid "Retry sending queued messages" +msgstr "Försök igen att skicka köade meddelanden" + +#: src/client/components/main-window-info-bar.vala:155 +msgid "A database problem has occurred" +msgstr "Ett databasproblem har uppstått" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:157 +#, c-format +msgid "Messages for %s must be downloaded again." +msgstr "Meddelanden för %s måste hämtas ned igen." + +#: src/client/components/main-window-info-bar.vala:170 +msgid "Geary has encountered a problem" +msgstr "Geary har stött på ett problem" + +#: src/client/components/main-window-info-bar.vala:171 +msgid "" +"Please check the technical details and report the problem if it persists." +msgstr "" +"Kontrollera de tekniska inställningarna och rapportera problemet om det " +"kvarstår." + +#: src/client/components/main-window-info-bar.vala:179 +msgid "_Details" +msgstr "_Detaljer" + +#. Button tooltip for displaying technical details about an account problem +#: src/client/components/main-window-info-bar.vala:180 ui/main-window.ui:250 +msgid "View technical details about the error" +msgstr "Visa teknisk information om felet" + +#: src/client/components/main-window-info-bar.vala:184 +msgid "_Retry" +msgstr "_Försök igen" + +#: src/client/components/search-bar.vala:8 +#: src/client/folder-list/folder-list-search-branch.vala:38 +#: src/engine/api/geary-special-folder-type.vala:51 msgid "Search" msgstr "Sök" #. Search entry. -#: ../src/client/components/search-bar.vala:23 +#: src/client/components/search-bar.vala:23 msgid "Search all mail in account for keywords (Ctrl+S)" msgstr "Sök i all e-post i alla konton efter nyckelord (Ctrl+S)" -#: ../src/client/components/search-bar.vala:100 +#: src/client/components/search-bar.vala:101 #, c-format msgid "Indexing %s account" msgstr "Indexerar %s konto" -#: ../src/client/components/search-bar.vala:111 -#: ../src/client/folder-list/folder-list-search-branch.vala:39 +#: src/client/components/search-bar.vala:112 +#: src/client/folder-list/folder-list-search-branch.vala:39 #, c-format msgid "Search %s account" msgstr "Sök i konto %s" #. / Displayed in the space-limited status bar while a message is in the process of being sent. -#: ../src/client/components/status-bar.vala:26 +#: src/client/components/status-bar.vala:26 msgid "Sending…" msgstr "Skickar…" -#: ../src/client/components/stock.vala:18 ../ui/account_cannot_remove.glade.h:3 +#. / Displayed in the space-limited status bar when a message fails to be sent due to error. +#: src/client/components/status-bar.vala:29 +msgid "Error sending email" +msgstr "Misslyckades med att skicka e-post" + +#. Displayed in the space-limited status bar when a message fails to be uploaded +#. to Sent Mail after being sent. +#: src/client/components/status-bar.vala:33 +msgid "Error saving sent mail" +msgstr "Misslyckades med att spara skickat e-postmeddelande" + +#: src/client/components/stock.vala:18 msgid "_OK" msgstr "_OK" -#: ../src/client/components/stock.vala:19 ../ui/edit_alternate_emails.glade.h:3 -#: ../ui/password-dialog.glade.h:5 ../ui/remove_confirm.glade.h:5 +#: src/client/components/stock.vala:19 ui/password-dialog.glade:196 msgid "_Cancel" msgstr "_Avbryt" -#: ../src/client/components/stock.vala:21 ../ui/gtk/menus.ui.h:6 +#: src/client/components/stock.vala:21 msgid "_About" msgstr "_Om" -#: ../src/client/components/stock.vala:23 +#: src/client/components/stock.vala:22 +msgid "_Add" +msgstr "_Lägg till" + +#: src/client/components/stock.vala:23 msgid "_Close" msgstr "_Stäng" -#: ../src/client/components/stock.vala:24 +#: src/client/components/stock.vala:24 msgid "_Discard" msgstr "_Förkasta" -#: ../src/client/components/stock.vala:25 ../ui/gtk/menus.ui.h:5 +#: src/client/components/stock.vala:25 ui/main-toolbar-menus.ui:56 msgid "_Help" msgstr "_Hjälp" -#: ../src/client/components/stock.vala:26 ../ui/conversation-email-menus.ui.h:9 +#: src/client/components/stock.vala:26 ui/conversation-email-menus.ui:77 msgid "_Open" msgstr "_Öppna" -#: ../src/client/components/stock.vala:27 ../ui/gtk/menus.ui.h:3 +#: src/client/components/stock.vala:27 ui/main-toolbar-menus.ui:46 msgid "_Preferences" msgstr "_Inställningar" -#: ../src/client/components/stock.vala:28 ../ui/conversation-email-menus.ui.h:7 +#. Translators: Menu item to print a single, specific message +#: src/client/components/stock.vala:28 ui/conversation-email-menus.ui:64 msgid "_Print…" msgstr "_Skriv ut…" -#: ../src/client/components/stock.vala:29 ../ui/gtk/menus.ui.h:7 +#: src/client/components/stock.vala:29 msgid "_Quit" msgstr "_Avsluta" -#: ../src/client/components/stock.vala:30 ../ui/remove_confirm.glade.h:6 +#: src/client/components/stock.vala:30 msgid "_Remove" msgstr "_Ta bort" -#: ../src/client/components/stock.vala:32 +#: src/client/components/stock.vala:31 ui/conversation-email-menus.ui:83 +msgid "_Save" +msgstr "_Spara" + +#: src/client/components/stock.vala:32 msgid "_Keep" msgstr "_Behåll" -#: ../src/client/composer/composer-link-popover.vala:150 +#: src/client/composer/composer-link-popover.vala:149 msgid "Link URL is not correctly formatted, e.g. http://example.com" msgstr "" "Länk-URL är inte korrekt formaterad, det vill säga t.ex. http://exempel.se" -#: ../src/client/composer/composer-link-popover.vala:157 +#: src/client/composer/composer-link-popover.vala:156 msgid "Invalid link URL" msgstr "Ogiltig länk-URL" -#: ../src/client/composer/composer-link-popover.vala:157 +#: src/client/composer/composer-link-popover.vala:156 msgid "Invalid email address" msgstr "Ogiltig e-postadress" -#: ../src/client/composer/composer-widget.vala:154 +#: src/client/composer/composer-widget.vala:158 msgid "Saved" msgstr "Sparat" -#: ../src/client/composer/composer-widget.vala:155 +#: src/client/composer/composer-widget.vala:159 msgid "Saving" msgstr "Sparar" -#: ../src/client/composer/composer-widget.vala:156 +#: src/client/composer/composer-widget.vala:160 msgid "Error saving" msgstr "Misslyckades med att spara" -#: ../src/client/composer/composer-widget.vala:157 +#: src/client/composer/composer-widget.vala:161 msgid "Press Backspace to delete quote" msgstr "Tryck på Backsteg för att ta bort citat" -#: ../src/client/composer/composer-widget.vala:158 -msgid "New Message" -msgstr "Nytt meddelande" - #. Translators: This is list of keywords, separated by pipe ("|") #. characters, that suggest an attachment; since this is full-word #. checking, include all variants of each word. No spaces are #. allowed. -#: ../src/client/composer/composer-widget.vala:167 +#: src/client/composer/composer-widget.vala:170 msgid "" "attach|attaching|attaches|attachment|attachments|attached|enclose|enclosed|" "enclosing|encloses|enclosure|enclosures" @@ -921,28 +1437,37 @@ "bifoga|bifogar|bifogat|inkludera|inkluderar|inkluderat|bilaga|bilagor|" "skickar med|medföljande|attachment" -#: ../src/client/composer/composer-widget.vala:1122 -#: ../src/client/composer/composer-widget.vala:1141 -msgid "Do you want to discard this message?" -msgstr "Vill du förkasta detta meddelande?" +#. Translators: This dialog text is displayed to the +#. user when closing a composer where the options are +#. Keep, Discard or Cancel. +#: src/client/composer/composer-widget.vala:1131 +msgid "Do you want to keep or discard this draft message?" +msgstr "Vill du behålla eller förkasta detta utkast?" + +#. Translators: This dialog text is displayed to the +#. user when closing a composer where the options are +#. only Discard or Cancel. +#: src/client/composer/composer-widget.vala:1159 +msgid "Do you want to discard this draft message?" +msgstr "Vill du förkasta detta utkast?" -#: ../src/client/composer/composer-widget.vala:1245 +#: src/client/composer/composer-widget.vala:1276 msgid "Send message with an empty subject and body?" msgstr "Skicka meddelande med en tom ämnesrad och utan innehåll?" -#: ../src/client/composer/composer-widget.vala:1247 +#: src/client/composer/composer-widget.vala:1278 msgid "Send message with an empty subject?" msgstr "Skicka meddelande med en tom ämnesrad?" -#: ../src/client/composer/composer-widget.vala:1249 +#: src/client/composer/composer-widget.vala:1280 msgid "Send message with an empty body?" msgstr "Skicka meddelande utan innehåll?" -#: ../src/client/composer/composer-widget.vala:1253 +#: src/client/composer/composer-widget.vala:1284 msgid "Send message without an attachment?" msgstr "Skicka meddelande utan att bifoga filer?" -#: ../src/client/composer/composer-widget.vala:1515 +#: src/client/composer/composer-widget.vala:1589 #, c-format msgid "“%s” already attached for delivery." msgstr "”%s” är redan bifogad och kommer att skickas." @@ -952,170 +1477,262 @@ #. description of the document type, the second will #. be a human-friendly size string. For example: #. Document (100.9MB) -#: ../src/client/composer/composer-widget.vala:1523 -#: ../src/client/conversation-viewer/conversation-email.vala:138 +#: src/client/composer/composer-widget.vala:1597 +#: src/client/conversation-viewer/conversation-email.vala:173 #, c-format msgid "%s (%s)" msgstr "%s (%s)" -#: ../src/client/composer/composer-widget.vala:1560 +#: src/client/composer/composer-widget.vala:1634 #, c-format msgid "“%s” could not be found." msgstr "”%s” kunde inte hittas." -#: ../src/client/composer/composer-widget.vala:1566 +#: src/client/composer/composer-widget.vala:1640 #, c-format msgid "“%s” is a folder." msgstr "”%s” är en mapp." -#: ../src/client/composer/composer-widget.vala:1572 +#: src/client/composer/composer-widget.vala:1646 #, c-format msgid "“%s” is an empty file." msgstr "”%s” är en tom fil." -#: ../src/client/composer/composer-widget.vala:1585 +#: src/client/composer/composer-widget.vala:1659 #, c-format msgid "“%s” could not be opened for reading." msgstr "”%s” kunde inte öppnas för läsning." -#: ../src/client/composer/composer-widget.vala:1593 +#: src/client/composer/composer-widget.vala:1667 msgid "Cannot add attachment" msgstr "Kan inte bifoga din fil" -#: ../src/client/composer/composer-widget.vala:1645 -msgid "To: " -msgstr "Till: " - -#: ../src/client/composer/composer-widget.vala:1648 -msgid "Cc: " -msgstr "Cc: " - -#: ../src/client/composer/composer-widget.vala:1651 -msgid "Bcc: " -msgstr "Bcc: " +#. Translators: Human-readable version of the RFC 822 To header +#: src/client/composer/composer-widget.vala:1717 +#: src/client/conversation-viewer/conversation-email.vala:981 +#: src/engine/rfc822/rfc822-utils.vala:298 ui/conversation-message.ui:312 +msgid "To:" +msgstr "Till:" + +#. Translators: Human-readable version of the RFC 822 CC header +#: src/client/composer/composer-widget.vala:1723 +#: src/client/conversation-viewer/conversation-email.vala:986 +#: src/engine/rfc822/rfc822-utils.vala:303 ui/conversation-message.ui:357 +msgid "Cc:" +msgstr "Cc:" + +#. Translators: Human-readable version of the RFC 822 BCC header +#: src/client/composer/composer-widget.vala:1729 +#: src/client/conversation-viewer/conversation-email.vala:991 +#: ui/conversation-message.ui:402 +msgid "Bcc:" +msgstr "Bcc:" -#: ../src/client/composer/composer-widget.vala:1654 +#. Translators: Human-readable version of the RFC 822 Reply-To header +#: src/client/composer/composer-widget.vala:1735 msgid "Reply-To: " msgstr "Svara-till: " -#: ../src/client/composer/composer-widget.vala:1786 +#: src/client/composer/composer-widget.vala:1875 msgid "Select Color" msgstr "Välj färg" -#. Displayed in the From dropdown to indicate an "alternate email address" -#. for an account. The first printf argument will be the alternate email -#. address, and the second will be the account's primary email address. -#: ../src/client/composer/composer-widget.vala:1986 +#. Displayed in the From dropdown to indicate an +#. "alternate email address" for an account. The first +#. printf argument will be the alternate email address, +#. and the second will be the account's primary email +#. address. +#: src/client/composer/composer-widget.vala:2065 #, c-format msgid "%1$s via %2$s" msgstr "%1$s via %2$s" #. Composer label (with mnemonic underscore) for the account selector #. when choosing what address to send a message from. -#: ../src/client/composer/composer-widget.vala:2028 +#: src/client/composer/composer-widget.vala:2126 msgid "_From:" msgstr "_Från:" #. Translators: This is the name of the file chooser filter #. when inserting an image in the composer. -#: ../src/client/composer/composer-widget.vala:2252 +#: src/client/composer/composer-widget.vala:2352 msgid "Images" msgstr "Bilder" -#: ../src/client/composer/spell-check-popover.vala:117 +#: src/client/composer/composer-window.vala:14 +msgid "New Message" +msgstr "Nytt meddelande" + +#: src/client/composer/spell-check-popover.vala:117 msgid "Remove this language from the preferred list" msgstr "Ta bort detta språk från listan över föredragna språk" -#: ../src/client/composer/spell-check-popover.vala:121 +#: src/client/composer/spell-check-popover.vala:121 msgid "Add this language to the preferred list" msgstr "Lägg till detta språk till listan över föredragna språk" -#: ../src/client/composer/spell-check-popover.vala:217 +#: src/client/composer/spell-check-popover.vala:217 msgid "Search for more languages" msgstr "Sök efter fler språk" -#: ../src/client/conversation-list/formatted-conversation-data.vala:11 +#: src/client/conversation-list/conversation-list-view.vala:283 +msgid "Delete conversation" +msgstr "Radera konversation" + +#: src/client/conversation-list/conversation-list-view.vala:286 +#: ui/main-toolbar-menus.ui:5 +msgid "Mark as _Read" +msgstr "Markera som _läst" + +#: src/client/conversation-list/conversation-list-view.vala:289 +#: ui/main-toolbar-menus.ui:9 +msgid "Mark as _Unread" +msgstr "Markera som _oläst" + +#: src/client/conversation-list/conversation-list-view.vala:292 +#: ui/main-toolbar-menus.ui:17 +msgid "U_nstar" +msgstr "Markera som ov_iktigt" + +#: src/client/conversation-list/conversation-list-view.vala:294 +#: ui/main-toolbar-menus.ui:13 +msgid "_Star" +msgstr "Markera som _viktigt" + +#. Translators: Menu item to reply to a specific message. +#: src/client/conversation-list/conversation-list-view.vala:297 +#: ui/conversation-email-menus.ui:9 +msgid "_Reply" +msgstr "Sva_ra" + +#: src/client/conversation-list/conversation-list-view.vala:298 +msgid "R_eply All" +msgstr "Svara _alla" + +#. Translators: Menu item to forward a specific message. +#: src/client/conversation-list/conversation-list-view.vala:299 +#: ui/conversation-email-menus.ui:21 +msgid "_Forward" +msgstr "Vidare_befordra" + +#: src/client/conversation-list/formatted-conversation-data.vala:11 msgid "Me" msgstr "Jag" #. Translators: This is the file type displayed for #. attachments with unknown file types. -#: ../src/client/conversation-viewer/conversation-email.vala:124 +#: src/client/conversation-viewer/conversation-email.vala:159 msgid "Unknown" msgstr "Okänd" -#. Preview headers +#. Translators: Human-readable version of the RFC 822 From header +#: src/client/conversation-viewer/conversation-email.vala:976 +#: src/engine/rfc822/rfc822-utils.vala:289 +msgid "From:" +msgstr "Från:" + +#. Translators: Human-readable version of the RFC 822 Date header +#: src/client/conversation-viewer/conversation-email.vala:996 +#: src/engine/rfc822/rfc822-utils.vala:294 +msgid "Date:" +msgstr "Datum:" + +#. Translators: Human-readable version of the RFC 822 Subject header +#: src/client/conversation-viewer/conversation-email.vala:1001 +#: src/engine/rfc822/rfc822-utils.vala:292 +msgid "Subject:" +msgstr "Ämne:" + +#: src/client/conversation-viewer/conversation-message.vala:65 +msgid "This email address may have been forged" +msgstr "E-postadressen kan ha förfalskats" + +#. Compact headers #. Translators: This is displayed in place of the from address #. when the message has no from address. -#: ../src/client/conversation-viewer/conversation-message.vala:331 +#: src/client/conversation-viewer/conversation-message.vala:394 msgid "No sender" msgstr "Ingen avsändare" #. Translators: This separates multiple 'from' -#. addresses in the header preview for a message. -#: ../src/client/conversation-viewer/conversation-message.vala:586 +#. addresses in the compact header for a message. +#: src/client/conversation-viewer/conversation-message.vala:766 msgid ", " msgstr ", " #. Translators: This string is used as the HTML IMG ALT #. attribute value when displaying an inline image in an email #. that did not specify a file name. E.g. ImageCannot remove account " -msgstr "Kan inte avlägsna kontot " +#: ui/accounts_editor_edit_pane.ui:262 ui/accounts_editor_remove_pane.ui:27 +msgid "Remove this account from Geary" +msgstr "Ta bort detta konto från Geary" -#: ../ui/account_cannot_remove.glade.h:2 -msgid "" -"A composer window associated with this account is currently open. Send or " -"discard the message and try again." -msgstr "" -"Ett textinmatningsfönster som tillhör detta konto är redan öppet. Skicka " -"eller förkasta det meddelandet och försök igen." +#: ui/accounts_editor_list_pane.ui:8 +msgid "Accounts" +msgstr "Konton" -#: ../ui/account_list.glade.h:1 -msgid "Add account" -msgstr "Lägg till konto" +#: ui/accounts_editor_list_pane.ui:63 +msgid "To get started, select an email provider below." +msgstr "För att komma igång, välj en e-postleverantör nedan." -#: ../ui/account_list.glade.h:2 -msgid "Edit account" -msgstr "Redigera konto" +#: ui/accounts_editor_list_pane.ui:76 +msgid "Welcome to Geary" +msgstr "Välkommen till Geary" + +#. This title is shown to users when confirming if they want to remove an account. The string substitution is replaced with the account's name. +#: ui/accounts_editor_remove_pane.ui:73 +#, c-format +msgid "Confirm removing: %s" +msgstr "Bekräfta borttagning: %s" + +#: ui/accounts_editor_remove_pane.ui:91 +msgid "" +"Removing an account will remove it from Geary and delete locally cached " +"email data from your computer, but not from your service provider." +msgstr "" +"Att ta bort ett konto kommer att ta bort det från Geary och ta bort lokalt " +"cachade e-postdata från din dator, men inte från din tjänsteleverantör." -#: ../ui/account_list.glade.h:3 +#: ui/accounts_editor_remove_pane.ui:122 msgid "Remove account" msgstr "Ta bort konto" -#: ../ui/account_spinner.glade.h:1 -msgid "Please wait while Geary validates your account." -msgstr "Vänta medan Geary bekräftar dina kontoinställningar." +#: ui/accounts_editor_servers_pane.ui:17 +msgid "Cancel" +msgstr "Avbryt" + +#: ui/accounts_editor_servers_pane.ui:47 +msgid "Apply" +msgstr "Verkställ" -#: ../ui/certificate_warning_dialog.glade.h:1 +#: ui/certificate_warning_dialog.glade:7 msgid "Untrusted Connection" msgstr "Opålitlig anslutning" -#: ../ui/certificate_warning_dialog.glade.h:2 +#: ui/certificate_warning_dialog.glade:29 msgid "_Always Trust This Server" msgstr "Lita _alltid på denna server" -#: ../ui/certificate_warning_dialog.glade.h:3 +#: ui/certificate_warning_dialog.glade:43 msgid "_Trust This Server" msgstr "Li_ta på denna server" -#: ../ui/certificate_warning_dialog.glade.h:4 +#: ui/certificate_warning_dialog.glade:57 msgid "_Don’t Trust This Server" msgstr "Lita inte på _denna server" -#: ../ui/composer-headerbar.ui.h:1 +#: ui/composer-headerbar.ui:17 ui/composer-headerbar.ui:174 msgid "Detach (Ctrl+D)" msgstr "Frikoppla (Ctrl+D)" -#: ../ui/composer-headerbar.ui.h:2 +#: ui/composer-headerbar.ui:57 ui/composer-headerbar.ui:83 msgid "Attach File (Ctrl+T)" msgstr "Bifoga fil (Ctrl+T)" -#: ../ui/composer-headerbar.ui.h:3 +#: ui/composer-headerbar.ui:107 msgid "Include Original Attachments" msgstr "Inkludera bifogade filer från original" -#: ../ui/composer-headerbar.ui.h:4 -msgid "Send (Ctrl+Enter)" -msgstr "Skicka (Ctrl+Enter)" - -#: ../ui/composer-headerbar.ui.h:5 +#: ui/composer-headerbar.ui:202 msgid "_Send" msgstr "_Skicka" -#: ../ui/composer-headerbar.ui.h:6 +#: ui/composer-headerbar.ui:207 +msgid "Send (Ctrl+Enter)" +msgstr "Skicka (Ctrl+Enter)" + +#: ui/composer-headerbar.ui:230 msgid "Discard and Close" msgstr "Förkasta och stäng" -#: ../ui/composer-headerbar.ui.h:7 +#: ui/composer-headerbar.ui:254 msgid "Save and Close" msgstr "Spara och stäng" #. Note that this button and the Update button will never be shown at the same time to the user. -#: ../ui/composer-link-popover.ui.h:2 +#: ui/composer-link-popover.ui:41 msgid "Insert the new link with this URL" msgstr "Infoga den nya länken med denna URL" -#: ../ui/composer-link-popover.ui.h:3 +#: ui/composer-link-popover.ui:52 msgid "Link URL" msgstr "Länk-URL" #. Note that this button and the Insert button will never be shown at the same time to the user. -#: ../ui/composer-link-popover.ui.h:5 +#: ui/composer-link-popover.ui:66 msgid "Update this link’s URL" msgstr "Uppdatera denna länk-URL" -#: ../ui/composer-link-popover.ui.h:6 +#: ui/composer-link-popover.ui:86 msgid "Delete this link" msgstr "Ta bort denna länk" -#: ../ui/composer-link-popover.ui.h:7 +#: ui/composer-link-popover.ui:106 msgid "Open this link" msgstr "Öppna denna länk" -#: ../ui/composer-menus.ui.h:1 +#: ui/composer-menus.ui:7 msgid "S_ans Serif" msgstr "S_ans Serif" -#: ../ui/composer-menus.ui.h:2 +#: ui/composer-menus.ui:12 msgid "S_erif" msgstr "S_erif" -#: ../ui/composer-menus.ui.h:3 +#: ui/composer-menus.ui:17 msgid "_Fixed Width" msgstr "_Fast bredd" -#: ../ui/composer-menus.ui.h:4 +#: ui/composer-menus.ui:24 msgid "_Small" msgstr "_Liten" -#: ../ui/composer-menus.ui.h:5 +#: ui/composer-menus.ui:29 msgid "_Medium" msgstr "_Medel" -#: ../ui/composer-menus.ui.h:6 +#: ui/composer-menus.ui:34 msgid "Lar_ge" msgstr "_Stor" -#: ../ui/composer-menus.ui.h:7 +#: ui/composer-menus.ui:41 msgid "C_olor" msgstr "Fär_g" -#: ../ui/composer-menus.ui.h:8 +#: ui/composer-menus.ui:47 ui/composer-menus.ui:62 msgid "_Rich Text" msgstr "_Rich Text" -#: ../ui/composer-menus.ui.h:9 +#: ui/composer-menus.ui:53 ui/composer-menus.ui:68 msgid "Show Extended Fields" msgstr "Visa utökade fält" -#: ../ui/composer-menus.ui.h:10 +#: ui/composer-menus.ui:78 msgid "_Undo" msgstr "_Ångra" -#: ../ui/composer-menus.ui.h:11 +#: ui/composer-menus.ui:82 msgid "_Redo" msgstr "_Upprepa" -#: ../ui/composer-menus.ui.h:12 +#: ui/composer-menus.ui:88 ui/composer-menus.ui:106 msgid "Cu_t" msgstr "Klipp u_t" -#: ../ui/composer-menus.ui.h:13 ../ui/conversation-message-menus.ui.h:7 +#: ui/composer-menus.ui:92 ui/composer-menus.ui:110 +#: ui/conversation-message-menus.ui:37 msgid "_Copy" msgstr "_Kopiera" -#: ../ui/composer-menus.ui.h:14 +#: ui/composer-menus.ui:96 ui/composer-menus.ui:114 msgid "_Paste" msgstr "_Klistra in" -#: ../ui/composer-menus.ui.h:15 -msgctxt "Clipboard paste with rich text" -msgid "Paste _With Formatting" -msgstr "Klistra in _med formatering" +#: ui/composer-menus.ui:100 +msgctxt "Clipboard paste as plain text" +msgid "Paste _Without Formatting" +msgstr "Klistra in _utan formatering" -#: ../ui/composer-menus.ui.h:16 +#: ui/composer-menus.ui:120 msgid "Select _All" msgstr "Välj _alla" -#: ../ui/composer-menus.ui.h:17 ../ui/conversation-message-menus.ui.h:9 +#: ui/composer-menus.ui:127 ui/conversation-message-menus.ui:49 msgid "_Inspect…" msgstr "_Inspektera…" #. Address(es) e-mail is to be sent to -#: ../ui/composer-widget.ui.h:2 +#: ui/composer-widget.ui:56 msgid "_To" msgstr "_Till" -#: ../ui/composer-widget.ui.h:3 +#: ui/composer-widget.ui:75 msgid "_Cc" msgstr "_Cc" -#: ../ui/composer-widget.ui.h:4 +#: ui/composer-widget.ui:130 msgid "_Subject" msgstr "_Ämne" -#: ../ui/composer-widget.ui.h:5 +#: ui/composer-widget.ui:149 msgid "_Bcc" msgstr "_Bcc" -#: ../ui/composer-widget.ui.h:6 +#: ui/composer-widget.ui:179 msgid "_Reply-To" msgstr "Sva_ra-till" #. Geary account mail will be sent from -#: ../ui/composer-widget.ui.h:8 +#: ui/composer-widget.ui:208 msgid "From" msgstr "Från" -#: ../ui/composer-widget.ui.h:9 +#: ui/composer-widget.ui:293 msgid "Drop files here" msgstr "Släpp filer här" -#: ../ui/composer-widget.ui.h:10 +#: ui/composer-widget.ui:309 msgid "To add them as attachments" msgstr "För att bifoga dem" -#: ../ui/composer-widget.ui.h:11 +#: ui/composer-widget.ui:353 msgid "Undo last edit (Ctrl+Z)" msgstr "Ångra senaste redigering (Ctrl+Z)" -#: ../ui/composer-widget.ui.h:12 +#: ui/composer-widget.ui:377 msgid "Redo last edit (Ctrl+Shift+Z)" msgstr "Gör om senaste redigering (Ctrl+Skift+Z)" -#: ../ui/composer-widget.ui.h:13 +#: ui/composer-widget.ui:415 msgid "Bold (Ctrl+B)" msgstr "Fet (Ctrl+B)" -#: ../ui/composer-widget.ui.h:14 +#: ui/composer-widget.ui:439 msgid "Italic (Ctrl+I)" msgstr "Kursiv (Ctrl+I)" -#: ../ui/composer-widget.ui.h:15 +#: ui/composer-widget.ui:463 msgid "Underline (Ctrl+U)" msgstr "Understruken (Ctrl+U)" -#: ../ui/composer-widget.ui.h:16 +#: ui/composer-widget.ui:487 msgid "Strikethrough (Ctrl+K)" msgstr "Genomstruken (Ctrl+K)" -#: ../ui/composer-widget.ui.h:17 +#: ui/composer-widget.ui:525 +msgid "Insert unordered list" +msgstr "Infoga en osorterad lista" + +#: ui/composer-widget.ui:549 +msgid "Insert ordered list" +msgstr "Infoga en sorterad lista" + +#: ui/composer-widget.ui:587 msgid "Quote text (Ctrl+])" msgstr "Citera text (Ctrl+])" -#: ../ui/composer-widget.ui.h:18 +#: ui/composer-widget.ui:611 msgid "Unquote text (Ctrl+[)" msgstr "Avcitera text (Ctrl+[)" -#: ../ui/composer-widget.ui.h:19 +#: ui/composer-widget.ui:649 msgid "Insert or update selection link (Ctrl+L)" msgstr "Infoga eller uppdatera markeringslänk (Ctrl+L)" -#: ../ui/composer-widget.ui.h:20 +#: ui/composer-widget.ui:673 msgid "Insert an image (Ctrl+G)" msgstr "Infoga bild (Ctrl+G)" -#: ../ui/composer-widget.ui.h:21 +#: ui/composer-widget.ui:707 msgid "Remove selection formatting (Ctrl+Space)" msgstr "Ta bort markeringsformatering (Ctrl+Blanksteg)" -#: ../ui/composer-widget.ui.h:22 +#: ui/composer-widget.ui:731 msgid "Select spell checking languages" msgstr "Välj språk för stavningskontroll" -#: ../ui/conversation-email.ui.h:1 +#: ui/conversation-email.ui:27 msgid "Save all attachments" msgstr "Spara alla bifogade filer" #. Note: The application will never show this button at the same time as unstar_button, one will always be hidden. -#: ../ui/conversation-email.ui.h:3 +#: ui/conversation-email.ui:50 msgid "Mark this message as starred" msgstr "Markera detta meddelande som stjärnmärkt" #. Note: The application will never show this button at the same time as star_button, one will always be hidden. -#: ../ui/conversation-email.ui.h:5 +#: ui/conversation-email.ui:72 msgid "Mark this message as not starred" msgstr "Markera detta meddelande som ej stjärnmärkt" -#: ../ui/conversation-email.ui.h:6 +#: ui/conversation-email.ui:95 msgid "Display the message menu" msgstr "Visa meddelandemenyn" -#: ../ui/conversation-email.ui.h:7 +#: ui/conversation-email.ui:161 msgid "Open selected attachments" msgstr "Öppna markerade bifogade filer" -#: ../ui/conversation-email.ui.h:8 +#: ui/conversation-email.ui:178 msgid "Save selected attachments" msgstr "Spara markerade bifogade filer" -#: ../ui/conversation-email.ui.h:9 +#: ui/conversation-email.ui:195 msgid "Select all attachments" msgstr "Markera alla bifogade filer" -#: ../ui/conversation-email.ui.h:10 +#: ui/conversation-email.ui:240 msgid "Edit Draft" msgstr "Redigera utkast" -#: ../ui/conversation-email.ui.h:11 +#: ui/conversation-email.ui:267 msgid "Draft message" msgstr "Utkastmeddelande" -#: ../ui/conversation-email.ui.h:12 +#: ui/conversation-email.ui:283 msgid "This message has not yet been sent." msgstr "Det här meddelandet har inte skickats ännu." -#: ../ui/conversation-email.ui.h:13 -msgid "Try Again" -msgstr "Prova igen" - -#: ../ui/conversation-email.ui.h:14 +#: ui/conversation-email.ui:329 msgid "Message not saved" msgstr "Meddelandet sparades inte" -#: ../ui/conversation-email.ui.h:15 +#: ui/conversation-email.ui:345 msgid "This message was sent, but has not been saved to your account." msgstr "Detta meddelande har skickats, men sparades inte till ditt konto." -#: ../ui/conversation-email-menus.ui.h:2 +#. Translators: Menu item to reply to a specific message. +#: ui/conversation-email-menus.ui:15 msgid "Reply to _All" msgstr "Svara till _alla" -#: ../ui/conversation-email-menus.ui.h:4 +#. Translators: Menu item to mark a specific message as +#. read. +#: ui/conversation-email-menus.ui:30 msgid "_Mark Read" msgstr "Markera som _läst" -#: ../ui/conversation-email-menus.ui.h:5 +#: ui/conversation-email-menus.ui:36 msgid "_Mark Unread" msgstr "Markera som _oläst" -#: ../ui/conversation-email-menus.ui.h:6 +#. Translators: Menu item to mark all messages in a +#. conversation from this one as unread. +#: ui/conversation-email-menus.ui:42 msgid "Mark Unread From _Here" msgstr "Markera olästa _härifrån" -#: ../ui/conversation-email-menus.ui.h:8 +#. Translators: Menu item to move a single, specific message +#. to the trash +#: ui/conversation-email-menus.ui:50 +msgid "_Trash" +msgstr "Flytta till _papperskorg" + +#. Translators: Menu item to delete a single, specific message +#: ui/conversation-email-menus.ui:57 +msgid "_Delete…" +msgstr "_Radera…" + +#. Translators: Menu item to view the source for a message +#: ui/conversation-email-menus.ui:69 msgid "_View Source" msgstr "_Visa källa" -#: ../ui/conversation-email-menus.ui.h:11 +#: ui/conversation-email-menus.ui:87 msgid "_Save All" msgstr "_Spara alla" -#: ../ui/conversation-message-menus.ui.h:1 +#: ui/conversation-message-menus.ui:7 msgid "_Open Link" msgstr "_Öppna länk" -#: ../ui/conversation-message-menus.ui.h:2 +#: ui/conversation-message-menus.ui:11 msgid "Copy Link _Address" msgstr "Kopiera länk_adress" -#: ../ui/conversation-message-menus.ui.h:3 +#: ui/conversation-message-menus.ui:17 msgid "Send New _Message…" msgstr "Skicka nytt _meddelande…" -#: ../ui/conversation-message-menus.ui.h:4 +#: ui/conversation-message-menus.ui:21 msgid "Copy Email _Address" msgstr "Kopiera _e-postadress" -#: ../ui/conversation-message-menus.ui.h:5 +#: ui/conversation-message-menus.ui:27 msgid "Save _Image As…" msgstr "_Spara bild som…" -#: ../ui/conversation-message-menus.ui.h:6 +#: ui/conversation-message-menus.ui:33 msgid "_Select All" msgstr "Välj _alla" -#: ../ui/conversation-message-menus.ui.h:8 +#: ui/conversation-message-menus.ui:43 msgid "Search for messages from" msgstr "Sök efter meddelanden från" -#: ../ui/conversation-message.ui.h:1 +#: ui/conversation-message.ui:63 msgid "From " msgstr "Från " -#: ../ui/conversation-message.ui.h:2 +#: ui/conversation-message.ui:79 ui/conversation-message.ui:178 msgid "1/1/1970\t" msgstr "1/1/1970\t" -#: ../ui/conversation-message.ui.h:3 +#: ui/conversation-message.ui:102 msgid "Preview body text." msgstr "Förhandsgranska meddelandetext." -#: ../ui/conversation-message.ui.h:4 +#: ui/conversation-message.ui:202 msgid "Sent by:" msgstr "Skickat av:" -#: ../ui/conversation-message.ui.h:5 +#: ui/conversation-message.ui:247 msgid "Reply to:" msgstr "Svara-till:" -#: ../ui/conversation-message.ui.h:6 +#: ui/conversation-message.ui:291 msgid "Subject" msgstr "Ämne" -#: ../ui/conversation-message.ui.h:7 -msgid "To:" -msgstr "Till:" - -#: ../ui/conversation-message.ui.h:8 -msgid "Cc:" -msgstr "Cc:" - -#: ../ui/conversation-message.ui.h:9 -msgid "Bcc:" -msgstr "Bcc:" - -#: ../ui/conversation-message.ui.h:10 +#: ui/conversation-message.ui:501 msgid "Show Images" msgstr "Visa bilder" -#: ../ui/conversation-message.ui.h:11 +#: ui/conversation-message.ui:514 msgid "Always Show From Sender" msgstr "Visa alltid från avsändare" -#: ../ui/conversation-message.ui.h:12 +#: ui/conversation-message.ui:542 msgid "Remote images not shown" msgstr "Fjärrbilder visas inte" -#: ../ui/conversation-message.ui.h:13 +#: ui/conversation-message.ui:559 msgid "Only show remote images from senders you trust." msgstr "Visa endast fjärrbilder från avsändare du litar på." -#: ../ui/conversation-message.ui.h:14 +#: ui/conversation-message.ui:692 msgid "But actually goes to:" -msgstr "men går egentligen till:" +msgstr "Men går egentligen till:" -#: ../ui/conversation-message.ui.h:15 +#: ui/conversation-message.ui:723 msgid "The link appears to go to:" msgstr "Denna länk ser ut att gå till:" -#: ../ui/conversation-message.ui.h:16 +#: ui/conversation-message.ui:735 msgid "Deceptive link found" msgstr "Bedräglig länk funnen" -#: ../ui/conversation-message.ui.h:17 +#: ui/conversation-message.ui:750 msgid "The email sender may be leading you to the wrong web site." msgstr "E-postavsändaren kan försöka att leda dig till fel webbplats." -#: ../ui/conversation-message.ui.h:18 +#: ui/conversation-message.ui:763 msgid "If unsure, contact the sender and ask before continuing." msgstr "Om osäker, kontakta avsändaren och fråga innan du fortsätter." -#: ../ui/conversation-viewer.ui.h:1 +#: ui/conversation-viewer.ui:60 msgid "Find in conversation" msgstr "Hitta i konversation" -#: ../ui/conversation-viewer.ui.h:2 +#: ui/conversation-viewer.ui:74 msgid "Find the previous occurrence of the search string." msgstr "Hitta föregående förekomst av söksträngen." -#: ../ui/conversation-viewer.ui.h:3 +#: ui/conversation-viewer.ui:95 msgid "Find the next occurrence of the search string." msgstr "Hitta nästa förekomst av söksträngen." -#: ../ui/edit_alternate_emails.glade.h:1 -msgid "Remove email address" -msgstr "Ta bort e-postadress" - -#: ../ui/edit_alternate_emails.glade.h:2 -msgid "" -"Some email services require additional addresses be configured on the " -"server. Contact your email provider for more information." -msgstr "" -"Vissa e-posttjänster kräver att ytterligare adresser har konfigurerats på " -"servern. Kontakta din e-postleverantör för vidare information." - -#: ../ui/edit_alternate_emails.glade.h:4 -msgid "_Update" -msgstr "_Uppdatera" - -#: ../ui/find_bar.glade.h:1 +#: ui/find_bar.glade:66 msgid "Find:" msgstr "Sök:" -#: ../ui/find_bar.glade.h:2 +#: ui/find_bar.glade:89 msgid "_Previous" msgstr "_Föregående" -#: ../ui/find_bar.glade.h:3 +#: ui/find_bar.glade:107 msgid "_Next" msgstr "_Nästa" -#: ../ui/find_bar.glade.h:4 +#: ui/find_bar.glade:125 msgid "_Case sensitive" msgstr "_Gör skillnad på gemener/VERSALER" -#: ../ui/find_bar.glade.h:5 +#: ui/find_bar.glade:145 msgid "label" msgstr "etikett" -#: ../ui/gtk/help-overlay.ui.h:1 +#: ui/gtk/help-overlay.ui:9 msgid "Conversation Shortcuts" msgstr "Konversationsgenvägar" -#: ../ui/gtk/help-overlay.ui.h:2 +#: ui/gtk/help-overlay.ui:13 ui/gtk/help-overlay.ui:254 msgctxt "shortcut window" msgid "General" msgstr "Allmänt" -#: ../ui/gtk/help-overlay.ui.h:3 +#: ui/gtk/help-overlay.ui:17 msgctxt "shortcut window" msgid "Move focus to the next/previous pane" msgstr "Flytta fokus till nästa/föregående panel" -#: ../ui/gtk/help-overlay.ui.h:4 +#: ui/gtk/help-overlay.ui:24 msgctxt "shortcut window" msgid "Move focus to conversation list" msgstr "Flytta fokus till konversationslistan" -#: ../ui/gtk/help-overlay.ui.h:5 +#: ui/gtk/help-overlay.ui:31 msgctxt "shortcut window" msgid "Detach composer window" msgstr "Koppla loss textinmatningsfönstret" -#: ../ui/gtk/help-overlay.ui.h:6 +#: ui/gtk/help-overlay.ui:38 msgctxt "shortcut window" msgid "Close composer window" msgstr "Stäng textinmatningsfönster" -#: ../ui/gtk/help-overlay.ui.h:7 +#: ui/gtk/help-overlay.ui:45 msgctxt "shortcut window" msgid "Show keyboard shortcuts" msgstr "Visa tangentbordsgenvägar" -#: ../ui/gtk/help-overlay.ui.h:8 +#: ui/gtk/help-overlay.ui:52 msgctxt "shortcut window" msgid "Show help" msgstr "Visa hjälp" -#: ../ui/gtk/help-overlay.ui.h:9 +#: ui/gtk/help-overlay.ui:59 msgctxt "shortcut window" msgid "Quit the application" msgstr "Avsluta programmet" -#: ../ui/gtk/help-overlay.ui.h:10 +#: ui/gtk/help-overlay.ui:68 msgctxt "shortcut window" msgid "Search" msgstr "Sök" -#: ../ui/gtk/help-overlay.ui.h:11 +#: ui/gtk/help-overlay.ui:72 msgctxt "shortcut window" msgid "Jump to search box" msgstr "Hoppa till sökfältet" -#: ../ui/gtk/help-overlay.ui.h:12 +#: ui/gtk/help-overlay.ui:79 msgctxt "shortcut window" msgid "Find in current conversation" msgstr "Hitta i aktuell konversation" -#: ../ui/gtk/help-overlay.ui.h:13 +#: ui/gtk/help-overlay.ui:86 msgctxt "shortcut window" msgid "Find next/previous in current conversation" msgstr "Hitta nästa/föregående i aktuell konversation" -#: ../ui/gtk/help-overlay.ui.h:14 +#: ui/gtk/help-overlay.ui:95 ui/gtk/help-overlay.ui:274 msgctxt "shortcut window" msgid "Actions" msgstr "Åtgärder" -#: ../ui/gtk/help-overlay.ui.h:15 +#: ui/gtk/help-overlay.ui:99 msgctxt "shortcut window" msgid "Compose a new message" msgstr "Skapa ett nytt meddelande" -#: ../ui/gtk/help-overlay.ui.h:16 +#: ui/gtk/help-overlay.ui:106 msgctxt "shortcut window" msgid "Reply to sender " msgstr "Svar till avsändare " -#: ../ui/gtk/help-overlay.ui.h:17 +#: ui/gtk/help-overlay.ui:113 msgctxt "shortcut window" msgid "Reply to all" msgstr "Svar till alla" -#: ../ui/gtk/help-overlay.ui.h:18 +#: ui/gtk/help-overlay.ui:120 msgctxt "shortcut window" msgid "Forward" msgstr "Vidarebefordra" -#: ../ui/gtk/help-overlay.ui.h:19 +#: ui/gtk/help-overlay.ui:127 msgctxt "shortcut window" msgid "Archive" msgstr "Arkiv" -#: ../ui/gtk/help-overlay.ui.h:20 +#: ui/gtk/help-overlay.ui:134 msgctxt "shortcut window" msgid "Move to trash" msgstr "Flytta till papperskorgen" -#: ../ui/gtk/help-overlay.ui.h:21 +#: ui/gtk/help-overlay.ui:141 msgctxt "shortcut window" msgid "Toggle spam" msgstr "Växla spam" -#: ../ui/gtk/help-overlay.ui.h:22 +#: ui/gtk/help-overlay.ui:148 msgctxt "shortcut window" msgid "Move the conversation" msgstr "Flytta konversationen" -#: ../ui/gtk/help-overlay.ui.h:23 +#: ui/gtk/help-overlay.ui:155 msgctxt "shortcut window" msgid "Label the conversation" msgstr "Lägg till en etikett till konversationen" -#: ../ui/gtk/help-overlay.ui.h:24 +#: ui/gtk/help-overlay.ui:163 msgctxt "shortcut window" msgid "Mark read" msgstr "Markera som läst" -#: ../ui/gtk/help-overlay.ui.h:25 +#: ui/gtk/help-overlay.ui:170 msgctxt "shortcut window" msgid "Mark unread" msgstr "Markera som oläst" -#: ../ui/gtk/help-overlay.ui.h:26 +#: ui/gtk/help-overlay.ui:179 msgctxt "shortcut window" msgid "View" msgstr "Visa" -#: ../ui/gtk/help-overlay.ui.h:27 +#: ui/gtk/help-overlay.ui:183 msgctxt "shortcut window" msgid "Zoom in" msgstr "Zooma in" -#: ../ui/gtk/help-overlay.ui.h:28 +#: ui/gtk/help-overlay.ui:190 msgctxt "shortcut window" msgid "Zoom out" msgstr "Zooma ut" -#: ../ui/gtk/help-overlay.ui.h:29 +#: ui/gtk/help-overlay.ui:197 msgctxt "shortcut window" msgid "Reset zoom" msgstr "Återställ zoom" -#: ../ui/gtk/help-overlay.ui.h:30 +#: ui/gtk/help-overlay.ui:206 msgctxt "shortcut window" msgid "Additional Shortcuts" msgstr "Ytterligare genvägar" -#: ../ui/gtk/help-overlay.ui.h:31 +#: ui/gtk/help-overlay.ui:210 msgctxt "shortcut window" msgid "Star" msgstr "Stjärnmärk" -#: ../ui/gtk/help-overlay.ui.h:32 +#: ui/gtk/help-overlay.ui:217 msgctxt "shortcut window" msgid "Unstar" -msgstr "Ta bort stjärmärkning" +msgstr "Ta bort stjärnmärkning" -#: ../ui/gtk/help-overlay.ui.h:33 +#: ui/gtk/help-overlay.ui:224 msgctxt "shortcut window" msgid "Delete" msgstr "Ta bort" -#: ../ui/gtk/help-overlay.ui.h:34 +#: ui/gtk/help-overlay.ui:231 msgctxt "shortcut window" msgid "Jump to next (older) conversation" msgstr "Hoppa till nästa (äldre) konversation" -#: ../ui/gtk/help-overlay.ui.h:35 +#: ui/gtk/help-overlay.ui:238 msgctxt "shortcut window" msgid "Jump to previous (newer) conversation" msgstr "Hoppa till föregående (nyare) konversation" -#: ../ui/gtk/help-overlay.ui.h:36 +#: ui/gtk/help-overlay.ui:250 msgid "Composer Shortcuts" msgstr "Redigeringsgenvägar" -#: ../ui/gtk/help-overlay.ui.h:37 +#: ui/gtk/help-overlay.ui:258 msgctxt "shortcut window" msgid "Quote text" msgstr "Citera text" -#: ../ui/gtk/help-overlay.ui.h:38 +#: ui/gtk/help-overlay.ui:265 msgctxt "shortcut window" msgid "Unquote text" msgstr "Avcitera text" -#: ../ui/gtk/help-overlay.ui.h:39 +#: ui/gtk/help-overlay.ui:278 msgctxt "shortcut window" msgid "Send" msgstr "Skicka" -#: ../ui/gtk/help-overlay.ui.h:40 +#: ui/gtk/help-overlay.ui:285 msgctxt "shortcut window" msgid "Add attachment" msgstr "Bifoga bilaga" -#: ../ui/gtk/help-overlay.ui.h:41 +#: ui/gtk/help-overlay.ui:294 msgctxt "shortcut window" msgid "Rich text mode" msgstr "Rich Text-läge" -#: ../ui/gtk/help-overlay.ui.h:42 +#: ui/gtk/help-overlay.ui:298 msgctxt "shortcut window" msgid "Bold text" msgstr "Fet text" -#: ../ui/gtk/help-overlay.ui.h:43 +#: ui/gtk/help-overlay.ui:305 msgctxt "shortcut window" msgid "Italicize text" msgstr "Kursiv text" -#: ../ui/gtk/help-overlay.ui.h:44 +#: ui/gtk/help-overlay.ui:312 msgctxt "shortcut window" msgid "Underline text" msgstr "Understryk text" -#: ../ui/gtk/help-overlay.ui.h:45 +#: ui/gtk/help-overlay.ui:319 msgctxt "shortcut window" msgid "Strike text" msgstr "Genomstruken text" -#: ../ui/gtk/help-overlay.ui.h:46 +#: ui/gtk/help-overlay.ui:326 msgctxt "shortcut window" msgid "Insert a link" msgstr "Infoga en länk" -#: ../ui/gtk/help-overlay.ui.h:47 +#: ui/gtk/help-overlay.ui:333 msgctxt "shortcut window" msgid "Remove formatting" msgstr "Ta bort formatering" -#: ../ui/gtk/menus.ui.h:2 -msgid "A_ccounts" -msgstr "_Konton" +#: ui/main-toolbar.ui:23 +msgctxt "tooltip" +msgid "Compose Message" +msgstr "Skriv meddelande" -#: ../ui/gtk/menus.ui.h:4 -msgid "_Keyboard Shortcuts" -msgstr "_Tangenbordsgenvägar" +#: ui/main-toolbar.ui:52 +msgid "Toggle search bar" +msgstr "Växla visning av sökrad" -#: ../ui/login.glade.h:1 -msgid "email@example.com" -msgstr "epost@exempel.se" +#: ui/main-toolbar.ui:112 +msgid "Reply" +msgstr "Svara" + +#: ui/main-toolbar.ui:135 +msgid "Reply All" +msgstr "Svara alla" -#: ../ui/login.glade.h:2 ../ui/password-dialog.glade.h:3 -msgid "Password" -msgstr "Lösenord" +#: ui/main-toolbar.ui:158 +msgid "Forward" +msgstr "Vidarebefordra" -#: ../ui/login.glade.h:3 -msgid "E_mail address" -msgstr "_E-postadress" - -#: ../ui/login.glade.h:4 -msgid "_Password" -msgstr "_Lösenord" - -#: ../ui/login.glade.h:5 -msgid "S_ervice" -msgstr "_Tjänst" - -#: ../ui/login.glade.h:6 -msgid "N_ame" -msgstr "N_amn" - -#: ../ui/login.glade.h:8 -msgid "N_ickname" -msgstr "Al_ias" - -#: ../ui/login.glade.h:9 -msgid "Work, Home, etc." -msgstr "Arbete, Hemma, osv." - -#: ../ui/login.glade.h:10 -msgid "_Save sent mail" -msgstr "_Spara skickad e-post" - -#: ../ui/login.glade.h:11 -msgid "Addi_tional email addresses…" -msgstr "Y_tterligare e-postadresser…" - -#: ../ui/login.glade.h:12 -msgid "IMAP settings" -msgstr "IMAP-inställningar" - -#: ../ui/login.glade.h:13 -msgid "Se_rver" -msgstr "Se_rver" +#: ui/main-toolbar.ui:264 +msgid "Toggle find bar" +msgstr "Växla visning av sökrad" -#: ../ui/login.glade.h:14 -msgid "imap.example.com" -msgstr "email@exempel.se" +#: ui/main-toolbar.ui:306 +msgid "_Archive" +msgstr "_Arkivera" -#: ../ui/login.glade.h:15 -msgid "P_ort" -msgstr "P_ort" +#: ui/main-toolbar-menus.ui:21 +msgid "Mark as S_pam" +msgstr "Markera som skrä_ppost" -#: ../ui/login.glade.h:16 -msgid "smtp.example.com" -msgstr "smtp.exempel.se" +#: ui/main-toolbar-menus.ui:25 +msgid "Mark as not S_pam" +msgstr "Markera som icke skrä_ppost" + +#: ui/main-toolbar-menus.ui:32 +msgid "Empty _Spam…" +msgstr "Töm _skräpkorg…" + +#: ui/main-toolbar-menus.ui:36 +msgid "Empty _Trash…" +msgstr "_Töm papperskorg…" + +#: ui/main-toolbar-menus.ui:42 +msgid "_Accounts" +msgstr "_Konton" + +#: ui/main-toolbar-menus.ui:50 +msgid "_Keyboard Shortcuts" +msgstr "_Tangentbordsgenvägar" -#: ../ui/login.glade.h:17 -msgid "Ser_ver" -msgstr "Ser_ver" - -#: ../ui/login.glade.h:18 -msgid "Por_t" -msgstr "Por_t" - -#: ../ui/login.glade.h:19 -msgid "SMTP settings" -msgstr "SMTP-inställningar" - -#: ../ui/login.glade.h:20 -msgid "User_name" -msgstr "Användar_namn" - -#: ../ui/login.glade.h:21 -msgid "Pass_word" -msgstr "Lösen_ord" - -#: ../ui/login.glade.h:22 -msgid "SMTP username" -msgstr "SMTP-användarnamn" - -#: ../ui/login.glade.h:23 -msgid "SMTP password" -msgstr "SMTP-lösenord" - -#: ../ui/login.glade.h:24 -msgid "_Username" -msgstr "An_vändarnamn" - -#: ../ui/login.glade.h:25 -msgid "IMAP username" -msgstr "IMAP-användarnamn" - -#: ../ui/login.glade.h:26 -msgid "IMAP password" -msgstr "IMAP-lösenord" - -#: ../ui/login.glade.h:27 -msgid "Encr_yption" -msgstr "Kr_yptering" - -#: ../ui/login.glade.h:28 -msgid "Encrypt_ion" -msgstr "Krypter_ing" - -#: ../ui/login.glade.h:30 -msgid "SSL/TLS" -msgstr "SSL/TLS" - -#: ../ui/login.glade.h:31 -msgid "STARTTLS" -msgstr "STARTTLS" - -#: ../ui/login.glade.h:32 -msgid "No authentication re_quired" -msgstr "Ingen autentisering _krävs" - -#: ../ui/login.glade.h:33 -msgid "Use IMAP cre_dentials" -msgstr "Använ_d autentiseringsuppgifter för IMAP" - -#: ../ui/login.glade.h:34 -msgid "Composer" -msgstr "Textinmatningsfönster" - -#: ../ui/login.glade.h:35 -msgid "Save dra_fts on server" -msgstr "Spara _utkast på servern" - -#: ../ui/login.glade.h:36 -msgid "Si_gn emails (HTML allowed):" -msgstr "Si_gnera e-postmeddelanden (HTML tillåtet):" - -#: ../ui/login.glade.h:37 -msgid "Storage" -msgstr "Lagring" - -#: ../ui/login.glade.h:38 -msgid "_Download mail" -msgstr "_Hämta e-post" - -#: ../ui/main-toolbar.ui.h:1 -msgid "Empty Spam or Trash folders" -msgstr "Töm skräp- eller papperskorgar" +#: ui/main-toolbar-menus.ui:61 +msgid "_About Geary" +msgstr "_Om Geary" + +#. Infobar title when one or more accounts are offline +#: ui/main-window.ui:183 +msgid "Working offline" +msgstr "Arbetar frånkopplad" + +#. Label and tooltip for offline infobar +#: ui/main-window.ui:197 +msgid "" +"Your computer does not appear to be connected to the Internet.\n" +"You will not be able to send or receive email until it is re-connected." +msgstr "" +"Din dator verkar inte vara ansluten till internet.\n" +"Du kommer inte att kunna skicka eller ta emot e-post innan den är ansluten " +"igen." + +#. Label and tooltip for offline infobar +#: ui/main-window.ui:200 +msgid "You will not be able to send or receive email until re-connected." +msgstr "" +"Du kommer inte att kunna skicka eller ta emot e-post innan du är ansluten " +"igen." + +#. Button label for displaying technical details about an account problem +#. Dialog title for displaying technical details of a problem. Same as the button that invokes it. +#: ui/main-window.ui:247 ui/problem-details-dialog.ui:13 +msgid "Details" +msgstr "Detaljer" + +#. Button label for retrying an account problem +#: ui/main-window.ui:261 +msgid "Retry" +msgstr "Försök igen" + +#. Infobar title when one or more accounts have encounted an error +#: ui/main-window.ui:294 +msgid "Account problem" +msgstr "Kontoproblem" + +#. Label and tooltip for account service problem infobar +#: ui/main-window.ui:308 +msgid "" +"Geary encountered a problem connecting to an account.\n" +"Please check your Internet connection, the server configuration and try " +"again." +msgstr "" +"Geary stötte på ett problem vid anslutning till ett konto.\n" +"Kontrollera din internetanslutning och serverkonfigurationen och prova igen." + +#. Label and tooltip for account service problem infobar +#: ui/main-window.ui:311 +msgid "Geary encountered a problem connecting to an account." +msgstr "Geary stötte på ett problem vid anslutning till ett konto." + +#. Button label for retrying TLS cert validation +#: ui/main-window.ui:358 +msgid "Check" +msgstr "Kontrollera" + +#. Button tooltip for retrying TLS cert validation +#: ui/main-window.ui:362 +msgid "Check the security details for the connection" +msgstr "Kontrollera anslutningens säkerhetsdetaljer" + +#. Infobar title when one or more accounts have a TLS cert validation error +#: ui/main-window.ui:391 +msgid "Security problem" +msgstr "Säkerhetsproblem" + +#. Label and tooltip for TLS cert validation error infobar +#: ui/main-window.ui:405 +msgid "" +"An account has reported an untrusted server.\n" +"Please check the server configuration and try again." +msgstr "" +"Ett konto har rapporterat en opålitlig server.\n" +"Kontrollera serverkonfigurationen och försök igen." + +#. Label and tooltip for TLS cert validation error infobar +#: ui/main-window.ui:408 +msgid "An account has reported an untrusted server." +msgstr "Ett konto har rapporterat en opålitlig server." + +#. Button tooltip for retrying when a login error has occurred +#: ui/main-window.ui:459 +msgid "Retry login, you will be prompted for your password" +msgstr "Försök igen att logga in, du kommer att efterfrågas om ditt lösenord" + +#. Infobar title when one or more accounts have a login error +#: ui/main-window.ui:488 +msgid "Login problem" +msgstr "Inloggningsproblem" + +#. Label and tooltip for authentication problem infobar +#: ui/main-window.ui:502 +msgid "" +"An account has reported an incorrect login or password.\n" +"Please check your login name and try again." +msgstr "" +"Ett konto har rapporterat en felaktig inloggning eller lösenord.\n" +"Kontrollera ditt inloggningsnamn och försök igen." + +#. Label and tooltip for authentication problem infobar +#: ui/main-window.ui:505 +msgid "An account has reported an incorrect login or password." +msgstr "Ett konto har rapporterat en felaktig inloggning eller lösenord." -#: ../ui/password-dialog.glade.h:1 +#: ui/password-dialog.glade:74 msgid "SMTP Credentials" msgstr "SMTP-certifikation" -#: ../ui/password-dialog.glade.h:2 +#: ui/password-dialog.glade:91 msgid "Username" msgstr "Användarnamn" -#: ../ui/password-dialog.glade.h:4 +#: ui/password-dialog.glade:152 msgid "_Remember password" msgstr "_Kom ihåg lösenord" -#: ../ui/password-dialog.glade.h:6 +#: ui/password-dialog.glade:210 msgid "_Authenticate" msgstr "_Autentisera" -#: ../ui/preferences-dialog.ui.h:1 +#: ui/preferences-dialog.ui:38 msgid "Reading" msgstr "Läser" -#: ../ui/preferences-dialog.ui.h:2 +#: ui/preferences-dialog.ui:51 msgid "_Automatically select next message" msgstr "Välj nästa meddelande _automatiskt" -#: ../ui/preferences-dialog.ui.h:3 +#: ui/preferences-dialog.ui:70 msgid "_Display conversation preview" msgstr "_Visa förhandsvisning av konversation" -#: ../ui/preferences-dialog.ui.h:4 +#: ui/preferences-dialog.ui:89 msgid "Use _three pane view" msgstr "Använd tredelad vy" -#: ../ui/preferences-dialog.ui.h:5 +#: ui/preferences-dialog.ui:113 msgid "Notifications" msgstr "Aviseringar" -#: ../ui/preferences-dialog.ui.h:6 +#: ui/preferences-dialog.ui:126 msgid "_Play notification sounds" msgstr "S_pela aviseringsljud" -#: ../ui/preferences-dialog.ui.h:7 +#: ui/preferences-dialog.ui:145 msgid "Show _notifications for new mail" msgstr "Visa _aviseringar för ny e-post" -#: ../ui/preferences-dialog.ui.h:8 -msgid "Always _watch for new mail" -msgstr "_Kontrollera alltid om det finns ny e-post" - -#: ../ui/preferences-dialog.ui.h:9 -msgid "Geary will run in the background and notify of new mail" -msgstr "Geary kommer att köra i bakgrunden och avisera om ny e-post" +#: ui/preferences-dialog.ui:164 +msgid "_Watch for new mail when closed" +msgstr "_Kontrollera om det finns ny e-post vid avslut" + +#: ui/preferences-dialog.ui:168 +msgid "Geary will keep running after all windows are closed" +msgstr "Geary kommer att fortsätta köra efter att alla fönster stängts" -#: ../ui/preferences-dialog.ui.h:10 +#: ui/preferences-dialog.ui:195 msgid "Preferences" msgstr "Inställningar" -#: ../ui/remove_confirm.glade.h:1 +#. Button label for copying technical information to the clipboard +#: ui/problem-details-dialog.ui:17 +msgid "Copy to Clipboard" +msgstr "Kopiera till urklipp" + +#. Button tooltip for copying technical information to the clipboard +#: ui/problem-details-dialog.ui:21 msgid "" -"Are you sure you want to remove this " -"account? " +"Copy technical details to clipboard for pasting into an email or bug report" msgstr "" -"Är du säker på att du vill ta bort " -"detta konto? " +"Kopiera teknisk information till urklipp för att klippa in i ett e-" +"postmeddelande eller en felrapport" -#: ../ui/remove_confirm.glade.h:2 +#: ui/problem-details-dialog.ui:73 msgid "" -"All email associated with this account will be removed from your computer. " -"This will not affect email on the server." +"If the problem is serious or persists, please copy and send these details to " +"the mailing list " +"or file a new " +"bug report." msgstr "" -"All e-post som tillhör detta konto kommer att tas bort från din dator. Detta " -"påverkar inte e-posten på servern." +"Om problemet är allvarligt eller kvarstår, vänligen kopiera och skicka dessa " +"detaljer till sändlistan eller skicka in en ny felrapport." -#: ../ui/remove_confirm.glade.h:3 -msgid "Nickname:" -msgstr "Alias:" +#: ui/problem-details-dialog.ui:89 +msgid "Details:" +msgstr "Detaljer:" -#: ../ui/remove_confirm.glade.h:4 -msgid "Email address:" -msgstr "E-postadress:" - -#: ../ui/upgrade_dialog.glade.h:1 +#: ui/upgrade_dialog.glade:60 msgid "Geary update in progress…" msgstr "Geary-uppgradering pågår…" +#~ msgid "Base URL to look up contact avatars" +#~ msgstr "Bas-URL för att slå upp profilbilder för kontakter" + +#~ msgid "" +#~ "A Gravatar or Libravatar compatible URL, set to the empty string to " +#~ "disable." +#~ msgstr "" +#~ "En Gravatar- eller Libravatar-kompatibel URL, ställ in till tom sträng " +#~ "för att inaktivera." + +#~ msgid "Delete conversations (Shift+Delete)" +#~ msgstr "Radera konversationerna (Skift+Delete)" + +#~ msgid "Move conversations to Trash (Delete, Backspace)" +#~ msgstr "Flytta konversationerna till papperskorgen (Delete, Backsteg)" + +#~ msgid "Archive conversations (A)" +#~ msgstr "Arkivera konversationerna (A)" + +#~ msgid "Mark conversations" +#~ msgstr "Markera konversationer" + +#~ msgid "Add label to conversations" +#~ msgstr "Lägg till etikett till konversationerna" + +#~ msgid "Move conversations" +#~ msgstr "Flytta konversationerna" + +#~ msgid "A_ccounts" +#~ msgstr "_Konton" + +#~ msgid "Empty Spam or Trash folders" +#~ msgstr "Töm skräp- eller papperskorgar" + +#~ msgid "Email address:" +#~ msgstr "E-postadress:" + +#~ msgid "Retry connecting now" +#~ msgstr "Försök att ansluta igen" + +#~ msgid "Try reconnecting now" +#~ msgstr "Försök att ansluta igen" + +#~ msgid "Problem with connection to incoming server for %s" +#~ msgstr "Problem med anslutningen till inkommande server för %s" + +#~ msgid "Problem with connection to outgoing server for %s" +#~ msgstr "Fel vid anslutning till utgående server för %s" + +#~ msgid "To: " +#~ msgstr "Till: " + +#~ msgid "Cc: " +#~ msgstr "Cc: " + +#~ msgid "Bcc: " +#~ msgstr "Bcc: " + +#~ msgid "From: %s\n" +#~ msgstr "Från: %s\n" + +#~ msgid "Subject: %s\n" +#~ msgstr "Ämne: %s\n" + +#~ msgid "Date: %s\n" +#~ msgstr "Datum: %s\n" + +#~ msgid "To: %s\n" +#~ msgstr "Till: %s\n" + +#~ msgid "Cc: %s\n" +#~ msgstr "Cc: %s\n" + +#~ msgid "Default attachments directory" +#~ msgstr "Standardkatalog för bilagor" + +#~ msgid "Location used when opening and saving attachments." +#~ msgstr "Plats att använda vid öppnandet och sparandet av bilagor." + +#~ msgid "Default print output directory" +#~ msgstr "Standardkatalog för utskrifter" + +#~ msgid "Location used when printing to a file." +#~ msgstr "Plats att använda vid utskrift till fil." + +#~ msgid "Additional addresses for %s" +#~ msgstr "Ytterligare adresser för %s" + +#~ msgid "First Last" +#~ msgstr "Förnamn Efternamn" + +#~ msgid "Enter your account information to get started." +#~ msgstr "Fyll i din kontoinformation för att komma igång." + +#~ msgid "Edit" +#~ msgstr "Redigera" + +#~ msgid "Preview" +#~ msgstr "Förhandsgranska" + +#~ msgid "Remem_ber passwords" +#~ msgstr "_Kom ihåg lösenord" + +#~ msgid "Remem_ber password" +#~ msgstr "_Kom ihåg lösenord" + +#~ msgid "Unable to validate:\n" +#~ msgstr "Kan inte bekräfta:\n" + +#~ msgid " • Invalid account nickname.\n" +#~ msgstr " • Ogiltigt konto-alias.\n" + +#~ msgid " • Email address already added to Geary.\n" +#~ msgstr " • E-postadress redan tillagd i Geary.\n" + +#~ msgid " • IMAP connection error.\n" +#~ msgstr " • IMAP-anslutningsfel.\n" + +#~ msgid " • IMAP username or password incorrect.\n" +#~ msgstr " • Felaktigt IMAP-användarnamn eller -lösenord.\n" + +#~ msgid " • SMTP connection error.\n" +#~ msgstr " • SMTP-anslutningsfel.\n" + +#~ msgid " • SMTP username or password incorrect.\n" +#~ msgstr " • Felaktigt SMTP-användarnamn eller -lösenord.\n" + +#~ msgid " • Connection error.\n" +#~ msgstr " • Anslutningsfel.\n" + +#~ msgid " • Username or password incorrect.\n" +#~ msgstr " • Felaktigt användarnamn eller lösenord.\n" + +#~ msgid "Unable to store server trust exception" +#~ msgstr "Kan inte lagra undantag för tillit till server" + +#~ msgid "Your settings are insecure" +#~ msgstr "Dina inställningar är osäkra" + +#~ msgid "" +#~ "Your IMAP and/or SMTP settings do not specify SSL or TLS. This means " +#~ "your username and password could be read by another person on the " +#~ "network. Are you sure you want to do this?" +#~ msgstr "" +#~ "Dina IMAP- och/eller SMTP-inställningar specificerar inte SSL eller TLS. " +#~ "Detta innebär att ditt användarnamn och lösenord kan avläsas av någon " +#~ "annan på nätverket. Är du säker på att du vill göra detta?" + +#~ msgid "Co_ntinue" +#~ msgstr "_Fortsätt" + +#~ msgid "" +#~ "Geary encountered an error sending an email. If the problem persists, " +#~ "please manually delete the email from your Outbox folder." +#~ msgstr "" +#~ "Geary råkade ut för ett fel vid skickande av e-post. Om problemen " +#~ "kvarstår kan du manuellt ta bort e-postmeddelandet från din utkorg." + +#~ msgid "" +#~ "Geary encountered an error saving a sent message to Sent Mail. The " +#~ "message will stay in your Outbox folder until you delete it." +#~ msgstr "" +#~ "Geary råkade ut för ett fel vid sparande av ett skickat e-postmeddelande " +#~ "till \"Skickad e-post\". Meddelandet kommer att ligga kvar i utkorgen " +#~ "tills du tar bort det." + +#~ msgid "Unable to open local mailbox for %s" +#~ msgstr "Kunde inte öppna lokal brevlåda för %s" + +#~ msgid "" +#~ "There was an error opening the local mail database for this account. This " +#~ "is possibly due to a file permissions problem.\n" +#~ "\n" +#~ "Please check that you have read/write permissions for all files in this " +#~ "directory:\n" +#~ "\n" +#~ "%s" +#~ msgstr "" +#~ "Ett fel inträffade när den lokala e-postdatabasen för detta konto " +#~ "öppnades. En möjlig orsak till detta kan vara problem med felaktiga " +#~ "filrättigheter. \n" +#~ "\n" +#~ "Vänligen kontrollera att du har läs- och skrivrättigheter för alla filer " +#~ "i följande katalog:\n" +#~ "\n" +#~ "%s" + +#~ msgid "" +#~ "The version number of the local mail database is formatted for a newer " +#~ "version of Geary. Unfortunately, the database cannot be “rolled back” to " +#~ "work with this version of Geary.\n" +#~ "\n" +#~ "Please install the latest version of Geary and try again." +#~ msgstr "" +#~ "Versionsnumret för den lokala e-postdatabasen är formaterad för en nyare " +#~ "version av Geary. Tyvärr kan databasen inte återställas att fungera med " +#~ "denna version av Geary.\n" +#~ "\n" +#~ "Vänligen installera den senaste versionen av Geary och försök igen." + +#~ msgid "" +#~ "There was an error opening the local account. This is probably due to " +#~ "connectivity issues.\n" +#~ "\n" +#~ "Please check your network connection and restart Geary." +#~ msgstr "" +#~ "Det uppstod ett fel när det lokala kontot öppnades. Detta beror " +#~ "förmodligen på problem med anslutningen. \n" +#~ "\n" +#~ "Vänligen kontrollera din nätverksanslutning och starta om Geary." + +#~ msgid "Geary will exit if you have no other open email accounts." +#~ msgstr "Geary kommer att avsluta om du inte har andra e-postkonton öppna." + +#~ msgid "IMAP" +#~ msgstr "IMAP" + +#~ msgid "SMTP" +#~ msgstr "SMTP" + +#~ msgid "Other" +#~ msgstr "Annan" + +#~ msgid "Cannot remove account " +#~ msgstr "" +#~ "Kan inte avlägsna kontot " + +#~ msgid "" +#~ "A composer window associated with this account is currently open. Send or " +#~ "discard the message and try again." +#~ msgstr "" +#~ "Ett textinmatningsfönster som tillhör detta konto är redan öppet. Skicka " +#~ "eller förkasta det meddelandet och försök igen." + +#~ msgid "Please wait while Geary validates your account." +#~ msgstr "Vänta medan Geary bekräftar dina kontoinställningar." + +#~ msgid "" +#~ "Some email services require additional addresses be configured on the " +#~ "server. Contact your email provider for more information." +#~ msgstr "" +#~ "Vissa e-posttjänster kräver att ytterligare adresser har konfigurerats på " +#~ "servern. Kontakta din e-postleverantör för vidare information." + +#~ msgid "_Update" +#~ msgstr "_Uppdatera" + +#~ msgid "E_mail address" +#~ msgstr "_E-postadress" + +#~ msgid "_Password" +#~ msgstr "_Lösenord" + +#~ msgid "S_ervice" +#~ msgstr "_Tjänst" + +#~ msgid "N_ame" +#~ msgstr "N_amn" + +#~ msgid "N_ickname" +#~ msgstr "Al_ias" + +#~ msgid "Work, Home, etc." +#~ msgstr "Arbete, Hemma, osv." + +#~ msgid "Addi_tional email addresses…" +#~ msgstr "Y_tterligare e-postadresser…" + +#~ msgid "IMAP settings" +#~ msgstr "IMAP-inställningar" + +#~ msgid "Se_rver" +#~ msgstr "Se_rver" + +#~ msgid "P_ort" +#~ msgstr "P_ort" + +#~ msgid "Ser_ver" +#~ msgstr "Ser_ver" + +#~ msgid "Por_t" +#~ msgstr "Por_t" + +#~ msgid "User_name" +#~ msgstr "Användar_namn" + +#~ msgid "Pass_word" +#~ msgstr "Lösen_ord" + +#~ msgid "SMTP password" +#~ msgstr "SMTP-lösenord" + +#~ msgid "_Username" +#~ msgstr "An_vändarnamn" + +#~ msgid "IMAP password" +#~ msgstr "IMAP-lösenord" + +#~ msgid "Encr_yption" +#~ msgstr "Kr_yptering" + +#~ msgid "Encrypt_ion" +#~ msgstr "Krypter_ing" + +#~ msgid "STARTTLS" +#~ msgstr "STARTTLS" + +#~ msgid "No authentication re_quired" +#~ msgstr "Ingen autentisering _krävs" + +#~ msgid "Use IMAP cre_dentials" +#~ msgstr "Använ_d autentiseringsuppgifter för IMAP" + +#~ msgid "Composer" +#~ msgstr "Textinmatningsfönster" + +#~ msgid "Si_gn emails (HTML allowed):" +#~ msgstr "Si_gnera e-postmeddelanden (HTML tillåtet):" + +#~ msgid "Storage" +#~ msgstr "Lagring" + +#~ msgid "" +#~ "Are you sure you want to remove " +#~ "this account? " +#~ msgstr "" +#~ "Är du säker på att du vill ta bort " +#~ "detta konto? " + +#~ msgid "" +#~ "All email associated with this account will be removed from your " +#~ "computer. This will not affect email on the server." +#~ msgstr "" +#~ "All e-post som tillhör detta konto kommer att tas bort från din dator. " +#~ "Detta påverkar inte e-posten på servern." + +#~ msgid "Nickname:" +#~ msgstr "Alias:" + +#~ msgid "Geary Email" +#~ msgstr "Geary E-post" + +#~ msgid "Geary Mail" +#~ msgstr "Geary Mail" + +#~ msgid "_Mark as…" +#~ msgstr "_Markera som…" + +#~ msgid "Add label" +#~ msgstr "Lägg till etikett" + +#~ msgid "_Label" +#~ msgstr "Tilldela _etikett" + +#~ msgid "_Move" +#~ msgstr "_Flytta" + +#~ msgid "Compose new message (Ctrl+N, N)" +#~ msgstr "Skriv nytt meddelande (Ctrl+N, N)" + +#~ msgid "Reply (Ctrl+R, R)" +#~ msgstr "Svara (Ctrl+R, R)" + +#~ msgid "Reply all (Ctrl+Shift+R, Shift+R)" +#~ msgstr "Svara alla (Ctrl+Skift+R, Skift+R)" + +#~ msgid "Forward (Ctrl+L, F)" +#~ msgstr "Vidarebefordra (Ctrl+L, F)" + +#~ msgid "" +#~ "Geary encountered an error while connecting to the server. Please try " +#~ "again in a few moments." +#~ msgstr "" +#~ "Geary påträffade ett fel vid anslutning till server. Prova igen efter en " +#~ "stund." + +#~ msgid "Try Again" +#~ msgstr "Prova igen" + +#~ msgid "Geary will run in the background and notify of new mail" +#~ msgstr "Geary kommer att köra i bakgrunden och avisera om ny e-post" + #~ msgid "Mail Client" #~ msgstr "E-postklient" @@ -2716,12 +3742,6 @@ #~ msgid "No search results found." #~ msgstr "Inga sökresultat funna." -#~ msgid "From:" -#~ msgstr "Från:" - -#~ msgid "Date:" -#~ msgstr "Datum:" - #~ msgid "Select _Message" #~ msgstr "_Markera meddelande" @@ -2790,9 +3810,6 @@ #~ msgid "Fixed Width" #~ msgstr "Fast bredd" -#~ msgid "Detach" -#~ msgstr "Frikoppla" - #~ msgid "_Attach File" #~ msgstr "_Bifoga fil" diff -Nru geary-0.12.4/po/tr.po geary-3.32.0/po/tr.po --- geary-0.12.4/po/tr.po 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/po/tr.po 2019-03-17 13:39:29.000000000 +0000 @@ -11,15 +11,14 @@ # Necdet Yücel , 2015. # Simge Sezgin , 2015. # Muhammet Kara , 2014, 2016. -# Emin Tufan Çetin , 2016, 2017. +# Emin Tufan Çetin , 2016-2019. # msgid "" msgstr "" "Project-Id-Version: geary-0.4.1\n" -"Report-Msgid-Bugs-To: https://bugzilla.gnome.org/enter_bug.cgi?" -"product=geary&keywords=I18N+L10N&component=internationalization\n" -"POT-Creation-Date: 2017-09-28 14:35+0000\n" -"PO-Revision-Date: 2017-09-30 15:06+0300\n" +"Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/geary/issues\n" +"POT-Creation-Date: 2019-03-06 08:11+0000\n" +"PO-Revision-Date: 2019-03-06 13:38+0300\n" "Last-Translator: Emin Tufan Çetin \n" "Language-Team: Türkçe \n" "Language: tr\n" @@ -27,30 +26,58 @@ "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Gtranslator 2.91.7\n" +"X-Generator: Gtranslator 3.30.1\n" "X-POOTLE-MTIME: 1423290848.000000\n" +#: desktop/geary-attach.contract.desktop.in:3 +msgid "Send by email" +msgstr "E-posta ile gönder" + +#. Translators: Do NOT translate or transliterate this text (this is an icon file name)! +#: desktop/geary-attach.contract.desktop.in:5 +msgid "mail-send" +msgstr "mail-send" + +#: desktop/geary-attach.contract.desktop.in:6 +msgid "Send files using Geary" +msgstr "Dosyaları Geary kullanarak gönderin" + #. Translators: The application name -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:2 -#: ../desktop/org.gnome.Geary.desktop.in.h:1 -#: ../desktop/geary-autostart.desktop.in.h:1 +#: desktop/geary-autostart.desktop.in:3 +#: desktop/org.gnome.Geary.appdata.xml.in:11 +#: desktop/org.gnome.Geary.desktop.in:3 +#: src/client/accounts/accounts-editor-servers-pane.vala:555 msgid "Geary" msgstr "Geary" -#. Translators: The development team's name -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:4 -msgid "Geary Development Team" -msgstr "Geary Geliştirme Takımı" +#: desktop/geary-autostart.desktop.in:4 desktop/org.gnome.Geary.desktop.in:4 +msgid "Email" +msgstr "E-posta" #. Translators: The application's summary / tagline -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:6 -#: ../desktop/org.gnome.Geary.desktop.in.h:4 -#: ../desktop/geary-autostart.desktop.in.h:4 -#: ../src/client/application/geary-application.vala:22 +#: desktop/geary-autostart.desktop.in:5 +#: desktop/org.gnome.Geary.appdata.xml.in:15 +#: desktop/org.gnome.Geary.desktop.in:5 +#: src/client/application/geary-application.vala:23 msgid "Send and receive email" msgstr "E-posta gönder ve al" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:7 +#. Translators: These are desktop search terms. Do not translate semicolons, end line with a semicolon. +#: desktop/geary-autostart.desktop.in:7 +msgid "Email;E-mail;Mail;" +msgstr "Eposta;E-posta;Posta;E Posta;Elektronik Posta;Mektup;" + +#. Translators: Do NOT translate or transliterate this text (this is an icon file name)! +#: desktop/geary-autostart.desktop.in:9 desktop/org.gnome.Geary.desktop.in:9 +msgid "org.gnome.Geary" +msgstr "org.gnome.Geary" + +#. Translators: The development team's name +#: desktop/org.gnome.Geary.appdata.xml.in:13 +msgid "Geary Development Team" +msgstr "Geary Geliştirme Takımı" + +#: desktop/org.gnome.Geary.appdata.xml.in:17 msgid "" "Geary is an email application built around conversations, for the GNOME 3 " "desktop. It allows you to read, find and send email with a straightforward, " @@ -60,7 +87,7 @@ "uygulamasıdır. Dolambaçsız çağdaş bir arayüzle e-posta okumanızı, bulmanızı " "ve göndermenizi sağlar." -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:8 +#: desktop/org.gnome.Geary.appdata.xml.in:22 msgid "" "Conversations allow you to read a complete discussion without having to find " "and click from message to message." @@ -68,215 +95,693 @@ "Konuşmalar, arayıp bulmanızı ve iletiden iletiye tıklamanızı gerektirmeden " "tüm tartışmayı okumanızı sağlar." -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:9 +#: desktop/org.gnome.Geary.appdata.xml.in:26 msgid "Geary’s features include:" msgstr "Geary’nin özellikleri şunlardır:" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:10 +#: desktop/org.gnome.Geary.appdata.xml.in:28 msgid "Quick email account setup" msgstr "Hızlı e-posta hesabı kurulumu" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:11 +#: desktop/org.gnome.Geary.appdata.xml.in:29 msgid "Shows related messages together in conversations" msgstr "İlişkili iletileri konuşmalarda bir arada gösterir" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:12 +#: desktop/org.gnome.Geary.appdata.xml.in:30 msgid "Fast, full text and keyword search" msgstr "Hızlı, tam metin ve anahtar sözcük arama" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:13 +#: desktop/org.gnome.Geary.appdata.xml.in:31 msgid "Full-featured HTML and plain text message composer" msgstr "Tam donanımlı HTML ve düz metin ileti oluşturucu" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:14 +#: desktop/org.gnome.Geary.appdata.xml.in:32 msgid "Desktop notification of new mail" msgstr "Yeni postanın masaüstü bildirimi" -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:15 +#: desktop/org.gnome.Geary.appdata.xml.in:33 msgid "Compatible with GMail, Yahoo! Mail, Outlook.com and other IMAP servers" msgstr "GMail, Yahoo! Mail, Outlook.com ve diğer IMAP sunucularıyla uyumludur" #. Translators: A screenshot description. -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:17 -#| msgid "_Display conversation preview" +#: desktop/org.gnome.Geary.appdata.xml.in:47 msgid "Geary displaying a conversation" msgstr "Geary bir konuşmayı gösteriyor" #. Translators: A screenshot description. -#: ../desktop/org.gnome.Geary.appdata.xml.in.h:19 +#: desktop/org.gnome.Geary.appdata.xml.in:52 msgid "Geary showing the rich text composer" msgstr "Geary zengin metin oluşturucusunu gösteriyor" -#: ../desktop/org.gnome.Geary.desktop.in.h:2 -#: ../desktop/geary-autostart.desktop.in.h:2 -msgid "Email" -msgstr "E-posta" - -#: ../desktop/org.gnome.Geary.desktop.in.h:3 -msgid "Geary Email" -msgstr "Geary E-posta" - #. Translators: These are desktop search terms. Do not translate semicolons, end line with a semicolon. -#: ../desktop/org.gnome.Geary.desktop.in.h:6 +#: desktop/org.gnome.Geary.desktop.in:7 msgid "Mail;E-mail;IMAP;GMail;Yahoo;Hotmail;Outlook;" msgstr "Posta;E-posta;Mail;E-mail;IMAP;GMail;Yahoo;Hotmail;Outlook;" -#: ../desktop/org.gnome.Geary.desktop.in.h:7 ../ui/gtk/menus.ui.h:1 +#: desktop/org.gnome.Geary.desktop.in:21 msgid "Compose Message" msgstr "İleti Oluştur" -#: ../desktop/geary-autostart.desktop.in.h:3 -msgid "Geary Mail" -msgstr "Geary Posta" - -#: ../desktop/geary-autostart.desktop.in.h:5 -msgid "Email;E-mail;Mail;" -msgstr "Eposta;E-posta;Posta;E Posta;Elektronik Posta;Mektup;" - -#: ../desktop/geary-attach.contract.in.h:1 -msgid "Send by email" -msgstr "E-posta ile gönder" - -#: ../desktop/geary-attach.contract.in.h:2 -msgid "Send files using Geary" -msgstr "Dosyaları Geary kullanarak gönderin" +#: desktop/org.gnome.Geary.gschema.xml:8 +msgid "Maximize window" +msgstr "Pencereyi büyüt" + +#: desktop/org.gnome.Geary.gschema.xml:9 +msgid "True if the application window is maximized, false otherwise." +msgstr "Eğer uygulama penceresi büyütüldüyse doğru, değilse yanlış." + +#: desktop/org.gnome.Geary.gschema.xml:14 +msgid "Width of window" +msgstr "Pencerenin genişliği" + +#: desktop/org.gnome.Geary.gschema.xml:15 +msgid "The last recorded width of the application window." +msgstr "Uygulama penceresinin kaydedilen son genişliği." + +#: desktop/org.gnome.Geary.gschema.xml:20 +msgid "Height of window" +msgstr "Pencerenin yüksekliği" + +#: desktop/org.gnome.Geary.gschema.xml:21 +msgid "The last recorded height of the application window." +msgstr "Uygulama penceresinin kaydedilen son yüksekliği." + +#: desktop/org.gnome.Geary.gschema.xml:26 +msgid "Position of folder list pane" +msgstr "Klasör listesi bölmesinin konumu" + +#: desktop/org.gnome.Geary.gschema.xml:27 +msgid "Position of the folder list Paned grabber." +msgstr "Klasör listesi bölmesi yakalayıcının konumu." + +#: desktop/org.gnome.Geary.gschema.xml:32 +msgid "Position of folder list pane when horizontal" +msgstr "Klasör listesi bölmesinin yataykenki konumu" + +#: desktop/org.gnome.Geary.gschema.xml:33 +msgid "" +"Position of the folder list Paned grabber in the horizontal orientation." +msgstr "Yatay yönelimde klasör listesi bölmesi yakalayıcının konumu." + +#: desktop/org.gnome.Geary.gschema.xml:38 +msgid "Position of folder list pane when vertical" +msgstr "Klasör listesi bölmesinin dikeykenki konumu" + +#: desktop/org.gnome.Geary.gschema.xml:39 +msgid "Position of the folder list Paned grabber in the vertical orientation." +msgstr "Dikey yönelimde klasör listesi bölmesi yakalayıcının konumu." + +#: desktop/org.gnome.Geary.gschema.xml:44 +msgid "Orientation of the folder list pane" +msgstr "Klasör listesi bölmesinin konumlandırması" + +#: desktop/org.gnome.Geary.gschema.xml:45 +msgid "True if the folder list Paned is in the horizontal orientation." +msgstr "Eğer klasör listesi bölmesi yatay yönelimdeyse doğru." + +#: desktop/org.gnome.Geary.gschema.xml:50 +msgid "Position of message list pane" +msgstr "İleti listesi bölmesinin konumu" + +#: desktop/org.gnome.Geary.gschema.xml:51 +msgid "Position of the message list Paned grabber." +msgstr "İleti listesi bölmesi yakalayıcının konumu." + +#: desktop/org.gnome.Geary.gschema.xml:56 +msgid "Autoselect next message" +msgstr "Sonraki iletiyi kendiliğinden seç" + +#: desktop/org.gnome.Geary.gschema.xml:57 +msgid "True if we should autoselect the next available conversation." +msgstr "Eğer sonraki uygun konuşmayı kendiliğinden seçmemiz gerekiyorsa doğru." + +#: desktop/org.gnome.Geary.gschema.xml:62 +msgid "Display message previews" +msgstr "İleti ön izlemelerini göster" + +#: desktop/org.gnome.Geary.gschema.xml:63 +msgid "True if we should display a short preview of each message." +msgstr "Her iletinin kısa bir ön izlemesini göstermemiz gerekiyorsa doğru." + +#: desktop/org.gnome.Geary.gschema.xml:68 +msgid "Languages that shall be used in the spell checker" +msgstr "Yazım denetleyicide kullanılacak diller" + +#: desktop/org.gnome.Geary.gschema.xml:69 +msgid "List of the languages to use in the spell checker." +msgstr "Yazım denetleyicide kullanılacak dillerin listesi." + +#: desktop/org.gnome.Geary.gschema.xml:74 +msgid "Languages that are displayed in the spell checker popover" +msgstr "Yazım denetleyici açılır penceresinde gösterilecek diller" + +#: desktop/org.gnome.Geary.gschema.xml:75 +msgid "" +"List of languages that are always displayed in the popover of the spell " +"checker." +msgstr "" +"Yazım denetleyicinin açılır penceresinde her zaman gösterilecek dillerin " +"listesi." + +#: desktop/org.gnome.Geary.gschema.xml:80 +msgid "Enable notification sounds" +msgstr "Bildirim seslerini etkinleştir" + +#: desktop/org.gnome.Geary.gschema.xml:81 +msgid "True to play sounds for notifications and sending." +msgstr "Gönderimler ve bildirimler için ses oynatılacaksa doğru." + +#: desktop/org.gnome.Geary.gschema.xml:86 +msgid "Show notifications for new mail" +msgstr "Yeni posta için bildirimleri göster" + +#: desktop/org.gnome.Geary.gschema.xml:87 +msgid "True to show notification bubbles." +msgstr "Bildirim balonlarını göstermek için doğru." + +#: desktop/org.gnome.Geary.gschema.xml:92 +msgid "Notify of new mail at startup" +msgstr "Başlangıçta yeni postanın bildirilmesi" + +#: desktop/org.gnome.Geary.gschema.xml:93 +msgid "True to notify of new mail at startup." +msgstr "Başlangıçta yeni postaların bildirilmesi için doğru." + +#: desktop/org.gnome.Geary.gschema.xml:98 +msgid "Ask when opening an attachment" +msgstr "Ek açarken sor" + +#: desktop/org.gnome.Geary.gschema.xml:99 +msgid "True to ask when opening an attachment." +msgstr "Eki açarken sormak için doğru." + +#: desktop/org.gnome.Geary.gschema.xml:104 +msgid "Whether to compose emails in HTML" +msgstr "E-postaların HTML’de oluşturulup oluşturulmayacağı" + +#: desktop/org.gnome.Geary.gschema.xml:105 +msgid "True to compose emails in HTML; false for plain text." +msgstr "E-postaları HTML’de oluşturmak için doğru, düz metin için yanlış." + +#: desktop/org.gnome.Geary.gschema.xml:110 +msgid "Advisory strategy for full-text searching" +msgstr "Tam metin arama için tavsiye niteliğinde izlem" + +#: desktop/org.gnome.Geary.gschema.xml:111 +msgid "" +"Acceptable values are “exact”, “conservative”, “aggressive”, and “horizon”." +msgstr "" +"Kabul edilebilir değerler şunlardır: “exact” (birebir), " +"“conservative” (ılımlı), “aggressive” (sert) ve “horizon”." + +#: desktop/org.gnome.Geary.gschema.xml:116 +msgid "Zoom of conversation viewer" +msgstr "Konuşma göstericisinin yakınlaşması" + +#: desktop/org.gnome.Geary.gschema.xml:117 +msgid "The zoom to apply on the conservation view." +msgstr "Konuşma görünümünde uygulanacak yakınlaşma." + +#: desktop/org.gnome.Geary.gschema.xml:122 +msgid "Size of detached composer window" +msgstr "Ayrılan oluşturucu penceresinin boyutu" + +#: desktop/org.gnome.Geary.gschema.xml:123 +msgid "The last recorded size of the detached composer window." +msgstr "Ayrılmış oluşturucu penceresinin kaydedilen son boyutu." + +#: desktop/org.gnome.Geary.gschema.xml:128 +msgid "Base URL to look up contact avatars" +msgstr "Kişi avatarlarının aranıp bulunacağı temel URL" + +#: desktop/org.gnome.Geary.gschema.xml:129 +msgid "" +"A Gravatar or Libravatar compatible URL, set to the empty string to disable." +msgstr "" +"Gravatar veya Libravatar uyumlu URL, devre dışı bırakmak için dizgeyi boş " +"bırakın." + +#: desktop/org.gnome.Geary.gschema.xml:134 +msgid "Whether we migrated the old settings" +msgstr "Eski ayarları taşıyıp taşımayacağımız" + +#: desktop/org.gnome.Geary.gschema.xml:135 +msgid "" +"False to check for the old “org.yorba.geary”-schema and copy its values." +msgstr "" +"Eski “org.yorba.geary”-şemasını denetlemek ve değerlerini kopyalamak için " +"yanlış." + +#. Translators: In-app notification label, when +#. the app had a problem pinning an otherwise +#. untrusted TLS certificate +#: src/client/accounts/accounts-editor.vala:203 +msgid "Failed to store certificate" +msgstr "Sertifika kaydetme başarısız oldu" + +#. Translators: Label for adding an email account +#. account for a generic IMAP service provider. +#: src/client/accounts/accounts-editor-add-pane.vala:109 +msgid "All others" +msgstr "Tüm diğerleri" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:196 +#: src/client/accounts/accounts-editor-servers-pane.vala:316 +msgid "Check your receiving login and password" +msgstr "Alıcı giriş ve parolanızı gözden geçirin" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:211 +#: src/client/accounts/accounts-editor-servers-pane.vala:329 +msgid "Check your receiving server details" +msgstr "Alıcı sunucu ayrıntılarınızı gözden geçirin" + +#. Translators: In-app notification label +#. There was an SMTP auth error, but IMAP already +#. succeeded, so the user probably needs to +#. specify custom creds here +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:233 +#: src/client/accounts/accounts-editor-servers-pane.vala:350 +msgid "Check your sending login and password" +msgstr "Gönderici giriş ve parolanızı gözden geçirin" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:247 +#: src/client/accounts/accounts-editor-servers-pane.vala:363 +msgid "Check your sending server details" +msgstr "Gönderici sunucu ayrıntılarınızı gözden geçirin" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:262 +msgid "Check your email address and password" +msgstr "E-posta adres ve parolanızı gözden geçirin" + +#. Translators: In-app notification label +#: src/client/accounts/accounts-editor-add-pane.vala:273 +msgid "Could not connect, check your network" +msgstr "Bağlanamadı, ağınızı gözden geçirin" + +#. Translators: In-app notification label for a +#. generic error creating an account +#: src/client/accounts/accounts-editor-add-pane.vala:286 +msgid "An unexpected problem occurred" +msgstr "Beklenmedik sorun oluştu" + +#. Translators: In-app notification label, the +#. string substitution is a more detailed reason. +#: src/client/accounts/accounts-editor-add-pane.vala:304 +#, c-format +msgid "Account not created: %s" +msgstr "Hesap oluşturulmadı: %s" + +#. Translators: Label for the person's actual name when adding +#. an account +#: src/client/accounts/accounts-editor-add-pane.vala:551 +msgid "Your name" +msgstr "Adınız" + +#. Translators: Label used for the address part of an +#. email address when editing a user's sender address +#. preferences for an account. +#: src/client/accounts/accounts-editor-add-pane.vala:568 +#: src/client/accounts/accounts-editor-edit-pane.vala:501 +msgid "Email address" +msgstr "E-posta adresi" + +#. Translators: Placeholder for the default sender address +#. when adding an account +#. Translators: This is used as a placeholder for the +#. address part of an email address when editing a user's +#. sender address preferences for an account. +#: src/client/accounts/accounts-editor-add-pane.vala:571 +#: src/client/accounts/accounts-editor-edit-pane.vala:469 +msgid "person@example.com" +msgstr "kisi@ornek.com" + +#. Translators: Label for an IMAP/SMTP service login/user name +#. when adding an account +#. Translators: Label for the user's login name for an +#. IMAP, SMTP, etc service +#: src/client/accounts/accounts-editor-add-pane.vala:585 +#: src/client/accounts/accounts-editor-servers-pane.vala:880 +msgid "Login name" +msgstr "Giriş adınız" + +#. Translators: Label for the user's password for an IMAP, +#. SMTP, etc service +#: src/client/accounts/accounts-editor-add-pane.vala:599 +#: src/client/accounts/accounts-editor-servers-pane.vala:999 +#: ui/password-dialog.glade:108 +msgid "Password" +msgstr "Parola" -#: ../src/client/accounts/account-dialog-add-edit-pane.vala:51 -#: ../src/client/components/stock.vala:31 -#: ../ui/conversation-email-menus.ui.h:10 -msgid "_Save" -msgstr "_Kaydet" +#. Translators: Label for the IMAP server hostname when +#. adding an account. +#. Translators: This label describes the host name or IP +#. address and port used by an account's IMAP service. +#: src/client/accounts/accounts-editor-add-pane.vala:621 +#: src/client/accounts/accounts-editor-servers-pane.vala:727 +msgid "IMAP server" +msgstr "IMAP sunucusu" + +#. Translators: Placeholder for the IMAP server hostname +#. when adding an account. +#: src/client/accounts/accounts-editor-add-pane.vala:624 +msgid "imap.example.com" +msgstr "imap.ornek.com" -#: ../src/client/accounts/account-dialog-add-edit-pane.vala:51 -#: ../src/client/components/stock.vala:22 -msgid "_Add" -msgstr "_Ekle" +#. Translators: Label for the SMTP server hostname when +#. adding an account. +#. Translators: This label describes the host name or IP +#. address and port used by an account's SMTP service. +#: src/client/accounts/accounts-editor-add-pane.vala:630 +#: src/client/accounts/accounts-editor-servers-pane.vala:733 +msgid "SMTP server" +msgstr "SMTP sunucusu" + +#. Translators: Placeholder for the SMTP server hostname +#. when adding an account. +#: src/client/accounts/accounts-editor-add-pane.vala:633 +msgid "smtp.example.com" +msgstr "smtp.ornek.com" -#. reset/clear widgets -#: ../src/client/accounts/account-dialog-edit-alternate-emails-pane.vala:120 +#. Translators: Label in the account editor for the user's +#. custom name for an account. +#: src/client/accounts/accounts-editor-edit-pane.vala:278 +#: ui/accounts_editor_remove_pane.ui:123 +msgid "Account name" +msgstr "Hesap adı" + +#. Translators: Tooltip used to undo changing +#. the name of an account. The string +#. substitution is the old name of the +#. account. +#: src/client/accounts/accounts-editor-edit-pane.vala:312 +#, c-format +msgid "Change account name back to “%s”" +msgstr "Hesap adınızı “%s” olarak değiştirin" + +#. Translators: Tooltip for adding a new email sender/from +#. address's address to an account +#: src/client/accounts/accounts-editor-edit-pane.vala:336 +msgid "Add a new sender email address" +msgstr "Yeni gönderen e-posta adresi ekle" + +#. Translators: Label used to indicate the user has +#. provided no display name for one of their sender +#. email addresses in their account settings. +#: src/client/accounts/accounts-editor-edit-pane.vala:417 +msgid "Name not set" +msgstr "Ad belirlenmedi" + +#. Translators: This is used as a placeholder for the +#. display name for an email address when editing a user's +#. sender address preferences for an account. +#: src/client/accounts/accounts-editor-edit-pane.vala:456 +msgid "Sender Name" +msgstr "Gönderen Adı" + +#: src/client/accounts/accounts-editor-edit-pane.vala:479 +msgid "Remove" +msgstr "Kaldır" + +#. Translators: Label used for the display name part of an +#. email address when editing a user's sender address +#. preferences for an account. +#: src/client/accounts/accounts-editor-edit-pane.vala:494 +msgid "Sender name" +msgstr "Gönderen adı" + +#. Translators: Label used as the undo tooltip after adding an +#. new sender email address to an account. The string +#. substitution is the email address added. +#: src/client/accounts/accounts-editor-edit-pane.vala:561 +#, c-format +msgid "Remove “%s”" +msgstr "“%s” adresini kaldır" + +#. Translators: Label used as the undo tooltip after editing a +#. sender address for an account. The string substitution is +#. the email address edited. +#: src/client/accounts/accounts-editor-edit-pane.vala:601 +#, c-format +msgid "Undo changes to “%s”" +msgstr "“%s” adresine yapılan değişikliği geri al" + +#. Translators: Label used as the undo tooltip after removing +#. a sender address from an account. The string substitution +#. is the email address edited. +#: src/client/accounts/accounts-editor-edit-pane.vala:688 +#, c-format +msgid "Add “%s” back" +msgstr "“%s” adresini geri ekle" + +#. Translators: Label used as the undo tooltip after removing +#. a sender address from an account. The string substitution +#. is the email address edited. +#: src/client/accounts/accounts-editor-edit-pane.vala:730 +msgid "Undo signature changes" +msgstr "İmza değişikliklerini geri al" + +#. Translators: This label describes the account +#. preference for the length of time (weeks, months or +#. years) that past email should be downloaded. +#: src/client/accounts/accounts-editor-edit-pane.vala:778 +msgid "Download mail" +msgstr "Posta indir" + +#. Translators: Tooltip for undoing a change +#. to the length of time that past email +#. should be downloaded for an account. The +#. string substitution is the duration, +#. e.g. "1 month back". +#: src/client/accounts/accounts-editor-edit-pane.vala:810 #, c-format -msgid "Additional addresses for %s" -msgstr "%s için ek adresler" +msgid "Change download period back to: %s" +msgstr "İndirme sıklığını şuna geri al: %s" -#. Sets min size. -#: ../src/client/accounts/account-dialog.vala:21 -msgid "Accounts" -msgstr "Hesaplar" - -#. Copyright 2016 Software Freedom Conservancy Inc. -#. * -#. * This software is licensed under the GNU Lesser General Public License -#. * (version 2.1 or later). See the COPYING file in this distribution. -#. -#. Page for adding or editing an account. -#. / Placeholder text indicating that the user should type their first name and last name -#: ../src/client/accounts/add-edit-page.vala:10 -msgid "First Last" -msgstr "Ad Soyad" - -#: ../src/client/accounts/add-edit-page.vala:235 -msgid "Welcome to Geary." -msgstr "Geary’e hoş geldiniz." - -#: ../src/client/accounts/add-edit-page.vala:235 -msgid "Enter your account information to get started." -msgstr "Başlamak için hesap bilginizi giriniz." +#: src/client/accounts/accounts-editor-edit-pane.vala:831 +msgid "Everything" +msgstr "Her şey" -#: ../src/client/accounts/add-edit-page.vala:255 +#: src/client/accounts/accounts-editor-edit-pane.vala:835 msgid "2 weeks back" msgstr "2 hafta öncesinden" -#. IDs are # of days -#: ../src/client/accounts/add-edit-page.vala:256 +#: src/client/accounts/accounts-editor-edit-pane.vala:839 msgid "1 month back" msgstr "1 ay öncesinden" -#: ../src/client/accounts/add-edit-page.vala:257 +#: src/client/accounts/accounts-editor-edit-pane.vala:843 msgid "3 months back" msgstr "3 ay öncesinden" -#: ../src/client/accounts/add-edit-page.vala:258 +#: src/client/accounts/accounts-editor-edit-pane.vala:847 msgid "6 months back" msgstr "6 ay öncesinden" -#: ../src/client/accounts/add-edit-page.vala:259 +#: src/client/accounts/accounts-editor-edit-pane.vala:851 msgid "1 year back" msgstr "1 yıl öncesinden" -#: ../src/client/accounts/add-edit-page.vala:260 +#: src/client/accounts/accounts-editor-edit-pane.vala:855 msgid "2 years back" msgstr "2 yıl öncesinden" -#: ../src/client/accounts/add-edit-page.vala:261 +#: src/client/accounts/accounts-editor-edit-pane.vala:859 msgid "4 years back" msgstr "4 yıl öncesinden" -#. Separator -#: ../src/client/accounts/add-edit-page.vala:263 -msgid "Everything" -msgstr "Her şey" +#: src/client/accounts/accounts-editor-edit-pane.vala:865 +#, c-format +msgid "%d day back" +msgid_plural "%d days back" +msgstr[0] "%d gün öncesinden" + +#: src/client/accounts/accounts-editor-list-pane.vala:243 +msgid "Undo" +msgstr "Geri Al" + +#: src/client/accounts/accounts-editor-list-pane.vala:251 +msgid "Redo" +msgstr "Yinele" + +#: src/client/accounts/accounts-editor-list-pane.vala:345 +#: src/client/accounts/accounts-editor-list-pane.vala:433 +#: src/client/accounts/accounts-editor-row.vala:278 +msgid "Gmail" +msgstr "Gmail" -#: ../src/client/accounts/add-edit-page.vala:283 -msgid "Edit" -msgstr "Düzenle" - -#: ../src/client/accounts/add-edit-page.vala:285 -msgid "Preview" -msgstr "Ön izle" - -#: ../src/client/accounts/add-edit-page.vala:751 -msgid "Remem_ber passwords" -msgstr "Parolaları _anımsa" +#: src/client/accounts/accounts-editor-list-pane.vala:349 +#: src/client/accounts/accounts-editor-list-pane.vala:437 +#: src/client/accounts/accounts-editor-row.vala:282 +msgid "Outlook.com" +msgstr "Outlook.com" -#: ../src/client/accounts/add-edit-page.vala:758 ../ui/login.glade.h:7 -msgid "Remem_ber password" -msgstr "Parolayı _anımsa" +#: src/client/accounts/accounts-editor-list-pane.vala:353 +#: src/client/accounts/accounts-editor-list-pane.vala:441 +#: src/client/accounts/accounts-editor-row.vala:286 +msgid "Yahoo" +msgstr "Yahoo" + +#. Translators: Tooltip for accounts that have been +#. loaded but disabled by the user. +#: src/client/accounts/accounts-editor-list-pane.vala:371 +msgid "This account has been disabled" +msgstr "Bu hesap devre dışı bırakıldı" + +#. Translators: Tooltip for accounts that have been +#. loaded but because of some error are not able to be +#. used. +#: src/client/accounts/accounts-editor-list-pane.vala:380 +msgid "This account has encountered a problem and is unavailable" +msgstr "Bu hesap sorunla karşılaştı ve kullanılabilir değil" + +#. Translators: Label for adding a generic email account +#: src/client/accounts/accounts-editor-list-pane.vala:430 +msgid "Other email providers" +msgstr "Diğer e-posta sağlayıcıları" + +#. Translators: Notification shown after removing an +#. account. The string substitution is the name of the +#. account. +#: src/client/accounts/accounts-editor-list-pane.vala:547 +#, c-format +msgid "Account “%s” removed" +msgstr "“%s” hesabı kaldırıldı" + +#. Translators: Notification shown after removing an account +#. is undone. The string substitution is the name of the +#. account. +#: src/client/accounts/accounts-editor-list-pane.vala:554 +#, c-format +msgid "Account “%s” restored" +msgstr "“%s” hesabı kurtarıldı" + +#. Translators: Tooltip for dragging list items +#: src/client/accounts/accounts-editor-row.vala:49 +msgid "Drag to move this item" +msgstr "Bu ögeyi taşımak için sürükle" + +#. Translators: Label describes the service provider +#. hosting the email account, e.g. Gmail, Yahoo, or some +#. other generic IMAP service. +#: src/client/accounts/accounts-editor-row.vala:294 +msgid "Service provider" +msgstr "Hizmet sağlayıcı" + +#. Translators: This label describes what form of transport +#. security (TLS, StartTLS, etc) used by an account's IMAP or SMTP +#. service. +#: src/client/accounts/accounts-editor-row.vala:467 +msgid "Connection security" +msgstr "Bağlantı güvenliği" + +#. Translators: Label used when no auth scheme is used +#. by an account's IMAP or SMTP service. +#: src/client/accounts/accounts-editor-row.vala:478 +#: src/client/accounts/accounts-editor-servers-pane.vala:752 +#: src/client/accounts/accounts-editor-servers-pane.vala:964 +#: src/engine/api/geary-special-folder-type.vala:58 +msgid "None" +msgstr "Hiç" -#: ../src/client/accounts/add-edit-page.vala:792 -msgid "Unable to validate:\n" -msgstr "Doğrulanamadı:\n" - -#: ../src/client/accounts/add-edit-page.vala:794 -msgid " • Invalid account nickname.\n" -msgstr " • Geçersiz hesap takma adı.\n" - -#: ../src/client/accounts/add-edit-page.vala:797 -msgid " • Email address already added to Geary.\n" -msgstr " • E-posta adresi zaten Geary’e eklenmiş.\n" - -#: ../src/client/accounts/add-edit-page.vala:801 -msgid " • IMAP connection error.\n" -msgstr " • IMAP bağlantı hatası.\n" - -#: ../src/client/accounts/add-edit-page.vala:804 -msgid " • IMAP username or password incorrect.\n" -msgstr " • IMAP kullanıcı adı veya parola hatalı.\n" - -#: ../src/client/accounts/add-edit-page.vala:807 -msgid " • SMTP connection error.\n" -msgstr " • SMTP bağlantı hatası.\n" - -#: ../src/client/accounts/add-edit-page.vala:810 -msgid " • SMTP username or password incorrect.\n" -msgstr " • SMTP kullanıcı adı veya parola hatalı.\n" - -#: ../src/client/accounts/add-edit-page.vala:814 -msgid " • Connection error.\n" -msgstr " • Bağlantı hatası.\n" - -#: ../src/client/accounts/add-edit-page.vala:818 -msgid " • Username or password incorrect.\n" -msgstr " • Kullanıcı adı veya parola hatalı.\n" +#: src/client/accounts/accounts-editor-row.vala:485 +msgid "StartTLS" +msgstr "StartTLS" + +#: src/client/accounts/accounts-editor-row.vala:492 +msgid "TLS" +msgstr "TLS" + +#. Translators: Label for source of SMTP authentication +#. credentials (none, use IMAP, custom) when adding a new +#. account +#. Button label for retrying when a login error has occurred +#: src/client/accounts/accounts-editor-row.vala:533 ui/main-window.ui:455 +msgid "Login" +msgstr "Giriş" + +#. Translators: ComboBox value for source of SMTP +#. authentication credentials (none) when adding a new +#. account +#: src/client/accounts/accounts-editor-row.vala:540 +msgid "No login needed" +msgstr "Giriş gerekmiyor" + +#. Translators: ComboBox value for source of SMTP +#. authentication credentials (use IMAP) when adding a new +#. account +#: src/client/accounts/accounts-editor-row.vala:548 +msgid "Use same login as receiving" +msgstr "Alıcıyla aynı girişi kullan" + +#. Translators: ComboBox value for source of SMTP +#. authentication credentials (custom) when adding a new +#. account +#: src/client/accounts/accounts-editor-row.vala:556 +msgid "Use a different login" +msgstr "Başka giriş kullan" + +#. Translators: In-app notification label, the +#. string substitution is a more detailed reason. +#: src/client/accounts/accounts-editor-servers-pane.vala:377 +#, c-format +msgid "Account not updated: %s" +msgstr "Hesap güncellenmedi: %s" + +#. Translators: This label describes the program that +#. created the account, e.g. an SSO service like GOA, or +#. locally by Geary. +#: src/client/accounts/accounts-editor-servers-pane.vala:540 +msgid "Account source" +msgstr "Hesap kaynağı" + +#: src/client/accounts/accounts-editor-servers-pane.vala:552 +msgid "GNOME Online Accounts" +msgstr "GNOME Çevrim İçi Hesaplar" + +#. Translators: This label describes an account +#. preference. +#: src/client/accounts/accounts-editor-servers-pane.vala:611 +msgid "Save drafts on server" +msgstr "Taslakları sunucuya kaydet" + +#. Translators: This label describes an account +#. preference. +#: src/client/accounts/accounts-editor-servers-pane.vala:666 +msgid "Save sent email on server" +msgstr "Gönderilmiş postayı sunucuya kaydet" + +#. Add a suffix for OAuth2 auth so people know they +#. shouldn't expect to be prompted for a password +#. Translators: Label used when an account's IMAP or +#. SMTP service uses OAuth2. The string replacement is +#. the service's login name. +#: src/client/accounts/accounts-editor-servers-pane.vala:950 +#, c-format +msgid "%s using OAuth2" +msgstr "%s OAuth2 kullanıyor" + +#: src/client/accounts/accounts-editor-servers-pane.vala:960 +msgid "Use receiving server login" +msgstr "Alıcı sunucu girişini kullan" -#: ../src/client/application/geary-application.vala:21 +#: src/client/application/geary-application.vala:24 msgid "Copyright 2016 Software Freedom Conservancy Inc." msgstr "Telif Hakkı 2016 Software Freedom Conservancy Inc." -#: ../src/client/application/geary-application.vala:24 +#: src/client/application/geary-application.vala:25 +msgid "Copyright 2016-2019 Geary Development Team." +msgstr "Telif Hakkı 2016-2019 Geary Geliştirme Takımı." + +#: src/client/application/geary-application.vala:27 msgid "Visit the Geary web site" msgstr "Geary web sitesini ziyaret et" -#: ../src/client/application/geary-application.vala:464 +#: src/client/application/geary-application.vala:454 #, c-format msgid "About %s" msgstr "%s hakkında" @@ -284,7 +789,7 @@ #. Translators: add your name and email address to receive #. credit in the About dialog For example: Yamada Taro #. -#: ../src/client/application/geary-application.vala:468 +#: src/client/application/geary-application.vala:458 msgid "translator-credits" msgstr "" "Ferhat TUNÇTAN \n" @@ -292,312 +797,107 @@ "Muhammet Kara \n" "Emin Tufan Çetin " -#: ../src/client/application/geary-args.vala:10 +#: src/client/application/geary-args.vala:10 msgid "Start Geary with hidden main window" msgstr "Geary’i ana pencere gizlenmiş olarak başlat" -#: ../src/client/application/geary-args.vala:11 +#: src/client/application/geary-args.vala:11 msgid "Output debugging information" msgstr "Çıktı hata ayıklama bilgisi" -#: ../src/client/application/geary-args.vala:12 +#: src/client/application/geary-args.vala:12 msgid "Log conversation monitoring" msgstr "Konuşma gözetimini kayda al" -#: ../src/client/application/geary-args.vala:13 +#: src/client/application/geary-args.vala:13 msgid "Log network deserialization" msgstr "Ağ paralelleştirilmesini kayda al" -#: ../src/client/application/geary-args.vala:14 +#: src/client/application/geary-args.vala:14 msgid "Log network activity" msgstr "Ağ etkinliğini kayda al" #. / The IMAP replay queue is how changes on the server are replicated on the client. #. / It could also be called the IMAP events queue. -#: ../src/client/application/geary-args.vala:17 +#: src/client/application/geary-args.vala:17 msgid "Log IMAP replay queue" msgstr "IMAP tekrar sırasını kayda al" #. / Serialization is how commands and responses are converted into a stream of bytes for #. / network transmission -#: ../src/client/application/geary-args.vala:20 +#: src/client/application/geary-args.vala:20 msgid "Log network serialization" msgstr "Ağ serileştirmeyi kayda al" -#: ../src/client/application/geary-args.vala:21 +#: src/client/application/geary-args.vala:21 msgid "Log periodic activity" msgstr "Dönemsel etkinliği kayda al" -#: ../src/client/application/geary-args.vala:22 +#: src/client/application/geary-args.vala:22 msgid "Log database queries (generates lots of messages)" msgstr "Veri tabanı sorgularını kayda al (birçok ileti oluşturur)" #. / "Normalization" can also be called "synchronization" -#: ../src/client/application/geary-args.vala:24 +#: src/client/application/geary-args.vala:24 msgid "Log folder normalization" msgstr "Klasör düzgelemeyi kayda al" -#: ../src/client/application/geary-args.vala:25 +#: src/client/application/geary-args.vala:25 msgid "Allow inspection of WebView" msgstr "WebView denetimine izin ver" -#: ../src/client/application/geary-args.vala:26 +#: src/client/application/geary-args.vala:26 msgid "Revoke all server certificates with TLS warnings" msgstr "TLS uyarılı sunucu sertifikalarının tümünü iptal et" -#: ../src/client/application/geary-args.vala:27 +#: src/client/application/geary-args.vala:27 msgid "Perform a graceful quit" msgstr "Hoş bir çıkış gerçekleştir" -#: ../src/client/application/geary-args.vala:28 +#: src/client/application/geary-args.vala:28 msgid "Display program version" msgstr "Uygulama sürümünü göster" #. This gives a command-line hint on how to open new composer windows with mailto: -#: ../src/client/application/geary-args.vala:53 +#: src/client/application/geary-args.vala:53 #, c-format msgid "Use %s to open a new composer window" msgstr "Yeni bir oluşturucu pencere açmak için %s kullan" -#: ../src/client/application/geary-args.vala:54 +#: src/client/application/geary-args.vala:56 msgid "Please report comments, suggestions and bugs to:" msgstr "Lütfen yorumlarınızı, önerilerinizi ve hataları bildirin:" #. i18n: Command line arguments are invalid -#: ../src/client/application/geary-args.vala:61 +#: src/client/application/geary-args.vala:63 #, c-format msgid "Failed to parse command line options: %s\n" msgstr "Komut satırı seçenekleri ayrıştırılamadı: %s\n" -#: ../src/client/application/geary-args.vala:72 +#: src/client/application/geary-args.vala:74 #, c-format msgid "Unrecognized command line option “%s”\n" msgstr "Tanınmayan komut satırı seçeneği “%s”\n" -#: ../src/client/application/geary-controller.vala:57 -msgid "Delete conversation" -msgstr "Konuşmayı sil" - -#: ../src/client/application/geary-controller.vala:58 -msgid "Delete conversation (Shift+Delete)" -msgstr "Konuşmayı sil (Shift+Delete)" - -#: ../src/client/application/geary-controller.vala:59 -msgid "Delete conversations (Shift+Delete)" -msgstr "Konuşmaları sil (Shift+Delete)" - -#. This refers to the action ("move email to the trash"), not the Trash folder itself -#: ../src/client/application/geary-controller.vala:63 -msgid "Move conversation to Trash (Delete, Backspace)" -msgstr "Konuşmayı çöpe taşı (Delete, Backspace)" - -#: ../src/client/application/geary-controller.vala:64 -msgid "Move conversations to Trash (Delete, Backspace)" -msgstr "Konuşmaları çöpe taşı (Delete, Backspace, A)" - -#. This refers to the action ("archive an email"), not the Archive folder itself -#: ../src/client/application/geary-controller.vala:68 -msgid "_Archive" -msgstr "_Arşivle" - -#: ../src/client/application/geary-controller.vala:69 -msgid "Archive conversation (A)" -msgstr "Konuşmayı arşivle (A)" - -#: ../src/client/application/geary-controller.vala:70 -msgid "Archive conversations (A)" -msgstr "Konuşmaları arşivle (A)" - -#: ../src/client/application/geary-controller.vala:73 -msgid "Mark as S_pam" -msgstr "İ_stenmeyen olarak imle" - -#: ../src/client/application/geary-controller.vala:74 -msgid "Mark as not S_pam" -msgstr "İ_stenen olarak imle" - -#: ../src/client/application/geary-controller.vala:76 -#: ../src/client/application/geary-controller.vala:448 -msgid "Mark conversation" -msgstr "Konuşmayı imle" - -#: ../src/client/application/geary-controller.vala:77 -msgid "Mark conversations" -msgstr "Konuşmaları imle" - -#: ../src/client/application/geary-controller.vala:78 -msgid "Add label to conversation" -msgstr "Konuşmayı etiketle" - -#: ../src/client/application/geary-controller.vala:79 -msgid "Add label to conversations" -msgstr "Konuşmaları etiketle" - -#: ../src/client/application/geary-controller.vala:80 -#: ../src/client/application/geary-controller.vala:487 -msgid "Move conversation" -msgstr "Daha çok konuşma" - -#: ../src/client/application/geary-controller.vala:81 -msgid "Move conversations" -msgstr "Daha çok konuşma" - -#: ../src/client/application/geary-controller.vala:450 -msgid "_Mark as…" -msgstr "…olarak _imle" - -#: ../src/client/application/geary-controller.vala:456 -msgid "Mark as _Read" -msgstr "_Okundu olarak imle" - -#: ../src/client/application/geary-controller.vala:462 -msgid "Mark as _Unread" -msgstr "Ok_unmamış olarak imle" - -#: ../src/client/application/geary-controller.vala:468 -msgid "_Star" -msgstr "_Yıldızla" - -#: ../src/client/application/geary-controller.vala:473 -msgid "U_nstar" -msgstr "Y_ıldızı kaldır" - -#: ../src/client/application/geary-controller.vala:483 -msgid "Add label" -msgstr "Etiket ekle" - -#: ../src/client/application/geary-controller.vala:484 -msgid "_Label" -msgstr "_Etiketle" - -#: ../src/client/application/geary-controller.vala:488 -msgid "_Move" -msgstr "_Taşı" - -#: ../src/client/application/geary-controller.vala:492 -msgid "Compose new message (Ctrl+N, N)" -msgstr "Yeni ileti oluştur (Ctrl+N, N)" - -#: ../src/client/application/geary-controller.vala:496 -#: ../ui/conversation-email-menus.ui.h:1 -msgid "_Reply" -msgstr "_Yanıtla" - -#: ../src/client/application/geary-controller.vala:497 -msgid "Reply (Ctrl+R, R)" -msgstr "Yanıtla (Ctrl+R, R)" - -#: ../src/client/application/geary-controller.vala:501 -msgid "R_eply All" -msgstr "Tümüne _Yanıtla" - -#: ../src/client/application/geary-controller.vala:502 -msgid "Reply all (Ctrl+Shift+R, Shift+R)" -msgstr "Tümünü yanıtla (Ctrl+Shift+R, Shift+R)" - -#: ../src/client/application/geary-controller.vala:507 -#: ../ui/conversation-email-menus.ui.h:3 -msgid "_Forward" -msgstr "_Yönlendir" - -#: ../src/client/application/geary-controller.vala:508 -msgid "Forward (Ctrl+L, F)" -msgstr "Yönlendir (Ctrl+L, F)" - -#: ../src/client/application/geary-controller.vala:538 -msgid "Empty _Spam…" -msgstr "_İstenmeyeni boşalt…" - -#: ../src/client/application/geary-controller.vala:542 -msgid "Empty _Trash…" -msgstr "_Çöpü boşalt…" - -#. No callback is connected, since we bind the toggle button to the search bar visibility -#: ../src/client/application/geary-controller.vala:574 -msgid "Toggle search bar" -msgstr "Arama çubuğunu aç" - -#. No callback is connected, since we bind the toggle button to the find bar visibility -#: ../src/client/application/geary-controller.vala:579 -msgid "Toggle find bar" -msgstr "Bulma çubuğunu aç" - -#: ../src/client/application/geary-controller.vala:757 -msgid "Unable to store server trust exception" -msgstr "Sunucu güven ayrıcalığı depolanamadı" - -#: ../src/client/application/geary-controller.vala:992 -msgid "Your settings are insecure" -msgstr "Ayarlarınız güvensiz" - -#: ../src/client/application/geary-controller.vala:993 -msgid "" -"Your IMAP and/or SMTP settings do not specify SSL or TLS. This means your " -"username and password could be read by another person on the network. Are " -"you sure you want to do this?" -msgstr "" -"IMAP ve/veya SMTP ayarlarınız SSL veya TLS belirtmiyor. Bunun anlamı " -"kullanıcı adınız ve parolanız ağdaki başka biri tarafından okunabilir " -"demektir. Bunu yapmak istediğinize emin misiniz?" - -#: ../src/client/application/geary-controller.vala:994 -msgid "Co_ntinue" -msgstr "Devam _et" - -#: ../src/client/application/geary-controller.vala:1041 -msgid "Error connecting to the server" -msgstr "Sunucuya bağlanma hatası" - -#: ../src/client/application/geary-controller.vala:1042 -msgid "" -"Geary encountered an error while connecting to the server. Please try again " -"in a few moments." -msgstr "" -"Geary, sunucuya bağlanırken bir hatayla karşılaştı. Lütfen bir süre sonra " -"yeniden deneyin." - -#. / Displayed in the space-limited status bar when a message fails to be sent due to error. -#: ../src/client/application/geary-controller.vala:1079 -#: ../src/client/components/status-bar.vala:29 -msgid "Error sending email" -msgstr "E-posta gönderme hatası" - -#: ../src/client/application/geary-controller.vala:1080 -msgid "" -"Geary encountered an error sending an email. If the problem persists, " -"please manually delete the email from your Outbox folder." -msgstr "" -"Geary e-posta gönderilirken bir hata ile karşılaştı. Eğer sorun devam " -"ederse, lütfen e-postayı Giden klasöründen elle siliniz." - -#. Displayed in the space-limited status bar when a message fails to be uploaded -#. to Sent Mail after being sent. -#: ../src/client/application/geary-controller.vala:1084 -#: ../src/client/components/status-bar.vala:33 -msgid "Error saving sent mail" -msgstr "Gönderilmiş posta kaydedilirken hata" - -#: ../src/client/application/geary-controller.vala:1085 -msgid "" -"Geary encountered an error saving a sent message to Sent Mail. The message " -"will stay in your Outbox folder until you delete it." -msgstr "" -"Geary, Gönderilmiş Postaya gönderilmiş iletiyi kaydederken bir hata ile " -"karşılaştı. İleti, siz silene kadar Giden klasörünüzde kalacak." +#. Translators: File name used in save chooser when saving +#. attachments that do not otherwise have a name. +#: src/client/application/geary-controller.vala:58 +msgid "Untitled" +msgstr "Başlıksız" -#: ../src/client/application/geary-controller.vala:1154 +#: src/client/application/geary-controller.vala:919 msgid "Labels" msgstr "Etiketler" #. give the user two options: reset the Account local store, or exit Geary. A third #. could be done to leave the Account in an unopened state, but we don't currently #. have provisions for that. -#: ../src/client/application/geary-controller.vala:1166 +#: src/client/application/geary-controller.vala:932 #, c-format msgid "Unable to open the database for %s" msgstr "%s için veri tabanı açılamadı" -#: ../src/client/application/geary-controller.vala:1167 +#: src/client/application/geary-controller.vala:933 #, c-format msgid "" "There was an error opening the local mail database for this account. This is " @@ -621,20 +921,20 @@ "Veri tabanının yeniden inşa edilmesi tüm yerel e-postaları ve eklerini yok " "edecektir.Sizin sunucunuzdaki postalar etkilenmeyecektir." -#: ../src/client/application/geary-controller.vala:1169 +#: src/client/application/geary-controller.vala:935 msgid "_Rebuild" msgstr "_Yeniden oluştur" -#: ../src/client/application/geary-controller.vala:1169 +#: src/client/application/geary-controller.vala:935 msgid "E_xit" msgstr "Ç_ık" -#: ../src/client/application/geary-controller.vala:1178 +#: src/client/application/geary-controller.vala:944 #, c-format msgid "Unable to rebuild database for “%s”" msgstr "“%s” için veri tabanı yeniden oluşturulamadı" -#: ../src/client/application/geary-controller.vala:1179 +#: src/client/application/geary-controller.vala:945 #, c-format msgid "" "Error during rebuild:\n" @@ -645,69 +945,15 @@ "\n" "%s" -#. some other problem opening the account ... as with other flow path, can't run -#. Geary today with an account in unopened state, so have to exit -#: ../src/client/application/geary-controller.vala:1201 -#: ../src/client/application/geary-controller.vala:1211 -#: ../src/client/application/geary-controller.vala:1222 -#, c-format -msgid "Unable to open local mailbox for %s" -msgstr "%s için yerel posta kutusu açılamadı" - -#: ../src/client/application/geary-controller.vala:1202 -#, c-format -msgid "" -"There was an error opening the local mail database for this account. This is " -"possibly due to a file permissions problem.\n" -"\n" -"Please check that you have read/write permissions for all files in this " -"directory:\n" -"\n" -"%s" -msgstr "" -"Bu hesap için yerel posta veri tabanı açılırken bir hata oluştu. Hata " -"görünüşe bakılırsa dosya izin sorunlarından kaynaklanmaktadır.\n" -"\n" -"Lütfen bu dizindeki tüm dosyalar için okuma/yazma izniniz olup olmadığınızı " -"denetleyin:\n" -"\n" -"%s" - -#: ../src/client/application/geary-controller.vala:1212 -msgid "" -"The version number of the local mail database is formatted for a newer " -"version of Geary. Unfortunately, the database cannot be “rolled back” to " -"work with this version of Geary.\n" -"\n" -"Please install the latest version of Geary and try again." -msgstr "" -"Yerel posta veri tabanı sürüm numarası daha yeni bir Geary sürümü için " -"biçimlendirildi. Ne yazık ki; veri tabanı, Geary uygulamasının bu sürümü ile " -"çalışabilmesi için “geri alınır” durumda değildir.\n" -"\n" -"Lütfen Geary uygulamasının son sürümünü kurun ve yeniden deneyin." - -#: ../src/client/application/geary-controller.vala:1223 -msgid "" -"There was an error opening the local account. This is probably due to " -"connectivity issues.\n" -"\n" -"Please check your network connection and restart Geary." -msgstr "" -"Yerel hesap açılırken bir hata oluştu. Hata görünüşe bakılırsa bağlantı " -"sorunları nedeniyle oluştu.\n" -"\n" -"Lütfen ağ bağlantılarınızı denetleyin ve Geary uygulamasını yeniden başlatın." - -#: ../src/client/application/geary-controller.vala:1999 +#: src/client/application/geary-controller.vala:1800 msgid "Undo move (Ctrl+Z)" msgstr "Taşımayı geri al (Ctrl+Z)" -#: ../src/client/application/geary-controller.vala:2009 +#: src/client/application/geary-controller.vala:1810 msgid "Are you sure you want to open these attachments?" msgstr "Bu ekleri açmak istediğinize emin misiniz?" -#: ../src/client/application/geary-controller.vala:2010 +#: src/client/application/geary-controller.vala:1811 msgid "" "Attachments may cause damage to your system if opened. Only open files from " "trusted sources." @@ -715,16 +961,22 @@ "Eklerin açılması sisteminizin zarar görmesine neden olabilir. Yalnızca " "güvendiğiniz kaynaklardan gelen dosyaları açın." -#: ../src/client/application/geary-controller.vala:2011 +#: src/client/application/geary-controller.vala:1812 msgid "Don’t _ask me again" msgstr "Yeniden _sorma" -#: ../src/client/application/geary-controller.vala:2121 +#. Translators: Dialog primary label when prompting to +#. overwrite a file. The string substitution is the file'sx +#. name. +#: src/client/application/geary-controller.vala:1941 #, c-format msgid "A file named “%s” already exists. Do you want to replace it?" -msgstr "“%s” adlı bir dosya zaten var. Değiştirmek istiyor musunuz?" +msgstr "“%s” adlı dosya zaten var. Değiştirmek istiyor musunuz?" -#: ../src/client/application/geary-controller.vala:2123 +#. Translators: Dialog secondary label when prompting to +#. overwrite a file. The string substitution is the parent +#. folder's name. +#: src/client/application/geary-controller.vala:1948 #, c-format msgid "" "The file already exists in “%s”. Replacing it will overwrite its contents." @@ -732,182 +984,451 @@ "Dosya “%s”’in içinde zaten var. Bunu var olanla değiştirmek, içeriğini " "üzerine yazacak." -#: ../src/client/application/geary-controller.vala:2126 +#: src/client/application/geary-controller.vala:1952 msgid "_Replace" msgstr "_Değiştir" -#. Find out what to do with the inline composers. -#. TODO: Remove this in favor of automatically saving drafts -#: ../src/client/application/geary-controller.vala:2374 -msgid "Close open draft messages?" -msgstr "Açık taslak iletiler kapatılsın mı?" +#: src/client/application/geary-controller.vala:2228 +msgid "Close the draft message?" +msgid_plural "Close all draft messages?" +msgstr[0] "Tüm taslak iletiyi kapat?" -#: ../src/client/application/geary-controller.vala:2496 +#: src/client/application/geary-controller.vala:2354 #, c-format msgid "Empty all email from your %s folder?" msgstr "%s klasörünüzdeki tüm e-postaları boşalt?" -#: ../src/client/application/geary-controller.vala:2497 +#: src/client/application/geary-controller.vala:2355 msgid "This removes the email from Geary and your email server." msgstr "Bu işlem e-postayı Geary’den ve e-posta sunucunuzdan kaldırır." -#: ../src/client/application/geary-controller.vala:2498 +#: src/client/application/geary-controller.vala:2356 msgid "This cannot be undone." msgstr "Bu geri alınamaz." -#: ../src/client/application/geary-controller.vala:2499 +#: src/client/application/geary-controller.vala:2357 #, c-format msgid "Empty %s" msgstr "%s boşalt" -#: ../src/client/application/geary-controller.vala:2516 +#: src/client/application/geary-controller.vala:2374 #, c-format msgid "Error emptying %s" msgstr "%s boşaltılırken hata" -#: ../src/client/application/geary-controller.vala:2546 +#: src/client/application/geary-controller.vala:2406 msgid "Do you want to permanently delete this message?" msgid_plural "Do you want to permanently delete these messages?" msgstr[0] "Bu ileti(ler)i kalıcı olarak silmek istiyor musunuz?" -#: ../src/client/application/geary-controller.vala:2548 +#: src/client/application/geary-controller.vala:2408 msgid "Delete" msgstr "Sil" -#: ../src/client/application/geary-controller.vala:2580 -msgid "Undo archive (Ctrl+Z)" -msgstr "Arşivden geri al (Ctrl+Z)" - -#: ../src/client/application/geary-controller.vala:2595 +#: src/client/application/geary-controller.vala:2422 msgid "Undo trash (Ctrl+Z)" msgstr "Çöpten geri al (Ctrl+Z)" -#: ../src/client/application/geary-controller.vala:2649 +#: src/client/application/geary-controller.vala:2472 +msgid "Undo archive (Ctrl+Z)" +msgstr "Arşivden geri al (Ctrl+Z)" + +#: src/client/application/geary-controller.vala:2517 msgid "Undo (Ctrl+Z)" msgstr "Geri al (Ctrl+Z)" -#: ../src/client/application/geary-controller.vala:2780 +#. Translators: The label for an in-app notification. The +#. string substitution is a list of recipients of the email. +#: src/client/application/geary-controller.vala:2598 +#, c-format +msgid "Successfully sent mail to %s." +msgstr "Posta, %s adresine başarıyla gönderildi." + +#: src/client/application/geary-controller.vala:2680 msgid "Failed to open default text editor." msgstr "Öntanımlı metin düzenleyici açılamadı." -#: ../src/client/components/main-window.vala:389 +#. Translators: Tooltip used when an entry requires a valid +#. email address to be entered, but one is not provided. +#: src/client/components/components-validator.vala:378 +msgid "An email address is required" +msgstr "E-posta adresi gerekli" + +#. Translators: Tooltip used when an entry requires a valid +#. email address to be entered, but the address is invalid. +#: src/client/components/components-validator.vala:382 +msgid "Not a valid email address" +msgstr "Geçersiz e-posta adresi" + +#. Translators: Tooltip used when an entry requires a valid, +#. resolvable server name to be entered, but one is not +#. provided. +#: src/client/components/components-validator.vala:428 +msgid "A server name is required" +msgstr "Sunucu adı gerekli" + +#. Translators: Tooltip used when an entry requires a valid +#. server name to be entered, but it was unable to be +#. looked-up in the DNS. +#: src/client/components/components-validator.vala:433 +msgid "Could not look up server name" +msgstr "Sunucu adı yoklanamıyor" + +#: src/client/components/main-toolbar.vala:151 +msgid "Mark conversation" +msgid_plural "Mark conversations" +msgstr[0] "Konuşmayı imle" + +#: src/client/components/main-toolbar.vala:156 +msgid "Add label to conversation" +msgid_plural "Add label to conversations" +msgstr[0] "Konuşmayı etiketle" + +#: src/client/components/main-toolbar.vala:161 +msgid "Move conversation" +msgid_plural "Move conversations" +msgstr[0] "Konuşmayı taşı" + +#: src/client/components/main-toolbar.vala:166 +msgid "Archive conversation (A)" +msgid_plural "Archive conversations (A)" +msgstr[0] "Konuşmayı arşivle (A)" + +#: src/client/components/main-toolbar.vala:175 +msgid "Move conversation to Trash (Delete, Backspace)" +msgid_plural "Move conversations to Trash (Delete, Backspace)" +msgstr[0] "Konuşmayı çöpe taşı (Del tuşu, Silme Tuşu)" + +#: src/client/components/main-toolbar.vala:183 +msgid "Delete conversation (Shift+Delete)" +msgid_plural "Delete conversations (Shift+Delete)" +msgstr[0] "Konuşmayı sil (Shift+Delete)" + +#: src/client/components/main-window.vala:503 #, c-format msgid "%s (%d)" msgstr "%s (%d)" -#: ../src/client/components/search-bar.vala:8 -#: ../src/client/folder-list/folder-list-search-branch.vala:38 -#: ../src/engine/api/geary-special-folder-type.vala:51 +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:47 +#, c-format +msgid "Problem connecting to incoming server for %s" +msgstr "%s için gelen sunucusuna bağlanmada sorun" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:49 +#: src/client/components/main-window-info-bar.vala:58 +#, c-format +msgid "" +"Could not connect to %s, check your Internet access and the server name and " +"try again" +msgstr "" +"%s konumuna bağlanılamadı, lütfen İnternet erişiminizi ve sunucu adını " +"denetleyin ve yeniden deneyin" + +#. Translators: Tooltip label for Retry button +#. Button tooltip for retrying an account problem +#: src/client/components/main-window-info-bar.vala:51 +#: src/client/components/main-window-info-bar.vala:60 +#: src/client/components/main-window-info-bar.vala:69 +#: src/client/components/main-window-info-bar.vala:78 +#: src/client/components/main-window-info-bar.vala:87 +#: src/client/components/main-window-info-bar.vala:96 +#: src/client/components/main-window-info-bar.vala:136 ui/main-window.ui:265 +msgid "Try reconnecting" +msgstr "Yeniden bağlanmayı dene" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:56 +#, c-format +msgid "Problem connecting to outgoing server for %s" +msgstr "%s için giden sunucusuna bağlanmada sorun" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:65 +#: src/client/components/main-window-info-bar.vala:83 +#, c-format +msgid "Problem communicating with incoming server for %s" +msgstr "%s için gelen sunucusuyla iletişime geçmede sorun" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:67 +#: src/client/components/main-window-info-bar.vala:76 +#, c-format +msgid "Network error talking to %s, check your Internet access and try again" +msgstr "" +"%s ile konuşurken ağ hatası, İnternet erişiminizi gözden geçirin ve yeniden " +"deneyin" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:74 +#: src/client/components/main-window-info-bar.vala:91 +msgid "Problem communicating with outgoing mail server" +msgstr "%s için giden posta sunucusuyla iletişime geçmede sorun" + +#. Translators: String substitution is the server name +#: src/client/components/main-window-info-bar.vala:85 +#, c-format +msgid "" +"Geary did not understand a message from %s or vice versa, please file a bug " +"report" +msgstr "" +"Geary %s konumundan gelen iletiyi anlamadı veya tam tersi, lütfen hata " +"bildiriminde bulunun" + +#. Translators: First string substitution is the server +#. name, second is the account name +#: src/client/components/main-window-info-bar.vala:94 +#, c-format +msgid "" +"Could not communicate with %s for %s, check the server name and try again in " +"a moment" +msgstr "" +"%2$s için %1$s ile iletişim kurulamadı, sunucu adını gözden geçirin ve hemen " +"yeniden deneyin" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:101 +#, c-format +msgid "Incoming mail server password required for %s" +msgstr "%s için gelen posta sunucusu parolası gerekiyor" + +#: src/client/components/main-window-info-bar.vala:102 +msgid "Messages cannot be received without the correct password." +msgstr "İletiler doğru parola olmadan alınamaz." + +#. Translators: Tooltip label for Retry button +#: src/client/components/main-window-info-bar.vala:104 +msgid "Retry receiving email, you will be prompted for a password" +msgstr "E-posta almayı yeniden dene, parola girmeniz gerekecek" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:109 +#, c-format +msgid "Outgoing mail server password required for %s" +msgstr "%s için giden posta sunucusu parolası gerekiyor" + +#: src/client/components/main-window-info-bar.vala:110 +msgid "Messages cannot be sent without the correct password." +msgstr "İletiler doğru parola olmadan gönderilemez." + +#. Translators: Tooltip label for Retry button +#: src/client/components/main-window-info-bar.vala:112 +msgid "Retry sending queued messages, you will be prompted for a password" +msgstr "" +"Kuyruğa alınmış iletileri göndermeyi yeniden dene, parola girmeniz gerekecek" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:117 +#, c-format +msgid "Incoming mail server security is not trusted for %s" +msgstr "%s için gelen posta sunucusu güvenliği güvenilir değil" + +#: src/client/components/main-window-info-bar.vala:118 +msgid "Messages will not be received until checked." +msgstr "Gözden geçirilmeden iletiler alınmayacak." + +#. Translators: Tooltip label for Retry button +#: src/client/components/main-window-info-bar.vala:120 +#: src/client/components/main-window-info-bar.vala:128 +msgid "Check security details" +msgstr "Güvenlik ayrıntılarını gözden geçirin" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:125 +#, c-format +msgid "Outgoing mail server security is not trusted for %s" +msgstr "%s için giden posta sunucusu güvenli değil" + +#: src/client/components/main-window-info-bar.vala:126 +msgid "Messages cannot be sent until checked." +msgstr "Gözden geçirilmeden iletiler gönderilemez." + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:133 +#, c-format +msgid "A problem occurred checking mail for %s" +msgstr "%s için posta denetlenirken sorun oluştu" + +#: src/client/components/main-window-info-bar.vala:134 +#: src/client/components/main-window-info-bar.vala:142 +msgid "Something went wrong, please file a bug report if the problem persists" +msgstr "" +"Bir şeyler yanlış gitti, eğer sorun kalıcıysa lütfen hata bildiriminde " +"bulunun" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:141 +#, c-format +msgid "A problem occurred sending mail for %s" +msgstr "%s için posta gönderilirken sorun oluştu" + +#. Translators: Tooltip label for Retry button +#: src/client/components/main-window-info-bar.vala:144 +msgid "Retry sending queued messages" +msgstr "Kuyruğa alınmış iletileri göndermeyi yeniden dene" + +#: src/client/components/main-window-info-bar.vala:155 +msgid "A database problem has occurred" +msgstr "Veri tabanı sorunu oluştu" + +#. Translators: String substitution is the account name +#: src/client/components/main-window-info-bar.vala:157 +#, c-format +msgid "Messages for %s must be downloaded again." +msgstr "%s için iletiler yeniden indirilmelidir." + +#: src/client/components/main-window-info-bar.vala:170 +msgid "Geary has encountered a problem" +msgstr "Geary sorunla karşılaştı" + +#: src/client/components/main-window-info-bar.vala:171 +msgid "" +"Please check the technical details and report the problem if it persists." +msgstr "" +"Lütfen teknik ayrıntıları gözden geçirin ve eğer sorun kalıcıysa bildirin." + +#: src/client/components/main-window-info-bar.vala:179 +msgid "_Details" +msgstr "_Ayrıntılar" + +#. Button tooltip for displaying technical details about an account problem +#: src/client/components/main-window-info-bar.vala:180 ui/main-window.ui:250 +msgid "View technical details about the error" +msgstr "Hatayla ilgili teknik ayrıntıları gör" + +#: src/client/components/main-window-info-bar.vala:184 +msgid "_Retry" +msgstr "_Yeniden dene" + +#: src/client/components/search-bar.vala:8 +#: src/client/folder-list/folder-list-search-branch.vala:38 +#: src/engine/api/geary-special-folder-type.vala:51 msgid "Search" msgstr "Ara" #. Search entry. -#: ../src/client/components/search-bar.vala:23 +#: src/client/components/search-bar.vala:23 msgid "Search all mail in account for keywords (Ctrl+S)" msgstr "Anahtar sözcükler için hesaptaki tüm postaları ara (Ctrl+S)" -#: ../src/client/components/search-bar.vala:100 +#: src/client/components/search-bar.vala:101 #, c-format msgid "Indexing %s account" msgstr "%s hesabı dizinleniyor" -#: ../src/client/components/search-bar.vala:111 -#: ../src/client/folder-list/folder-list-search-branch.vala:39 +#: src/client/components/search-bar.vala:112 +#: src/client/folder-list/folder-list-search-branch.vala:39 #, c-format msgid "Search %s account" msgstr "%s hesabını ara" #. / Displayed in the space-limited status bar while a message is in the process of being sent. -#: ../src/client/components/status-bar.vala:26 +#: src/client/components/status-bar.vala:26 msgid "Sending…" msgstr "Gönderiliyor…" -#: ../src/client/components/stock.vala:18 ../ui/account_cannot_remove.glade.h:3 +#. / Displayed in the space-limited status bar when a message fails to be sent due to error. +#: src/client/components/status-bar.vala:29 +msgid "Error sending email" +msgstr "E-posta gönderme hatası" + +#. Displayed in the space-limited status bar when a message fails to be uploaded +#. to Sent Mail after being sent. +#: src/client/components/status-bar.vala:33 +msgid "Error saving sent mail" +msgstr "Gönderilmiş posta kaydedilirken hata" + +#: src/client/components/stock.vala:18 msgid "_OK" msgstr "_Tamam" -#: ../src/client/components/stock.vala:19 ../ui/edit_alternate_emails.glade.h:3 -#: ../ui/password-dialog.glade.h:5 ../ui/remove_confirm.glade.h:5 +#: src/client/components/stock.vala:19 ui/password-dialog.glade:196 msgid "_Cancel" msgstr "_İptal et" -#: ../src/client/components/stock.vala:21 ../ui/gtk/menus.ui.h:6 +#: src/client/components/stock.vala:21 msgid "_About" msgstr "_Hakkında" -#: ../src/client/components/stock.vala:23 +#: src/client/components/stock.vala:22 +msgid "_Add" +msgstr "_Ekle" + +#: src/client/components/stock.vala:23 msgid "_Close" msgstr "_Kapat" -#: ../src/client/components/stock.vala:24 +#: src/client/components/stock.vala:24 msgid "_Discard" -msgstr "_At" +msgstr "_Gözden Çıkar" -#: ../src/client/components/stock.vala:25 ../ui/gtk/menus.ui.h:5 +#: src/client/components/stock.vala:25 ui/main-toolbar-menus.ui:56 msgid "_Help" msgstr "_Yardım" -#: ../src/client/components/stock.vala:26 ../ui/conversation-email-menus.ui.h:9 +#: src/client/components/stock.vala:26 ui/conversation-email-menus.ui:77 msgid "_Open" msgstr "_Aç" -#: ../src/client/components/stock.vala:27 ../ui/gtk/menus.ui.h:3 +#: src/client/components/stock.vala:27 ui/main-toolbar-menus.ui:46 msgid "_Preferences" msgstr "_Tercihler" -#: ../src/client/components/stock.vala:28 ../ui/conversation-email-menus.ui.h:7 +#. Translators: Menu item to print a single, specific message +#: src/client/components/stock.vala:28 ui/conversation-email-menus.ui:64 msgid "_Print…" msgstr "_Yazdır…" -#: ../src/client/components/stock.vala:29 ../ui/gtk/menus.ui.h:7 +#: src/client/components/stock.vala:29 msgid "_Quit" msgstr "_Çık" -#: ../src/client/components/stock.vala:30 ../ui/remove_confirm.glade.h:6 +#: src/client/components/stock.vala:30 msgid "_Remove" msgstr "_Kaldır" -#: ../src/client/components/stock.vala:32 +#: src/client/components/stock.vala:31 ui/conversation-email-menus.ui:83 +msgid "_Save" +msgstr "_Kaydet" + +#: src/client/components/stock.vala:32 msgid "_Keep" msgstr "_Sakla" -#: ../src/client/composer/composer-link-popover.vala:150 +#: src/client/composer/composer-link-popover.vala:149 msgid "Link URL is not correctly formatted, e.g. http://example.com" msgstr "Bağlantı URL’si doğru biçimlendirilmemiş, örn. http://ornek.com" -#: ../src/client/composer/composer-link-popover.vala:157 +#: src/client/composer/composer-link-popover.vala:156 msgid "Invalid link URL" msgstr "Geçersiz bağlantı URL’si" -#: ../src/client/composer/composer-link-popover.vala:157 +#: src/client/composer/composer-link-popover.vala:156 msgid "Invalid email address" msgstr "Geçersiz e-posta adresi" -#: ../src/client/composer/composer-widget.vala:154 +#: src/client/composer/composer-widget.vala:158 msgid "Saved" msgstr "Kaydedildi" -#: ../src/client/composer/composer-widget.vala:155 +#: src/client/composer/composer-widget.vala:159 msgid "Saving" msgstr "Kaydediliyor" -#: ../src/client/composer/composer-widget.vala:156 +#: src/client/composer/composer-widget.vala:160 msgid "Error saving" msgstr "Kaydedilirken hata" -#: ../src/client/composer/composer-widget.vala:157 +#: src/client/composer/composer-widget.vala:161 msgid "Press Backspace to delete quote" msgstr "Alıntıyı silmek için Geri tuşuna basın" -#: ../src/client/composer/composer-widget.vala:158 -msgid "New Message" -msgstr "Yeni İleti" - #. Translators: This is list of keywords, separated by pipe ("|") #. characters, that suggest an attachment; since this is full-word #. checking, include all variants of each word. No spaces are #. allowed. -#: ../src/client/composer/composer-widget.vala:167 +#: src/client/composer/composer-widget.vala:170 msgid "" "attach|attaching|attaches|attachment|attachments|attached|enclose|enclosed|" "enclosing|encloses|enclosure|enclosures" @@ -915,28 +1436,37 @@ "ekle|ekleniyor|ekler|ek|ekler|eklendi|içer|içerdi|içeriyor|içerir|koruncak|" "koruncaklar" -#: ../src/client/composer/composer-widget.vala:1122 -#: ../src/client/composer/composer-widget.vala:1141 -msgid "Do you want to discard this message?" -msgstr "Bu iletiyi atmak istiyor musunuz?" +#. Translators: This dialog text is displayed to the +#. user when closing a composer where the options are +#. Keep, Discard or Cancel. +#: src/client/composer/composer-widget.vala:1131 +msgid "Do you want to keep or discard this draft message?" +msgstr "Bu iletiyi saklamak mı yoksa gözden çıkarmak mı istersiniz?" + +#. Translators: This dialog text is displayed to the +#. user when closing a composer where the options are +#. only Discard or Cancel. +#: src/client/composer/composer-widget.vala:1159 +msgid "Do you want to discard this draft message?" +msgstr "Bu taslak iletiyi gözden çıkarmak istiyor musunuz?" -#: ../src/client/composer/composer-widget.vala:1245 +#: src/client/composer/composer-widget.vala:1276 msgid "Send message with an empty subject and body?" msgstr "İleti konusu ve gövdesi olmadan gönderilsin mi?" -#: ../src/client/composer/composer-widget.vala:1247 +#: src/client/composer/composer-widget.vala:1278 msgid "Send message with an empty subject?" msgstr "İleti konusu olmadan gönderilsin mi?" -#: ../src/client/composer/composer-widget.vala:1249 +#: src/client/composer/composer-widget.vala:1280 msgid "Send message with an empty body?" msgstr "İleti, ileti gövdesi olmadan gönderilsin mi?" -#: ../src/client/composer/composer-widget.vala:1253 +#: src/client/composer/composer-widget.vala:1284 msgid "Send message without an attachment?" msgstr "İleti eki olmadan gönderilsin mi?" -#: ../src/client/composer/composer-widget.vala:1515 +#: src/client/composer/composer-widget.vala:1589 #, c-format msgid "“%s” already attached for delivery." msgstr "“%s” gönderim için zaten eklendi." @@ -946,168 +1476,260 @@ #. description of the document type, the second will #. be a human-friendly size string. For example: #. Document (100.9MB) -#: ../src/client/composer/composer-widget.vala:1523 -#: ../src/client/conversation-viewer/conversation-email.vala:138 +#: src/client/composer/composer-widget.vala:1597 +#: src/client/conversation-viewer/conversation-email.vala:173 #, c-format msgid "%s (%s)" msgstr "%s (%s)" -#: ../src/client/composer/composer-widget.vala:1560 +#: src/client/composer/composer-widget.vala:1634 #, c-format msgid "“%s” could not be found." msgstr "“%s” bulunamadı." -#: ../src/client/composer/composer-widget.vala:1566 +#: src/client/composer/composer-widget.vala:1640 #, c-format msgid "“%s” is a folder." msgstr "“%s” bir klasör." -#: ../src/client/composer/composer-widget.vala:1572 +#: src/client/composer/composer-widget.vala:1646 #, c-format msgid "“%s” is an empty file." msgstr "“%s” boş bir dosya." -#: ../src/client/composer/composer-widget.vala:1585 +#: src/client/composer/composer-widget.vala:1659 #, c-format msgid "“%s” could not be opened for reading." msgstr "“%s” okuma için açılamadı." -#: ../src/client/composer/composer-widget.vala:1593 +#: src/client/composer/composer-widget.vala:1667 msgid "Cannot add attachment" msgstr "Eklenti eklenemiyor" -#: ../src/client/composer/composer-widget.vala:1645 -msgid "To: " -msgstr "Kime: " - -#: ../src/client/composer/composer-widget.vala:1648 -msgid "Cc: " -msgstr "Cc: " +#. Translators: Human-readable version of the RFC 822 To header +#: src/client/composer/composer-widget.vala:1717 +#: src/client/conversation-viewer/conversation-email.vala:975 +#: src/engine/rfc822/rfc822-utils.vala:298 ui/conversation-message.ui:313 +msgid "To:" +msgstr "Kime:" + +#. Translators: Human-readable version of the RFC 822 CC header +#: src/client/composer/composer-widget.vala:1723 +#: src/client/conversation-viewer/conversation-email.vala:980 +#: src/engine/rfc822/rfc822-utils.vala:303 ui/conversation-message.ui:358 +msgid "Cc:" +msgstr "Cc:" -#: ../src/client/composer/composer-widget.vala:1651 -msgid "Bcc: " +#. Translators: Human-readable version of the RFC 822 BCC header +#: src/client/composer/composer-widget.vala:1729 +#: src/client/conversation-viewer/conversation-email.vala:985 +#: ui/conversation-message.ui:403 +msgid "Bcc:" msgstr "Bcc:" -#: ../src/client/composer/composer-widget.vala:1654 +#. Translators: Human-readable version of the RFC 822 Reply-To header +#: src/client/composer/composer-widget.vala:1735 msgid "Reply-To: " msgstr "Yanıtla: " -#: ../src/client/composer/composer-widget.vala:1786 +#: src/client/composer/composer-widget.vala:1875 msgid "Select Color" msgstr "Renk Seç" -#. Displayed in the From dropdown to indicate an "alternate email address" -#. for an account. The first printf argument will be the alternate email -#. address, and the second will be the account's primary email address. -#: ../src/client/composer/composer-widget.vala:1986 +#. Displayed in the From dropdown to indicate an +#. "alternate email address" for an account. The first +#. printf argument will be the alternate email address, +#. and the second will be the account's primary email +#. address. +#: src/client/composer/composer-widget.vala:2065 #, c-format msgid "%1$s via %2$s" msgstr "%2$s aracılığıyla %1$s" #. Composer label (with mnemonic underscore) for the account selector #. when choosing what address to send a message from. -#: ../src/client/composer/composer-widget.vala:2028 +#: src/client/composer/composer-widget.vala:2126 msgid "_From:" msgstr "_Gönderen:" #. Translators: This is the name of the file chooser filter #. when inserting an image in the composer. -#: ../src/client/composer/composer-widget.vala:2252 +#: src/client/composer/composer-widget.vala:2352 msgid "Images" msgstr "Resimler" -#: ../src/client/composer/spell-check-popover.vala:117 +#: src/client/composer/composer-window.vala:14 +msgid "New Message" +msgstr "Yeni İleti" + +#: src/client/composer/spell-check-popover.vala:117 msgid "Remove this language from the preferred list" msgstr "Bu dili tercih edilenler listesinden kaldır" -#: ../src/client/composer/spell-check-popover.vala:121 +#: src/client/composer/spell-check-popover.vala:121 msgid "Add this language to the preferred list" msgstr "Bu dili tercih edilenler listesine ekle" -#: ../src/client/composer/spell-check-popover.vala:217 +#: src/client/composer/spell-check-popover.vala:217 msgid "Search for more languages" msgstr "Daha çok dil için ara" -#: ../src/client/conversation-list/formatted-conversation-data.vala:11 +#: src/client/conversation-list/conversation-list-view.vala:283 +msgid "Delete conversation" +msgstr "Konuşmayı sil" + +#: src/client/conversation-list/conversation-list-view.vala:286 +#: ui/main-toolbar-menus.ui:5 +msgid "Mark as _Read" +msgstr "_Okundu olarak imle" + +#: src/client/conversation-list/conversation-list-view.vala:289 +#: ui/main-toolbar-menus.ui:9 +msgid "Mark as _Unread" +msgstr "Ok_unmamış olarak imle" + +#: src/client/conversation-list/conversation-list-view.vala:292 +#: ui/main-toolbar-menus.ui:17 +msgid "U_nstar" +msgstr "Y_ıldızı kaldır" + +#: src/client/conversation-list/conversation-list-view.vala:294 +#: ui/main-toolbar-menus.ui:13 +msgid "_Star" +msgstr "_Yıldızla" + +#. Translators: Menu item to reply to a specific message. +#: src/client/conversation-list/conversation-list-view.vala:297 +#: ui/conversation-email-menus.ui:9 +msgid "_Reply" +msgstr "_Yanıtla" + +#: src/client/conversation-list/conversation-list-view.vala:298 +msgid "R_eply All" +msgstr "Tümüne _Yanıtla" + +#. Translators: Menu item to forward a specific message. +#: src/client/conversation-list/conversation-list-view.vala:299 +#: ui/conversation-email-menus.ui:21 +msgid "_Forward" +msgstr "_Yönlendir" + +#: src/client/conversation-list/formatted-conversation-data.vala:11 msgid "Me" msgstr "Ben" #. Translators: This is the file type displayed for #. attachments with unknown file types. -#: ../src/client/conversation-viewer/conversation-email.vala:124 +#: src/client/conversation-viewer/conversation-email.vala:159 msgid "Unknown" msgstr "Bilinmiyor" -#. Preview headers +#. Translators: Human-readable version of the RFC 822 From header +#: src/client/conversation-viewer/conversation-email.vala:970 +#: src/engine/rfc822/rfc822-utils.vala:289 +msgid "From:" +msgstr "Gönderen:" + +#. Translators: Human-readable version of the RFC 822 Date header +#: src/client/conversation-viewer/conversation-email.vala:990 +#: src/engine/rfc822/rfc822-utils.vala:294 +msgid "Date:" +msgstr "Tarih:" + +#. Translators: Human-readable version of the RFC 822 Subject header +#: src/client/conversation-viewer/conversation-email.vala:995 +#: src/engine/rfc822/rfc822-utils.vala:292 +msgid "Subject:" +msgstr "Konu:" + +#: src/client/conversation-viewer/conversation-message.vala:65 +msgid "This email address may have been forged" +msgstr "Bu e-posta adresi taklit edilmiş olabilir" + +#. Compact headers #. Translators: This is displayed in place of the from address #. when the message has no from address. -#: ../src/client/conversation-viewer/conversation-message.vala:331 +#: src/client/conversation-viewer/conversation-message.vala:394 msgid "No sender" msgstr "Gönderen yok" #. Translators: This separates multiple 'from' -#. addresses in the header preview for a message. -#: ../src/client/conversation-viewer/conversation-message.vala:586 +#. addresses in the compact header for a message. +#: src/client/conversation-viewer/conversation-message.vala:767 msgid ", " msgstr ", " #. Translators: This string is used as the HTML IMG ALT #. attribute value when displaying an inline image in an email #. that did not specify a file name. E.g. ImageCannot remove account " -msgstr "Hesap kaldırılamadı " +#. This title is shown to users when confirming if they want to remove an account. The string substitution is replaced with the account's name. +#: ui/accounts_editor_remove_pane.ui:73 +#, c-format +msgid "Confirm removing: %s" +msgstr "Kaldırmayı onayla: %s" -#: ../ui/account_cannot_remove.glade.h:2 +#: ui/accounts_editor_remove_pane.ui:91 msgid "" -"A composer window associated with this account is currently open. Send or " -"discard the message and try again." +"Removing an account will remove it from Geary and delete locally cached " +"email data from your computer, but not from your service provider." msgstr "" -"Bu hesapla bağlantılı bir oluşturucu penceresi açık. İletiyi gönderip veya " -"iptal edip yeniden deneyin." - -#: ../ui/account_list.glade.h:1 -msgid "Add account" -msgstr "Hesap ekle" +"Hesabı kaldırmak onu Gearyʼden kaldıracak ve e-posta verisini hizmet " +"sağlayıcınızdan değil ama bilgisayarınızdaki yerel önbellekten silecek." -#: ../ui/account_list.glade.h:2 -msgid "Edit account" -msgstr "Hesabı düzenle" - -#: ../ui/account_list.glade.h:3 +#: ui/accounts_editor_remove_pane.ui:122 msgid "Remove account" msgstr "Hesabı kaldır" -#: ../ui/account_spinner.glade.h:1 -msgid "Please wait while Geary validates your account." -msgstr "Lütfen Geary hesabınızı doğrulayana kadar bekleyin." +#: ui/accounts_editor_servers_pane.ui:17 +msgid "Cancel" +msgstr "İptal Et" + +#: ui/accounts_editor_servers_pane.ui:47 +msgid "Apply" +msgstr "Uygula" -#: ../ui/certificate_warning_dialog.glade.h:1 +#: ui/certificate_warning_dialog.glade:7 msgid "Untrusted Connection" msgstr "Güvenilir Olmayan Bağlantı" -#: ../ui/certificate_warning_dialog.glade.h:2 +#: ui/certificate_warning_dialog.glade:29 msgid "_Always Trust This Server" msgstr "Bu Sunucuya _Her Zaman Güven" -#: ../ui/certificate_warning_dialog.glade.h:3 +#: ui/certificate_warning_dialog.glade:43 msgid "_Trust This Server" msgstr "Bu Sunucuya _Güven" -#: ../ui/certificate_warning_dialog.glade.h:4 +#: ui/certificate_warning_dialog.glade:57 msgid "_Don’t Trust This Server" msgstr "Bu Sunucuya Güven_me" -#: ../ui/composer-headerbar.ui.h:1 +#: ui/composer-headerbar.ui:17 ui/composer-headerbar.ui:174 msgid "Detach (Ctrl+D)" msgstr "Ayır (Ctrl+D)" -#: ../ui/composer-headerbar.ui.h:2 +#: ui/composer-headerbar.ui:57 ui/composer-headerbar.ui:83 msgid "Attach File (Ctrl+T)" msgstr "Dosya Ekle (Ctrl+T)" -#: ../ui/composer-headerbar.ui.h:3 +#: ui/composer-headerbar.ui:107 msgid "Include Original Attachments" msgstr "Özgün Ekleri İçer" -#: ../ui/composer-headerbar.ui.h:4 -msgid "Send (Ctrl+Enter)" -msgstr "Gönder (Ctrl+Enter)" - -#: ../ui/composer-headerbar.ui.h:5 +#: ui/composer-headerbar.ui:202 msgid "_Send" msgstr "_Gönder" -#: ../ui/composer-headerbar.ui.h:6 +#: ui/composer-headerbar.ui:207 +msgid "Send (Ctrl+Enter)" +msgstr "Gönder (Ctrl+Enter)" + +#: ui/composer-headerbar.ui:230 msgid "Discard and Close" -msgstr "At ve Kapat" +msgstr "Gözden Çıkar ve Kapat" -#: ../ui/composer-headerbar.ui.h:7 +#: ui/composer-headerbar.ui:254 msgid "Save and Close" msgstr "Kaydet ve Kapat" #. Note that this button and the Update button will never be shown at the same time to the user. -#: ../ui/composer-link-popover.ui.h:2 +#: ui/composer-link-popover.ui:41 msgid "Insert the new link with this URL" -msgstr "Bu URL ile yeni bağlantıyı ekle" +msgstr "Bu URL ile yeni bağlantıyı yerleştir" -#: ../ui/composer-link-popover.ui.h:3 +#: ui/composer-link-popover.ui:52 msgid "Link URL" msgstr "Bağlantı URL’si" #. Note that this button and the Insert button will never be shown at the same time to the user. -#: ../ui/composer-link-popover.ui.h:5 +#: ui/composer-link-popover.ui:66 msgid "Update this link’s URL" msgstr "Bu bağlantının URL’sini güncelle" -#: ../ui/composer-link-popover.ui.h:6 +#: ui/composer-link-popover.ui:86 msgid "Delete this link" msgstr "Bu bağlantıyı sil" -#: ../ui/composer-link-popover.ui.h:7 +#: ui/composer-link-popover.ui:106 msgid "Open this link" msgstr "Bu bağlantıyı aç" -#: ../ui/composer-menus.ui.h:1 +#: ui/composer-menus.ui:7 msgid "S_ans Serif" msgstr "S_ans Serif" -#: ../ui/composer-menus.ui.h:2 +#: ui/composer-menus.ui:12 msgid "S_erif" msgstr "S_erif" -#: ../ui/composer-menus.ui.h:3 +#: ui/composer-menus.ui:17 msgid "_Fixed Width" msgstr "_Sabit Genişlik" -#: ../ui/composer-menus.ui.h:4 +#: ui/composer-menus.ui:24 msgid "_Small" msgstr "_Küçük" -#: ../ui/composer-menus.ui.h:5 +#: ui/composer-menus.ui:29 msgid "_Medium" msgstr "_Orta" -#: ../ui/composer-menus.ui.h:6 +#: ui/composer-menus.ui:34 msgid "Lar_ge" msgstr "Büy_ük" -#: ../ui/composer-menus.ui.h:7 +#: ui/composer-menus.ui:41 msgid "C_olor" msgstr "R_enk" -#: ../ui/composer-menus.ui.h:8 +#: ui/composer-menus.ui:47 ui/composer-menus.ui:62 msgid "_Rich Text" msgstr "_Zengin Metin" -#: ../ui/composer-menus.ui.h:9 +#: ui/composer-menus.ui:53 ui/composer-menus.ui:68 msgid "Show Extended Fields" msgstr "Genişletilmiş Alanları Göster" -#: ../ui/composer-menus.ui.h:10 +#: ui/composer-menus.ui:78 msgid "_Undo" msgstr "_Geri Al" -#: ../ui/composer-menus.ui.h:11 +#: ui/composer-menus.ui:82 msgid "_Redo" msgstr "_Yinele" -#: ../ui/composer-menus.ui.h:12 +#: ui/composer-menus.ui:88 ui/composer-menus.ui:106 msgid "Cu_t" msgstr "Ke_s" -#: ../ui/composer-menus.ui.h:13 ../ui/conversation-message-menus.ui.h:7 +#: ui/composer-menus.ui:92 ui/composer-menus.ui:110 +#: ui/conversation-message-menus.ui:37 msgid "_Copy" msgstr "_Kopyala" -#: ../ui/composer-menus.ui.h:14 +#: ui/composer-menus.ui:96 ui/composer-menus.ui:114 msgid "_Paste" msgstr "_Yapıştır" -#: ../ui/composer-menus.ui.h:15 -msgctxt "Clipboard paste with rich text" -msgid "Paste _With Formatting" -msgstr "_Biçimlendirmeyle Yapıştır" +#: ui/composer-menus.ui:100 +msgctxt "Clipboard paste as plain text" +msgid "Paste _Without Formatting" +msgstr "Biçimlendirmede_n Yapıştır" -#: ../ui/composer-menus.ui.h:16 +#: ui/composer-menus.ui:120 msgid "Select _All" msgstr "Tümünü S_eç" -#: ../ui/composer-menus.ui.h:17 ../ui/conversation-message-menus.ui.h:9 +#: ui/composer-menus.ui:127 ui/conversation-message-menus.ui:49 msgid "_Inspect…" msgstr "_Denetle…" #. Address(es) e-mail is to be sent to -#: ../ui/composer-widget.ui.h:2 +#: ui/composer-widget.ui:56 msgid "_To" msgstr "_Kime" -#: ../ui/composer-widget.ui.h:3 +#: ui/composer-widget.ui:75 msgid "_Cc" msgstr "_Cc" -#: ../ui/composer-widget.ui.h:4 +#: ui/composer-widget.ui:130 msgid "_Subject" msgstr "_Konu" -#: ../ui/composer-widget.ui.h:5 +#: ui/composer-widget.ui:149 msgid "_Bcc" msgstr "_Bcc" -#: ../ui/composer-widget.ui.h:6 +#: ui/composer-widget.ui:179 msgid "_Reply-To" msgstr "_Yanıtla" #. Geary account mail will be sent from -#: ../ui/composer-widget.ui.h:8 +#: ui/composer-widget.ui:208 msgid "From" msgstr "Gönderen" -#: ../ui/composer-widget.ui.h:9 +#: ui/composer-widget.ui:293 msgid "Drop files here" msgstr "Dosyaları buraya bırak" -#: ../ui/composer-widget.ui.h:10 +#: ui/composer-widget.ui:309 msgid "To add them as attachments" msgstr "Onları ek olarak eklemek için" -#: ../ui/composer-widget.ui.h:11 +#: ui/composer-widget.ui:353 msgid "Undo last edit (Ctrl+Z)" msgstr "Son düzenlemeyi geri al (Ctrl+Z)" -#: ../ui/composer-widget.ui.h:12 +#: ui/composer-widget.ui:377 msgid "Redo last edit (Ctrl+Shift+Z)" msgstr "Son düzenlemeyi yinele (Ctrl+Shift+Z)" -#: ../ui/composer-widget.ui.h:13 +#: ui/composer-widget.ui:415 msgid "Bold (Ctrl+B)" msgstr "Kalın (Ctrl+B)" -#: ../ui/composer-widget.ui.h:14 +#: ui/composer-widget.ui:439 msgid "Italic (Ctrl+I)" msgstr "Eğik (Ctrl+I)" -#: ../ui/composer-widget.ui.h:15 +#: ui/composer-widget.ui:463 msgid "Underline (Ctrl+U)" msgstr "Altı çizili (Ctrl+U)" -#: ../ui/composer-widget.ui.h:16 +#: ui/composer-widget.ui:487 msgid "Strikethrough (Ctrl+K)" msgstr "Üstü çizili (Ctrl+K)" -#: ../ui/composer-widget.ui.h:17 +#: ui/composer-widget.ui:525 +msgid "Insert unordered list" +msgstr "Sırasız liste yerleştir" + +#: ui/composer-widget.ui:549 +msgid "Insert ordered list" +msgstr "Sıralı liste yerleştir" + +#: ui/composer-widget.ui:587 msgid "Quote text (Ctrl+])" msgstr "Metni alıntıla (Ctrl+])" -#: ../ui/composer-widget.ui.h:18 +#: ui/composer-widget.ui:611 msgid "Unquote text (Ctrl+[)" msgstr "Metni alıntılama (Ctrl+[)" -#: ../ui/composer-widget.ui.h:19 +#: ui/composer-widget.ui:649 msgid "Insert or update selection link (Ctrl+L)" -msgstr "Seçime bağlantı ekle veya kaldır (Ctrl+L)" +msgstr "Seçime bağlantı yerleştir veya kaldır (Ctrl+L)" -#: ../ui/composer-widget.ui.h:20 +#: ui/composer-widget.ui:673 msgid "Insert an image (Ctrl+G)" -msgstr "Resim ekle (Ctrl+G)" +msgstr "Resim yerleştir (Ctrl+G)" -#: ../ui/composer-widget.ui.h:21 +#: ui/composer-widget.ui:707 msgid "Remove selection formatting (Ctrl+Space)" msgstr "Seçimin biçimlendirmesini kaldır (Ctrl+Boşluk)" -#: ../ui/composer-widget.ui.h:22 +#: ui/composer-widget.ui:731 msgid "Select spell checking languages" msgstr "Yazım denetimi dillerini seç" -#: ../ui/conversation-email.ui.h:1 +#: ui/conversation-email.ui:27 msgid "Save all attachments" msgstr "Tüm ekleri kaydet" #. Note: The application will never show this button at the same time as unstar_button, one will always be hidden. -#: ../ui/conversation-email.ui.h:3 +#: ui/conversation-email.ui:50 msgid "Mark this message as starred" msgstr "Bu iletiyi yıldızlı olarak imle" #. Note: The application will never show this button at the same time as star_button, one will always be hidden. -#: ../ui/conversation-email.ui.h:5 +#: ui/conversation-email.ui:72 msgid "Mark this message as not starred" msgstr "Bu iletiyi yıldızsız olarak imle" -#: ../ui/conversation-email.ui.h:6 +#: ui/conversation-email.ui:95 msgid "Display the message menu" msgstr "İleti menüsünü göster" -#: ../ui/conversation-email.ui.h:7 +#: ui/conversation-email.ui:161 msgid "Open selected attachments" msgstr "Seçilen ekleri aç" -#: ../ui/conversation-email.ui.h:8 +#: ui/conversation-email.ui:178 msgid "Save selected attachments" msgstr "Seçilen ekleri kaydet" -#: ../ui/conversation-email.ui.h:9 +#: ui/conversation-email.ui:195 msgid "Select all attachments" msgstr "Tüm ekleri seç" -#: ../ui/conversation-email.ui.h:10 +#: ui/conversation-email.ui:240 msgid "Edit Draft" msgstr "Taslağı Düzenle" -#: ../ui/conversation-email.ui.h:11 +#: ui/conversation-email.ui:267 msgid "Draft message" msgstr "Taslak ileti" -#: ../ui/conversation-email.ui.h:12 +#: ui/conversation-email.ui:283 msgid "This message has not yet been sent." msgstr "Bu ileti henüz gönderilemedi." -#: ../ui/conversation-email.ui.h:13 -msgid "Try Again" -msgstr "Yeniden Dene" - -#: ../ui/conversation-email.ui.h:14 +#: ui/conversation-email.ui:329 msgid "Message not saved" msgstr "İleti kaydedilmedi" -#: ../ui/conversation-email.ui.h:15 +#: ui/conversation-email.ui:345 msgid "This message was sent, but has not been saved to your account." msgstr "Bu ileti gönderildi, ama hesabınıza kaydedilemedi." -#: ../ui/conversation-email-menus.ui.h:2 +#. Translators: Menu item to reply to a specific message. +#: ui/conversation-email-menus.ui:15 msgid "Reply to _All" msgstr "Tümüne _Yanıt Ver" -#: ../ui/conversation-email-menus.ui.h:4 +#. Translators: Menu item to mark a specific message as +#. read. +#: ui/conversation-email-menus.ui:30 msgid "_Mark Read" msgstr "Okunmuş olarak _imle" -#: ../ui/conversation-email-menus.ui.h:5 +#: ui/conversation-email-menus.ui:36 msgid "_Mark Unread" msgstr "Okunmadı olarak _imle" -#: ../ui/conversation-email-menus.ui.h:6 +#. Translators: Menu item to mark all messages in a +#. conversation from this one as unread. +#: ui/conversation-email-menus.ui:42 msgid "Mark Unread From _Here" msgstr "_Buradan Okunmadı İmle" -#: ../ui/conversation-email-menus.ui.h:8 +#. Translators: Menu item to move a single, specific message +#. to the trash +#: ui/conversation-email-menus.ui:50 +msgid "_Trash" +msgstr "_Çöp" + +#. Translators: Menu item to delete a single, specific message +#: ui/conversation-email-menus.ui:57 +msgid "_Delete…" +msgstr "_Sil…" + +#. Translators: Menu item to view the source for a message +#: ui/conversation-email-menus.ui:69 msgid "_View Source" msgstr "_Kaynağı İncele" -#: ../ui/conversation-email-menus.ui.h:11 +#: ui/conversation-email-menus.ui:87 msgid "_Save All" msgstr "Tümünü _Kaydet" -#: ../ui/conversation-message-menus.ui.h:1 +#: ui/conversation-message-menus.ui:7 msgid "_Open Link" msgstr "_Bağlantıyı Aç" -#: ../ui/conversation-message-menus.ui.h:2 +#: ui/conversation-message-menus.ui:11 msgid "Copy Link _Address" msgstr "Bağlantı _Adresini Kopyala" -#: ../ui/conversation-message-menus.ui.h:3 +#: ui/conversation-message-menus.ui:17 msgid "Send New _Message…" msgstr "Yeni _İleti Gönder…" -#: ../ui/conversation-message-menus.ui.h:4 +#: ui/conversation-message-menus.ui:21 msgid "Copy Email _Address" msgstr "E-posta _Adresini Kopyala" -#: ../ui/conversation-message-menus.ui.h:5 +#: ui/conversation-message-menus.ui:27 msgid "Save _Image As…" msgstr "_Resmi Farklı Kaydet…" -#: ../ui/conversation-message-menus.ui.h:6 +#: ui/conversation-message-menus.ui:33 msgid "_Select All" msgstr "Tümünü _Seç" -#: ../ui/conversation-message-menus.ui.h:8 +#: ui/conversation-message-menus.ui:43 msgid "Search for messages from" msgstr "Şuradan iletiler için ara" -#: ../ui/conversation-message.ui.h:1 +#: ui/conversation-message.ui:64 msgid "From " msgstr "Kimden " -#: ../ui/conversation-message.ui.h:2 +#: ui/conversation-message.ui:80 ui/conversation-message.ui:179 msgid "1/1/1970\t" msgstr "1/1/1970\t" -#: ../ui/conversation-message.ui.h:3 +#: ui/conversation-message.ui:103 msgid "Preview body text." msgstr "Gövde metnini ön izle." -#: ../ui/conversation-message.ui.h:4 +#: ui/conversation-message.ui:203 msgid "Sent by:" msgstr "Gönderen:" -#: ../ui/conversation-message.ui.h:5 +#: ui/conversation-message.ui:248 msgid "Reply to:" msgstr "Yanıtla:" -#: ../ui/conversation-message.ui.h:6 +#: ui/conversation-message.ui:292 msgid "Subject" msgstr "Konu" -#: ../ui/conversation-message.ui.h:7 -msgid "To:" -msgstr "Kime:" - -#: ../ui/conversation-message.ui.h:8 -msgid "Cc:" -msgstr "Cc:" - -#: ../ui/conversation-message.ui.h:9 -msgid "Bcc:" -msgstr "Bcc:" - -#: ../ui/conversation-message.ui.h:10 +#: ui/conversation-message.ui:502 msgid "Show Images" msgstr "Resimleri Göster" -#: ../ui/conversation-message.ui.h:11 +#: ui/conversation-message.ui:515 msgid "Always Show From Sender" msgstr "Gönderenden Her Zaman Göster" -#: ../ui/conversation-message.ui.h:12 +#: ui/conversation-message.ui:543 msgid "Remote images not shown" msgstr "Uzaktaki resimler gösterilmiyor" -#: ../ui/conversation-message.ui.h:13 +#: ui/conversation-message.ui:560 msgid "Only show remote images from senders you trust." msgstr "Yalnızca güvendiğiniz göndericilerden gelen uzak resimleri göster." -#: ../ui/conversation-message.ui.h:14 +#: ui/conversation-message.ui:693 msgid "But actually goes to:" msgstr "Aslında şuraya gider:" -#: ../ui/conversation-message.ui.h:15 +#: ui/conversation-message.ui:724 msgid "The link appears to go to:" msgstr "Bağlantı şuraya gidiyor gibi görünür:" -#: ../ui/conversation-message.ui.h:16 +#: ui/conversation-message.ui:736 msgid "Deceptive link found" msgstr "Aldatıcı bağlantı bulundu" -#: ../ui/conversation-message.ui.h:17 +#: ui/conversation-message.ui:751 msgid "The email sender may be leading you to the wrong web site." msgstr "E-posta göndericisi sizi yanlış web sitesine yönlendiriyor olabilir." -#: ../ui/conversation-message.ui.h:18 +#: ui/conversation-message.ui:764 msgid "If unsure, contact the sender and ask before continuing." msgstr "" -"Emin değilseniz, devam etmeden önce göndericiyle iletişime geçin ve sorun." +"Emin değilseniz devam etmeden önce göndericiyle iletişime geçin ve sorun." -#: ../ui/conversation-viewer.ui.h:1 +#: ui/conversation-viewer.ui:60 msgid "Find in conversation" msgstr "Konuşmada bul" -#: ../ui/conversation-viewer.ui.h:2 +#: ui/conversation-viewer.ui:74 msgid "Find the previous occurrence of the search string." -msgstr "Arama dizisinin önceki olayını bul" +msgstr "Arama dizisinin önceki olayını bul." -#: ../ui/conversation-viewer.ui.h:3 +#: ui/conversation-viewer.ui:95 msgid "Find the next occurrence of the search string." -msgstr "Arama dizisinin sonraki olayını bul" - -#: ../ui/edit_alternate_emails.glade.h:1 -msgid "Remove email address" -msgstr "E-posta adresini kaldır" +msgstr "Arama dizisinin sonraki olayını bul." -#: ../ui/edit_alternate_emails.glade.h:2 -msgid "" -"Some email services require additional addresses be configured on the " -"server. Contact your email provider for more information." -msgstr "" -"Bazı e-posta hizmetleri ek adreslerin sunucuda kurulu olmasını gerektirir. " -"Daha çok bilgi için e-posta sağlayıcınıza başvurun." - -#: ../ui/edit_alternate_emails.glade.h:4 -msgid "_Update" -msgstr "_Güncelle" - -#: ../ui/find_bar.glade.h:1 +#: ui/find_bar.glade:66 msgid "Find:" msgstr "Bul:" -#: ../ui/find_bar.glade.h:2 +#: ui/find_bar.glade:89 msgid "_Previous" msgstr "_Önceki" -#: ../ui/find_bar.glade.h:3 +#: ui/find_bar.glade:107 msgid "_Next" msgstr "_Sonraki" -#: ../ui/find_bar.glade.h:4 +#: ui/find_bar.glade:125 msgid "_Case sensitive" msgstr "_Büyük küçük harf duyarlı" -#: ../ui/find_bar.glade.h:5 +#: ui/find_bar.glade:145 msgid "label" msgstr "etiket" -#: ../ui/gtk/help-overlay.ui.h:1 +#: ui/gtk/help-overlay.ui:9 msgid "Conversation Shortcuts" msgstr "Konuşma Kısayolları" -#: ../ui/gtk/help-overlay.ui.h:2 +#: ui/gtk/help-overlay.ui:13 ui/gtk/help-overlay.ui:254 msgctxt "shortcut window" msgid "General" msgstr "Genel" -#: ../ui/gtk/help-overlay.ui.h:3 +#: ui/gtk/help-overlay.ui:17 msgctxt "shortcut window" msgid "Move focus to the next/previous pane" msgstr "Odağı önceki/sonraki bölmeye taşı" -#: ../ui/gtk/help-overlay.ui.h:4 +#: ui/gtk/help-overlay.ui:24 msgctxt "shortcut window" msgid "Move focus to conversation list" msgstr "Odağı konuşma listesine taşı" -#: ../ui/gtk/help-overlay.ui.h:5 +#: ui/gtk/help-overlay.ui:31 msgctxt "shortcut window" msgid "Detach composer window" msgstr "Oluşturucu penceresini ayır" -#: ../ui/gtk/help-overlay.ui.h:6 +#: ui/gtk/help-overlay.ui:38 msgctxt "shortcut window" msgid "Close composer window" msgstr "Oluşturucu penceresini kapat" -#: ../ui/gtk/help-overlay.ui.h:7 +#: ui/gtk/help-overlay.ui:45 msgctxt "shortcut window" msgid "Show keyboard shortcuts" msgstr "Klavye kısayollarını göster" -#: ../ui/gtk/help-overlay.ui.h:8 +#: ui/gtk/help-overlay.ui:52 msgctxt "shortcut window" msgid "Show help" msgstr "Yardımı göster" -#: ../ui/gtk/help-overlay.ui.h:9 +#: ui/gtk/help-overlay.ui:59 msgctxt "shortcut window" msgid "Quit the application" msgstr "Uygulamadan çık" -#: ../ui/gtk/help-overlay.ui.h:10 +#: ui/gtk/help-overlay.ui:68 msgctxt "shortcut window" msgid "Search" msgstr "Ara" -#: ../ui/gtk/help-overlay.ui.h:11 +#: ui/gtk/help-overlay.ui:72 msgctxt "shortcut window" msgid "Jump to search box" msgstr "Arama kutusuna atla" -#: ../ui/gtk/help-overlay.ui.h:12 +#: ui/gtk/help-overlay.ui:79 msgctxt "shortcut window" msgid "Find in current conversation" msgstr "Şimdiki konuşmada bul" -#: ../ui/gtk/help-overlay.ui.h:13 +#: ui/gtk/help-overlay.ui:86 msgctxt "shortcut window" msgid "Find next/previous in current conversation" msgstr "Şimdiki konuşmada öncekini/sonrakini bul" -#: ../ui/gtk/help-overlay.ui.h:14 +#: ui/gtk/help-overlay.ui:95 ui/gtk/help-overlay.ui:274 msgctxt "shortcut window" msgid "Actions" msgstr "Eylemler" -#: ../ui/gtk/help-overlay.ui.h:15 +#: ui/gtk/help-overlay.ui:99 msgctxt "shortcut window" msgid "Compose a new message" -msgstr "Yeni bir ileti yaz" +msgstr "Yeni ileti yaz" -#: ../ui/gtk/help-overlay.ui.h:16 +#: ui/gtk/help-overlay.ui:106 msgctxt "shortcut window" msgid "Reply to sender " -msgstr "Göndereni yanıtla" +msgstr "Göndereni yanıtla " -#: ../ui/gtk/help-overlay.ui.h:17 +#: ui/gtk/help-overlay.ui:113 msgctxt "shortcut window" msgid "Reply to all" msgstr "Tümünü yanıtla" -#: ../ui/gtk/help-overlay.ui.h:18 +#: ui/gtk/help-overlay.ui:120 msgctxt "shortcut window" msgid "Forward" msgstr "Yönlendir" -#: ../ui/gtk/help-overlay.ui.h:19 +#: ui/gtk/help-overlay.ui:127 msgctxt "shortcut window" msgid "Archive" msgstr "Arşiv" -#: ../ui/gtk/help-overlay.ui.h:20 +#: ui/gtk/help-overlay.ui:134 msgctxt "shortcut window" msgid "Move to trash" msgstr "Çöpe taşı" -#: ../ui/gtk/help-overlay.ui.h:21 +#: ui/gtk/help-overlay.ui:141 msgctxt "shortcut window" msgid "Toggle spam" msgstr "Gereksizleri aç" -#: ../ui/gtk/help-overlay.ui.h:22 +#: ui/gtk/help-overlay.ui:148 msgctxt "shortcut window" msgid "Move the conversation" msgstr "Konuşmayı taşı" -#: ../ui/gtk/help-overlay.ui.h:23 +#: ui/gtk/help-overlay.ui:155 msgctxt "shortcut window" msgid "Label the conversation" msgstr "Konuşmayı etiketle" -#: ../ui/gtk/help-overlay.ui.h:24 +#: ui/gtk/help-overlay.ui:163 msgctxt "shortcut window" msgid "Mark read" msgstr "Okunmuş olarak imle" -#: ../ui/gtk/help-overlay.ui.h:25 +#: ui/gtk/help-overlay.ui:170 msgctxt "shortcut window" msgid "Mark unread" msgstr "Okunmadı olarak imle" -#: ../ui/gtk/help-overlay.ui.h:26 +#: ui/gtk/help-overlay.ui:179 msgctxt "shortcut window" msgid "View" msgstr "Görünüm" -#: ../ui/gtk/help-overlay.ui.h:27 +#: ui/gtk/help-overlay.ui:183 msgctxt "shortcut window" msgid "Zoom in" msgstr "Yakınlaştır" -#: ../ui/gtk/help-overlay.ui.h:28 +#: ui/gtk/help-overlay.ui:190 msgctxt "shortcut window" msgid "Zoom out" msgstr "Uzaklaştır" -#: ../ui/gtk/help-overlay.ui.h:29 +#: ui/gtk/help-overlay.ui:197 msgctxt "shortcut window" msgid "Reset zoom" msgstr "Yakınlaştırmayı sıfırla" -#: ../ui/gtk/help-overlay.ui.h:30 +#: ui/gtk/help-overlay.ui:206 msgctxt "shortcut window" msgid "Additional Shortcuts" msgstr "Ek Kısayollar" -#: ../ui/gtk/help-overlay.ui.h:31 +#: ui/gtk/help-overlay.ui:210 msgctxt "shortcut window" msgid "Star" msgstr "Yıldızla" -#: ../ui/gtk/help-overlay.ui.h:32 +#: ui/gtk/help-overlay.ui:217 msgctxt "shortcut window" msgid "Unstar" msgstr "Yıldızı kaldır" -#: ../ui/gtk/help-overlay.ui.h:33 +#: ui/gtk/help-overlay.ui:224 msgctxt "shortcut window" msgid "Delete" msgstr "Sil" -#: ../ui/gtk/help-overlay.ui.h:34 +#: ui/gtk/help-overlay.ui:231 msgctxt "shortcut window" msgid "Jump to next (older) conversation" msgstr "Sonraki (eski) konuşmaya atla" -#: ../ui/gtk/help-overlay.ui.h:35 +#: ui/gtk/help-overlay.ui:238 msgctxt "shortcut window" msgid "Jump to previous (newer) conversation" msgstr "Önceki (yeni) konuşmaya atla" -#: ../ui/gtk/help-overlay.ui.h:36 +#: ui/gtk/help-overlay.ui:250 msgid "Composer Shortcuts" msgstr "Oluşturucu Kısayolları" -#: ../ui/gtk/help-overlay.ui.h:37 +#: ui/gtk/help-overlay.ui:258 msgctxt "shortcut window" msgid "Quote text" msgstr "Metni alıntıla" -#: ../ui/gtk/help-overlay.ui.h:38 +#: ui/gtk/help-overlay.ui:265 msgctxt "shortcut window" msgid "Unquote text" msgstr "Metni alıntılama" -#: ../ui/gtk/help-overlay.ui.h:39 +#: ui/gtk/help-overlay.ui:278 msgctxt "shortcut window" msgid "Send" msgstr "Gönder" -#: ../ui/gtk/help-overlay.ui.h:40 +#: ui/gtk/help-overlay.ui:285 msgctxt "shortcut window" msgid "Add attachment" msgstr "Ek ekle" -#: ../ui/gtk/help-overlay.ui.h:41 +#: ui/gtk/help-overlay.ui:294 msgctxt "shortcut window" msgid "Rich text mode" msgstr "Zengin metin kipi" -#: ../ui/gtk/help-overlay.ui.h:42 +#: ui/gtk/help-overlay.ui:298 msgctxt "shortcut window" msgid "Bold text" msgstr "Kalın yazı" -#: ../ui/gtk/help-overlay.ui.h:43 +#: ui/gtk/help-overlay.ui:305 msgctxt "shortcut window" msgid "Italicize text" msgstr "Eğik metin" -#: ../ui/gtk/help-overlay.ui.h:44 +#: ui/gtk/help-overlay.ui:312 msgctxt "shortcut window" msgid "Underline text" msgstr "Metnin altını çiz" -#: ../ui/gtk/help-overlay.ui.h:45 +#: ui/gtk/help-overlay.ui:319 msgctxt "shortcut window" msgid "Strike text" msgstr "Çizgili metin" -#: ../ui/gtk/help-overlay.ui.h:46 +#: ui/gtk/help-overlay.ui:326 msgctxt "shortcut window" msgid "Insert a link" -msgstr "Bir bağlantı yerleştir" +msgstr "Bağlantı yerleştir" -#: ../ui/gtk/help-overlay.ui.h:47 +#: ui/gtk/help-overlay.ui:333 msgctxt "shortcut window" msgid "Remove formatting" msgstr "Biçimlendirmeyi kaldır" -#: ../ui/gtk/menus.ui.h:2 -msgid "A_ccounts" -msgstr "_Hesaplar" +#: ui/main-toolbar.ui:23 +msgctxt "tooltip" +msgid "Compose Message" +msgstr "İleti Oluştur" -#: ../ui/gtk/menus.ui.h:4 -msgid "_Keyboard Shortcuts" -msgstr "_Klavye Kısayolları" +#: ui/main-toolbar.ui:52 +msgid "Toggle search bar" +msgstr "Arama çubuğunu aç" -#: ../ui/login.glade.h:1 -msgid "email@example.com" -msgstr "eposta@ornek.com" +#: ui/main-toolbar.ui:112 +msgid "Reply" +msgstr "Yanıtla" + +#: ui/main-toolbar.ui:135 +msgid "Reply All" +msgstr "Tümüne Yanıtla" -#: ../ui/login.glade.h:2 ../ui/password-dialog.glade.h:3 -msgid "Password" -msgstr "Parola" +#: ui/main-toolbar.ui:158 +msgid "Forward" +msgstr "Yönlendir" -#: ../ui/login.glade.h:3 -msgid "E_mail address" -msgstr "E-_posta adresi" - -#: ../ui/login.glade.h:4 -msgid "_Password" -msgstr "_Parola" - -#: ../ui/login.glade.h:5 -msgid "S_ervice" -msgstr "H_izmet" - -#: ../ui/login.glade.h:6 -msgid "N_ame" -msgstr "_Ad" - -#: ../ui/login.glade.h:8 -msgid "N_ickname" -msgstr "_Takma Ad" - -#: ../ui/login.glade.h:9 -msgid "Work, Home, etc." -msgstr "İş, Ev, vs." - -#: ../ui/login.glade.h:10 -msgid "_Save sent mail" -msgstr "Gönderilmiş postayı _kaydet" - -#: ../ui/login.glade.h:11 -msgid "Addi_tional email addresses…" -msgstr "E_k e-posta adresleri…" - -#: ../ui/login.glade.h:12 -msgid "IMAP settings" -msgstr "IMAP ayarları" - -#: ../ui/login.glade.h:13 -msgid "Se_rver" -msgstr "Su_nucu" +#: ui/main-toolbar.ui:264 +msgid "Toggle find bar" +msgstr "Bulma çubuğunu aç" -#: ../ui/login.glade.h:14 -msgid "imap.example.com" -msgstr "imap.ornek.com" +#: ui/main-toolbar.ui:306 +msgid "_Archive" +msgstr "_Arşivle" -#: ../ui/login.glade.h:15 -msgid "P_ort" -msgstr "Bağlantı N_oktası" +#: ui/main-toolbar-menus.ui:21 +msgid "Mark as S_pam" +msgstr "İ_stenmeyen olarak imle" -#: ../ui/login.glade.h:16 -msgid "smtp.example.com" -msgstr "smtp.ornek.com" +#: ui/main-toolbar-menus.ui:25 +msgid "Mark as not S_pam" +msgstr "İ_stenen olarak imle" + +#: ui/main-toolbar-menus.ui:32 +msgid "Empty _Spam…" +msgstr "_İstenmeyeni boşalt…" + +#: ui/main-toolbar-menus.ui:36 +msgid "Empty _Trash…" +msgstr "_Çöpü boşalt…" + +#: ui/main-toolbar-menus.ui:42 +#| msgid "Accounts" +msgid "_Accounts" +msgstr "_Hesaplar" -#: ../ui/login.glade.h:17 -msgid "Ser_ver" -msgstr "Sun_ucu" - -#: ../ui/login.glade.h:18 -msgid "Por_t" -msgstr "Bağlan_tı Noktası" - -#: ../ui/login.glade.h:19 -msgid "SMTP settings" -msgstr "SMTP ayarları" - -#: ../ui/login.glade.h:20 -msgid "User_name" -msgstr "Kullanıcı _adı" - -#: ../ui/login.glade.h:21 -msgid "Pass_word" -msgstr "Paro_la" - -#: ../ui/login.glade.h:22 -msgid "SMTP username" -msgstr "SMTP kullanıcı adı" - -#: ../ui/login.glade.h:23 -msgid "SMTP password" -msgstr "SMTP parola" - -#: ../ui/login.glade.h:24 -msgid "_Username" -msgstr "_Kullanıcı adı" - -#: ../ui/login.glade.h:25 -msgid "IMAP username" -msgstr "IMAP kullanıcı adı" - -#: ../ui/login.glade.h:26 -msgid "IMAP password" -msgstr "IMAP parola" - -#: ../ui/login.glade.h:27 -msgid "Encr_yption" -msgstr "Şif_releme" - -#: ../ui/login.glade.h:28 -msgid "Encrypt_ion" -msgstr "Şifrele_me" - -#: ../ui/login.glade.h:30 -msgid "SSL/TLS" -msgstr "SSL/TLS" - -#: ../ui/login.glade.h:31 -msgid "STARTTLS" -msgstr "STARTTLS" - -#: ../ui/login.glade.h:32 -msgid "No authentication re_quired" -msgstr "Kimlik doğrulama ge_rekli değil" - -#: ../ui/login.glade.h:33 -msgid "Use IMAP cre_dentials" -msgstr "IMAP kim_liğini kullan" - -#: ../ui/login.glade.h:34 -msgid "Composer" -msgstr "Oluşturucu" - -#: ../ui/login.glade.h:35 -msgid "Save dra_fts on server" -msgstr "Tas_lakları sunucuya kaydet" - -#: ../ui/login.glade.h:36 -msgid "Si_gn emails (HTML allowed):" -msgstr "E-postaları im_zala (HTML izinli):" - -#: ../ui/login.glade.h:37 -msgid "Storage" -msgstr "Depolama" - -#: ../ui/login.glade.h:38 -msgid "_Download mail" -msgstr "Posta _indir" - -#: ../ui/main-toolbar.ui.h:1 -msgid "Empty Spam or Trash folders" -msgstr "İstenmeyen ya da Çöp klasörlerini boşalt" +#: ui/main-toolbar-menus.ui:50 +msgid "_Keyboard Shortcuts" +msgstr "_Klavye Kısayolları" -#: ../ui/password-dialog.glade.h:1 +#: ui/main-toolbar-menus.ui:61 +#| msgid "_About" +msgid "_About Geary" +msgstr "Geary _Hakkında" + +#. Infobar title when one or more accounts are offline +#: ui/main-window.ui:183 +msgid "Working offline" +msgstr "Çevrim dışı çalışıyor" + +#. Label and tooltip for offline infobar +#: ui/main-window.ui:197 +msgid "" +"Your computer does not appear to be connected to the Internet.\n" +"You will not be able to send or receive email until it is re-connected." +msgstr "" +"Bilgisayarınız internete bağlı görünmüyor.\n" +"Yeniden bağlanana dek e-posta gönderemez veya alamazsınız." + +#. Label and tooltip for offline infobar +#: ui/main-window.ui:200 +msgid "You will not be able to send or receive email until re-connected." +msgstr "Yeniden bağlanana dek e-posta gönderemez veya alamazsınız." + +#. Button label for displaying technical details about an account problem +#. Dialog title for displaying technical details of a problem. Same as the button that invokes it. +#: ui/main-window.ui:247 ui/problem-details-dialog.ui:13 +msgid "Details" +msgstr "Ayrıntılar" + +#. Button label for retrying an account problem +#: ui/main-window.ui:261 +msgid "Retry" +msgstr "Yeniden dene" + +#. Infobar title when one or more accounts have encounted an error +#: ui/main-window.ui:294 +msgid "Account problem" +msgstr "Hesap sorunu" + +#. Label and tooltip for account service problem infobar +#: ui/main-window.ui:308 +msgid "" +"Geary encountered a problem connecting to an account.\n" +"Please check your Internet connection, the server configuration and try " +"again." +msgstr "" +"Geary hesaba bağlanmakta sorunla karşılaştı.\n" +"Lütfen internet erişiminizi ve sunucu ayarlarınızı denetleyip yeniden " +"deneyin." + +#. Label and tooltip for account service problem infobar +#: ui/main-window.ui:311 +msgid "Geary encountered a problem connecting to an account." +msgstr "Geary hesaba bağlanmakta sorunla karşılaştı." + +#. Button label for retrying TLS cert validation +#: ui/main-window.ui:358 +msgid "Check" +msgstr "Gözden geçir" + +#. Button tooltip for retrying TLS cert validation +#: ui/main-window.ui:362 +msgid "Check the security details for the connection" +msgstr "Bağlantı güvenlik ayrıntılarını gözden geçirin" + +#. Infobar title when one or more accounts have a TLS cert validation error +#: ui/main-window.ui:391 +msgid "Security problem" +msgstr "Güvenlik sorunu" + +#. Label and tooltip for TLS cert validation error infobar +#: ui/main-window.ui:405 +msgid "" +"An account has reported an untrusted server.\n" +"Please check the server configuration and try again." +msgstr "" +"Hesap güvenilmeyen bir sunucu bildirdi.\n" +"Sunucu ayarlarınızı denetleyip yeniden deneyin." + +#. Label and tooltip for TLS cert validation error infobar +#: ui/main-window.ui:408 +msgid "An account has reported an untrusted server." +msgstr "Hesap güvenilmeyen bir sunucu bildirdi." + +#. Button tooltip for retrying when a login error has occurred +#: ui/main-window.ui:459 +msgid "Retry login, you will be prompted for your password" +msgstr "Giriş yapmayı yeniden dene, parola girmeniz istenecek" + +#. Infobar title when one or more accounts have a login error +#: ui/main-window.ui:488 +msgid "Login problem" +msgstr "Giriş sorunu" + +#. Label and tooltip for authentication problem infobar +#: ui/main-window.ui:502 +msgid "" +"An account has reported an incorrect login or password.\n" +"Please check your login name and try again." +msgstr "" +"Hesap yanlış bir giriş veya parola bildirdi.\n" +"Lütfen giriş adınızı denetleyip yeniden deneyin." + +#. Label and tooltip for authentication problem infobar +#: ui/main-window.ui:505 +msgid "An account has reported an incorrect login or password." +msgstr "Hesap yanlış bir giriş veya parola bildirdi." + +#: ui/password-dialog.glade:74 msgid "SMTP Credentials" msgstr "SMTP Kimliği" -#: ../ui/password-dialog.glade.h:2 +#: ui/password-dialog.glade:91 msgid "Username" msgstr "Kullanıcı adı" -#: ../ui/password-dialog.glade.h:4 +#: ui/password-dialog.glade:152 msgid "_Remember password" msgstr "Parolayı _anımsa" -#: ../ui/password-dialog.glade.h:6 +#: ui/password-dialog.glade:210 msgid "_Authenticate" msgstr "_Kimlik Doğrula" -#: ../ui/preferences-dialog.ui.h:1 +#: ui/preferences-dialog.ui:38 msgid "Reading" -msgstr "Okunuyor" +msgstr "Okuma" -#: ../ui/preferences-dialog.ui.h:2 +#: ui/preferences-dialog.ui:51 msgid "_Automatically select next message" -msgstr "Bir sonraki iletiyi _kendiliğinden seç" +msgstr "Sonraki iletiyi _kendiliğinden seç" -#: ../ui/preferences-dialog.ui.h:3 +#: ui/preferences-dialog.ui:70 msgid "_Display conversation preview" msgstr "Konuşma ön izlemesini _göster" -#: ../ui/preferences-dialog.ui.h:4 +#: ui/preferences-dialog.ui:89 msgid "Use _three pane view" msgstr "_Üç bölmeli görünümü kullan" -#: ../ui/preferences-dialog.ui.h:5 +#: ui/preferences-dialog.ui:113 msgid "Notifications" msgstr "Bildirimler" -#: ../ui/preferences-dialog.ui.h:6 +#: ui/preferences-dialog.ui:126 msgid "_Play notification sounds" msgstr "Bildirim seslerini _oynat" -#: ../ui/preferences-dialog.ui.h:7 +#: ui/preferences-dialog.ui:145 msgid "Show _notifications for new mail" msgstr "Yeni postalar için _bildirimleri göster" -#: ../ui/preferences-dialog.ui.h:8 -msgid "Always _watch for new mail" -msgstr "Yeni postalar için her zaman g_özetle" - -#: ../ui/preferences-dialog.ui.h:9 -msgid "Geary will run in the background and notify of new mail" -msgstr "Geary arka planda çalışacak ve yeni postayı bildirecek" +#: ui/preferences-dialog.ui:164 +msgid "_Watch for new mail when closed" +msgstr "Kapatıldığında yeni postayı _gözetle" + +#: ui/preferences-dialog.ui:168 +msgid "Geary will keep running after all windows are closed" +msgstr "Geary, tüm pencereler kapatıldıktan sonra çalışmayı sürdürecek" -#: ../ui/preferences-dialog.ui.h:10 +#: ui/preferences-dialog.ui:195 msgid "Preferences" msgstr "Tercihler" -#: ../ui/remove_confirm.glade.h:1 +#. Button label for copying technical information to the clipboard +#: ui/problem-details-dialog.ui:17 +msgid "Copy to Clipboard" +msgstr "Panoya Kopyala" + +#. Button tooltip for copying technical information to the clipboard +#: ui/problem-details-dialog.ui:21 msgid "" -"Are you sure you want to remove this " -"account? " +"Copy technical details to clipboard for pasting into an email or bug report" msgstr "" -"Bu hesabı kaldırmak istediğinize emin " -"misiniz? " +"Teknik ayrıntıları e-postaya veya hata bildirimine yapıştırmak için panoya " +"kopyala" -#: ../ui/remove_confirm.glade.h:2 +#: ui/problem-details-dialog.ui:73 msgid "" -"All email associated with this account will be removed from your computer. " -"This will not affect email on the server." +"If the problem is serious or persists, please copy and send these details to " +"the mailing list " +"or file a new " +"bug report." msgstr "" -"Bu hesapla ilişkili tüm iletiler bilgisayarınızdan kaldırılacak. Bu işlem " -"sunucudaki iletilerinizi etkilemeyecek." +"Sorun ciddi veya kalıcıysa lütfen bu ayrıntıları kopyalayıp postalaşma listesine " +"gönderin veya yeni hata bildiriminde bulunun." -#: ../ui/remove_confirm.glade.h:3 -msgid "Nickname:" -msgstr "Takma ad:" +#: ui/problem-details-dialog.ui:89 +msgid "Details:" +msgstr "Ayrıntılar:" -#: ../ui/remove_confirm.glade.h:4 -msgid "Email address:" -msgstr "E-posta adresi:" - -#: ../ui/upgrade_dialog.glade.h:1 +#: ui/upgrade_dialog.glade:60 msgid "Geary update in progress…" -msgstr "Geary güncellemesi devam ediyor…" +msgstr "Geary güncellemesi sürüyor…" + +#~ msgid "A_ccounts" +#~ msgstr "_Hesaplar" + +#~ msgid "Empty Spam or Trash folders" +#~ msgstr "İstenmeyen ya da Çöp klasörlerini boşalt" + +#~ msgid "Delete conversations (Shift+Delete)" +#~ msgstr "Konuşmaları sil (Shift+Delete)" + +#~ msgid "Move conversations to Trash (Delete, Backspace)" +#~ msgstr "Konuşmaları çöpe taşı (Del tuşu, Silme Tuşu)" + +#~ msgid "Archive conversations (A)" +#~ msgstr "Konuşmaları arşivle (A)" + +#~ msgid "Mark conversations" +#~ msgstr "Konuşmaları imle" + +#~ msgid "Add label to conversations" +#~ msgstr "Konuşmaları etiketle" + +#~ msgid "Move conversations" +#~ msgstr "Konuşmaları taşı" + +#~ msgid "Retry connecting now" +#~ msgstr "Şimdi bağlanmayı yeniden dene" + +#~ msgid "Try reconnecting now" +#~ msgstr "Şimdi yeniden bağlanmayı dene" + +#~ msgid "Problem with connection to incoming server for %s" +#~ msgstr "%s için gelen sunucusuyla bağlantıda sorun" + +#~ msgid "Problem with connection to outgoing server for %s" +#~ msgstr "%s için giden sunucusuyla bağlantıda sorun" + +#~ msgid "To: " +#~ msgstr "Kime: " + +#~ msgid "Cc: " +#~ msgstr "Cc: " + +#~ msgid "Bcc: " +#~ msgstr "Bcc: " + +#~ msgid "From: %s\n" +#~ msgstr "Gönderen: %s\n" + +#~ msgid "Subject: %s\n" +#~ msgstr "Konu: %s\n" + +#~ msgid "Date: %s\n" +#~ msgstr "Tarih: %s\n" + +#~ msgid "To: %s\n" +#~ msgstr "Kime: %s\n" + +#~ msgid "Cc: %s\n" +#~ msgstr "Cc: %s\n" + +#~ msgid "Email address:" +#~ msgstr "E-posta adresi:" + +#~ msgid "Unable to store server trust exception" +#~ msgstr "Sunucu güven ayrıcalığı depolanamadı" + +#~ msgid "" +#~ "Geary encountered an error sending an email. If the problem persists, " +#~ "please manually delete the email from your Outbox folder." +#~ msgstr "" +#~ "Geary e-posta gönderilirken bir hata ile karşılaştı. Eğer sorun sürerse " +#~ "lütfen e-postayı Giden klasöründen elle siliniz." + +#~ msgid "" +#~ "Geary encountered an error saving a sent message to Sent Mail. The " +#~ "message will stay in your Outbox folder until you delete it." +#~ msgstr "" +#~ "Geary, Gönderilmiş Postaya gönderilmiş iletiyi kaydederken bir hata ile " +#~ "karşılaştı. İleti, siz silene kadar Giden klasörünüzde kalacak." + +#~ msgid "Unable to open local mailbox for %s" +#~ msgstr "%s için yerel posta kutusu açılamadı" + +#~ msgid "" +#~ "There was an error opening the local mail database for this account. This " +#~ "is possibly due to a file permissions problem.\n" +#~ "\n" +#~ "Please check that you have read/write permissions for all files in this " +#~ "directory:\n" +#~ "\n" +#~ "%s" +#~ msgstr "" +#~ "Bu hesap için yerel posta veri tabanı açılırken bir hata oluştu. Hata " +#~ "görünüşe bakılırsa dosya izin sorunlarından kaynaklanmaktadır.\n" +#~ "\n" +#~ "Lütfen bu dizindeki tüm dosyalar için okuma/yazma izniniz olup " +#~ "olmadığınızı denetleyin:\n" +#~ "\n" +#~ "%s" + +#~ msgid "" +#~ "The version number of the local mail database is formatted for a newer " +#~ "version of Geary. Unfortunately, the database cannot be “rolled back” to " +#~ "work with this version of Geary.\n" +#~ "\n" +#~ "Please install the latest version of Geary and try again." +#~ msgstr "" +#~ "Yerel posta veri tabanı sürüm numarası daha yeni bir Geary sürümü için " +#~ "biçimlendirildi. Ne yazık ki; veri tabanı, Geary uygulamasının bu sürümü " +#~ "ile çalışabilmesi için “geri alınır” durumda değildir.\n" +#~ "\n" +#~ "Lütfen Geary uygulamasının son sürümünü kurun ve yeniden deneyin." + +#~ msgid "" +#~ "There was an error opening the local account. This is probably due to " +#~ "connectivity issues.\n" +#~ "\n" +#~ "Please check your network connection and restart Geary." +#~ msgstr "" +#~ "Yerel hesap açılırken bir hata oluştu. Hata görünüşe bakılırsa bağlantı " +#~ "sorunları nedeniyle oluştu.\n" +#~ "\n" +#~ "Lütfen ağ bağlantılarınızı denetleyin ve Geary uygulamasını yeniden " +#~ "başlatın." + +#~ msgid "Geary will exit if you have no other open email accounts." +#~ msgstr "Eğer başka açık e-posta hesabı yoksa Geary çıkacak." + +#~ msgid "Additional addresses for %s" +#~ msgstr "%s için ek adresler" + +#~ msgid "First Last" +#~ msgstr "Ad Soyad" + +#~ msgid "Enter your account information to get started." +#~ msgstr "Başlamak için hesap bilginizi giriniz." + +#~ msgid "Edit" +#~ msgstr "Düzenle" + +#~ msgid "Preview" +#~ msgstr "Ön izle" + +#~ msgid "Remem_ber passwords" +#~ msgstr "Parolaları _anımsa" + +#~ msgid "Remem_ber password" +#~ msgstr "Parolayı _anımsa" + +#~ msgid "Unable to validate:\n" +#~ msgstr "Doğrulanamadı:\n" + +#~ msgid " • Invalid account nickname.\n" +#~ msgstr " • Geçersiz hesap takma adı.\n" + +#~ msgid " • Email address already added to Geary.\n" +#~ msgstr " • E-posta adresi zaten Geary’e eklenmiş.\n" + +#~ msgid " • IMAP connection error.\n" +#~ msgstr " • IMAP bağlantı hatası.\n" + +#~ msgid " • IMAP username or password incorrect.\n" +#~ msgstr " • IMAP kullanıcı adı veya parola hatalı.\n" + +#~ msgid " • SMTP connection error.\n" +#~ msgstr " • SMTP bağlantı hatası.\n" + +#~ msgid " • SMTP username or password incorrect.\n" +#~ msgstr " • SMTP kullanıcı adı veya parola hatalı.\n" + +#~ msgid " • Connection error.\n" +#~ msgstr " • Bağlantı hatası.\n" + +#~ msgid " • Username or password incorrect.\n" +#~ msgstr " • Kullanıcı adı veya parola hatalı.\n" + +#~ msgid "Your settings are insecure" +#~ msgstr "Ayarlarınız güvensiz" + +#~ msgid "" +#~ "Your IMAP and/or SMTP settings do not specify SSL or TLS. This means " +#~ "your username and password could be read by another person on the " +#~ "network. Are you sure you want to do this?" +#~ msgstr "" +#~ "IMAP ve/veya SMTP ayarlarınız SSL veya TLS belirtmiyor. Bunun anlamı " +#~ "kullanıcı adınız ve parolanız ağdaki başka biri tarafından okunabilir " +#~ "demektir. Bunu yapmak istediğinize emin misiniz?" + +#~ msgid "Co_ntinue" +#~ msgstr "Devam _et" + +#~ msgid "IMAP" +#~ msgstr "IMAP" + +#~ msgid "SMTP" +#~ msgstr "SMTP" + +#~ msgid "Other" +#~ msgstr "Diğer" + +#~ msgid "Cannot remove account " +#~ msgstr "Hesap kaldırılamadı " + +#~ msgid "" +#~ "A composer window associated with this account is currently open. Send or " +#~ "discard the message and try again." +#~ msgstr "" +#~ "Bu hesapla bağlantılı bir oluşturucu penceresi açık. İletiyi gönderip " +#~ "veya gözden çıkarıp yeniden deneyin." + +#~ msgid "Please wait while Geary validates your account." +#~ msgstr "Lütfen Geary hesabınızı doğrulayana kadar bekleyin." + +#~ msgid "" +#~ "Some email services require additional addresses be configured on the " +#~ "server. Contact your email provider for more information." +#~ msgstr "" +#~ "Bazı e-posta hizmetleri ek adreslerin sunucuda kurulu olmasını " +#~ "gerektirir. Daha çok bilgi için e-posta sağlayıcınıza başvurun." + +#~ msgid "_Update" +#~ msgstr "_Güncelle" + +#~ msgid "E_mail address" +#~ msgstr "E-_posta adresi" + +#~ msgid "_Password" +#~ msgstr "_Parola" + +#~ msgid "S_ervice" +#~ msgstr "H_izmet" + +#~ msgid "N_ame" +#~ msgstr "_Ad" + +#~ msgid "N_ickname" +#~ msgstr "_Takma Ad" + +#~ msgid "Work, Home, etc." +#~ msgstr "İş, Ev, vs." + +#~ msgid "Addi_tional email addresses…" +#~ msgstr "E_k e-posta adresleri…" + +#~ msgid "IMAP settings" +#~ msgstr "IMAP ayarları" + +#~ msgid "Se_rver" +#~ msgstr "Su_nucu" + +#~ msgid "P_ort" +#~ msgstr "Bağlantı N_oktası" + +#~ msgid "Ser_ver" +#~ msgstr "Sun_ucu" + +#~ msgid "Por_t" +#~ msgstr "Bağlan_tı Noktası" + +#~ msgid "User_name" +#~ msgstr "Kullanıcı _adı" + +#~ msgid "Pass_word" +#~ msgstr "Paro_la" + +#~ msgid "SMTP password" +#~ msgstr "SMTP parola" + +#~ msgid "_Username" +#~ msgstr "_Kullanıcı adı" + +#~ msgid "IMAP password" +#~ msgstr "IMAP parola" + +#~ msgid "Encr_yption" +#~ msgstr "Şif_releme" + +#~ msgid "Encrypt_ion" +#~ msgstr "Şifrele_me" + +#~ msgid "STARTTLS" +#~ msgstr "STARTTLS" + +#~ msgid "No authentication re_quired" +#~ msgstr "Kimlik doğrulama ge_rekli değil" + +#~ msgid "Use IMAP cre_dentials" +#~ msgstr "IMAP kim_liğini kullan" + +#~ msgid "Composer" +#~ msgstr "Oluşturucu" + +#~ msgid "Si_gn emails (HTML allowed):" +#~ msgstr "E-postaları im_zala (HTML izinli):" + +#~ msgid "Storage" +#~ msgstr "Depolama" + +#~ msgid "" +#~ "Are you sure you want to remove " +#~ "this account? " +#~ msgstr "" +#~ "Bu hesabı kaldırmak istediğinize " +#~ "emin misiniz? " + +#~ msgid "" +#~ "All email associated with this account will be removed from your " +#~ "computer. This will not affect email on the server." +#~ msgstr "" +#~ "Bu hesapla ilişkili tüm iletiler bilgisayarınızdan kaldırılacak. Bu işlem " +#~ "sunucudaki iletilerinizi etkilemeyecek." + +#~ msgid "Nickname:" +#~ msgstr "Takma ad:" + +#~ msgid "Default attachments directory" +#~ msgstr "Öntanımlı ek dizini" + +#~ msgid "Location used when opening and saving attachments." +#~ msgstr "Ekleri açarken veya kaydederken kullanılan konum." + +#~ msgid "Default print output directory" +#~ msgstr "Öntanımlı yazdırma çıktı dizini" + +#~ msgid "Location used when printing to a file." +#~ msgstr "Dosyaya yazdırılırken kullanılacak konum." + +#~ msgid "Geary will run in the background and notify of new mail" +#~ msgstr "Geary arka planda çalışacak ve yeni postayı bildirecek" + +#~ msgid "" +#~ "Geary encountered an error while connecting to the server. Please try " +#~ "again in a few moments." +#~ msgstr "" +#~ "Geary, sunucuya bağlanırken bir hatayla karşılaştı. Lütfen bir süre " +#~ "sonra yeniden deneyin." + +#~ msgid "Geary Email" +#~ msgstr "Geary E-posta" + +#~ msgid "Geary Mail" +#~ msgstr "Geary Posta" + +#~ msgid "_Mark as…" +#~ msgstr "…olarak _imle" + +#~ msgid "Add label" +#~ msgstr "Etiket ekle" + +#~ msgid "_Label" +#~ msgstr "_Etiketle" + +#~ msgid "_Move" +#~ msgstr "_Taşı" + +#~ msgid "Compose new message (Ctrl+N, N)" +#~ msgstr "Yeni ileti oluştur (Ctrl+N, N)" + +#~ msgid "Reply (Ctrl+R, R)" +#~ msgstr "Yanıtla (Ctrl+R, R)" + +#~ msgid "Reply all (Ctrl+Shift+R, Shift+R)" +#~ msgstr "Tümünü yanıtla (Ctrl+Shift+R, Shift+R)" + +#~ msgid "Forward (Ctrl+L, F)" +#~ msgstr "Yönlendir (Ctrl+L, F)" + +#~ msgid "Try Again" +#~ msgstr "Yeniden Dene" #~ msgid "Mail Client" #~ msgstr "Posta İstemcisi" @@ -2700,12 +3718,6 @@ #~ msgid "No search results found." #~ msgstr "Hiç arama sonucu bulunamadı." -#~ msgid "From:" -#~ msgstr "Gönderen:" - -#~ msgid "Date:" -#~ msgstr "Tarih:" - #~ msgid "Select _Message" #~ msgstr "_İletiyi Seç" @@ -2791,21 +3803,12 @@ #~ msgid "Copyright 2011-2014 Yorba Foundation" #~ msgstr "Telif Hakkı 2011-2014 Yorba Foundation" -#~ msgid "_Delete" -#~ msgstr "_Sil" - -#~ msgid "_Trash" -#~ msgstr "_Çöp" - #~ msgid "_Donate" #~ msgstr "_Bağış Yap" #~ msgid "Do you want to discard the unsaved message?" #~ msgstr "Kaydedilmemiş iletiyi iptal etmek istiyor musunuz?" -#~ msgid "Notify of new mail at start_up" -#~ msgstr "Başlangıç_ta yeni posta bildir" - #~ msgid "Archive conversation (Delete, Backspace, A)" #~ msgstr "Konuşmayı arşivle (Delete, Backspace, A)" @@ -2815,9 +3818,6 @@ #~ msgid "Port:" #~ msgstr "Port:" -#~ msgid "Real name:" -#~ msgstr "Gerçek isim:" - #~ msgid "SSL" #~ msgstr "SSL" @@ -2829,6 +3829,3 @@ #~ msgid "Unable to login to email server" #~ msgstr "E-posta sunucusuna giriş yapılamıyor" - -#~ msgid "_Details" -#~ msgstr "_Ayrıntılar" diff -Nru geary-0.12.4/README.md geary-3.32.0/README.md --- geary-0.12.4/README.md 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/README.md 2019-03-17 13:39:29.000000000 +0000 @@ -2,6 +2,8 @@ Geary: Send and receive email ============================= +![Geary icon](https://wiki.gnome.org/Apps/Geary?action=AttachFile&do=get&target=geary-3-32-256-logo.png) + Geary is an email application built around conversations, for the GNOME 3 desktop. It allows you to read, find and send email with a straightforward, modern interface. @@ -13,7 +15,7 @@ accept tickets or pull requests on GitHub. Please see the links below for more information. -![Geary displaying a conversation](https://wiki.gnome.org/Apps/Geary?action=AttachFile&do=get&target=geary-conversation-0.12.png) +![Geary displaying a conversation](https://wiki.gnome.org/Apps/Geary?action=AttachFile&do=get&target=geary-3-32-main-window.png) Installation & Licensing ------------------------ diff -Nru geary-0.12.4/sql/CMakeLists.txt geary-3.32.0/sql/CMakeLists.txt --- geary-0.12.4/sql/CMakeLists.txt 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/sql/CMakeLists.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,27 +0,0 @@ -set(SQL_DEST share/geary/sql) - -install(FILES version-001.sql DESTINATION ${SQL_DEST}) -install(FILES version-002.sql DESTINATION ${SQL_DEST}) -install(FILES version-003.sql DESTINATION ${SQL_DEST}) -install(FILES version-004.sql DESTINATION ${SQL_DEST}) -install(FILES version-005.sql DESTINATION ${SQL_DEST}) -install(FILES version-006.sql DESTINATION ${SQL_DEST}) -install(FILES version-007.sql DESTINATION ${SQL_DEST}) -install(FILES version-008.sql DESTINATION ${SQL_DEST}) -install(FILES version-009.sql DESTINATION ${SQL_DEST}) -install(FILES version-010.sql DESTINATION ${SQL_DEST}) -install(FILES version-011.sql DESTINATION ${SQL_DEST}) -install(FILES version-012.sql DESTINATION ${SQL_DEST}) -install(FILES version-013.sql DESTINATION ${SQL_DEST}) -install(FILES version-014.sql DESTINATION ${SQL_DEST}) -install(FILES version-015.sql DESTINATION ${SQL_DEST}) -install(FILES version-016.sql DESTINATION ${SQL_DEST}) -install(FILES version-017.sql DESTINATION ${SQL_DEST}) -install(FILES version-018.sql DESTINATION ${SQL_DEST}) -install(FILES version-019.sql DESTINATION ${SQL_DEST}) -install(FILES version-020.sql DESTINATION ${SQL_DEST}) -install(FILES version-021.sql DESTINATION ${SQL_DEST}) -install(FILES version-022.sql DESTINATION ${SQL_DEST}) -install(FILES version-023.sql DESTINATION ${SQL_DEST}) -install(FILES version-024.sql DESTINATION ${SQL_DEST}) -install(FILES version-025.sql DESTINATION ${SQL_DEST}) diff -Nru geary-0.12.4/sql/meson.build geary-3.32.0/sql/meson.build --- geary-0.12.4/sql/meson.build 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/sql/meson.build 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,31 @@ +sql_files = [ + 'version-001.sql', + 'version-002.sql', + 'version-003.sql', + 'version-004.sql', + 'version-005.sql', + 'version-006.sql', + 'version-007.sql', + 'version-008.sql', + 'version-009.sql', + 'version-010.sql', + 'version-011.sql', + 'version-012.sql', + 'version-013.sql', + 'version-014.sql', + 'version-015.sql', + 'version-016.sql', + 'version-017.sql', + 'version-018.sql', + 'version-019.sql', + 'version-020.sql', + 'version-021.sql', + 'version-022.sql', + 'version-023.sql', + 'version-024.sql', + 'version-025.sql', +] + +install_data(sql_files, + install_dir: join_paths(datadir, meson.project_name(), 'sql') +) diff -Nru geary-0.12.4/src/client/accounts/account-dialog-account-list-pane.vala geary-3.32.0/src/client/accounts/account-dialog-account-list-pane.vala --- geary-0.12.4/src/client/accounts/account-dialog-account-list-pane.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/accounts/account-dialog-account-list-pane.vala 1970-01-01 00:00:00.000000000 +0000 @@ -1,231 +0,0 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. - * - * This software is licensed under the GNU Lesser General Public License - * (version 2.1 or later). See the COPYING file in this distribution. - */ - -// List of accounts. Used with AccountDialog. -public class AccountDialogAccountListPane : AccountDialogPane { - public enum Column { - ACCOUNT_ID = 0, - ACCOUNT_NAME, - ACCOUNT_ADDRESS; - } - - private Gtk.TreeView list_view; - private Gtk.ListStore list_model = new Gtk.ListStore(3, typeof(string), typeof(string), typeof(string)); - private Gtk.Action edit_action; - private Gtk.Action delete_action; - - public signal void add_account(); - - public signal void edit_account(string id); - - public signal void delete_account(string id); - - public AccountDialogAccountListPane(Gtk.Stack stack) { - base(stack); - Gtk.Builder builder = GearyApplication.instance.create_builder("account_list.glade"); - pack_end((Gtk.Box) builder.get_object("container")); - Gtk.ActionGroup actions = (Gtk.ActionGroup) builder.get_object("account list actions"); - edit_action = actions.get_action("edit_account"); - delete_action = actions.get_action("delete_account"); - - // Set up list. - list_view = (Gtk.TreeView) builder.get_object("account_list"); - list_view.set_model(list_model); - list_view.insert_column_with_attributes(-1, "Name", new Gtk.CellRendererText(), "text", - Column.ACCOUNT_NAME); - list_view.get_column(0).set_expand(true); - list_view.insert_column_with_attributes(-1, "Email", new Gtk.CellRendererText(), "text", - Column.ACCOUNT_ADDRESS); - list_view.get_column(1).set_expand(true); - list_view.reorderable = true; - - // Get all accounts and add them to a list. - Gee.LinkedList account_list = - new Gee.LinkedList(); - try { - account_list.insert_all(0, Geary.Engine.instance.get_accounts().values); - } catch (Error e) { - debug("Error enumerating accounts: %s", e.message); - } - - // Sort accounts and add them to the UI. - account_list.sort(Geary.AccountInformation.compare_ascending); - foreach (Geary.AccountInformation account in account_list) - on_account_added(account); - - // Hook up signals. - actions.get_action("add_account").activate.connect(() => { add_account(); }); - edit_action.activate.connect(notify_edit_account); - delete_action.activate.connect(notify_delete_account); - list_view.get_selection().changed.connect(update_buttons); - list_view.button_press_event.connect(on_button_press); - list_model.row_deleted.connect(update_ordinals); - - // Theme hint: "join" the toolbar to the scrolled window above it. - Gtk.Toolbar toolbar = (Gtk.Toolbar) builder.get_object("toolbar"); - Gtk.ScrolledWindow scroll = (Gtk.ScrolledWindow) builder.get_object("scrolledwindow"); - toolbar.get_style_context().set_junction_sides(Gtk.JunctionSides.TOP); - scroll.get_style_context().set_junction_sides(Gtk.JunctionSides.BOTTOM); - - // Watch for accounts to be added/removed. - Geary.Engine.instance.account_added.connect(on_account_added); - Geary.Engine.instance.account_removed.connect(on_account_removed); - } - - private void notify_edit_account() { - string? account = get_selected_account(); - if (account != null) - edit_account(account); - } - - private void notify_delete_account() { - string? account = get_selected_account(); - if (account != null) - delete_account(account); - } - - private bool on_button_press(Gdk.EventButton event) { - if (event.type != Gdk.EventType.2BUTTON_PRESS) - return false; - - // Get the path. - int cell_x; - int cell_y; - Gtk.TreePath? path; - list_view.get_path_at_pos((int) event.x, (int) event.y, out path, null, out cell_x, out cell_y); - if (path == null) - return false; - - // If the user didn't click on an element in the list, we've already returned. - notify_edit_account(); - return true; - } - - // Returns the id of the selected account. Returns null if no account is selected. - private string? get_selected_account() { - if (list_view.get_selection().count_selected_rows() != 1) - return null; - - Gtk.TreeModel model; - Gtk.TreeIter iter; - Gtk.TreePath path = list_view.get_selection().get_selected_rows(out model).nth_data(0); - if (!list_model.get_iter(out iter, path)) - return null; - - string? account = null; - list_model.get(iter, Column.ACCOUNT_ID, out account); - return account; - } - - private void update_buttons() { - edit_action.sensitive = get_selected_account() != null; - delete_action.sensitive = edit_action.sensitive && - GearyApplication.instance.controller.get_num_accounts() > 1; - } - - private void on_account_added(Geary.AccountInformation account) { - Gtk.TreeIter? iter = list_contains(account.id); - if (iter != null) - return; // Already listed. - - add_account_to_list(account); - account.notify.connect(on_account_changed); - update_buttons(); - update_ordinals(); - } - - private void on_account_removed(Geary.AccountInformation account) { - remove_account_from_list(account.id); - account.notify.disconnect(on_account_changed); - update_buttons(); - update_ordinals(); - } - - // Adds an account to the list. - // Note: does NOT check if the account is already listed. - private void add_account_to_list(Geary.AccountInformation account) { - Gtk.TreeIter iter; - list_model.append(out iter); - list_model.set(iter, Column.ACCOUNT_ID, account.id); - list_model.set(iter, Column.ACCOUNT_NAME, account.display_name); - list_model.set(iter, Column.ACCOUNT_ADDRESS, account.primary_mailbox.address); - } - - // Removes an account on the list. - private void remove_account_from_list(string id) { - Gtk.TreeIter? iter = list_contains(id); - if (iter == null) - return; - -#if VALA_0_36 - list_model.remove(ref iter); -#else - list_model.remove(iter); -#endif - } - - private void on_account_changed(Object object, ParamSpec p) { - Geary.AccountInformation account = (Geary.AccountInformation) object; - - Gtk.TreeIter? iter = list_contains(account.id); - if (iter == null) - return; - - list_model.set_value(iter, Column.ACCOUNT_NAME, account.display_name); - list_model.set_value(iter, Column.ACCOUNT_ADDRESS, account.primary_mailbox.address); - } - - // Returns TreeIter of the id in the account list, else null. - private Gtk.TreeIter? list_contains(string id) { - Gtk.TreeIter iter; - - if (!list_model.get_iter_first(out iter)) - return null; - - do { - string list_id = ""; - list_model.get(iter, Column.ACCOUNT_ID, out list_id); - if (list_id == id) - return iter; - } while (list_model.iter_next(ref iter)); - - return null; - } - - // Call this to update ordinals when rows are added or removed. - private void update_ordinals() { - Gtk.TreeIter iter; - if (!list_model.get_iter_first(out iter)) - return; - - Gee.Map all_accounts; - try { - all_accounts = Geary.Engine.instance.get_accounts(); - } catch (Error e) { - debug("Error enumerating accounts: %s", e.message); - - return; - } - - int i = 0; - do { - string? list_id = null; - list_model.get(iter, Column.ACCOUNT_ID, out list_id); - if (list_id != null) { - Geary.AccountInformation account = all_accounts.get(list_id); - - // To prevent unnecessary work, only set ordinal if there's a change. - if (i != account.ordinal) { - account.ordinal = i; - account.store_async.begin(null); - } - } - - i++; - } while (list_model.iter_next(ref iter)); - } -} - diff -Nru geary-0.12.4/src/client/accounts/account-dialog-add-edit-pane.vala geary-3.32.0/src/client/accounts/account-dialog-add-edit-pane.vala --- geary-0.12.4/src/client/accounts/account-dialog-add-edit-pane.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/accounts/account-dialog-add-edit-pane.vala 1970-01-01 00:00:00.000000000 +0000 @@ -1,86 +0,0 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. - * - * This software is licensed under the GNU Lesser General Public License - * (version 2.1 or later). See the COPYING file in this distribution. - */ - -// Add or edit an account. Used with AccountDialog. -public class AccountDialogAddEditPane : AccountDialogPane { - public AddEditPage add_edit_page { get; private set; default = new AddEditPage(); } - private Gtk.ButtonBox button_box = new Gtk.ButtonBox(Gtk.Orientation.HORIZONTAL); - private Gtk.Button ok_button = new Gtk.Button.with_mnemonic(Stock._OK); - private Gtk.Button cancel_button = new Gtk.Button.with_mnemonic(Stock._CANCEL); - - public signal void ok(Geary.AccountInformation info); - - public signal void cancel(); - - public signal void size_changed(); - - public signal void edit_alternate_emails(string id); - - public AccountDialogAddEditPane(Gtk.Stack stack) { - base(stack); - - button_box.set_layout(Gtk.ButtonBoxStyle.END); - button_box.expand = false; - button_box.spacing = 6; - button_box.pack_start(cancel_button, false, false, 0); - button_box.pack_start(ok_button, false, false, 0); - ok_button.can_default = true; - - add_edit_page.info_changed.connect(on_info_changed); - - // Since we're not yet in a window, we have to wait before setting the default action. - realize.connect(() => { ok_button.has_default = true; }); - - ok_button.clicked.connect(on_ok); - cancel_button.clicked.connect(() => { cancel(); }); - - add_edit_page.size_changed.connect(() => { size_changed(); }); - add_edit_page.edit_alternate_emails.connect(() => { edit_alternate_emails(add_edit_page.id); }); - - pack_start(add_edit_page); - pack_start(button_box, false, false); - - // Default mode is Welcome. - set_mode(AddEditPage.PageMode.WELCOME); - } - - public void set_mode(AddEditPage.PageMode mode) { - ok_button.label = (mode == AddEditPage.PageMode.EDIT) ? _("_Save") : _("_Add"); - add_edit_page.set_mode(mode); - } - - public AddEditPage.PageMode get_mode() { - return add_edit_page.get_mode(); - } - - public void set_account_information(Geary.AccountInformation info, - Geary.Engine.ValidationResult result = Geary.Engine.ValidationResult.OK) { - add_edit_page.set_account_information(info, result); - } - - public void set_validation_result(Geary.Engine.ValidationResult result) { - add_edit_page.set_validation_result(result); - } - - public void reset_all() { - add_edit_page.reset_all(); - } - - private void on_ok() { - ok(add_edit_page.get_account_information()); - } - - public override void present() { - base.present(); - add_edit_page.update_ui(); - on_info_changed(); - } - - private void on_info_changed() { - ok_button.has_default = ok_button.sensitive = add_edit_page.is_complete(); - } -} - diff -Nru geary-0.12.4/src/client/accounts/account-dialog-edit-alternate-emails-pane.vala geary-3.32.0/src/client/accounts/account-dialog-edit-alternate-emails-pane.vala --- geary-0.12.4/src/client/accounts/account-dialog-edit-alternate-emails-pane.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/accounts/account-dialog-edit-alternate-emails-pane.vala 1970-01-01 00:00:00.000000000 +0000 @@ -1,201 +0,0 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. - * - * This software is licensed under the GNU Lesser General Public License - * (version 2.1 or later). See the COPYING file in this distribution. - */ - -public class AccountDialogEditAlternateEmailsPane : AccountDialogPane { - private class ListItem : Gtk.Label { - public Geary.RFC822.MailboxAddress mailbox; - - public ListItem(Geary.RFC822.MailboxAddress mailbox) { - this.mailbox = mailbox; - - label = "%s".printf(Geary.HTML.escape_markup(mailbox.get_full_address())); - use_markup = true; - ellipsize = Pango.EllipsizeMode.END; - GtkUtil.set_label_xalign(this, 0.0f); - } - } - - public bool changed { get; private set; default = false; } - - private Gtk.Label title_label; - private Gtk.Entry email_entry; - private Gtk.Button add_button; - private Gtk.ListBox address_listbox; - private Gtk.ToolButton delete_button; - private Gtk.Button cancel_button; - private Gtk.Button update_button; - private ListItem? selected_item = null; - - private Geary.AccountInformation? account_info = null; - private Geary.RFC822.MailboxAddress? primary_mailbox = null; - private Gee.HashSet mailboxes = new Gee.HashSet(); - - public signal void done(); - - public AccountDialogEditAlternateEmailsPane(Gtk.Stack stack) { - base (stack); - - Gtk.Builder builder = GearyApplication.instance.create_builder("edit_alternate_emails.glade"); - - // Primary container - pack_start((Gtk.Widget) builder.get_object("container")); - - title_label = (Gtk.Label) builder.get_object("title_label"); - email_entry = (Gtk.Entry) builder.get_object("email_entry"); - add_button = (Gtk.Button) builder.get_object("add_button"); - address_listbox = (Gtk.ListBox) builder.get_object("address_listbox"); - delete_button = (Gtk.ToolButton) builder.get_object("delete_button"); - cancel_button = (Gtk.Button) builder.get_object("cancel_button"); - update_button = (Gtk.Button) builder.get_object("update_button"); - - // Clear text when the secondary icon (not always available) is pressed - email_entry.icon_release.connect((pos) => { - if (pos == Gtk.EntryIconPosition.SECONDARY) - email_entry.text = ""; - }); - - email_entry.bind_property("text", add_button, "sensitive", BindingFlags.SYNC_CREATE, - transform_email_to_sensitive); - email_entry.notify["text-length"].connect(on_email_entry_text_length_changed); - bind_property("changed", update_button, "sensitive", BindingFlags.SYNC_CREATE); - - delete_button.sensitive = false; - - address_listbox.row_selected.connect(on_row_selected); - add_button.clicked.connect(on_add_clicked); - delete_button.clicked.connect(on_delete_clicked); - cancel_button.clicked.connect(() => { done(); }); - update_button.clicked.connect(on_update_clicked); - } - - private bool validate_address_text(string email_address, out Geary.RFC822.MailboxAddress? parsed) { - parsed = null; - - Geary.RFC822.MailboxAddresses mailboxes = new Geary.RFC822.MailboxAddresses.from_rfc822_string( - email_address); - if (mailboxes.size != 1) - return false; - - Geary.RFC822.MailboxAddress mailbox = mailboxes.get(0); - - if (!mailbox.is_valid()) - return false; - - if (Geary.String.stri_equal(mailbox.address, primary_mailbox.address)) - return false; - - if (Geary.String.is_empty(mailbox.address)) - return false; - - parsed = mailbox; - - return true; - } - - private bool transform_email_to_sensitive(Binding binding, Value source, ref Value target) { - Geary.RFC822.MailboxAddress? parsed; - target = validate_address_text(email_entry.text, out parsed) && !mailboxes.contains(parsed); - - return true; - } - - private void on_email_entry_text_length_changed() { - bool has_text = email_entry.text_length != 0; - - email_entry.secondary_icon_name = has_text ? "edit-clear-symbolic" : null; - email_entry.secondary_icon_sensitive = has_text; - email_entry.secondary_icon_activatable = has_text; - } - - public void set_account(Geary.AccountInformation account_info) { - this.account_info = account_info; - this.primary_mailbox = account_info.primary_mailbox; - this.mailboxes.clear(); - this.changed = false; - - // reset/clear widgets - this.title_label.label = _("Additional addresses for %s").printf(this.account_info.display_name); - this.email_entry.text = ""; - - // clear listbox - foreach (Gtk.Widget widget in this.address_listbox.get_children()) - address_listbox.remove(widget); - - // Add all email addresses; add_email_address() silently drops the primary address - foreach (Geary.RFC822.MailboxAddress mailbox in account_info.get_all_mailboxes()) - add_mailbox(mailbox, false); - } - - public override void present() { - base.present(); - - // because in a Gtk.Stack, need to do this manually after presenting - email_entry.grab_focus(); - add_button.has_default = true; - } - - private void add_mailbox(Geary.RFC822.MailboxAddress mailbox, bool is_change) { - if (mailboxes.contains(mailbox) || primary_mailbox.equal_to(mailbox)) - return; - - mailboxes.add(mailbox); - - ListItem item = new ListItem(mailbox); - item.show_all(); - address_listbox.add(item); - - if (is_change) - changed = true; - } - - private void remove_mailbox(Geary.RFC822.MailboxAddress address) { - if (!mailboxes.remove(address)) - return; - - foreach (Gtk.Widget widget in address_listbox.get_children()) { - Gtk.ListBoxRow row = (Gtk.ListBoxRow) widget; - ListItem item = (ListItem) row.get_child(); - - if (item.mailbox.equal_to(address)) { - address_listbox.remove(widget); - - changed = true; - - break; - } - } - } - - private void on_row_selected(Gtk.ListBoxRow? row) { - selected_item = (row != null) ? (ListItem) row.get_child() : null; - delete_button.sensitive = (selected_item != null); - } - - private void on_add_clicked() { - Geary.RFC822.MailboxAddress? parsed; - if (!validate_address_text(email_entry.text, out parsed) || parsed == null) - return; - - add_mailbox(parsed, true); - - // reset state for next input - email_entry.text = ""; - email_entry.grab_focus(); - add_button.has_default = true; - } - - private void on_delete_clicked() { - if (selected_item != null) - remove_mailbox(selected_item.mailbox); - } - - private void on_update_clicked() { - account_info.replace_alternate_mailboxes(mailboxes); - - done(); - } -} - diff -Nru geary-0.12.4/src/client/accounts/account-dialog-pane.vala geary-3.32.0/src/client/accounts/account-dialog-pane.vala --- geary-0.12.4/src/client/accounts/account-dialog-pane.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/accounts/account-dialog-pane.vala 1970-01-01 00:00:00.000000000 +0000 @@ -1,23 +0,0 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. - * - * This software is licensed under the GNU Lesser General Public License - * (version 2.1 or later). See the COPYING file in this distribution. - */ - -// Base class for account dialog panes. -// Could be factored into a generic "StackPage" class if needed. -public class AccountDialogPane : Gtk.Box { - private weak Gtk.Stack parent_stack; - - public class AccountDialogPane(Gtk.Stack parent_stack) { - Object(orientation: Gtk.Orientation.VERTICAL, spacing: 4); - - this.parent_stack = parent_stack; - parent_stack.add(this); - } - - public virtual void present() { - parent_stack.set_visible_child(this); - } -} - diff -Nru geary-0.12.4/src/client/accounts/account-dialog-remove-confirm-pane.vala geary-3.32.0/src/client/accounts/account-dialog-remove-confirm-pane.vala --- geary-0.12.4/src/client/accounts/account-dialog-remove-confirm-pane.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/accounts/account-dialog-remove-confirm-pane.vala 1970-01-01 00:00:00.000000000 +0000 @@ -1,37 +0,0 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. - * - * This software is licensed under the GNU Lesser General Public License - * (version 2.1 or later). See the COPYING file in this distribution. - */ - -// Confirmation of the deletion of an account -public class AccountDialogRemoveConfirmPane : AccountDialogPane { - private Geary.AccountInformation? account = null; - private Gtk.Label account_nickname_label; - private Gtk.Label email_address_label; - - public signal void ok(Geary.AccountInformation? account); - - public signal void cancel(); - - public AccountDialogRemoveConfirmPane(Gtk.Stack stack) { - base(stack); - - Gtk.Builder builder = GearyApplication.instance.create_builder("remove_confirm.glade"); - pack_end((Gtk.Box) builder.get_object("container")); - Gtk.ActionGroup actions = (Gtk.ActionGroup) builder.get_object("actions"); - account_nickname_label = (Gtk.Label) builder.get_object("account_nickname_label"); - email_address_label = (Gtk.Label) builder.get_object("email_address_label"); - - // Hook up signals. - actions.get_action("cancel_action").activate.connect(() => { cancel(); }); - actions.get_action("remove_action").activate.connect(() => { ok(account); }); - } - - public void set_account(Geary.AccountInformation a) { - this.account = a; - account_nickname_label.label = a.nickname; - email_address_label.label = a.primary_mailbox.address; - } -} - diff -Nru geary-0.12.4/src/client/accounts/account-dialog-remove-fail-pane.vala geary-3.32.0/src/client/accounts/account-dialog-remove-fail-pane.vala --- geary-0.12.4/src/client/accounts/account-dialog-remove-fail-pane.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/accounts/account-dialog-remove-fail-pane.vala 1970-01-01 00:00:00.000000000 +0000 @@ -1,20 +0,0 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. - * - * This software is licensed under the GNU Lesser General Public License - * (version 2.1 or later). See the COPYING file in this distribution. - */ - -// Lets user know that account removal cannot be completed.. -public class AccountDialogRemoveFailPane : AccountDialogPane { - public signal void ok(); - - public AccountDialogRemoveFailPane(Gtk.Stack stack) { - base(stack); - - Gtk.Builder builder = GearyApplication.instance.create_builder("account_cannot_remove.glade"); - pack_end((Gtk.Box) builder.get_object("container")); - Gtk.ActionGroup actions = (Gtk.ActionGroup) builder.get_object("actions"); - actions.get_action("ok_action").activate.connect(() => { ok(); }); - } -} - diff -Nru geary-0.12.4/src/client/accounts/account-dialog-spinner-pane.vala geary-3.32.0/src/client/accounts/account-dialog-spinner-pane.vala --- geary-0.12.4/src/client/accounts/account-dialog-spinner-pane.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/accounts/account-dialog-spinner-pane.vala 1970-01-01 00:00:00.000000000 +0000 @@ -1,15 +0,0 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. - * - * This software is licensed under the GNU Lesser General Public License - * (version 2.1 or later). See the COPYING file in this distribution. - */ - -// Lets user know that account removal cannot be completed.. -public class AccountDialogSpinnerPane : AccountDialogPane { - public AccountDialogSpinnerPane(Gtk.Stack stack) { - base(stack); - - pack_end(new AccountSpinnerPage()); - } -} - diff -Nru geary-0.12.4/src/client/accounts/account-dialog.vala geary-3.32.0/src/client/accounts/account-dialog.vala --- geary-0.12.4/src/client/accounts/account-dialog.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/accounts/account-dialog.vala 1970-01-01 00:00:00.000000000 +0000 @@ -1,218 +0,0 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. - * - * This software is licensed under the GNU Lesser General Public License - * (version 2.1 or later). See the COPYING file in this distribution. - */ - -public class AccountDialog : Gtk.Dialog { - private const int MARGIN = 12; - - private Gtk.Stack stack = new Gtk.Stack(); - private AccountDialogAccountListPane account_list_pane; - private AccountDialogAddEditPane add_edit_pane; - private AccountDialogSpinnerPane spinner_pane; - private AccountDialogRemoveConfirmPane remove_confirm_pane; - private AccountDialogRemoveFailPane remove_fail_pane; - private AccountDialogEditAlternateEmailsPane edit_alternate_emails_pane; - private Gtk.HeaderBar headerbar = new Gtk.HeaderBar(); - - public AccountDialog(Gtk.Window parent) { - set_size_request(450, -1); // Sets min size. - headerbar.title = _("Accounts"); - headerbar.show_close_button = true; - set_transient_for(parent); - set_modal(true); - set_titlebar (headerbar); - get_content_area().margin_top = MARGIN; - get_content_area().margin_start = MARGIN; - get_content_area().margin_end = MARGIN; - get_content_area().margin_bottom = MARGIN; - - // Add pages to stack. - account_list_pane = new AccountDialogAccountListPane(stack); - add_edit_pane = new AccountDialogAddEditPane(stack); - spinner_pane = new AccountDialogSpinnerPane(stack); - remove_confirm_pane = new AccountDialogRemoveConfirmPane(stack); - remove_fail_pane = new AccountDialogRemoveFailPane(stack); - edit_alternate_emails_pane = new AccountDialogEditAlternateEmailsPane(stack); - - // Connect signals from pages. - account_list_pane.add_account.connect(on_add_account); - account_list_pane.edit_account.connect(on_edit_account); - account_list_pane.delete_account.connect(on_delete_account); - add_edit_pane.ok.connect(on_save_add_or_edit); - add_edit_pane.cancel.connect(on_cancel_back_to_list); - add_edit_pane.size_changed.connect(() => { resize(1, 1); }); - add_edit_pane.edit_alternate_emails.connect(on_edit_alternate_emails); - remove_confirm_pane.ok.connect(on_delete_account_confirmed); - remove_confirm_pane.cancel.connect(on_cancel_back_to_list); - remove_fail_pane.ok.connect(on_cancel_back_to_list); - edit_alternate_emails_pane.done.connect(on_done_back_to_editor); - - // Set default page. - account_list_pane.present(); - - get_content_area().pack_start(stack, true, true, 0); - - set_default_response(Gtk.ResponseType.OK); - - } - - private void on_add_account() { - add_edit_pane.reset_all(); - add_edit_pane.set_mode(AddEditPage.PageMode.ADD); - add_edit_pane.present(); - } - - // Grab the account info. While the addresses passed into this method should *always* be - // available in Geary, we double-check to be defensive. - private Geary.AccountInformation? get_account_info(string id) { - Gee.Map accounts; - try { - accounts = Geary.Engine.instance.get_accounts(); - } catch (Error e) { - debug("Error getting account info: %s", e.message); - - return null; - } - - if (!accounts.has_key(id)) { - debug("No such account: %s", id); - - return null; - } - - return accounts.get(id); - } - - private void on_edit_account(string id) { - on_edit_account_async.begin(id); - } - - private async void on_edit_account_async(string id) { - Geary.AccountInformation? account = get_account_info(id); - if (account == null) - return; - - try { - yield account.get_passwords_async(Geary.ServiceFlag.IMAP | Geary.ServiceFlag.SMTP); - } catch (Error err) { - debug("Unable to fetch password(s) for account: %s", err.message); - } - - add_edit_pane.set_mode(AddEditPage.PageMode.EDIT); - add_edit_pane.set_account_information(account); - add_edit_pane.present(); - } - - private void on_delete_account(string id) { - Geary.AccountInformation? account = get_account_info(id); - if (account == null) - return; - - // Check for open composer windows. - bool composer_widget_found = false; - Gee.List? widgets = - GearyApplication.instance.controller.get_composer_widgets_for_account(account); - - if (widgets != null) { - foreach (ComposerWidget cw in widgets) { - if (cw.account.information == account && - cw.compose_type != ComposerWidget.ComposeType.NEW_MESSAGE) { - composer_widget_found = true; - - break; - } - } - } - - if (composer_widget_found) { - // Warn user that account cannot be deleted until composer is closed. - remove_fail_pane.present(); - } else { - // Send user to confirmation screen. - remove_confirm_pane.set_account(account); - remove_confirm_pane.present(); - } - } - - private void on_edit_alternate_emails(string id) { - Geary.AccountInformation? account_info = get_account_info(id); - if (account_info == null) - return; - - edit_alternate_emails_pane.set_account(account_info); - edit_alternate_emails_pane.present(); - } - - private void on_delete_account_confirmed(Geary.AccountInformation? account) { - assert(account != null); // Should not be able to happen since we checked earlier. - - // Remove account, then set the page back to the account list. - GearyApplication.instance.controller.remove_account_async.begin(account, null, () => { - account_list_pane.present(); }); - } - - private void on_save_add_or_edit(Geary.AccountInformation info) { - // Show the busy spinner. - spinner_pane.present(); - - // determine if editing an existing Account or adding a new one - Geary.Engine.ValidationOption options = (add_edit_pane.get_mode() == AddEditPage.PageMode.EDIT) - ? Geary.Engine.ValidationOption.UPDATING_EXISTING - : Geary.Engine.ValidationOption.NONE; - - // For account edits, we only need to validate the connection if the credentials have changed. - bool validate_connection = true; - if (add_edit_pane.get_mode() == AddEditPage.PageMode.EDIT && info.is_copy()) { - Geary.AccountInformation? real_info = - GearyApplication.instance.controller.get_real_account_information(info); - if (real_info != null) { - validate_connection = !real_info.imap_credentials.equal_to(info.imap_credentials) || - (info.smtp_credentials != null && !real_info.smtp_credentials.equal_to(info.smtp_credentials)); - } - } - - if (validate_connection) - options |= Geary.Engine.ValidationOption.CHECK_CONNECTIONS; - - // Validate account. - do_save_or_edit_async.begin(info, options); - } - - private async void do_save_or_edit_async(Geary.AccountInformation account_information, - Geary.Engine.ValidationOption options) { - Geary.Engine.ValidationResult validation_result = Geary.Engine.ValidationResult.OK; - for (;;) { - validation_result = yield GearyApplication.instance.controller.validate_async( - account_information, options); - - // If account was successfully added return to the account list. - if (validation_result == Geary.Engine.ValidationResult.OK) { - account_list_pane.present(); - - return; - } - - // check for TLS warnings - bool retry_required; - validation_result = yield GearyApplication.instance.controller.validation_check_for_tls_warnings_async( - account_information, validation_result, out retry_required); - if (!retry_required) - break; - } - - // Otherwise, go back to the account add page so the user can try again. - add_edit_pane.set_validation_result(validation_result); - add_edit_pane.present(); - } - - private void on_cancel_back_to_list() { - account_list_pane.present(); - } - - private void on_done_back_to_editor() { - add_edit_pane.present(); - } -} - diff -Nru geary-0.12.4/src/client/accounts/accounts-editor-add-pane.vala geary-3.32.0/src/client/accounts/accounts-editor-add-pane.vala --- geary-0.12.4/src/client/accounts/accounts-editor-add-pane.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/src/client/accounts/accounts-editor-add-pane.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,670 @@ +/* + * Copyright 2018-2019 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +/** + * An account editor pane for adding a new account. + */ +[GtkTemplate (ui = "/org/gnome/Geary/accounts_editor_add_pane.ui")] +internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane { + + + internal Gtk.Widget initial_widget { + get { return this.real_name.value; } + } + + /** {@inheritDoc} */ + internal bool is_operation_running { + get { return !this.sensitive; } + protected set { update_operation_ui(value); } + } + + /** {@inheritDoc} */ + internal GLib.Cancellable? op_cancellable { + get; protected set; default = new GLib.Cancellable(); + } + + protected weak Accounts.Editor editor { get; set; } + + private Geary.ServiceProvider provider; + + private Manager accounts; + private Geary.Engine engine; + + + [GtkChild] + private Gtk.HeaderBar header; + + [GtkChild] + private Gtk.Grid pane_content; + + [GtkChild] + private Gtk.Adjustment pane_adjustment; + + [GtkChild] + private Gtk.ListBox details_list; + + [GtkChild] + private Gtk.Grid receiving_panel; + + [GtkChild] + private Gtk.ListBox receiving_list; + + [GtkChild] + private Gtk.Grid sending_panel; + + [GtkChild] + private Gtk.ListBox sending_list; + + [GtkChild] + private Gtk.Button create_button; + + [GtkChild] + private Gtk.Button back_button; + + [GtkChild] + private Gtk.Spinner create_spinner; + + private NameRow real_name; + private EmailRow email = new EmailRow(); + private string last_valid_email = ""; + + private HostnameRow imap_hostname = new HostnameRow(Geary.Protocol.IMAP); + private TransportSecurityRow imap_tls = new TransportSecurityRow(); + private LoginRow imap_login = new LoginRow(); + private PasswordRow imap_password = new PasswordRow(); + + private HostnameRow smtp_hostname = new HostnameRow(Geary.Protocol.SMTP); + private TransportSecurityRow smtp_tls = new TransportSecurityRow(); + private OutgoingAuthRow smtp_auth = new OutgoingAuthRow(); + private LoginRow smtp_login = new LoginRow(); + private PasswordRow smtp_password = new PasswordRow(); + + private bool controls_valid = false; + + + internal EditorAddPane(Editor editor, Geary.ServiceProvider provider) { + this.editor = editor; + this.provider = provider; + + GearyApplication application = (GearyApplication) editor.application; + this.accounts = application.controller.account_manager; + this.engine = application.engine; + + this.pane_content.set_focus_vadjustment(this.pane_adjustment); + + this.details_list.set_header_func(Editor.seperator_headers); + this.receiving_list.set_header_func(Editor.seperator_headers); + this.sending_list.set_header_func(Editor.seperator_headers); + + if (provider != Geary.ServiceProvider.OTHER) { + this.details_list.add( + new ServiceProviderRow( + provider, + // Translators: Label for adding an email account + // account for a generic IMAP service provider. + _("All others") + ) + ); + this.receiving_panel.hide(); + this.sending_panel.hide(); + } + + this.real_name = new NameRow(this.accounts.get_account_name()); + + this.details_list.add(this.real_name); + this.details_list.add(this.email); + + this.real_name.validator.state_changed.connect(on_validated); + this.real_name.value.activate.connect(on_activated); + this.email.validator.state_changed.connect(on_validated); + this.email.value.activate.connect(on_activated); + this.email.value.changed.connect(on_email_changed); + + this.imap_hostname.validator.state_changed.connect(on_validated); + this.imap_hostname.value.activate.connect(on_activated); + this.imap_tls.hide(); + this.imap_login.validator.state_changed.connect(on_validated); + this.imap_login.value.activate.connect(on_activated); + this.imap_password.validator.state_changed.connect(on_validated); + this.imap_password.value.activate.connect(on_activated); + + this.smtp_hostname.validator.state_changed.connect(on_validated); + this.smtp_hostname.value.activate.connect(on_activated); + this.smtp_tls.hide(); + this.smtp_auth.value.changed.connect(on_smtp_auth_changed); + this.smtp_login.validator.state_changed.connect(on_validated); + this.smtp_login.value.activate.connect(on_activated); + this.smtp_password.validator.state_changed.connect(on_validated); + this.smtp_password.value.activate.connect(on_activated); + + if (provider == Geary.ServiceProvider.OTHER) { + this.receiving_list.add(this.imap_hostname); + this.receiving_list.add(this.imap_tls); + this.receiving_list.add(this.imap_login); + this.receiving_list.add(this.imap_password); + + this.sending_list.add(this.smtp_hostname); + this.sending_list.add(this.smtp_tls); + this.sending_list.add(this.smtp_auth); + } else { + this.details_list.add(this.imap_password); + } + } + + internal Gtk.HeaderBar get_header() { + return this.header; + } + + private async void validate_account(GLib.Cancellable? cancellable) { + this.is_operation_running = true; + + bool is_valid = false; + string? message = null; + Gtk.Widget? to_focus = null; + + Geary.AccountInformation account = + yield this.accounts.new_orphan_account( + this.provider, + new Geary.RFC822.MailboxAddress( + this.real_name.value.text.strip(), + this.email.value.text.strip() + ), + cancellable + ); + + account.incoming = new_imap_service(); + account.outgoing = new_smtp_service(); + account.untrusted_host.connect(on_untrusted_host); + + if (this.provider == Geary.ServiceProvider.OTHER) { + bool imap_valid = false; + bool smtp_valid = false; + + try { + yield this.engine.validate_imap( + account, account.incoming, cancellable + ); + imap_valid = true; + } catch (Geary.ImapError.UNAUTHENTICATED err) { + debug("Error authenticating IMAP service: %s", err.message); + to_focus = this.imap_login.value; + // Translators: In-app notification label + message = _("Check your receiving login and password"); + } catch (GLib.TlsError.BAD_CERTIFICATE err) { + debug("Error validating IMAP certifiate: %s", err.message); + // Nothing to do here, since the untrusted host + // handler will be dealing with it + } catch (GLib.IOError.CANCELLED err) { + // Nothing to do here, someone just cancelled + debug("IMAP validation was cancelled: %s", err.message); + } catch (GLib.Error err) { + Geary.ErrorContext context = new Geary.ErrorContext(err); + debug("Error validating IMAP service: %s", + context.format_full_error()); + this.imap_tls.show(); + to_focus = this.imap_hostname.value; + // Translators: In-app notification label + message = _("Check your receiving server details"); + } + + if (imap_valid) { + debug("Validating SMTP..."); + try { + yield this.engine.validate_smtp( + account, + account.outgoing, + account.incoming.credentials, + cancellable + ); + smtp_valid = true; + } catch (Geary.SmtpError.AUTHENTICATION_FAILED err) { + debug("Error authenticating SMTP service: %s", err.message); + // There was an SMTP auth error, but IMAP already + // succeeded, so the user probably needs to + // specify custom creds here + this.smtp_auth.value.source = + Geary.Credentials.Requirement.CUSTOM; + to_focus = this.smtp_login.value; + // Translators: In-app notification label + message = _("Check your sending login and password"); + } catch (GLib.TlsError.BAD_CERTIFICATE err) { + // Nothing to do here, since the untrusted host + // handler will be dealing with it + } catch (GLib.IOError.CANCELLED err) { + // Nothing to do here, someone just cancelled + debug("SMTP validation was cancelled: %s", err.message); + } catch (GLib.Error err) { + Geary.ErrorContext context = new Geary.ErrorContext(err); + debug("Error validating SMTP service: %s", + context.format_full_error()); + this.smtp_tls.show(); + to_focus = this.smtp_hostname.value; + // Translators: In-app notification label + message = _("Check your sending server details"); + } + } + + is_valid = imap_valid && smtp_valid; + } else { + try { + yield this.engine.validate_imap( + account, account.incoming, cancellable + ); + is_valid = true; + } catch (Geary.ImapError.UNAUTHENTICATED err) { + debug("Error authenticating provider: %s", err.message); + to_focus = this.email.value; + // Translators: In-app notification label + message = _("Check your email address and password"); + } catch (GLib.TlsError.BAD_CERTIFICATE err) { + // Nothing to do here, since the untrusted host + // handler will be dealing with it + debug("Error validating SMTP certifiate: %s", err.message); + } catch (GLib.Error err) { + Geary.ErrorContext context = new Geary.ErrorContext(err); + debug("Error validating SMTP service: %s", + context.format_full_error()); + is_valid = false; + // Translators: In-app notification label + message = _("Could not connect, check your network"); + } + } + + if (is_valid) { + try { + yield this.accounts.create_account(account, cancellable); + this.editor.pop(); + } catch (GLib.Error err) { + debug("Failed to create new local account: %s", err.message); + is_valid = false; + // Translators: In-app notification label for a + // generic error creating an account + message = _("An unexpected problem occurred"); + } + } + + account.untrusted_host.disconnect(on_untrusted_host); + this.is_operation_running = false; + + // Focus and pop up the notification after re-sensitising + // so it actually succeeds. + if (!is_valid) { + if (to_focus != null) { + to_focus.grab_focus(); + } + if (message != null) { + this.editor.add_notification( + new InAppNotification( + // Translators: In-app notification label, the + // string substitution is a more detailed reason. + _("Account not created: %s").printf(message) + ) + ); + } + } + } + + private Geary.ServiceInformation new_imap_service() { + Geary.ServiceInformation service = new Geary.ServiceInformation( + Geary.Protocol.IMAP, this.provider + ); + + if (this.provider == Geary.ServiceProvider.OTHER) { + service.credentials = new Geary.Credentials( + Geary.Credentials.Method.PASSWORD, + this.imap_login.value.get_text().strip(), + this.imap_password.value.get_text().strip() + ); + + Components.NetworkAddressValidator host = + (Components.NetworkAddressValidator) + this.imap_hostname.validator; + GLib.NetworkAddress address = host.validated_address; + service.host = address.hostname; + service.port = (uint16) address.port; + service.transport_security = this.imap_tls.value.method; + + if (service.port == 0) { + service.port = service.get_default_port(); + } + } else { + service.credentials = new Geary.Credentials( + Geary.Credentials.Method.PASSWORD, + this.email.value.get_text().strip(), + this.imap_password.value.get_text().strip() + ); + } + + return service; + } + + private Geary.ServiceInformation new_smtp_service() { + Geary.ServiceInformation service = new Geary.ServiceInformation( + Geary.Protocol.SMTP, this.provider + ); + + if (this.provider == Geary.ServiceProvider.OTHER) { + service.credentials_requirement = this.smtp_auth.value.source; + if (service.credentials_requirement == + Geary.Credentials.Requirement.CUSTOM) { + service.credentials = new Geary.Credentials( + Geary.Credentials.Method.PASSWORD, + this.smtp_login.value.get_text().strip(), + this.smtp_password.value.get_text().strip() + ); + } + + Components.NetworkAddressValidator host = + (Components.NetworkAddressValidator) + this.smtp_hostname.validator; + GLib.NetworkAddress address = host.validated_address; + + service.host = address.hostname; + service.port = (uint16) address.port; + service.transport_security = this.smtp_tls.value.method; + + if (service.port == 0) { + service.port = service.get_default_port(); + } + } + + return service; + } + + private void check_validation() { + bool controls_valid = true; + foreach (Gtk.ListBox list in new Gtk.ListBox[] { + this.details_list, this.receiving_list, this.sending_list + }) { + list.foreach((child) => { + AddPaneRow? validatable = child as AddPaneRow; + if (validatable != null && !validatable.validator.is_valid) { + controls_valid = false; + } + }); + } + this.create_button.set_sensitive(controls_valid); + this.controls_valid = controls_valid; + } + + private void update_operation_ui(bool is_running) { + this.create_spinner.visible = is_running; + this.create_spinner.active = is_running; + this.create_button.sensitive = !is_running; + this.back_button.sensitive = !is_running; + this.sensitive = !is_running; + } + + private void on_validated(Components.Validator.Trigger reason) { + check_validation(); + if (this.controls_valid && reason == Components.Validator.Trigger.ACTIVATED) { + this.create_button.clicked(); + } + } + + private void on_activated() { + if (this.controls_valid) { + this.create_button.clicked(); + } + } + + private void on_email_changed() { + string email = ""; + if (this.email.validator.state == Components.Validator.Validity.VALID) { + email = this.email.value.text; + } + + if (this.imap_login.value.text == this.last_valid_email) { + this.imap_login.value.text = email; + } + if (this.smtp_login.value.text == this.last_valid_email) { + this.smtp_login.value.text = email; + } + + this.last_valid_email = email; + } + + private void on_smtp_auth_changed() { + if (this.smtp_auth.value.source == Geary.Credentials.Requirement.CUSTOM) { + this.sending_list.add(this.smtp_login); + this.sending_list.add(this.smtp_password); + } else if (this.smtp_login.parent != null) { + this.sending_list.remove(this.smtp_login); + this.sending_list.remove(this.smtp_password); + } + check_validation(); + } + + private void on_untrusted_host(Geary.AccountInformation account, + Geary.ServiceInformation service, + Geary.Endpoint endpoint, + GLib.TlsConnection cx) { + this.editor.prompt_pin_certificate.begin( + account, service, endpoint, this.op_cancellable, + (obj, res) => { + try { + this.editor.prompt_pin_certificate.end(res); + } catch (Application.CertificateManagerError err) { + // All good, just drop back into the editor + // window. + return; + } + + // Kick off another attempt to validate + this.validate_account.begin(this.op_cancellable); + }); + } + + [GtkCallback] + private void on_create_button_clicked() { + this.validate_account.begin(this.op_cancellable); + } + + [GtkCallback] + private void on_back_button_clicked() { + this.editor.pop(); + } + + [GtkCallback] + private bool on_list_keynav_failed(Gtk.Widget widget, + Gtk.DirectionType direction) { + bool ret = Gdk.EVENT_PROPAGATE; + Gtk.Container? next = null; + if (direction == Gtk.DirectionType.DOWN) { + if (widget == this.details_list) { + debug("Have details!"); + next = this.receiving_list; + } else if (widget == this.receiving_list) { + next = this.sending_list; + } + } else if (direction == Gtk.DirectionType.UP) { + if (widget == this.sending_list) { + next = this.receiving_list; + } else if (widget == this.receiving_list) { + next = this.details_list; + } + } + + if (next != null) { + next.child_focus(direction); + ret = Gdk.EVENT_STOP; + } + return ret; + } + +} + + +private abstract class Accounts.AddPaneRow : + LabelledEditorRow { + + + internal Components.Validator? validator { get; protected set; } + + + protected AddPaneRow(string label, Value value) { + base(label, new Gtk.Entry()); + this.activatable = false; + } + +} + + +private abstract class Accounts.EntryRow : AddPaneRow { + + + protected EntryRow(string label, string? placeholder = null) { + base(label, new Gtk.Entry()); + + this.value.placeholder_text = placeholder ?? ""; + this.value.width_chars = 32; + } + + public override bool focus(Gtk.DirectionType direction) { + bool ret = Gdk.EVENT_PROPAGATE; + switch (direction) { + case Gtk.DirectionType.TAB_FORWARD: + case Gtk.DirectionType.TAB_BACKWARD: + ret = this.value.child_focus(direction); + break; + + default: + ret = base.focus(direction); + break; + } + + return ret; + } + +} + + +private class Accounts.NameRow : EntryRow { + + public NameRow(string default_name) { + // Translators: Label for the person's actual name when adding + // an account + base(_("Your name")); + this.validator = new Components.Validator(this.value); + if (default_name.strip() != "") { + // Set the text after hooking up the validator, so if the + // string is non-null it will already be valid + this.value.set_text(default_name); + } + } + +} + + +private class Accounts.EmailRow : EntryRow { + + + public EmailRow() { + base( + _("Email address"), + // Translators: Placeholder for the default sender address + // when adding an account + _("person@example.com") + ); + this.value.input_purpose = Gtk.InputPurpose.EMAIL; + this.validator = new Components.EmailValidator(this.value); + } + +} + + +private class Accounts.LoginRow : EntryRow { + + public LoginRow() { + // Translators: Label for an IMAP/SMTP service login/user name + // when adding an account + base(_("Login name")); + // Logins are not infrequently the same as the user's email + // address + this.value.input_purpose = Gtk.InputPurpose.EMAIL; + this.validator = new Components.Validator(this.value); + } + +} + + +private class Accounts.PasswordRow : EntryRow { + + + public PasswordRow() { + base(_("Password")); + this.value.visibility = false; + this.value.input_purpose = Gtk.InputPurpose.PASSWORD; + this.validator = new Components.Validator(this.value); + } + +} + + +private class Accounts.HostnameRow : EntryRow { + + + private Geary.Protocol type; + + + public HostnameRow(Geary.Protocol type) { + string label = ""; + string placeholder = ""; + switch (type) { + case Geary.Protocol.IMAP: + // Translators: Label for the IMAP server hostname when + // adding an account. + label = _("IMAP server"); + // Translators: Placeholder for the IMAP server hostname + // when adding an account. + placeholder = _("imap.example.com"); + break; + + case Geary.Protocol.SMTP: + // Translators: Label for the SMTP server hostname when + // adding an account. + label = _("SMTP server"); + // Translators: Placeholder for the SMTP server hostname + // when adding an account. + placeholder = _("smtp.example.com"); + break; + } + + base(label, placeholder); + this.type = type; + + this.validator = new Components.NetworkAddressValidator(this.value, 0); + } + +} + + +private class Accounts.TransportSecurityRow : + LabelledEditorRow { + + public TransportSecurityRow() { + TlsComboBox value = new TlsComboBox(); + base(value.label, value); + // Set to Transport TLS by default per RFC 8314 + this.value.method = Geary.TlsNegotiationMethod.TRANSPORT; + } + +} + + +private class Accounts.OutgoingAuthRow : + LabelledEditorRow { + + public OutgoingAuthRow() { + OutgoingAuthComboBox value = new OutgoingAuthComboBox(); + base(value.label, value); + + this.activatable = false; + this.value.source = Geary.Credentials.Requirement.USE_INCOMING; + } + +} diff -Nru geary-0.12.4/src/client/accounts/accounts-editor-edit-pane.vala geary-3.32.0/src/client/accounts/accounts-editor-edit-pane.vala --- geary-0.12.4/src/client/accounts/accounts-editor-edit-pane.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/src/client/accounts/accounts-editor-edit-pane.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,882 @@ +/* + * Copyright 2018-2019 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +/** + * An account editor pane for editing a specific account's preferences. + */ +[GtkTemplate (ui = "/org/gnome/Geary/accounts_editor_edit_pane.ui")] +internal class Accounts.EditorEditPane : + Gtk.Grid, EditorPane, AccountPane, CommandPane { + + + /** {@inheritDoc} */ + internal Gtk.Widget initial_widget { + get { return this.details_list.get_row_at_index(0); } + } + + /** {@inheritDoc} */ + internal Geary.AccountInformation account { get ; protected set; } + + /** {@inheritDoc} */ + internal Application.CommandStack commands { + get; protected set; default = new Application.CommandStack(); + } + + /** {@inheritDoc} */ + internal bool is_operation_running { get; protected set; default = false; } + + /** {@inheritDoc} */ + internal GLib.Cancellable? op_cancellable { + get; protected set; default = null; + } + + /** {@inheritDoc} */ + protected weak Accounts.Editor editor { get; set; } + + [GtkChild] + private Gtk.HeaderBar header; + + [GtkChild] + private Gtk.Grid pane_content; + + [GtkChild] + private Gtk.Adjustment pane_adjustment; + + [GtkChild] + private Gtk.ListBox details_list; + + [GtkChild] + private Gtk.ListBox senders_list; + + [GtkChild] + private Gtk.Frame signature_frame; + + private SignatureWebView signature_preview; + private bool signature_changed = false; + + [GtkChild] + private Gtk.ListBox settings_list; + + [GtkChild] + private Gtk.Button undo_button; + + [GtkChild] + private Gtk.Button remove_button; + + + public EditorEditPane(Editor editor, Geary.AccountInformation account) { + this.editor = editor; + this.account = account; + + this.pane_content.set_focus_vadjustment(this.pane_adjustment); + + this.details_list.set_header_func(Editor.seperator_headers); + this.details_list.add( + new DisplayNameRow(account, this.commands, this.op_cancellable) + ); + + this.senders_list.set_header_func(Editor.seperator_headers); + foreach (Geary.RFC822.MailboxAddress sender in + account.sender_mailboxes) { + this.senders_list.add(new_mailbox_row(sender)); + } + this.senders_list.add(new AddMailboxRow()); + + this.signature_preview = new SignatureWebView( + ((GearyApplication) editor.application).config + ); + this.signature_preview.events = ( + this.signature_preview.events | Gdk.EventType.FOCUS_CHANGE + ); + this.signature_preview.content_loaded.connect(() => { + // Only enable editability after the content has fully + // loaded to avoid the WebProcess crashing. + this.signature_preview.set_editable.begin( + true, this.op_cancellable + ); + }); + this.signature_preview.document_modified.connect(() => { + this.signature_changed = true; + }); + this.signature_preview.focus_out_event.connect(() => { + // This event will also be fired if the top-level + // window loses focus, e.g. if the user alt-tabs away, + // so don't execute the command if the signature web + // view no longer the focus widget + if (!this.signature_preview.is_focus && + this.signature_changed) { + this.commands.execute.begin( + new SignatureChangedCommand( + this.signature_preview, account + ), + this.op_cancellable + ); + } + return Gdk.EVENT_PROPAGATE; + }); + + this.signature_preview.show(); + this.signature_preview.load_html( + Geary.HTML.smart_escape(account.signature) + ); + + this.signature_frame.add(this.signature_preview); + + this.settings_list.set_header_func(Editor.seperator_headers); + this.settings_list.add(new EmailPrefetchRow(this)); + + this.remove_button.set_visible( + !this.editor.accounts.is_goa_account(account) + ); + + connect_account_signals(); + connect_command_signals(); + } + + ~EditorEditPane() { + disconnect_account_signals(); + disconnect_command_signals(); + } + + internal string? get_default_name() { + string? name = account.primary_mailbox.name; + + if (Geary.String.is_empty_or_whitespace(name)) { + name = this.editor.accounts.get_account_name(); + } + + return name; + } + + /** {@inheritDoc} */ + internal Gtk.HeaderBar get_header() { + return this.header; + } + + internal MailboxRow new_mailbox_row(Geary.RFC822.MailboxAddress sender) { + MailboxRow row = new MailboxRow(this.account, sender); + row.move_to.connect(on_sender_row_moved); + row.dropped.connect(on_sender_row_dropped); + return row; + } + + /** {@inheritDoc} */ + protected void command_executed() { + this.editor.update_command_actions(); + + Application.Command next_undo = this.commands.peek_undo(); + this.undo_button.set_tooltip_text( + (next_undo != null && next_undo.undo_label != null) + ? next_undo.undo_label : "" + ); + + // Ensure the account is notified that is has changed. This + // might not be 100% correct, but it's close enough. + this.account.changed(); + } + + private void on_sender_row_moved(EditorRow source, int new_position) { + this.commands.execute.begin( + new ReorderMailboxCommand( + (MailboxRow) source, + new_position, + this.account, + this.senders_list + ), + this.op_cancellable + ); + } + + private void on_sender_row_dropped(EditorRow source, EditorRow target) { + this.commands.execute.begin( + new ReorderMailboxCommand( + (MailboxRow) source, + target.get_index(), + this.account, + this.senders_list + ), + this.op_cancellable + ); + } + + [GtkCallback] + private void on_setting_activated(Gtk.ListBoxRow row) { + EditorRow? setting = row as EditorRow; + if (setting != null) { + setting.activated(this); + } + } + + [GtkCallback] + private void on_server_settings_clicked() { + this.editor.push(new EditorServersPane(this.editor, this.account)); + } + + [GtkCallback] + private void on_remove_account_clicked() { + if (!this.editor.accounts.is_goa_account(account)) { + this.editor.push(new EditorRemovePane(this.editor, this.account)); + } + } + + [GtkCallback] + private void on_back_button_clicked() { + this.editor.pop(); + } + + [GtkCallback] + private bool on_list_keynav_failed(Gtk.Widget widget, + Gtk.DirectionType direction) { + bool ret = Gdk.EVENT_PROPAGATE; + Gtk.Container? next = null; + if (direction == Gtk.DirectionType.DOWN) { + if (widget == this.details_list) { + next = this.senders_list; + } else if (widget == this.senders_list) { + this.signature_preview.grab_focus(); + } else if (widget == this.signature_preview) { + next = this.settings_list; + } + } else if (direction == Gtk.DirectionType.UP) { + if (widget == this.settings_list) { + this.signature_preview.grab_focus(); + } else if (widget == this.signature_preview) { + next = this.senders_list; + } else if (widget == this.senders_list) { + next = this.details_list; + } + } + + if (next != null) { + next.child_focus(direction); + ret = Gdk.EVENT_STOP; + } + return ret; + } + +} + + +private class Accounts.DisplayNameRow : AccountRow { + + + private Application.CommandStack commands; + private GLib.Cancellable? cancellable; + + + public DisplayNameRow(Geary.AccountInformation account, + Application.CommandStack commands, + GLib.Cancellable? cancellable) { + base( + account, + // Translators: Label in the account editor for the user's + // custom name for an account. + _("Account name"), + new Gtk.Entry() + ); + this.activatable = false; + this.commands = commands; + this.cancellable = cancellable; + + update(); + + this.value.focus_out_event.connect(on_focus_out); + } + + public override void update() { + this.value.set_placeholder_text(this.account.primary_mailbox.address); + this.value.set_text(this.account.display_name); + } + + private void commit() { + string value = this.value.text.strip(); + if (value == "") { + value = this.account.primary_mailbox.address; + this.value.text = this.account.primary_mailbox.address; + } + + if (value != this.account.display_name) { + this.commands.execute.begin( + new Application.PropertyCommand( + this.account, + "label", + value, + // Translators: Tooltip used to undo changing + // the name of an account. The string + // substitution is the old name of the + // account. + _("Change account name back to “%s”") + ), + this.cancellable + ); + } + + if (Geary.String.is_empty(value)) { + } + } + + private bool on_focus_out() { + commit(); + return Gdk.EVENT_PROPAGATE; + } + +} + + +private class Accounts.AddMailboxRow : AddRow { + + + public AddMailboxRow() { + // Translators: Tooltip for adding a new email sender/from + // address's address to an account + this.set_tooltip_text(_("Add a new sender email address")); + } + + public override void activated(EditorEditPane pane) { + MailboxEditorPopover popover = new MailboxEditorPopover( + pane.get_default_name() ?? "", "", false + ); + popover.activated.connect(() => { + pane.commands.execute.begin( + new AppendMailboxCommand( + (Gtk.ListBox) get_parent(), + pane.new_mailbox_row( + new Geary.RFC822.MailboxAddress( + popover.display_name, + popover.address + ) + ) + ), + pane.op_cancellable + ); + popover.popdown(); + }); + + popover.set_relative_to(this); + popover.popup(); + } +} + + +private class Accounts.MailboxRow : AccountRow { + + + internal Geary.RFC822.MailboxAddress mailbox; + + + public MailboxRow(Geary.AccountInformation account, + Geary.RFC822.MailboxAddress mailbox) { + base(account, "", new Gtk.Label("")); + this.mailbox = mailbox; + enable_drag(); + + update(); + } + + public override void activated(EditorEditPane pane) { + MailboxEditorPopover popover = new MailboxEditorPopover( + this.mailbox.name ?? "", + this.mailbox.address, + this.account.has_sender_aliases + ); + popover.activated.connect(() => { + pane.commands.execute.begin( + new UpdateMailboxCommand( + this, + new Geary.RFC822.MailboxAddress( + popover.display_name, + popover.address + ) + ), + pane.op_cancellable + ); + popover.popdown(); + }); + popover.remove_clicked.connect(() => { + pane.commands.execute.begin( + new RemoveMailboxCommand(this), + pane.op_cancellable + ); + popover.popdown(); + }); + + popover.set_relative_to(this); + popover.popup(); + } + + public override void update() { + string? name = this.mailbox.name; + if (Geary.String.is_empty_or_whitespace(name)) { + // Translators: Label used to indicate the user has + // provided no display name for one of their sender + // email addresses in their account settings. + name = _("Name not set"); + set_dim_label(true); + } else { + set_dim_label(false); + } + + this.label.set_text(name); + this.value.set_text(mailbox.address.strip()); + } + +} + +internal class Accounts.MailboxEditorPopover : EditorPopover { + + + public string display_name { get; private set; } + public string address { get; private set; } + + + private Gtk.Entry name_entry = new Gtk.Entry(); + private Gtk.Entry address_entry = new Gtk.Entry(); + private Components.EmailValidator address_validator; + private Gtk.Button remove_button; + + public signal void activated(); + public signal void remove_clicked(); + + + public MailboxEditorPopover(string? display_name, + string? address, + bool can_remove) { + this.display_name = display_name; + this.address = address; + + this.name_entry.set_text(display_name ?? ""); + this.name_entry.set_placeholder_text( + // Translators: This is used as a placeholder for the + // display name for an email address when editing a user's + // sender address preferences for an account. + _("Sender Name") + ); + this.name_entry.set_width_chars(20); + this.name_entry.changed.connect(on_name_changed); + this.name_entry.activate.connect(on_activate); + this.name_entry.show(); + + this.address_entry.input_purpose = Gtk.InputPurpose.EMAIL; + this.address_entry.set_text(address ?? ""); + this.address_entry.set_placeholder_text( + // Translators: This is used as a placeholder for the + // address part of an email address when editing a user's + // sender address preferences for an account. + _("person@example.com") + ); + this.address_entry.set_width_chars(20); + this.address_entry.changed.connect(on_address_changed); + this.address_entry.activate.connect(on_activate); + this.address_entry.show(); + + this.address_validator = + new Components.EmailValidator(this.address_entry); + + this.remove_button = new Gtk.Button.with_label(_("Remove")); + this.remove_button.halign = Gtk.Align.END; + this.remove_button.get_style_context().add_class( + "geary-setting-remove" + ); + this.remove_button.get_style_context().add_class( + Gtk.STYLE_CLASS_DESTRUCTIVE_ACTION + ); + this.remove_button.clicked.connect(on_remove_clicked); + this.remove_button.show(); + + add_labelled_row( + // Translators: Label used for the display name part of an + // email address when editing a user's sender address + // preferences for an account. + _("Sender name"), + this.name_entry + ); + add_labelled_row( + // Translators: Label used for the address part of an + // email address when editing a user's sender address + // preferences for an account. + _("Email address"), + this.address_entry + ); + + if (can_remove) { + this.layout.attach(this.remove_button, 0, 2, 2, 1); + } + + this.popup_focus = this.name_entry; + } + + ~MailboxEditorPopover() { + this.name_entry.changed.disconnect(on_name_changed); + this.name_entry.activate.disconnect(on_activate); + + this.address_entry.changed.disconnect(on_address_changed); + this.address_entry.activate.disconnect(on_activate); + + this.remove_button.clicked.disconnect(on_remove_clicked); + } + + private void on_name_changed() { + this.display_name = this.name_entry.get_text().strip(); + } + + private void on_address_changed() { + this.address = this.address_entry.get_text().strip(); + } + + private void on_remove_clicked() { + remove_clicked(); + } + + private void on_activate() { + if (this.address_validator.state == Components.Validator.Validity.INDETERMINATE || this.address_validator.is_valid) { + activated(); + } + } + +} + + +internal class Accounts.AppendMailboxCommand : Application.Command { + + + private Gtk.ListBox senders_list; + private MailboxRow new_row = null; + + private int mailbox_index; + + + public AppendMailboxCommand(Gtk.ListBox senders_list, MailboxRow new_row) { + this.senders_list = senders_list; + this.new_row = new_row; + + this.mailbox_index = new_row.account.sender_mailboxes.size; + + // Translators: Label used as the undo tooltip after adding an + // new sender email address to an account. The string + // substitution is the email address added. + this.undo_label = _("Remove “%s”").printf(new_row.mailbox.address); + } + + public async override void execute(GLib.Cancellable? cancellable) { + this.senders_list.insert(this.new_row, this.mailbox_index); + this.new_row.account.append_sender(this.new_row.mailbox); + this.new_row.account.changed(); + } + + public async override void undo(GLib.Cancellable? cancellable) { + this.senders_list.remove(this.new_row); + this.new_row.account.remove_sender(this.new_row.mailbox); + this.new_row.account.changed(); + } + +} + + +internal class Accounts.UpdateMailboxCommand : Application.Command { + + + private MailboxRow row; + private Geary.RFC822.MailboxAddress new_mailbox; + + private Geary.RFC822.MailboxAddress old_mailbox; + private int mailbox_index; + + + public UpdateMailboxCommand(MailboxRow row, + Geary.RFC822.MailboxAddress new_mailbox) { + this.row = row; + this.new_mailbox = new_mailbox; + + this.old_mailbox = row.mailbox; + this.mailbox_index = + row.account.sender_mailboxes.index_of(this.old_mailbox); + + // Translators: Label used as the undo tooltip after editing a + // sender address for an account. The string substitution is + // the email address edited. + this.undo_label = _("Undo changes to “%s”").printf( + this.old_mailbox.address + ); + } + + public async override void execute(GLib.Cancellable? cancellable) { + this.row.mailbox = this.new_mailbox; + this.row.account.replace_sender(this.mailbox_index, this.new_mailbox); + this.row.account.changed(); + } + + public async override void undo(GLib.Cancellable? cancellable) { + this.row.mailbox = this.old_mailbox; + this.row.account.replace_sender(this.mailbox_index, this.old_mailbox); + this.row.account.changed(); + } + +} + + +internal class Accounts.ReorderMailboxCommand : Application.Command { + + + private MailboxRow source; + private int source_index; + private int target_index; + + private Geary.AccountInformation account; + private Gtk.ListBox list; + + + public ReorderMailboxCommand(MailboxRow source, + int target_index, + Geary.AccountInformation account, + Gtk.ListBox list) { + this.source = source; + this.source_index = source.get_index(); + this.target_index = target_index; + + this.account = account; + this.list = list; + } + + public async override void execute(GLib.Cancellable? cancellable) + throws GLib.Error { + move_source(this.target_index); + } + + public async override void undo(GLib.Cancellable? cancellable) + throws GLib.Error { + move_source(this.source_index); + } + + private void move_source(int destination) { + this.account.remove_sender(this.source.mailbox); + this.account.insert_sender(destination, this.source.mailbox); + + this.list.remove(this.source); + this.list.insert(this.source, destination); + + this.source.grab_focus(); + } + +} + + +internal class Accounts.RemoveMailboxCommand : Application.Command { + + + private MailboxRow row; + + private Geary.RFC822.MailboxAddress mailbox; + private int mailbox_index; + private Gtk.ListBox list; + + + public RemoveMailboxCommand(MailboxRow row) { + this.row = row; + + this.mailbox = row.mailbox; + this.mailbox_index = + row.account.sender_mailboxes.index_of(mailbox); + this.list = (Gtk.ListBox) row.get_parent(); + + // Translators: Label used as the undo tooltip after removing + // a sender address from an account. The string substitution + // is the email address edited. + this.undo_label = _("Add “%s” back").printf(this.mailbox.address); + } + + public async override void execute(GLib.Cancellable? cancellable) { + this.list.remove(this.row); + this.row.account.remove_sender(this.mailbox); + this.row.account.changed(); + } + + public async override void undo(GLib.Cancellable? cancellable) { + this.list.insert(this.row, this.mailbox_index); + this.row.account.insert_sender(this.mailbox_index, this.mailbox); + this.row.account.changed(); + } + +} + + +internal class Accounts.SignatureChangedCommand : Application.Command { + + + private ClientWebView signature_view; + private Geary.AccountInformation account; + + private string old_value; + private bool old_enabled; + + private string? new_value = null; + private bool new_enabled = false; + + + public SignatureChangedCommand(ClientWebView signature_view, + Geary.AccountInformation account) { + this.signature_view = signature_view; + this.account = account; + + this.old_value = Geary.HTML.smart_escape(account.signature); + this.old_enabled = account.use_signature; + + // Translators: Label used as the undo tooltip after removing + // a sender address from an account. The string substitution + // is the email address edited. + this.undo_label = _("Undo signature changes"); + } + + public async override void execute(GLib.Cancellable? cancellable) + throws GLib.Error { + this.new_value = yield this.signature_view.get_html(); + this.new_enabled = !Geary.String.is_empty_or_whitespace( + Geary.HTML.html_to_text(this.new_value) + ); + update_account_signature(this.new_value, this.new_enabled); + } + + public async override void undo(GLib.Cancellable? cancellable) { + this.signature_view.load_html(this.old_value); + update_account_signature(this.old_value, this.old_enabled); + } + + public async override void redo(GLib.Cancellable? cancellable) { + this.signature_view.load_html(this.new_value); + update_account_signature(this.new_value, this.new_enabled); + } + + private inline void update_account_signature(string sig, bool enabled) { + this.account.signature = sig; + this.account.use_signature = enabled; + this.account.changed(); + } + +} + + +private class Accounts.EmailPrefetchRow : + AccountRow { + + + private static bool row_separator(Gtk.TreeModel model, Gtk.TreeIter iter) { + GLib.Value v; + model.get_value(iter, 0, out v); + return v.get_string() == "."; + } + + + public EmailPrefetchRow(EditorEditPane pane) { + base( + pane.account, + // Translators: This label describes the account + // preference for the length of time (weeks, months or + // years) that past email should be downloaded. + _("Download mail"), + new Gtk.ComboBoxText() + ); + set_activatable(false); + + this.value.set_row_separator_func(row_separator); + + // Populate the model + get_label(14, true); + get_label(30, true); + get_label(90, true); + get_label(180, true); + get_label(365, true); + get_label(720, true); + get_label(1461, true); + get_label(-1, true); + + // Update before connecting to the changed signal to avoid + // getting a spurious command. + update(); + + this.value.changed.connect(() => { + pane.commands.execute.begin( + new Application.PropertyCommand( + this.account, + "prefetch-period-days", + int.parse(this.value.get_active_id()), + // Translators: Tooltip for undoing a change + // to the length of time that past email + // should be downloaded for an account. The + // string substitution is the duration, + // e.g. "1 month back". + _("Change download period back to: %s").printf( + get_label(this.account.prefetch_period_days) + ) + ), + pane.op_cancellable + ); + }); + } + + public override void update() { + string id = this.account.prefetch_period_days.to_string(); + if (this.value.get_active_id() != id) { + this.value.set_active_id(id); + } + } + + private string get_label(int duration, bool append = false) { + string label = ""; + bool is_custom = false; + switch (duration) { + case -1: + label = _("Everything"); + break; + + case 14: + label = _("2 weeks back"); + break; + + case 30: + label = _("1 month back"); + break; + + case 90: + label = _("3 months back"); + break; + + case 180: + label = _("6 months back"); + break; + + case 365: + label = _("1 year back"); + break; + + case 720: + label = _("2 years back"); + break; + + case 1461: + label = _("4 years back"); + break; + + default: + is_custom = true; + label = GLib.ngettext( + "%d day back", + "%d days back", + duration + ).printf(duration); + break; + } + + if (append) { + if (duration == -1 || is_custom) { + this.value.append(".", "."); // Separator + } + this.value.append(duration.to_string(), label); + } + + return label; + } + +} diff -Nru geary-0.12.4/src/client/accounts/accounts-editor-list-pane.vala geary-3.32.0/src/client/accounts/accounts-editor-list-pane.vala --- geary-0.12.4/src/client/accounts/accounts-editor-list-pane.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/src/client/accounts/accounts-editor-list-pane.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,569 @@ +/* + * Copyright 2018-2019 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +/** + * An account editor pane for listing all known accounts. + */ +[GtkTemplate (ui = "/org/gnome/Geary/accounts_editor_list_pane.ui")] +internal class Accounts.EditorListPane : Gtk.Grid, EditorPane, CommandPane { + + + private static int ordinal_sort(Gtk.ListBoxRow a, Gtk.ListBoxRow b) { + AccountListRow? account_a = a as AccountListRow; + AccountListRow? account_b = b as AccountListRow; + + if (account_a == null) { + return (account_b == null) ? 0 : 1; + } else if (account_b == null) { + return -1; + } + + return Geary.AccountInformation.compare_ascending( + account_a.account, account_b.account + ); + } + + + /** {@inheritDoc} */ + internal Gtk.Widget initial_widget { + get { + return this.show_welcome ? this.service_list : this.accounts_list; + } + } + + /** {@inheritDoc} */ + internal Application.CommandStack commands { + get; protected set; default = new Application.CommandStack(); + } + + /** {@inheritDoc} */ + internal bool is_operation_running { get; protected set; default = false; } + + /** {@inheritDoc} */ + internal GLib.Cancellable? op_cancellable { + get; protected set; default = null; + } + + internal Manager accounts { get; private set; } + + /** {@inheritDoc} */ + protected weak Accounts.Editor editor { get; set; } + + private bool show_welcome { + get { + return (this.accounts_list.get_row_at_index(0) == null); + } + } + + [GtkChild] + private Gtk.HeaderBar header; + + [GtkChild] + private Gtk.Grid pane_content; + + [GtkChild] + private Gtk.Adjustment pane_adjustment; + + [GtkChild] + private Gtk.Grid welcome_panel; + + [GtkChild] + private Gtk.ListBox accounts_list; + + [GtkChild] + private Gtk.Frame accounts_list_frame; + + [GtkChild] + private Gtk.Label add_service_label; + + [GtkChild] + private Gtk.ListBox service_list; + + private Gee.Map edit_pane_cache = + new Gee.HashMap(); + + + public EditorListPane(Editor editor) { + this.editor = editor; + + // keep our own copy of this so we can disconnect from its signals + // without worrying about the editor's lifecycle + this.accounts = editor.accounts; + + this.pane_content.set_focus_vadjustment(this.pane_adjustment); + + this.accounts_list.set_header_func(Editor.seperator_headers); + this.accounts_list.set_sort_func(ordinal_sort); + foreach (Geary.AccountInformation account in this.accounts.iterable()) { + add_account(account, this.accounts.get_status(account)); + } + + this.service_list.set_header_func(Editor.seperator_headers); + this.service_list.add(new AddServiceProviderRow(Geary.ServiceProvider.GMAIL)); + this.service_list.add(new AddServiceProviderRow(Geary.ServiceProvider.OUTLOOK)); + this.service_list.add(new AddServiceProviderRow(Geary.ServiceProvider.YAHOO)); + this.service_list.add(new AddServiceProviderRow(Geary.ServiceProvider.OTHER)); + + this.accounts.account_added.connect(on_account_added); + this.accounts.account_status_changed.connect(on_account_status_changed); + this.accounts.account_removed.connect(on_account_removed); + + this.commands.executed.connect(on_execute); + this.commands.undone.connect(on_undo); + this.commands.redone.connect(on_execute); + connect_command_signals(); + update_welcome_panel(); + } + + public override void destroy() { + this.commands.executed.disconnect(on_execute); + this.commands.undone.disconnect(on_undo); + this.commands.redone.disconnect(on_execute); + disconnect_command_signals(); + + this.accounts.account_added.disconnect(on_account_added); + this.accounts.account_status_changed.disconnect(on_account_status_changed); + this.accounts.account_removed.disconnect(on_account_removed); + + this.edit_pane_cache.clear(); + base.destroy(); + } + + internal void show_new_account(Geary.ServiceProvider provider) { + this.editor.push(new EditorAddPane(this.editor, provider)); + } + + internal void show_existing_account(Geary.AccountInformation account) { + EditorEditPane? edit_pane = this.edit_pane_cache.get(account); + if (edit_pane == null) { + edit_pane = new EditorEditPane(this.editor, account); + this.edit_pane_cache.set(account, edit_pane); + } + this.editor.push(edit_pane); + } + + /** Removes an account from the list. */ + internal void remove_account(Geary.AccountInformation account) { + AccountListRow? row = get_account_row(account); + if (row != null) { + this.commands.execute.begin( + new RemoveAccountCommand(account, this.accounts), + this.op_cancellable + ); + } + } + + /** {@inheritDoc} */ + internal Gtk.HeaderBar get_header() { + return this.header; + } + + private void add_account(Geary.AccountInformation account, + Manager.Status status) { + AccountListRow row = new AccountListRow(account, status); + row.move_to.connect(on_editor_row_moved); + row.dropped.connect(on_editor_row_dropped); + this.accounts_list.add(row); + } + + private void update_welcome_panel() { + if (this.show_welcome) { + // No accounts are available, so show only the welcome + // pane and service list. + this.welcome_panel.show(); + this.accounts_list_frame.hide(); + this.add_service_label.hide(); + } else { + // There are some accounts available, so show them and + // the full add service UI. + this.welcome_panel.hide(); + this.accounts_list_frame.show(); + this.add_service_label.show(); + } + } + + private AccountListRow? get_account_row(Geary.AccountInformation account) { + AccountListRow? row = null; + this.accounts_list.foreach((child) => { + AccountListRow? account_row = child as AccountListRow; + if (account_row != null && account_row.account == account) { + row = account_row; + } + }); + return row; + } + + private void on_account_added(Geary.AccountInformation account, + Manager.Status status) { + add_account(account, status); + update_welcome_panel(); + } + + private void on_account_status_changed(Geary.AccountInformation account, + Manager.Status status) { + AccountListRow? row = get_account_row(account); + if (row != null) { + row.update_status(status); + } + } + + private void on_editor_row_moved(EditorRow source, int new_position) { + this.commands.execute.begin( + new ReorderAccountCommand( + (AccountListRow) source, new_position, this.accounts + ), + this.op_cancellable + ); + } + + private void on_editor_row_dropped(EditorRow source, EditorRow target) { + this.commands.execute.begin( + new ReorderAccountCommand( + (AccountListRow) source, target.get_index(), this.accounts + ), + this.op_cancellable + ); + } + + private void on_account_removed(Geary.AccountInformation account) { + AccountListRow? row = get_account_row(account); + if (row != null) { + this.accounts_list.remove(row); + update_welcome_panel(); + } + } + + private void on_execute(Application.Command command) { + if (command.executed_label != null) { + InAppNotification ian = new InAppNotification(command.executed_label); + ian.set_button(_("Undo"), "win." + GearyApplication.ACTION_UNDO); + this.editor.add_notification(ian); + } + } + + private void on_undo(Application.Command command) { + if (command.undone_label != null) { + InAppNotification ian = new InAppNotification(command.undone_label); + ian.set_button(_("Redo"), "win." + GearyApplication.ACTION_REDO); + this.editor.add_notification(ian); + } + } + + [GtkCallback] + private void on_row_activated(Gtk.ListBoxRow row) { + EditorRow? setting = row as EditorRow; + if (setting != null) { + setting.activated(this); + } + } + + [GtkCallback] + private bool on_list_keynav_failed(Gtk.Widget widget, + Gtk.DirectionType direction) { + bool ret = Gdk.EVENT_PROPAGATE; + if (direction == Gtk.DirectionType.DOWN && + widget == this.accounts_list) { + this.service_list.child_focus(direction); + ret = Gdk.EVENT_STOP; + } else if (direction == Gtk.DirectionType.UP && + widget == this.service_list) { + this.accounts_list.child_focus(direction); + ret = Gdk.EVENT_STOP; + } + return ret; + } + +} + + +private class Accounts.AccountListRow : AccountRow { + + + private Gtk.Label service_label = new Gtk.Label(""); + private Gtk.Image unavailable_icon = new Gtk.Image.from_icon_name( + "dialog-warning-symbolic", Gtk.IconSize.BUTTON + ); + + public AccountListRow(Geary.AccountInformation account, + Manager.Status status) { + base(account, "", new Gtk.Grid()); + enable_drag(); + + this.value.add(this.unavailable_icon); + this.value.add(this.service_label); + + this.service_label.show(); + + this.account.changed.connect(on_account_changed); + update(); + update_status(status); + } + + ~AccountListRow() { + this.account.changed.disconnect(on_account_changed); + } + + public override void activated(EditorListPane pane) { + Manager manager = pane.accounts; + if (manager.is_goa_account(this.account) && + manager.get_status(this.account) != Manager.Status.ENABLED) { + // GOA account but it's disabled, so just take people + // directly to the GOA panel + manager.show_goa_account.begin( + account, pane.op_cancellable, + (obj, res) => { + try { + manager.show_goa_account.end(res); + } catch (GLib.Error err) { + // XXX display an error to the user + debug( + "Failed to show GOA account \"%s\": %s", + account.id, + err.message + ); + } + }); + } else { + pane.show_existing_account(this.account); + } + } + + public override void update() { + string name = this.account.display_name; + if (Geary.String.is_empty(name)) { + name = account.primary_mailbox.to_address_display("", ""); + } + this.label.set_text(name); + + string? details = this.account.service_label; + switch (account.service_provider) { + case Geary.ServiceProvider.GMAIL: + details = _("Gmail"); + break; + + case Geary.ServiceProvider.OUTLOOK: + details = _("Outlook.com"); + break; + + case Geary.ServiceProvider.YAHOO: + details = _("Yahoo"); + break; + } + this.service_label.set_text(details); + } + + public void update_status(Manager.Status status) { + bool enabled = false; + switch (status) { + case ENABLED: + enabled = true; + this.set_tooltip_text(""); + break; + + case DISABLED: + this.set_tooltip_text( + // Translators: Tooltip for accounts that have been + // loaded but disabled by the user. + _("This account has been disabled") + ); + break; + + case UNAVAILABLE: + this.set_tooltip_text( + // Translators: Tooltip for accounts that have been + // loaded but because of some error are not able to be + // used. + _("This account has encountered a problem and is unavailable") + ); + break; + } + + this.unavailable_icon.set_visible(!enabled); + + if (enabled) { + this.label.get_style_context().remove_class( + Gtk.STYLE_CLASS_DIM_LABEL + ); + this.service_label.get_style_context().remove_class( + Gtk.STYLE_CLASS_DIM_LABEL + ); + } else { + this.label.get_style_context().add_class( + Gtk.STYLE_CLASS_DIM_LABEL + ); + this.service_label.get_style_context().add_class( + Gtk.STYLE_CLASS_DIM_LABEL + ); + } + } + + private void on_account_changed() { + update(); + Gtk.ListBox? parent = get_parent() as Gtk.ListBox; + if (parent != null) { + parent.invalidate_sort(); + } + } + +} + + +private class Accounts.AddServiceProviderRow : EditorRow { + + + internal Geary.ServiceProvider provider; + + private Gtk.Label service_name = new Gtk.Label(""); + private Gtk.Image next_icon = new Gtk.Image.from_icon_name( + "go-next-symbolic", Gtk.IconSize.SMALL_TOOLBAR + ); + + + public AddServiceProviderRow(Geary.ServiceProvider provider) { + this.provider = provider; + + // Translators: Label for adding a generic email account + string? name = _("Other email providers"); + switch (provider) { + case Geary.ServiceProvider.GMAIL: + name = _("Gmail"); + break; + + case Geary.ServiceProvider.OUTLOOK: + name = _("Outlook.com"); + break; + + case Geary.ServiceProvider.YAHOO: + name = _("Yahoo"); + break; + } + this.service_name.set_text(name); + this.service_name.set_hexpand(true); + this.service_name.halign = Gtk.Align.START; + this.service_name.show(); + + this.next_icon.show(); + + this.layout.add(this.service_name); + this.layout.add(this.next_icon); + } + + public override void activated(EditorListPane pane) { + pane.accounts.add_goa_account.begin( + this.provider, pane.op_cancellable, + (obj, res) => { + bool add_local = false; + try { + pane.accounts.add_goa_account.end(res); + } catch (GLib.IOError.NOT_SUPPORTED err) { + // Not a supported type, so don't bother logging the error + add_local = true; + } catch (GLib.Error err) { + debug("Failed to add %s via GOA: %s", + this.provider.to_string(), err.message); + add_local = true; + } + + if (add_local) { + pane.show_new_account(this.provider); + } + }); + } + +} + + +internal class Accounts.ReorderAccountCommand : Application.Command { + + + private AccountListRow source; + private int source_index; + private int target_index; + + private Manager manager; + + + public ReorderAccountCommand(AccountListRow source, + int target_index, + Manager manager) { + this.source = source; + this.source_index = source.get_index(); + this.target_index = target_index; + + this.manager = manager; + } + + public async override void execute(GLib.Cancellable? cancellable) + throws GLib.Error { + move_source(this.target_index); + } + + public async override void undo(GLib.Cancellable? cancellable) + throws GLib.Error { + move_source(this.source_index); + } + + private void move_source(int destination) { + Gee.List accounts = + this.manager.iterable().to_linked_list(); + accounts.sort(Geary.AccountInformation.compare_ascending); + accounts.remove(this.source.account); + accounts.insert(destination, this.source.account); + + int ord = 0; + foreach (Geary.AccountInformation account in accounts) { + if (account.ordinal != ord) { + account.ordinal = ord; + account.changed(); + } + ord++; + } + + this.source.grab_focus(); + } + +} + + +internal class Accounts.RemoveAccountCommand : Application.Command { + + + private Geary.AccountInformation account; + private Manager manager; + + + public RemoveAccountCommand(Geary.AccountInformation account, + Manager manager) { + this.account = account; + this.manager = manager; + + // Translators: Notification shown after removing an + // account. The string substitution is the name of the + // account. + this.executed_label = _("Account “%s” removed").printf( + account.display_name + ); + + // Translators: Notification shown after removing an account + // is undone. The string substitution is the name of the + // account. + this.undone_label = _("Account “%s” restored").printf( + account.display_name + ); + } + + public async override void execute(GLib.Cancellable? cancellable) + throws GLib.Error { + yield this.manager.remove_account(this.account, cancellable); + } + + public async override void undo(GLib.Cancellable? cancellable) + throws GLib.Error { + yield this.manager.restore_account(this.account, cancellable); + } + +} diff -Nru geary-0.12.4/src/client/accounts/accounts-editor-remove-pane.vala geary-3.32.0/src/client/accounts/accounts-editor-remove-pane.vala --- geary-0.12.4/src/client/accounts/accounts-editor-remove-pane.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/src/client/accounts/accounts-editor-remove-pane.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,74 @@ +/* + * Copyright 2018-2019 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +/** + * An account editor pane for removing an account from the client. + */ +[GtkTemplate (ui = "/org/gnome/Geary/accounts_editor_remove_pane.ui")] +internal class Accounts.EditorRemovePane : Gtk.Grid, EditorPane, AccountPane { + + + /** {@inheritDoc} */ + internal weak Accounts.Editor editor { get; set; } + + /** {@inheritDoc} */ + internal Geary.AccountInformation account { get ; protected set; } + + /** {@inheritDoc} */ + internal Gtk.Widget initial_widget { + get { return this.remove_button; } + } + + /** {@inheritDoc} */ + internal bool is_operation_running { get; protected set; default = false; } + + /** {@inheritDoc} */ + internal GLib.Cancellable? op_cancellable { + get; protected set; default = null; + } + + [GtkChild] + private Gtk.HeaderBar header; + + [GtkChild] + private Gtk.Label warning_label; + + [GtkChild] + private Gtk.Button remove_button; + + + public EditorRemovePane(Editor editor, Geary.AccountInformation account) { + this.editor = editor; + this.account = account; + + this.warning_label.set_text( + this.warning_label.get_text().printf(account.display_name) + ); + + connect_account_signals(); + } + + ~EditorRemovePane() { + disconnect_account_signals(); + } + + /** {@inheritDoc} */ + internal Gtk.HeaderBar get_header() { + return this.header; + } + + [GtkCallback] + private void on_remove_button_clicked() { + this.editor.remove_account(this.account); + } + + [GtkCallback] + private void on_back_button_clicked() { + this.editor.pop(); + } + +} diff -Nru geary-0.12.4/src/client/accounts/accounts-editor-row.vala geary-3.32.0/src/client/accounts/accounts-editor-row.vala --- geary-0.12.4/src/client/accounts/accounts-editor-row.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/src/client/accounts/accounts-editor-row.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,632 @@ +/* + * Copyright 2018 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + + +internal class Accounts.EditorRow : Gtk.ListBoxRow { + + private const string DND_ATOM = "geary-editor-row"; + private const Gtk.TargetEntry[] DRAG_ENTRIES = { + { DND_ATOM, Gtk.TargetFlags.SAME_APP, 0 } + }; + + + protected Gtk.Grid layout { get; private set; default = new Gtk.Grid(); } + + private Gtk.Container drag_handle; + private bool drag_picked_up = false; + private bool drag_entered = false; + + + public signal void move_to(int new_position); + public signal void dropped(EditorRow target); + + + public EditorRow() { + get_style_context().add_class("geary-settings"); + + this.layout.orientation = Gtk.Orientation.HORIZONTAL; + this.layout.show(); + add(this.layout); + + // We'd like to add the drag handle only when needed, but + // GNOME/gtk#1495 prevents us from doing so. + Gtk.EventBox drag_box = new Gtk.EventBox(); + drag_box.add( + new Gtk.Image.from_icon_name( + "open-menu-symbolic", Gtk.IconSize.BUTTON + ) + ); + this.drag_handle = new Gtk.Grid(); + this.drag_handle.valign = Gtk.Align.CENTER; + this.drag_handle.add(drag_box); + this.drag_handle.show_all(); + this.drag_handle.hide(); + // Translators: Tooltip for dragging list items + this.drag_handle.set_tooltip_text(_("Drag to move this item")); + this.layout.add(drag_handle); + + this.show(); + } + + public virtual void activated(PaneType pane) { + // No-op by default + } + + public override bool key_press_event(Gdk.EventKey event) { + bool ret = Gdk.EVENT_PROPAGATE; + + if (event.state == Gdk.ModifierType.CONTROL_MASK) { + int index = get_index(); + if (event.keyval == Gdk.Key.Up) { + index -= 1; + if (index >= 0) { + move_to(index); + ret = Gdk.EVENT_STOP; + } + } else if (event.keyval == Gdk.Key.Down) { + index += 1; + Gtk.ListBox? parent = get_parent() as Gtk.ListBox; + if (parent != null && + index < parent.get_children().length() && + !(parent.get_row_at_index(index) is AddRow)) { + move_to(index); + ret = Gdk.EVENT_STOP; + } + } + } + + if (ret != Gdk.EVENT_STOP) { + ret = base.key_press_event(event); + } + + return ret; + } + + /** Adds a drag handle to the row and enables drag signals. */ + protected void enable_drag() { + Gtk.drag_source_set( + this.drag_handle, + Gdk.ModifierType.BUTTON1_MASK, + DRAG_ENTRIES, + Gdk.DragAction.MOVE + ); + + Gtk.drag_dest_set( + this, + // No highlight, we'll take care of that ourselves so we + // can avoid highlighting the row that was picked up + Gtk.DestDefaults.MOTION | Gtk.DestDefaults.DROP, + DRAG_ENTRIES, + Gdk.DragAction.MOVE + ); + + this.drag_handle.drag_begin.connect(on_drag_begin); + this.drag_handle.drag_end.connect(on_drag_end); + this.drag_handle.drag_data_get.connect(on_drag_data_get); + + this.drag_motion.connect(on_drag_motion); + this.drag_leave.connect(on_drag_leave); + this.drag_data_received.connect(on_drag_data_received); + + this.drag_handle.get_style_context().add_class("geary-drag-handle"); + this.drag_handle.show(); + + get_style_context().add_class("geary-draggable"); + } + + + private void on_drag_begin(Gdk.DragContext context) { + // Draw a nice drag icon + Gtk.Allocation alloc = Gtk.Allocation(); + this.get_allocation(out alloc); + + Cairo.ImageSurface surface = new Cairo.ImageSurface( + Cairo.Format.ARGB32, alloc.width, alloc.height + ); + Cairo.Context paint = new Cairo.Context(surface); + + + Gtk.StyleContext style = get_style_context(); + style.add_class("geary-drag-icon"); + draw(paint); + style.remove_class("geary-drag-icon"); + + int x, y; + this.drag_handle.translate_coordinates(this, 0, 0, out x, out y); + surface.set_device_offset(-x, -y); + Gtk.drag_set_icon_surface(context, surface); + + // Set a visual hint that the row is being dragged + style.add_class("geary-drag-source"); + this.drag_picked_up = true; + } + + private void on_drag_end(Gdk.DragContext context) { + get_style_context().remove_class("geary-drag-source"); + this.drag_picked_up = false; + } + + private bool on_drag_motion(Gdk.DragContext context, + int x, int y, + uint time_) { + if (!this.drag_entered) { + this.drag_entered = true; + + // Don't highlight the same row that was picked up + if (!this.drag_picked_up) { + Gtk.ListBox? parent = get_parent() as Gtk.ListBox; + if (parent != null) { + parent.drag_highlight_row(this); + } + } + } + + return true; + } + + private void on_drag_leave(Gdk.DragContext context, + uint time_) { + if (!this.drag_picked_up) { + Gtk.ListBox? parent = get_parent() as Gtk.ListBox; + if (parent != null) { + parent.drag_unhighlight_row(); + } + } + this.drag_entered = false; + } + + private void on_drag_data_get(Gdk.DragContext context, + Gtk.SelectionData selection_data, + uint info, uint time_) { + selection_data.set( + Gdk.Atom.intern_static_string(DND_ATOM), 8, + get_index().to_string().data + ); + } + + private void on_drag_data_received(Gdk.DragContext context, + int x, int y, + Gtk.SelectionData selection_data, + uint info, uint time_) { + int drag_index = int.parse((string) selection_data.get_data()); + Gtk.ListBox? parent = this.get_parent() as Gtk.ListBox; + if (parent != null) { + EditorRow? drag_row = parent.get_row_at_index(drag_index) as EditorRow; + if (drag_row != null && drag_row != this) { + drag_row.dropped(this); + } + } + } + +} + + +internal class Accounts.LabelledEditorRow : EditorRow { + + + public Gtk.Label label { get; private set; default = new Gtk.Label(""); } + public V value { get; private set; } + + + public LabelledEditorRow(string label, V value) { + this.label.halign = Gtk.Align.START; + this.label.valign = Gtk.Align.CENTER; + this.label.set_text(label); + this.label.show(); + this.layout.add(this.label); + + bool expand_label = true; + this.value = value; + Gtk.Widget? widget = value as Gtk.Widget; + if (widget != null) { + Gtk.Entry? entry = value as Gtk.Entry; + if (entry != null) { + expand_label = false; + entry.xalign = 1; + entry.hexpand = true; + } + + widget.valign = Gtk.Align.CENTER; + widget.show(); + this.layout.add(widget); + } + + this.label.hexpand = expand_label; + } + + public void set_dim_label(bool is_dim) { + if (is_dim) { + this.label.get_style_context().add_class(Gtk.STYLE_CLASS_DIM_LABEL); + } else { + this.label.get_style_context().remove_class(Gtk.STYLE_CLASS_DIM_LABEL); + } + } + +} + + +internal class Accounts.AddRow : EditorRow { + + + public AddRow() { + get_style_context().add_class("geary-add-row"); + Gtk.Image add_icon = new Gtk.Image.from_icon_name( + "list-add-symbolic", Gtk.IconSize.BUTTON + ); + add_icon.set_hexpand(true); + add_icon.show(); + + this.layout.add(add_icon); + } + +} + + +internal class Accounts.ServiceProviderRow : + LabelledEditorRow { + + + public ServiceProviderRow(Geary.ServiceProvider provider, + string other_type_label) { + string? label = other_type_label; + switch (provider) { + case Geary.ServiceProvider.GMAIL: + label = _("Gmail"); + break; + + case Geary.ServiceProvider.OUTLOOK: + label = _("Outlook.com"); + break; + + case Geary.ServiceProvider.YAHOO: + label = _("Yahoo"); + break; + } + + base( + // Translators: Label describes the service provider + // hosting the email account, e.g. Gmail, Yahoo, or some + // other generic IMAP service. + _("Service provider"), + new Gtk.Label(label) + ); + + // Can't change this, so deactivate and dim out + set_activatable(false); + this.value.get_style_context().add_class(Gtk.STYLE_CLASS_DIM_LABEL); + } + +} + + +internal abstract class Accounts.AccountRow : + LabelledEditorRow { + + + internal Geary.AccountInformation account { get; private set; } + + + protected AccountRow(Geary.AccountInformation account, string label, V value) { + base(label, value); + this.account = account; + this.account.changed.connect(on_account_changed); + + set_dim_label(true); + } + + ~AccountRow() { + this.account.changed.disconnect(on_account_changed); + } + + public abstract void update(); + + private void on_account_changed() { + update(); + } + +} + + +private abstract class Accounts.ServiceRow : AccountRow { + + + internal Geary.ServiceInformation service { get; private set; } + + protected virtual bool is_value_editable { + get { + return ( + this.account.service_provider == Geary.ServiceProvider.OTHER && + !this.is_goa_account + ); + } + } + + // XXX convenience method until we get a better way of doing this. + protected bool is_goa_account { + get { return (this.account.mediator is GoaMediator); } + } + + + protected ServiceRow(Geary.AccountInformation account, + Geary.ServiceInformation service, + string label, + V value) { + base(account, label, value); + this.service = service; + this.service.notify.connect_after(on_notify); + + bool is_editable = this.is_value_editable; + set_activatable(is_editable); + + Gtk.Widget? widget = value as Gtk.Widget; + if (widget != null && !is_editable) { + if (widget is Gtk.Label) { + widget.get_style_context().add_class(Gtk.STYLE_CLASS_DIM_LABEL); + } else { + widget.set_sensitive(false); + } + } + } + + ~ServiceRow() { + this.service.notify.disconnect(on_notify); + } + + private void on_notify() { + update(); + } + +} + + +/** Interface for rows that use a validator for editable values. */ +internal interface Accounts.ValidatingRow : EditorRow { + + + /** The row's validator */ + public abstract Components.Validator validator { get; protected set; } + + /** Determines if the row's value has actually changed. */ + public abstract bool has_changed { get; } + + /** Fired when validated and the value has actually changed. */ + public signal void changed(); + + /** Fired when validated and the value has actually changed. */ + public signal void committed(); + + /** + * Hooks up signals to the validator. + * + * Implementing classes should call this in their constructor + * after having constructed a validator + */ + protected void setup_validator() { + this.validator.changed.connect(on_validator_changed); + this.validator.activated.connect(on_validator_check_commit); + this.validator.focus_lost.connect(on_validator_check_commit); + } + + /** + * Called when the row's value should be stored. + * + * This is only called when the row's value has changed, is + * valid, and the user has activated or changed to a different + * row. */ + protected virtual void commit() { + // noop + } + + private void on_validator_changed() { + if (this.has_changed) { + changed(); + } + } + + private void on_validator_check_commit() { + if (this.has_changed) { + commit(); + committed(); + } + } + +} + + +internal class Accounts.TlsComboBox : Gtk.ComboBox { + + private const string INSECURE_ICON = "channel-insecure-symbolic"; + private const string SECURE_ICON = "channel-secure-symbolic"; + + + public string label { get; private set; default = ""; } + + + public Geary.TlsNegotiationMethod method { + get { + try { + return Geary.TlsNegotiationMethod.for_value(this.active_id); + } catch { + return Geary.TlsNegotiationMethod.TRANSPORT; + } + } + set { + this.active_id = value.to_value(); + } + } + + + public TlsComboBox() { + // Translators: This label describes what form of transport + // security (TLS, StartTLS, etc) used by an account's IMAP or SMTP + // service. + this.label = _("Connection security"); + + Gtk.ListStore store = new Gtk.ListStore( + 3, typeof(string), typeof(string), typeof(string) + ); + Gtk.TreeIter iter; + store.append(out iter); + store.set( + iter, + 0, Geary.TlsNegotiationMethod.NONE.to_value(), + 1, INSECURE_ICON, + 2, _("None") + ); + store.append(out iter); + store.set( + iter, + 0, Geary.TlsNegotiationMethod.START_TLS.to_value(), + 1, SECURE_ICON, + 2, _("StartTLS") + ); + store.append(out iter); + store.set( + iter, + 0, Geary.TlsNegotiationMethod.TRANSPORT.to_value(), + 1, SECURE_ICON, + 2, _("TLS") + ); + + this.model = store; + set_id_column(0); + + Gtk.CellRendererText text_renderer = new Gtk.CellRendererText(); + pack_start(text_renderer, true); + add_attribute(text_renderer, "text", 2); + + Gtk.CellRendererPixbuf icon_renderer = new Gtk.CellRendererPixbuf(); + pack_start(icon_renderer, true); + add_attribute(icon_renderer, "icon_name", 1); + } + +} + + +internal class Accounts.OutgoingAuthComboBox : Gtk.ComboBoxText { + + + public string label { get; private set; } + + public Geary.Credentials.Requirement source { + get { + try { + return Geary.Credentials.Requirement.for_value(this.active_id); + } catch { + return Geary.Credentials.Requirement.USE_INCOMING; + } + } + set { + this.active_id = value.to_value(); + } + } + + + public OutgoingAuthComboBox() { + // Translators: Label for source of SMTP authentication + // credentials (none, use IMAP, custom) when adding a new + // account + this.label = _("Login"); + + append( + Geary.Credentials.Requirement.NONE.to_value(), + // Translators: ComboBox value for source of SMTP + // authentication credentials (none) when adding a new + // account + _("No login needed") + ); + + append( + Geary.Credentials.Requirement.USE_INCOMING.to_value(), + // Translators: ComboBox value for source of SMTP + // authentication credentials (use IMAP) when adding a new + // account + _("Use same login as receiving") + ); + + append( + Geary.Credentials.Requirement.CUSTOM.to_value(), + // Translators: ComboBox value for source of SMTP + // authentication credentials (custom) when adding a new + // account + _("Use a different login") + ); + } + +} + + +/** + * Displaying and manages validation of popover-based forms. + */ +internal class Accounts.EditorPopover : Gtk.Popover { + + + internal Gtk.Grid layout { + get; private set; default = new Gtk.Grid(); + } + + protected Gtk.Widget popup_focus = null; + + + public EditorPopover() { + get_style_context().add_class("geary-editor"); + + this.layout.orientation = Gtk.Orientation.VERTICAL; + this.layout.set_row_spacing(6); + this.layout.set_column_spacing(12); + this.layout.show(); + add(this.layout); + + this.closed.connect_after(on_closed); + } + + ~EditorPopover() { + this.closed.disconnect(on_closed); + } + + /** {@inheritdoc} */ + public new void popup() { + // Work-around GTK+ issue #1138 + Gtk.Widget target = get_relative_to(); + + Gtk.Allocation content_area; + target.get_allocation(out content_area); + + Gtk.StyleContext style = target.get_style_context(); + Gtk.StateFlags flags = style.get_state(); + Gtk.Border margin = style.get_margin(flags); + + content_area.x = margin.left; + content_area.y = margin.bottom; + content_area.width -= (content_area.x + margin.right); + content_area.height -= (content_area.y + margin.top); + + set_pointing_to(content_area); + + base.popup(); + + if (this.popup_focus != null) { + this.popup_focus.grab_focus(); + } + } + + public void add_labelled_row(string label, Gtk.Widget value) { + Gtk.Label label_widget = new Gtk.Label(label); + label_widget.get_style_context().add_class(Gtk.STYLE_CLASS_DIM_LABEL); + label_widget.halign = Gtk.Align.END; + label_widget.show(); + + this.layout.add(label_widget); + this.layout.attach_next_to(value, label_widget, Gtk.PositionType.RIGHT); + } + + private void on_closed() { + destroy(); + } + +} diff -Nru geary-0.12.4/src/client/accounts/accounts-editor-servers-pane.vala geary-3.32.0/src/client/accounts/accounts-editor-servers-pane.vala --- geary-0.12.4/src/client/accounts/accounts-editor-servers-pane.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/src/client/accounts/accounts-editor-servers-pane.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,1113 @@ +/* + * Copyright 2016 Software Freedom Conservancy Inc. + * Copyright 2018-2019 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +/** + * An account editor pane for editing server details for an account. + */ +[GtkTemplate (ui = "/org/gnome/Geary/accounts_editor_servers_pane.ui")] +internal class Accounts.EditorServersPane : + Gtk.Grid, EditorPane, AccountPane, CommandPane { + + + /** {@inheritDoc} */ + internal weak Accounts.Editor editor { get; set; } + + /** {@inheritDoc} */ + internal Geary.AccountInformation account { get ; protected set; } + + /** {@inheritDoc} */ + internal Application.CommandStack commands { + get; protected set; default = new Application.CommandStack(); + } + + /** {@inheritDoc} */ + internal Gtk.Widget initial_widget { + get { return this.details_list; } + } + + /** {@inheritDoc} */ + internal bool is_operation_running { + get { return !this.sensitive; } + protected set { update_operation_ui(value); } + } + + /** {@inheritDoc} */ + internal GLib.Cancellable? op_cancellable { + get; protected set; default = new GLib.Cancellable(); + } + + private Geary.Engine engine; + + // These are copies of the originals that can be updated before + // validating on apply, without breaking anything. + private Geary.ServiceInformation incoming_mutable; + private Geary.ServiceInformation outgoing_mutable; + + private Gee.List validators = + new Gee.LinkedList(); + + + [GtkChild] + private Gtk.HeaderBar header; + + [GtkChild] + private Gtk.Grid pane_content; + + [GtkChild] + private Gtk.Adjustment pane_adjustment; + + [GtkChild] + private Gtk.ListBox details_list; + + [GtkChild] + private Gtk.ListBox receiving_list; + + [GtkChild] + private Gtk.ListBox sending_list; + + [GtkChild] + private Gtk.Button apply_button; + + [GtkChild] + private Gtk.Spinner apply_spinner; + + private SaveDraftsRow save_drafts; + private SaveSentRow save_sent; + + private ServiceLoginRow incoming_login; + private ServicePasswordRow incoming_password; + + private ServiceOutgoingAuthRow outgoing_auth; + private ServiceLoginRow outgoing_login; + private ServicePasswordRow outgoing_password; + + + public EditorServersPane(Editor editor, Geary.AccountInformation account) { + this.editor = editor; + this.account = account; + this.engine = ((GearyApplication) editor.application).engine; + this.incoming_mutable = new Geary.ServiceInformation.copy(account.incoming); + this.outgoing_mutable = new Geary.ServiceInformation.copy(account.outgoing); + + this.pane_content.set_focus_vadjustment(this.pane_adjustment); + + // Details + + this.details_list.set_header_func(Editor.seperator_headers); + // Only add an account provider if it is esoteric enough. + if (this.account.mediator is GoaMediator) { + this.details_list.add( + new AccountProviderRow(editor.accounts, this.account) + ); + } + ServiceProviderRow service_provider = + new ServiceProviderRow( + this.account.service_provider, + this.account.service_label + ); + service_provider.set_dim_label(true); + service_provider.activatable = false; + add_row(this.details_list, service_provider); + + this.save_drafts = new SaveDraftsRow( + this.account, this.commands, this.op_cancellable + ); + add_row(this.details_list, this.save_drafts); + + this.save_sent = new SaveSentRow( + this.account, this.commands, this.op_cancellable + ); + switch (account.service_provider) { + case YAHOO: + case OTHER: + add_row(this.details_list, this.save_sent); + break; + } + + // Receiving + + this.receiving_list.set_header_func(Editor.seperator_headers); + add_row( + this.receiving_list, + new ServiceHostRow( + account, + this.incoming_mutable, + this.commands, + this.op_cancellable + ) + ); + add_row( + this.receiving_list, + new ServiceSecurityRow( + account, + this.incoming_mutable, + this.commands, + this.op_cancellable + ) + ); + + this.incoming_password = new ServicePasswordRow( + account, + this.incoming_mutable, + this.commands, + this.op_cancellable + ); + + this.incoming_login = new ServiceLoginRow( + account, + this.incoming_mutable, + this.commands, + this.op_cancellable, + this.incoming_password + ); + + add_row(this.receiving_list, this.incoming_login); + add_row(this.receiving_list, this.incoming_password); + + // Sending + + this.sending_list.set_header_func(Editor.seperator_headers); + add_row( + this.sending_list, + new ServiceHostRow( + account, + this.outgoing_mutable, + this.commands, + this.op_cancellable + ) + ); + add_row( + this.sending_list, + new ServiceSecurityRow( + account, + this.outgoing_mutable, + this.commands, + this.op_cancellable + ) + ); + this.outgoing_auth = new ServiceOutgoingAuthRow( + account, + this.outgoing_mutable, + this.incoming_mutable, + this.commands, + this.op_cancellable + ); + this.outgoing_auth.value.changed.connect(on_outgoing_auth_changed); + add_row(this.sending_list, this.outgoing_auth); + + this.outgoing_password = new ServicePasswordRow( + account, + this.outgoing_mutable, + this.commands, + this.op_cancellable + ); + + this.outgoing_login = new ServiceLoginRow( + account, + this.outgoing_mutable, + this.commands, + this.op_cancellable, + this.outgoing_password + ); + + add_row(this.sending_list, this.outgoing_login); + add_row(this.sending_list, this.outgoing_password); + + // Misc plumbing + + connect_account_signals(); + connect_command_signals(); + + update_outgoing_auth(); + } + + ~EditorServersPane() { + disconnect_account_signals(); + disconnect_command_signals(); + } + + /** {@inheritDoc} */ + internal Gtk.HeaderBar get_header() { + return this.header; + } + + /** {@inheritDoc} */ + protected void command_executed() { + this.editor.update_command_actions(); + this.apply_button.set_sensitive(this.commands.can_undo); + } + + private bool is_valid() { + return Geary.traverse(this.validators).all((v) => v.is_valid); + } + + private async void save(GLib.Cancellable? cancellable) { + this.is_operation_running = true; + + // Only need to validate if a generic, local account since + // other account types have read-only incoming/outgoing + // settings + bool is_valid = true; + bool has_changed = false; + if (this.account.service_provider == Geary.ServiceProvider.OTHER && + !this.editor.accounts.is_goa_account(this.account)) { + is_valid = yield validate(cancellable); + if (is_valid) { + has_changed |= yield update_service( + this.account.incoming, this.incoming_mutable, cancellable + ); + has_changed |= yield update_service( + this.account.outgoing, this.outgoing_mutable, cancellable + ); + } + } + + this.is_operation_running = false; + + if (is_valid) { + if (this.save_drafts.value_changed) { + has_changed = true; + } + + if (this.save_sent.value_changed) { + has_changed = true; + } + + if (has_changed) { + this.account.changed(); + } + + this.editor.pop(); + } else { + // Re-enable apply so that the same config can be re-tried + // in the face of transient errors, without having to + // change something to re-enable it + this.apply_button.set_sensitive(true); + + // Undo these manually since it would have been updated + // already by the command + this.account.save_drafts = this.save_drafts.initial_value; + this.account.save_sent = this.save_sent.initial_value; + } + } + + private async bool validate(GLib.Cancellable? cancellable) { + // Use a copy here so we can handle any prompting needed + // (auth, certs) directly, rather than through the main window + Geary.AccountInformation local_account = + new Geary.AccountInformation.copy(this.account); + local_account.untrusted_host.connect(on_untrusted_host); + + string? message = null; + bool imap_valid = false; + try { + yield this.engine.validate_imap( + local_account, this.incoming_mutable, cancellable + ); + imap_valid = true; + } catch (Geary.ImapError.UNAUTHENTICATED err) { + debug("Error authenticating IMAP service: %s", err.message); + // Translators: In-app notification label + message = _("Check your receiving login and password"); + } catch (GLib.TlsError.BAD_CERTIFICATE err) { + // Nothing to do here, since the untrusted host + // handler will be dealing with it + debug("Error validating IMAP certifiate: %s", err.message); + } catch (GLib.IOError.CANCELLED err) { + // Nothing to do here, someone just cancelled + debug("IMAP validation was cancelled: %s", err.message); + } catch (GLib.Error err) { + Geary.ErrorContext context = new Geary.ErrorContext(err); + debug("Error validating IMAP service: %s", + context.format_full_error()); + // Translators: In-app notification label + message = _("Check your receiving server details"); + } + + bool smtp_valid = false; + if (imap_valid) { + debug("Validating SMTP..."); + try { + yield this.engine.validate_smtp( + local_account, + this.outgoing_mutable, + this.incoming_mutable.credentials, + cancellable + ); + smtp_valid = true; + } catch (Geary.SmtpError.AUTHENTICATION_FAILED err) { + debug("Error authenticating SMTP service: %s", err.message); + // There was an SMTP auth error, but IMAP already + // succeeded, so the user probably needs to + // specify custom creds here + this.outgoing_auth.value.source = Geary.Credentials.Requirement.CUSTOM; + // Translators: In-app notification label + message = _("Check your sending login and password"); + } catch (GLib.TlsError.BAD_CERTIFICATE err) { + // Nothing to do here, since the untrusted host + // handler will be dealing with it + debug("Error validating SMTP certifiate: %s", err.message); + } catch (GLib.IOError.CANCELLED err) { + // Nothing to do here, someone just cancelled + debug("SMTP validation was cancelled: %s", err.message); + } catch (GLib.Error err) { + Geary.ErrorContext context = new Geary.ErrorContext(err); + debug("Error validating SMTP service: %s", + context.format_full_error()); + // Translators: In-app notification label + message = _("Check your sending server details"); + } + } + + local_account.untrusted_host.disconnect(on_untrusted_host); + + bool is_valid = imap_valid && smtp_valid; + debug("Validation complete, is valid: %s", is_valid.to_string()); + + if (!is_valid && message != null) { + this.editor.add_notification( + new InAppNotification( + // Translators: In-app notification label, the + // string substitution is a more detailed reason. + _("Account not updated: %s").printf(message) + ) + ); + } + + return is_valid; + } + + private async bool update_service(Geary.ServiceInformation existing, + Geary.ServiceInformation copy, + GLib.Cancellable cancellable) { + bool has_changed = !existing.equal_to(copy); + if (has_changed) { + try { + yield this.editor.accounts.update_local_credentials( + this.account, existing, copy, cancellable + ); + } catch (GLib.Error err) { + warning( + "Could not update %s %s credentials: %s", + this.account.id, + existing.protocol.to_value(), + err.message + ); + } + + try { + yield this.engine.update_account_service( + this.account, copy, cancellable + ); + } catch (GLib.Error err) { + warning( + "Could not update %s %s service: %s", + this.account.id, + existing.protocol.to_value(), + err.message + ); + } + } + return has_changed; + } + + private void add_row(Gtk.ListBox list, EditorRow row) { + list.add(row); + ValidatingRow? validating = row as ValidatingRow; + if (validating != null) { + validating.changed.connect(on_validator_changed); + validating.validator.activated.connect_after(on_validator_activated); + this.validators.add(validating.validator); + } + } + + private void update_outgoing_auth() { + this.outgoing_login.set_visible( + this.outgoing_auth.value.source == CUSTOM + ); + } + + private void update_operation_ui(bool is_running) { + this.apply_spinner.visible = is_running; + this.apply_spinner.active = is_running; + this.apply_button.sensitive = !is_running; + this.sensitive = !is_running; + } + + private void on_validator_changed() { + this.apply_button.set_sensitive(is_valid()); + } + + private void on_validator_activated() { + if (is_valid()) { + this.apply_button.clicked(); + } + } + + private void on_untrusted_host(Geary.AccountInformation account, + Geary.ServiceInformation service, + Geary.Endpoint endpoint, + GLib.TlsConnection cx) { + this.editor.prompt_pin_certificate.begin( + account, service, endpoint, null, + (obj, res) => { + try { + this.editor.prompt_pin_certificate.end(res); + } catch (Application.CertificateManagerError err) { + // All good, just drop back into the editor + // window. + return; + } + + // Kick off another attempt to save + this.save.begin(null); + }); + } + + [GtkCallback] + private void on_cancel_button_clicked() { + if (this.is_operation_running) { + cancel_operation(); + } else { + this.editor.pop(); + } + } + + [GtkCallback] + private void on_apply_button_clicked() { + this.save.begin(this.op_cancellable); + } + + [GtkCallback] + private bool on_list_keynav_failed(Gtk.Widget widget, + Gtk.DirectionType direction) { + bool ret = Gdk.EVENT_PROPAGATE; + Gtk.Container? next = null; + if (direction == Gtk.DirectionType.DOWN) { + if (widget == this.details_list) { + next = this.receiving_list; + } else if (widget == this.receiving_list) { + next = this.sending_list; + } + } else if (direction == Gtk.DirectionType.UP) { + if (widget == this.sending_list) { + next = this.receiving_list; + } else if (widget == this.receiving_list) { + next = this.details_list; + } + } + + if (next != null) { + next.child_focus(direction); + ret = Gdk.EVENT_STOP; + } + return ret; + } + + private void on_outgoing_auth_changed() { + update_outgoing_auth(); + } + + [GtkCallback] + private void on_activate(Gtk.ListBoxRow row) { + Accounts.EditorRow server_row = + row as Accounts.EditorRow; + if (server_row != null) { + server_row.activated(this); + } + } + +} + + +private class Accounts.AccountProviderRow : + AccountRow { + + private Manager accounts; + + public AccountProviderRow(Manager accounts, + Geary.AccountInformation account) { + base( + account, + // Translators: This label describes the program that + // created the account, e.g. an SSO service like GOA, or + // locally by Geary. + _("Account source"), + new Gtk.Label("") + ); + + this.accounts = accounts; + update(); + } + + public override void update() { + string? source = null; + bool enabled = false; + if (this.account.mediator is GoaMediator) { + source = _("GNOME Online Accounts"); + enabled = true; + } else { + source = _("Geary"); + } + + this.value.set_text(source); + this.set_activatable(enabled); + Gtk.StyleContext style = this.value.get_style_context(); + if (enabled) { + style.remove_class(Gtk.STYLE_CLASS_DIM_LABEL); + } else { + style.add_class(Gtk.STYLE_CLASS_DIM_LABEL); + } + } + + public override void activated(EditorServersPane pane) { + if (this.accounts.is_goa_account(this.account)) { + this.accounts.show_goa_account.begin( + account, pane.op_cancellable, + (obj, res) => { + try { + this.accounts.show_goa_account.end(res); + } catch (GLib.Error err) { + // XXX display an error to the user + debug( + "Failed to show GOA account \"%s\": %s", + account.id, + err.message + ); + } + }); + } + } + +} + + +private class Accounts.SaveDraftsRow : + AccountRow { + + + public bool value_changed { + get { return this.initial_value != this.value.state; } + } + public bool initial_value { get; private set; } + + private Application.CommandStack commands; + private GLib.Cancellable? cancellable; + + + public SaveDraftsRow(Geary.AccountInformation account, + Application.CommandStack commands, + GLib.Cancellable? cancellable) { + Gtk.Switch value = new Gtk.Switch(); + base( + account, + // Translators: This label describes an account + // preference. + _("Save drafts on server"), + value + ); + update(); + this.commands = commands; + this.cancellable = cancellable; + this.activatable = false; + this.initial_value = this.account.save_drafts; + this.account.notify["save-drafts"].connect(on_account_changed); + this.value.notify["active"].connect(on_activate); + } + + public override void update() { + this.value.state = this.account.save_drafts; + } + + private void on_activate() { + if (this.value.state != this.account.save_drafts) { + this.commands.execute.begin( + new Application.PropertyCommand( + this.account, "save_drafts", this.value.state + ), + this.cancellable + ); + } + } + + private void on_account_changed() { + update(); + } + +} + + +private class Accounts.SaveSentRow : + AccountRow { + + + public bool value_changed { + get { return this.initial_value != this.value.state; } + } + public bool initial_value { get; private set; } + + private Application.CommandStack commands; + private GLib.Cancellable? cancellable; + + + public SaveSentRow(Geary.AccountInformation account, + Application.CommandStack commands, + GLib.Cancellable? cancellable) { + Gtk.Switch value = new Gtk.Switch(); + base( + account, + // Translators: This label describes an account + // preference. + _("Save sent email on server"), + value + ); + update(); + this.commands = commands; + this.cancellable = cancellable; + this.activatable = false; + this.initial_value = this.account.save_sent; + this.account.notify["save-sent"].connect(on_account_changed); + this.value.notify["active"].connect(on_activate); + } + + public override void update() { + this.value.state = this.account.save_sent; + } + + private void on_activate() { + if (this.value.state != this.account.save_sent) { + this.commands.execute.begin( + new Application.PropertyCommand( + this.account, "save_sent", this.value.state + ), + this.cancellable + ); + } + } + + private void on_account_changed() { + update(); + } + +} + + +private class Accounts.ServiceHostRow : + ServiceRow, ValidatingRow { + + + public Components.Validator validator { + get; protected set; + } + + public bool has_changed { + get { + return this.value.text.strip() != get_entry_text(); + } + } + + private Application.CommandStack commands; + private GLib.Cancellable? cancellable; + + + public ServiceHostRow(Geary.AccountInformation account, + Geary.ServiceInformation service, + Application.CommandStack commands, + GLib.Cancellable? cancellable) { + string label = ""; + switch (service.protocol) { + case Geary.Protocol.IMAP: + // Translators: This label describes the host name or IP + // address and port used by an account's IMAP service. + label = _("IMAP server"); + break; + + case Geary.Protocol.SMTP: + // Translators: This label describes the host name or IP + // address and port used by an account's SMTP service. + label = _("SMTP server"); + break; + } + + base(account, service, label, new Gtk.Entry()); + this.commands = commands; + this.cancellable = cancellable; + this.activatable = false; + this.validator = new Components.NetworkAddressValidator(this.value); + + // Update after the validator is wired up to ensure the value + // is validated + setup_validator(); + update(); + } + + public override void update() { + string value = get_entry_text(); + if (Geary.String.is_empty(value)) { + value = _("None"); + } + this.value.text = value; + } + + protected void commit() { + GLib.NetworkAddress? address = + ((Components.NetworkAddressValidator) this.validator) + .validated_address; + if (address != null) { + uint16 port = address.port != 0 + ? (uint16) address.port + : this.service.get_default_port(); + this.commands.execute.begin( + new Application.CommandSequence({ + new Application.PropertyCommand( + this.service, "host", address.hostname + ), + new Application.PropertyCommand( + this.service, "port", port + ) + }), + this.cancellable + ); + } + } + + private string? get_entry_text() { + string? value = this.service.host ?? ""; + if (!Geary.String.is_empty(value)) { + // Only show the port if it not the appropriate default port + uint16 port = this.service.port; + if (port != this.service.get_default_port()) { + value = "%s:%d".printf(value, this.service.port); + } + } + return value; + } + +} + + +private class Accounts.ServiceSecurityRow : + ServiceRow { + + + private Application.CommandStack commands; + private GLib.Cancellable? cancellable; + + + public ServiceSecurityRow(Geary.AccountInformation account, + Geary.ServiceInformation service, + Application.CommandStack commands, + GLib.Cancellable? cancellable) { + TlsComboBox value = new TlsComboBox(); + base(account, service, value.label, value); + update(); + + this.commands = commands; + this.cancellable = cancellable; + this.activatable = false; + value.changed.connect(on_value_changed); + } + + public override void update() { + this.value.method = this.service.transport_security; + } + + private void on_value_changed() { + if (this.service.transport_security != this.value.method) { + Application.Command cmd = new Application.PropertyCommand( + this.service, "transport-security", this.value.method + ); + + debug("Security port: %u", this.service.port); + + // Update the port if we're currently using the default, + // otherwise keep the custom port as-is. + if (this.service.port == this.service.get_default_port()) { + // Work out what the new port would be by copying the + // service and applying the new security param up + // front + Geary.ServiceInformation copy = + new Geary.ServiceInformation.copy(this.service); + copy.transport_security = this.value.method; + cmd = new Application.CommandSequence( + {cmd, + new Application.PropertyCommand( + this.service, "port", copy.get_default_port() + ) + }); + } + this.commands.execute.begin(cmd, this.cancellable); + } + } + +} + + +private class Accounts.ServiceLoginRow : + ServiceRow, ValidatingRow { + + + public Components.Validator validator { + get; protected set; + } + + public bool has_changed { + get { + return this.value.text.strip() != get_entry_text(); + } + } + + private Application.CommandStack commands; + private GLib.Cancellable? cancellable; + private ServicePasswordRow? password_row; + + + public ServiceLoginRow(Geary.AccountInformation account, + Geary.ServiceInformation service, + Application.CommandStack commands, + GLib.Cancellable? cancellable, + ServicePasswordRow? password_row = null) { + base( + account, + service, + // Translators: Label for the user's login name for an + // IMAP, SMTP, etc service + _("Login name"), + new Gtk.Entry() + ); + + this.commands = commands; + this.cancellable = cancellable; + this.activatable = false; + this.validator = new Components.Validator(this.value); + this.password_row = password_row; + + // If provided, only show the password row when the login has + // changed + if (password_row != null) { + password_row.hide(); + } + + // Update after the validator is wired up to ensure the value + // is validated + update(); + setup_validator(); + } + + public override void update() { + this.value.text = get_entry_text(); + } + + protected void commit() { + if (this.service.credentials != null) { + Application.Command cmd = + new Application.PropertyCommand( + this.service, + "credentials", + new Geary.Credentials( + this.service.credentials.supported_method, + this.value.text + ) + ); + + if (this.password_row != null) { + cmd = new Application.CommandSequence({ + cmd, + new Application.PropertyCommand( + this.password_row, + "visible", + true + ) + }); + } + + this.commands.execute.begin(cmd, this.cancellable); + } + } + + private string? get_entry_text() { + string? label = null; + if (this.service.credentials != null) { + string method = "%s"; + Gtk.StyleContext value_style = this.value.get_style_context(); + switch (this.service.credentials.supported_method) { + case Geary.Credentials.Method.PASSWORD: + value_style.remove_class(Gtk.STYLE_CLASS_DIM_LABEL); + break; + + case Geary.Credentials.Method.OAUTH2: + // Add a suffix for OAuth2 auth so people know they + // shouldn't expect to be prompted for a password + + // Translators: Label used when an account's IMAP or + // SMTP service uses OAuth2. The string replacement is + // the service's login name. + method = _("%s using OAuth2"); + + value_style.add_class(Gtk.STYLE_CLASS_DIM_LABEL); + break; + } + + label = method.printf(this.service.credentials.user ?? ""); + } else if (this.service.protocol == Geary.Protocol.SMTP && + this.service.credentials_requirement == + Geary.Credentials.Requirement.USE_INCOMING) { + label = _("Use receiving server login"); + } else { + // Translators: Label used when no auth scheme is used + // by an account's IMAP or SMTP service. + label = _("None"); + } + return label; + } + +} + + +private class Accounts.ServicePasswordRow : + ServiceRow, ValidatingRow { + + + public Components.Validator validator { + get; protected set; + } + + public bool has_changed { + get { + return this.value.text.strip() != get_entry_text(); + } + } + + private Application.CommandStack commands; + private GLib.Cancellable? cancellable; + + + public ServicePasswordRow(Geary.AccountInformation account, + Geary.ServiceInformation service, + Application.CommandStack commands, + GLib.Cancellable? cancellable) { + base( + account, + service, + // Translators: Label for the user's password for an IMAP, + // SMTP, etc service + _("Password"), + new Gtk.Entry() + ); + + this.commands = commands; + this.cancellable = cancellable; + this.activatable = false; + this.value.visibility = false; + this.value.input_purpose = Gtk.InputPurpose.PASSWORD; + this.validator = new Components.Validator(this.value); + + // Update after the validator is wired up to ensure the value + // is validated + update(); + setup_validator(); + } + + public override void update() { + this.value.text = get_entry_text(); + } + + protected void commit() { + if (this.service.credentials != null) { + this.commands.execute.begin( + new Application.PropertyCommand( + this.service, + "credentials", + this.service.credentials.copy_with_token(this.value.text) + ), + this.cancellable + ); + } + } + + private string get_entry_text() { + return (this.service.credentials != null) + ? this.service.credentials.token ?? "" + : ""; + } + +} + + +private class Accounts.ServiceOutgoingAuthRow : + ServiceRow { + + + private Application.CommandStack commands; + private GLib.Cancellable? cancellable; + private Geary.ServiceInformation imap_service; + + + public ServiceOutgoingAuthRow(Geary.AccountInformation account, + Geary.ServiceInformation smtp_service, + Geary.ServiceInformation imap_service, + Application.CommandStack commands, + GLib.Cancellable? cancellable) { + OutgoingAuthComboBox value = new OutgoingAuthComboBox(); + base(account, smtp_service, value.label, value); + update(); + + this.commands = commands; + this.cancellable = cancellable; + this.imap_service = imap_service; + this.activatable = false; + value.changed.connect(on_value_changed); + } + + public override void update() { + this.value.source = this.service.credentials_requirement; + } + + private void on_value_changed() { + if (this.service.credentials_requirement != this.value.source) { + // Need to update the credentials given the new + // requirements, too + Geary.Credentials? new_creds = null; + if (this.value.source == CUSTOM) { + new_creds = new Geary.Credentials( + Geary.Credentials.Method.PASSWORD, "" + ); + } + + Application.CommandSequence seq = new Application.CommandSequence({ + new Application.PropertyCommand( + this.service, "credentials", new_creds + ), + new Application.PropertyCommand( + this.service, "credentials-requirement", this.value.source + ) + }); + + // The default SMTP port also depends on the auth method + // used, so also update the port here if we're currently + // using the default, otherwise keep the custom port + // as-is. + if (this.service.port == this.service.get_default_port()) { + // Work out what the new port would be by copying the + // service and applying the new security param up + // front + Geary.ServiceInformation copy = + new Geary.ServiceInformation.copy(this.service); + copy.credentials_requirement = this.value.source; + seq.commands.add( + new Application.PropertyCommand( + this.service, "port", copy.get_default_port() + ) + ); + } + + this.commands.execute.begin(seq, this.cancellable); + } + } + +} diff -Nru geary-0.12.4/src/client/accounts/accounts-editor.vala geary-3.32.0/src/client/accounts/accounts-editor.vala --- geary-0.12.4/src/client/accounts/accounts-editor.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/src/client/accounts/accounts-editor.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,440 @@ +/* + * Copyright 2018-2019 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +/** + * The main account editor window. + * + * The editor is a dialog window that manages a stack of {@link + * EditorPane} instances. Each pane handles a specific task (listing + * accounts, adding a new account, editing an existing one, etc.). The + * editor displaying panes as needed, and provides some common command + * management, account management and other common code for the panes. + */ +[GtkTemplate (ui = "/org/gnome/Geary/accounts_editor.ui")] +public class Accounts.Editor : Gtk.Dialog { + + + private const ActionEntry[] ACTION_ENTRIES = { + { GearyApplication.ACTION_REDO, on_redo }, + { GearyApplication.ACTION_UNDO, on_undo }, + }; + + + internal static void seperator_headers(Gtk.ListBoxRow row, + Gtk.ListBoxRow? first) { + if (first == null) { + row.set_header(null); + } else if (row.get_header() == null) { + row.set_header(new Gtk.Separator(Gtk.Orientation.HORIZONTAL)); + } + } + + + internal Manager accounts { get; private set; } + + internal Application.CertificateManager certificates { + get; private set; + } + + private SimpleActionGroup actions = new SimpleActionGroup(); + + [GtkChild] + private Gtk.Overlay notifications_pane; + + [GtkChild] + private Gtk.Stack editor_panes; + + private EditorListPane editor_list_pane; + + private Gee.LinkedList editor_pane_stack = + new Gee.LinkedList(); + + + public Editor(GearyApplication application, Gtk.Window parent) { + this.application = application; + this.transient_for = parent; + + this.accounts = application.controller.account_manager; + this.certificates = application.controller.certificate_manager; + + // Can't set this in Glade 3.22.1 :( + this.get_content_area().border_width = 0; + + this.accounts = application.controller.account_manager; + + this.actions.add_action_entries(ACTION_ENTRIES, this); + insert_action_group("win", this.actions); + + this.editor_list_pane = new EditorListPane(this); + push(this.editor_list_pane); + + update_command_actions(); + } + + public override bool key_press_event(Gdk.EventKey event) { + bool ret = Gdk.EVENT_PROPAGATE; + + // Allow the user to use Esc, Back and Alt+arrow keys to + // navigate between panes. If a pane is executing a long + // running operation, only allow Esc and use it to cancel the + // operation instead. + EditorPane? current_pane = get_current_pane(); + if (current_pane != null && + current_pane != this.editor_list_pane) { + Gdk.ModifierType state = ( + event.state & Gtk.accelerator_get_default_mod_mask() + ); + bool is_ltr = (get_direction() == Gtk.TextDirection.LTR); + + switch (event.keyval) { + case Gdk.Key.Escape: + if (current_pane.is_operation_running) { + current_pane.cancel_operation(); + } else { + pop(); + } + ret = Gdk.EVENT_STOP; + break; + + case Gdk.Key.Back: + if (!current_pane.is_operation_running) { + pop(); + ret = Gdk.EVENT_STOP; + } + break; + + case Gdk.Key.Left: + if (!current_pane.is_operation_running && + state == Gdk.ModifierType.MOD1_MASK && + is_ltr) { + pop(); + ret = Gdk.EVENT_STOP; + } + break; + + case Gdk.Key.Right: + if (!current_pane.is_operation_running && + state == Gdk.ModifierType.MOD1_MASK && + !is_ltr) { + pop(); + ret = Gdk.EVENT_STOP; + } + break; + } + + } + + if (ret != Gdk.EVENT_STOP) { + ret = base.key_press_event(event); + } + + return ret; + } + + /** + * Adds and shows a new pane in the editor. + */ + internal void push(EditorPane pane) { + // Since we keep old, already-popped panes around (see pop for + // details), when a new pane is pushed on they need to be + // truncated. + EditorPane current = get_current_pane(); + int target_length = this.editor_pane_stack.index_of(current) + 1; + while (target_length < this.editor_pane_stack.size) { + EditorPane old = this.editor_pane_stack.remove_at(target_length); + this.editor_panes.remove(old); + } + + // Now push the new pane on + this.editor_pane_stack.add(pane); + this.editor_panes.add(pane); + this.editor_panes.set_visible_child(pane); + } + + /** + * Removes the current pane from the editor, showing the last one. + */ + internal void pop() { + // One can't simply remove old panes fro the GTK stack since + // there won't be any transition between them - the old one + // will simply disappear. So we need to keep old, popped panes + // around until a new one is pushed on. + EditorPane current = get_current_pane(); + int prev_index = this.editor_pane_stack.index_of(current) - 1; + EditorPane prev = this.editor_pane_stack.get(prev_index); + this.editor_panes.set_visible_child(prev); + } + + /** Displays an in-app notification in the dialog. */ + internal void add_notification(InAppNotification notification) { + this.notifications_pane.add_overlay(notification); + notification.show(); + } + + /** + * Prompts for pinning a certificate using the certificate manager. + * + * This provides a thing wrapper around {@link + * CertificateManager.prompt_pin_certificate} that uses the + * account editor as the dialog parent. + */ + internal async void prompt_pin_certificate(Geary.AccountInformation account, + Geary.ServiceInformation service, + Geary.Endpoint endpoint, + GLib.Cancellable? cancellable) + throws Application.CertificateManagerError { + try { + yield this.certificates.prompt_pin_certificate( + this, account, service, endpoint, true, cancellable + ); + } catch (Application.CertificateManagerError.UNTRUSTED err) { + throw err; + } catch (Application.CertificateManagerError.STORE_FAILED err) { + // XXX show error info bar rather than a notification? + add_notification( + new InAppNotification( + // Translators: In-app notification label, when + // the app had a problem pinning an otherwise + // untrusted TLS certificate + _("Failed to store certificate") + ) + ); + throw err; + } catch (Application.CertificateManagerError err) { + debug("Unexpected error pinning cert: %s", err.message); + throw err; + } + } + + /** Removes an account from the editor. */ + internal void remove_account(Geary.AccountInformation account) { + this.editor_panes.set_visible_child(this.editor_list_pane); + this.editor_list_pane.remove_account(account); + } + + /** Updates the state of the editor's undo and redo actions. */ + internal void update_command_actions() { + bool can_undo = false; + bool can_redo = false; + CommandPane? pane = get_current_pane() as CommandPane; + if (pane != null) { + can_undo = pane.commands.can_undo; + can_redo = pane.commands.can_redo; + } + + get_action(GearyApplication.ACTION_UNDO).set_enabled(can_undo); + get_action(GearyApplication.ACTION_REDO).set_enabled(can_redo); + } + + private inline EditorPane? get_current_pane() { + return this.editor_panes.get_visible_child() as EditorPane; + } + + private inline GLib.SimpleAction get_action(string name) { + return (GLib.SimpleAction) this.actions.lookup_action(name); + } + + private void on_undo() { + CommandPane? pane = get_current_pane() as CommandPane; + if (pane != null) { + pane.undo(); + } + } + + private void on_redo() { + CommandPane? pane = get_current_pane() as CommandPane; + if (pane != null) { + pane.redo(); + } + } + + [GtkCallback] + private void on_pane_changed() { + EditorPane? visible = get_current_pane(); + Gtk.Widget? header = null; + if (visible != null) { + // Do this in an idle callback since it's not 100% + // reliable to just call it here for some reason. :( + GLib.Idle.add(() => { + visible.initial_widget.grab_focus(); + return GLib.Source.REMOVE; + }); + header = visible.get_header(); + } + set_titlebar(header); + update_command_actions(); + } + +} + + +// XXX I'd really like to make EditorPane an abstract class, +// AccountPane an abstract class extending that, and the four concrete +// panes extend those, but the GTK+ Builder XML template system +// requires a template class to designate its immediate parent +// class. I.e. if accounts-editor-list-pane.ui specifies GtkGrid as +// the parent of EditorListPane, then it much exactly be that and not +// an instance of EditorPane, even if that extends GtkGrid. As a +// result, both EditorPane and AccountPane must both be interfaces so +// that the concrete pane classes can derive from GtkGrid directly, +// and everything becomes horrible. See GTK+ Issue #1151: +// https://gitlab.gnome.org/GNOME/gtk/issues/1151 + +/** + * Base interface for panes that can be shown by the accounts editor. + */ +internal interface Accounts.EditorPane : Gtk.Grid { + + + /** The editor displaying this pane. */ + internal abstract weak Accounts.Editor editor { get; set; } + + /** The editor displaying this pane. */ + internal abstract Gtk.Widget initial_widget { get; } + + /** + * Determines if a long running operation is being executed. + * + * @see cancel_operation + */ + internal abstract bool is_operation_running { get; protected set; } + + /** + * Long running operation cancellable. + * + * This cancellable must be passed to any long-running operations + * involving I/O. If not null and operation is cancelled, the + * value should be cancelled and replaced with a new instance. + * + * @see cancel_operation + */ + internal abstract GLib.Cancellable? op_cancellable { get; protected set; } + + /** The GTK header bar to display for this pane. */ + internal abstract Gtk.HeaderBar get_header(); + + /** + * Cancels this pane's current operation, any. + * + * Sets {@link is_operation_running} to false and if {@link + * op_cancellable} is not null, it is cancelled and replaced with + * a new instance. + */ + internal void cancel_operation() { + this.is_operation_running = false; + if (this.op_cancellable != null) { + this.op_cancellable.cancel(); + this.op_cancellable = new GLib.Cancellable(); + } + } +} + + +/** + * Interface for editor panes that display a specific account. + */ +internal interface Accounts.AccountPane : EditorPane { + + + /** Account being displayed by this pane. */ + internal abstract Geary.AccountInformation account { get; protected set; } + + + /** + * Connects to account signals. + * + * Implementing classes should call this in their constructor. + */ + protected void connect_account_signals() { + this.account.changed.connect(on_account_changed); + update_header(); + } + + /** + * Disconnects from account signals. + * + * Implementing classes should call this in their destructor. + */ + protected void disconnect_account_signals() { + this.account.changed.disconnect(on_account_changed); + } + + /** + * Called when an account has changed. + * + * By default, updates the editor's header subtitle. + */ + private void account_changed() { + update_header(); + } + + private inline void update_header() { + get_header().subtitle = this.account.display_name; + } + + private void on_account_changed() { + account_changed(); + } + +} + +/** + * Interface for editor panes that support undoing/redoing user actions. + */ +internal interface Accounts.CommandPane : EditorPane { + + + /** Stack for the user's commands. */ + internal abstract Application.CommandStack commands { get; protected set; } + + + /** Un-does the last user action, if enabled. */ + internal virtual void undo() { + this.commands.undo.begin(null); + } + + /** Re-does the last user action, if enabled. */ + internal virtual void redo() { + this.commands.redo.begin(null); + } + + /** + * Connects to command stack signals. + * + * Implementing classes should call this in their constructor. + */ + protected void connect_command_signals() { + this.commands.executed.connect(on_command); + this.commands.undone.connect(on_command); + this.commands.redone.connect(on_command); + } + + /** + * Disconnects from command stack signals. + * + * Implementing classes should call this in their destructor. + */ + protected void disconnect_command_signals() { + this.commands.executed.disconnect(on_command); + this.commands.undone.disconnect(on_command); + this.commands.redone.disconnect(on_command); + } + + /** + * Called when a command is executed, undone or redone. + * + * By default, calls {@link Accounts.Editor.update_command_actions}. + */ + protected virtual void command_executed() { + this.editor.update_command_actions(); + } + + private void on_command() { + command_executed(); + } + +} diff -Nru geary-0.12.4/src/client/accounts/accounts-manager.vala geary-3.32.0/src/client/accounts/accounts-manager.vala --- geary-0.12.4/src/client/accounts/accounts-manager.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/src/client/accounts/accounts-manager.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,1615 @@ +/* + * Copyright 2017 Software Freedom Conservancy Inc. + * Copyright 2018 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + + +/** + * Manages email account lifecycle for Geary. + * + * This class is responsible for creating, loading, saving and + * removing accounts and their persisted data (configuration, + * databases, caches, authentication tokens). The manager supports + * both locally-specified accounts (i.e. those created by the user in + * the app) and from SSO systems such as GOA via the Accounts.Provider + * interface. + * + * Newly loaded and newly created accounts are first added to the + * manager with a particular status (enabled, disabled, etc). Accounts + * can have their enabled or disabled status updated manually, + */ +public class Accounts.Manager : GLib.Object { + + + /** The name of the Geary configuration file. */ + public const string SETTINGS_FILENAME = "geary.ini"; + + private const string LOCAL_ID_PREFIX = "account_"; + private const string LOCAL_ID_FORMAT = "account_%02u"; + private const string GOA_ID_PREFIX = "goa_"; + + private const int CONFIG_VERSION = 1; + + private const string GROUP_METADATA = "Metadata"; + + private const string METADATA_STATUS = "status"; + private const string METADATA_VERSION = "version"; + private const string METADATA_GOA = "goa_id"; + + + /** + * Specifies the overall status of an account. + */ + public enum Status { + /** The account is enabled and operational. */ + ENABLED, + + /** The account was disabled by the user. */ + DISABLED, + + /** The account is unavailable to be used, but may come back. */ + UNAVAILABLE, + + /** The account has been removed and is scheduled for deletion. */ + REMOVED; + + public static Status for_value(string value) + throws Geary.EngineError { + return Geary.ObjectUtils.from_enum_nick( + typeof(Status), value.ascii_down() + ); + } + + public string to_value() { + return Geary.ObjectUtils.to_enum_nick( + typeof(Status), this + ); + } + } + + + /** Specifies an account's current state. */ + private class AccountState { + + + /** The account represented by this object. */ + public Geary.AccountInformation account { get; private set; } + + /** Determines the account's overall state. */ + public Status status { + get { + Status status = Status.ENABLED; + if (!this.enabled) { + status = Status.DISABLED; + } + if (!this.available) { + status = Status.UNAVAILABLE; + } + return status; + } + } + + /** Whether this account is enabled. */ + public bool enabled { get; set; default = true; } + + /** Whether this account is available. */ + public bool available { get; set; default = true; } + + + internal AccountState(Geary.AccountInformation account) { + this.account = account; + } + + } + + + /** Returns the number of currently known accounts. */ + public int size { get { return this.accounts.size; } } + + /** Returns the base directory for account configuration. */ + public GLib.File config_dir { get; private set; } + + /** Returns the base directory for account data. */ + public GLib.File data_dir { get; private set; } + + + private Gee.Map accounts = + new Gee.HashMap(); + + private Gee.LinkedList removed = + new Gee.LinkedList(); + + + private Geary.CredentialsMediator local_mediator; + private Goa.Client? goa_service = null; + + + /** Fired when a new account is created. */ + public signal void account_added(Geary.AccountInformation added, Status status); + + /** Fired when an existing account's state has changed. */ + public signal void account_status_changed(Geary.AccountInformation changed, + Status new_status); + + /** Fired when an account is deleted. */ + public signal void account_removed(Geary.AccountInformation removed); + + /** Emitted to notify an account problem has occurred. */ + public signal void report_problem(Geary.ProblemReport problem); + + + public Manager(Geary.CredentialsMediator local_mediator, + GLib.File config_dir, + GLib.File data_dir) { + this.local_mediator = local_mediator; + this.config_dir = config_dir; + this.data_dir = data_dir; + } + + public async void connect_goa(GLib.Cancellable? cancellable) + throws GLib.Error { + this.goa_service = yield new Goa.Client(cancellable); + this.goa_service.account_added.connect(on_goa_account_added); + this.goa_service.account_changed.connect(on_goa_account_changed); + this.goa_service.account_removed.connect(on_goa_account_removed); + } + + /** Returns the account with the given id. */ + public Geary.AccountInformation? get_account(string id) { + AccountState? state = this.accounts.get(id); + return (state != null) ? state.account : null; + } + + /** Returns the status for the given account. */ + public Status get_status(Geary.AccountInformation account) { + AccountState? state = this.accounts.get(account.id); + return (state != null) ? state.status : Status.UNAVAILABLE; + } + + /** Returns a read-only iterable of all currently known accounts. */ + public Geary.Iterable iterable() { + return Geary.traverse( + this.accounts.values + ).map( + ((state) => { return state.account; }) + ); + } + + /** + * Returns the display name for the current desktop login session. + */ + public string? get_account_name() { + string? name = Environment.get_real_name(); + if (Geary.String.is_empty(name) || name == "Unknown") { + name = null; + } + return name; + } + + /** + * Returns a new account, not yet stored on disk. + */ + public async Geary.AccountInformation + new_orphan_account(Geary.ServiceProvider provider, + Geary.RFC822.MailboxAddress primary_mailbox, + GLib.Cancellable? cancellable) { + string id = yield next_id(cancellable); + return new Geary.AccountInformation( + id, provider, this.local_mediator, primary_mailbox + ); + } + + /** + * Creates new account's disk and credential storage as needed. + */ + public async void create_account(Geary.AccountInformation account, + GLib.Cancellable? cancellable) + throws GLib.Error { + yield create_account_dirs(account, cancellable); + yield save_account(account, cancellable); + set_enabled(account, true); + + // if it's a local account, save the passwords now + SecretMediator? mediator = account.mediator as SecretMediator; + if (mediator != null) { + yield mediator.update_token(account, account.incoming, cancellable); + yield mediator.update_token(account, account.outgoing, cancellable); + } + } + + public async void load_accounts(GLib.Cancellable? cancellable) + throws GLib.Error { + // Step 1. Load existing accounts from the user config dir + GLib.FileEnumerator? enumerator = null; + try { + enumerator = yield this.config_dir.enumerate_children_async( + "standard::*", + FileQueryInfoFlags.NONE, + Priority.DEFAULT, + cancellable + ); + } catch (GLib.IOError.NOT_FOUND err) { + // Don't worry about the dir not being found, it just + // means we have no accounts to load. + } + + while (enumerator != null && !cancellable.is_cancelled()) { + // Get 10 at a time to batch the IO together + GLib.List info_list = yield enumerator.next_files_async( + 10, GLib.Priority.DEFAULT, cancellable + ); + + uint len = info_list.length(); + for (uint i = 0; i < len && !cancellable.is_cancelled(); i++) { + GLib.FileInfo file = info_list.nth_data(i); + if (file.get_file_type() == FileType.DIRECTORY) { + string id = file.get_name(); + try { + Geary.AccountInformation info = yield load_account( + id, cancellable + ); + set_enabled(info, true); + } catch (ConfigError.UNAVAILABLE err) { + // All good, this was handled properly by + // load_account. + } catch (ConfigError.REMOVED err) { + // All good, this was handled properly by + // load_account. + } catch (GLib.Error err) { + debug("Error loading account %s", id); + report_problem( + new Geary.ProblemReport( + Geary.ProblemType.GENERIC_ERROR, + err + )); + } + } + } + + if (len == 0) { + // We're done + enumerator = null; + } + } + + // Step 2. Load previously unseen accounts from GOA, if available. + if (this.goa_service != null) { + GLib.List list = this.goa_service.get_accounts(); + for (int i=0; i < list.length() && !cancellable.is_cancelled(); i++) { + Goa.Object account = list.nth_data(i); + string id = to_geary_id(account); + if (!this.accounts.has_key(id)) { + yield this.create_goa_account(account, cancellable); + } + } + } + } + + /** + * Marks an existing account as being unavailable. + * + * This keeps the account in the known set, but marks it as being + * unavailable. + */ + public void disable_account(Geary.AccountInformation account) { + if (this.accounts.has_key(account.id)) { + set_available(account, false); + } + } + + /** + * Removes an account from the manager's set of known accounts. + * + * This removes the account from the known set, marks the account + * as deleted, and queues it for deletion. The account will not + * actually be deleted until {@link expunge_accounts} is called, + * and until then the account can be re-added using {@link + * restore_account}. + */ + public async void remove_account(Geary.AccountInformation account, + GLib.Cancellable cancellable) + throws GLib.Error { + this.accounts.unset(account.id); + this.removed.add(account); + account.changed.disconnect(on_account_changed); + yield save_account(account, cancellable); + account_removed(account); + } + + /** + * Restores an account that has previously been removed. + * + * This restores an account previously removed via a call to + * {@link remove_account}, adding it back to the known set, as + * long as {@link expunge_accounts} has not been called since + * he account was removed. + */ + public async void restore_account(Geary.AccountInformation account, + GLib.Cancellable cancellable) + throws GLib.Error { + if (this.removed.remove(account)) { + yield save_account(account, cancellable); + set_enabled(account, true); + } + } + + /** + * Deletes all local data for all accounts that have been removed. + */ + public async void expunge_accounts(GLib.Cancellable? cancellable) + throws GLib.Error { + while (!this.removed.is_empty && !cancellable.is_cancelled()) { + yield delete_account(this.removed.remove_at(0), cancellable); + } + } + + /** + * Saves an account's configuration data to disk. + */ + public async void save_account(Geary.AccountInformation info, + GLib.Cancellable? cancellable) + throws GLib.Error { + // Ensure only one async task is saving an info at once, since + // at least the Engine can cause multiple saves to be called + // in quick succession when updating special folder config. + int token = yield info.write_lock.claim_async(cancellable); + + GLib.Error? thrown = null; + try { + yield save_account_locked(info, cancellable); + } catch (GLib.Error err) { + thrown = err; + } + + info.write_lock.release(ref token); + + if (thrown != null) { + throw thrown; + } + } + + /** Updates a local account service's credentials. */ + public async void update_local_credentials(Geary.AccountInformation account, + Geary.ServiceInformation old_service, + Geary.ServiceInformation new_service, + GLib.Cancellable? cancellable) + throws GLib.Error { + SecretMediator? mediator = account.mediator as SecretMediator; + if (mediator != null) { + if (new_service.credentials != null) { + yield mediator.update_token(account, new_service, cancellable); + } + + if (old_service.credentials != null && + (new_service.credentials == null || + (new_service.credentials != null && + old_service.credentials.user != old_service.credentials.user))) { + yield mediator.clear_token(account, old_service, cancellable); + } + } + } + + /** + * Determines if an account is a GOA account or not. + */ + public bool is_goa_account(Geary.AccountInformation account) { + return (account.mediator is GoaMediator); + } + + /** + * Opens GNOME Settings to add an account of a particular type. + * + * Throws an error if it was not possible to open GNOME Settings, + * or if the given type is not supported for by GOA. + */ + public async void add_goa_account(Geary.ServiceProvider type, + GLib.Cancellable? cancellable) + throws GLib.Error { + switch (type) { + case Geary.ServiceProvider.GMAIL: + yield open_goa_settings("add", "google", cancellable); + break; + + case Geary.ServiceProvider.OUTLOOK: + yield open_goa_settings("add", "windows_live", cancellable); + break; + + default: + throw new GLib.IOError.NOT_SUPPORTED("Not supported for GOA"); + } + } + + /** + * Opens GOA settings for the given account in GNOME Settings. + * + * Throws an error if it was not possible to open GNOME Settings, + * or if the given account is not backed by GOA. + */ + public async void show_goa_account(Geary.AccountInformation account, + GLib.Cancellable? cancellable) + throws GLib.Error { + if (!is_goa_account(account)) { + throw new GLib.IOError.NOT_SUPPORTED("Not a GOA Account"); + } + + yield open_goa_settings( + to_goa_id(account.id), null, cancellable + ); + } + + /** Returns the next id for a new local account. */ + private async string next_id(GLib.Cancellable? cancellable) { + // Get the next known free id + string? last_account = this.accounts.keys.fold((next, last) => { + string? result = last; + if (next.has_prefix(LOCAL_ID_PREFIX)) { + result = (last == null || strcmp(last, next) < 0) ? next : last; + } + return result; + }, + null); + + uint next_id = 1; + if (last_account != null) { + next_id = int.parse( + last_account.substring(LOCAL_ID_PREFIX.length) + ) + 1; + } + + // Check for existing directories that might conflict + string id = LOCAL_ID_FORMAT.printf(next_id); + try { + while ((yield Geary.Files.query_exists_async( + this.config_dir.get_child(id), cancellable)) || + (yield Geary.Files.query_exists_async( + this.data_dir.get_child(id), cancellable))) { + next_id++; + id = LOCAL_ID_FORMAT.printf(next_id); + } + } catch (GLib.Error err) { + // Not much we can do here except keep going anyway? + debug("Error checking for a free id on disk: %s", err.message); + } + + return id; + } + + /** + * Loads an account info from a config directory. + * + * Throws an error if the config file was not found, could not be + * parsed, or doesn't have all required fields. + */ + private async Geary.AccountInformation + load_account(string id, GLib.Cancellable? cancellable) + throws ConfigError { + GLib.File config_dir = this.config_dir.get_child(id); + GLib.File data_dir = this.data_dir.get_child(id); + + Geary.ConfigFile config = new Geary.ConfigFile( + config_dir.get_child(SETTINGS_FILENAME) + ); + + try { + yield config.load(cancellable); + } catch (GLib.KeyFileError err) { + throw new ConfigError.SYNTAX(err.message); + } catch (GLib.Error err) { + throw new ConfigError.IO(err.message); + } + + Geary.ConfigFile.Group metadata_config = + config.get_group(GROUP_METADATA); + int version = metadata_config.get_int(METADATA_VERSION, 0); + Status status = Status.ENABLED; + try { + status = Status.for_value( + metadata_config.get_string( + METADATA_STATUS, status.to_value() + )); + } catch (Geary.EngineError err) { + throw new ConfigError.SYNTAX("%s: Invalid status value", id); + } + + string? goa_id = metadata_config.get_string(METADATA_GOA, null); + bool is_goa = (goa_id != null); + + // This exists purely for people were running master with GOA + // accounts before the new accounts editor landed and 0.13 was + // released. It can be removed once 0.14 is out. + if (goa_id == null && id.has_prefix(GOA_ID_PREFIX)) { + goa_id = to_goa_id(id); + is_goa = true; + } + + Goa.Object? goa_handle = null; + GoaMediator? goa_mediator = null; + Geary.ServiceProvider? default_provider = null; + Geary.CredentialsMediator mediator = this.local_mediator; + + if (is_goa) { + if (this.goa_service == null) { + throw new ConfigError.MANAGEMENT("GOA service not available"); + } + + goa_handle = this.goa_service.lookup_by_id(goa_id); + if (goa_handle != null) { + mediator = goa_mediator = new GoaMediator(goa_handle); + default_provider = goa_mediator.get_service_provider(); + } else { + // The GOA account has gone away, so there's nothing + // we can do except to remove it locally as well + info( + "%s: GOA account %s has been removed, removing local data", + id, goa_id + ); + status = Status.REMOVED; + // Use the default mediator since we can't create a + // GOA equiv, but set a dummy default provider so we + // don't get an error loading the config + default_provider = Geary.ServiceProvider.OTHER; + } + } + + AccountConfig? accounts = null; + ServiceConfig? services = null; + switch (version) { + case 0: + accounts = new AccountConfigLegacy(); + services = new ServiceConfigLegacy(); + break; + + case 1: + accounts = new AccountConfigV1(is_goa); + services = new ServiceConfigV1(); + break; + + default: + throw new ConfigError.VERSION( + "Unsupported config version: %d", version + ); + } + + Geary.AccountInformation? account = null; + try { + account = accounts.load( + config, + id, + mediator, + default_provider, + get_account_name() + ); + account.set_account_directories(config_dir, data_dir); + } catch (GLib.KeyFileError err) { + throw new ConfigError.SYNTAX(err.message); + } + + // If the account has been marked as removed, now that we have + // an account object and its dirs have been set up we can add + // it to the removed list and just bail out. + if (status == Status.REMOVED) { + this.removed.add(account); + throw new ConfigError.REMOVED("Account marked for removal"); + } + + if (!is_goa) { + try { + services.load(config, account, account.incoming); + services.load(config, account, account.outgoing); + } catch (GLib.KeyFileError err) { + throw new ConfigError.SYNTAX(err.message); + } + } else { + account.service_label = goa_mediator.get_service_label(); + try { + yield goa_mediator.update(account, cancellable); + } catch (GLib.Error err) { + throw new ConfigError.MANAGEMENT(err.message); + } + + if (!is_valid_goa_account(goa_handle)) { + // If we get here, the GOA account's mail service used + // to exist (we just loaded Geary's config for it) but + // no longer does. This indicates the mail service has + // been disabled, so set it as disabled. + set_available(account, false); + throw new ConfigError.UNAVAILABLE( + "GOA Mail service not available" + ); + } + } + + // If the account has been marked as disabled, mark it as such + // and bail out. + if (status == Status.DISABLED) { + set_enabled(account, false); + throw new ConfigError.UNAVAILABLE("Account disabled"); + } + + return account; + } + + private async void save_account_locked(Geary.AccountInformation account, + GLib.Cancellable? cancellable) + throws GLib.Error { + if (account.config_dir == null) { + throw new GLib.IOError.NOT_SUPPORTED( + "Account %s does not have a config directory", account.id + ); + } + + Geary.ConfigFile config = new Geary.ConfigFile( + account.config_dir.get_child(SETTINGS_FILENAME) + ); + + // Load the file first so we maintain old settings + try { + yield config.load(cancellable); + } catch (GLib.Error err) { + // Oh well, just create a new one when saving + debug("Could not load existing config file: %s", err.message); + } + + Geary.ConfigFile.Group metadata_config = + config.get_group(GROUP_METADATA); + metadata_config.set_int( + METADATA_VERSION, CONFIG_VERSION + ); + metadata_config.set_string( + METADATA_STATUS, get_status(account).to_value() + ); + + bool is_goa = is_goa_account(account); + if (is_goa) { + metadata_config.set_string(METADATA_GOA, to_goa_id(account.id)); + } + + AccountConfig accounts = new AccountConfigV1(is_goa); + accounts.save(account, config); + + if (!is_goa) { + ServiceConfig services = new ServiceConfigV1(); + services.save(account, account.incoming, config); + services.save(account, account.outgoing, config); + } + + debug("Writing config to: %s", config.file.get_path()); + yield config.save(cancellable); + } + + private async void delete_account(Geary.AccountInformation info, + GLib.Cancellable? cancellable) + throws GLib.Error { + // If it's a local account, try clearing the passwords. Keep + // going if there's an error though since we really want to + // delete the account dirs. + SecretMediator? mediator = info.mediator as SecretMediator; + if (mediator != null) { + try { + yield mediator.clear_token(info, info.incoming, cancellable); + } catch (Error e) { + debug("Error clearing IMAP password: %s", e.message); + } + + try { + yield mediator.clear_token(info, info.outgoing, cancellable); + } catch (Error e) { + debug("Error clearing IMAP password: %s", e.message); + } + } + + if (info.data_dir != null) { + yield Geary.Files.recursive_delete_async( + info.data_dir, GLib.Priority.LOW, cancellable + ); + } + + // Delete config last so if there are any errors above, it + // will be re-tried at next startup. + if (info.config_dir != null) { + yield Geary.Files.recursive_delete_async( + info.config_dir, GLib.Priority.LOW, cancellable + ); + } + } + + private inline AccountState lookup_state(Geary.AccountInformation account) { + AccountState? state = this.accounts.get(account.id); + if (state == null) { + state = new AccountState(account); + this.accounts.set(account.id, state); + } + return state; + } + + private bool set_enabled(Geary.AccountInformation account, bool is_enabled) { + bool was_added = !this.accounts.has_key(account.id); + AccountState state = lookup_state(account); + Status existing_status = state.status; + state.enabled = is_enabled; + + bool ret = false; + if (was_added) { + account_added(state.account, state.status); + account.changed.connect(on_account_changed); + ret = true; + } else if (state.status != existing_status) { + account_status_changed(state.account, state.status); + ret = true; + } + return ret; + } + + private bool set_available(Geary.AccountInformation account, bool is_available) { + bool was_added = !this.accounts.has_key(account.id); + AccountState state = lookup_state(account); + Status existing_status = state.status; + state.available = is_available; + + bool ret = false; + if (was_added) { + account_added(state.account, state.status); + account.changed.connect(on_account_changed); + ret = true; + } else if (state.status != existing_status) { + account_status_changed(state.account, state.status); + ret = true; + } + return ret; + } + + private async void create_account_dirs(Geary.AccountInformation info, + Cancellable? cancellable) + throws GLib.Error { + GLib.File config = this.config_dir.get_child(info.id); + GLib.File data = this.data_dir.get_child(info.id); + + yield Geary.Files.make_directory_with_parents(config, cancellable); + yield Geary.Files.make_directory_with_parents(data, cancellable); + + info.set_account_directories(config, data); + } + + private inline string to_geary_id(Goa.Object account) { + return GOA_ID_PREFIX + account.get_account().id; + } + + private inline string to_goa_id(string id) { + return id.has_prefix(GOA_ID_PREFIX) + ? id.substring(GOA_ID_PREFIX.length) + : id; + } + + private bool is_valid_goa_account(Goa.Object handle) { + Goa.Mail? mail = handle.get_mail(); + return ( + mail != null && + !handle.get_account().mail_disabled && + !Geary.String.is_empty(mail.imap_host) && + !Geary.String.is_empty(mail.smtp_host) + ); + } + + private async void create_goa_account(Goa.Object account, + GLib.Cancellable? cancellable) { + if (is_valid_goa_account(account)) { + Goa.Mail? mail = account.get_mail(); + string? name = mail.name; + if (Geary.String.is_empty_or_whitespace(name)) { + name = get_account_name(); + } + + GoaMediator mediator = new GoaMediator(account); + Geary.AccountInformation info = new Geary.AccountInformation( + to_geary_id(account), + mediator.get_service_provider(), + mediator, + new Geary.RFC822.MailboxAddress(name, mail.email_address) + ); + + info.ordinal = Geary.AccountInformation.next_ordinal++; + info.service_label = mediator.get_service_label(); + info.label = account.get_account().presentation_identity; + + try { + yield create_account_dirs(info, cancellable); + yield save_account(info, cancellable); + yield mediator.update(info, cancellable); + } catch (GLib.Error err) { + report_problem( + new Geary.ProblemReport( + Geary.ProblemType.GENERIC_ERROR, + err + )); + } + + set_enabled(info, true); + } else { + debug( + "Ignoring GOA %s account %s, mail service not enabled", + account.get_account().provider_type, + account.get_account().id + ); + } + } + + private async void update_goa_account(Geary.AccountInformation account, + bool is_available, + GLib.Cancellable? cancellable) { + GoaMediator mediator = (GoaMediator) account.mediator; + try { + yield mediator.update(account, cancellable); + + if (is_available) { + // Update will clear the creds, so make sure they get + // refreshed + yield account.load_outgoing_credentials(cancellable); + yield account.load_incoming_credentials(cancellable); + } + } catch (GLib.Error err) { + report_problem( + new Geary.AccountProblemReport( + Geary.ProblemType.GENERIC_ERROR, + account, + err + )); + } + + set_available(account, is_available); + } + + + private async void open_goa_settings(string action, + string? param, + GLib.Cancellable? cancellable) + throws GLib.Error { + // This method was based on the implementation from: + // https://gitlab.gnome.org/GNOME/gnome-calendar/blob/master/src/gcal-source-dialog.c, + // Courtesy Georges Basile Stavracas Neto + GLib.DBusProxy settings = yield new GLib.DBusProxy.for_bus( + GLib.BusType.SESSION, + GLib.DBusProxyFlags.NONE, + null, + "org.gnome.ControlCenter", + "/org/gnome/ControlCenter", + "org.gtk.Actions", + cancellable + ); + + // @s "launch-panel" + // @av [<@(sav) ("online-accounts", [<@s "add">, <@s "google">])>] + // @a{sv} {} + + GLib.Variant[] args = new GLib.Variant[] { + new GLib.Variant.variant(new GLib.Variant.string(action)) + }; + if (param != null) { + args += new GLib.Variant.variant(new GLib.Variant.string(param)); + } + + GLib.Variant command = new GLib.Variant.tuple( + new GLib.Variant[] { + new GLib.Variant.string("online-accounts"), + new GLib.Variant.array(GLib.VariantType.VARIANT, args) + } + ); + + GLib.Variant params = new GLib.Variant.tuple( + new GLib.Variant[] { + new GLib.Variant.string("launch-panel"), + new GLib.Variant.array( + GLib.VariantType.VARIANT, + new GLib.Variant[] { + new GLib.Variant.variant(command) + } + ), + new GLib.Variant("a{sv}") + } + ); + + yield settings.call( + "Activate", params, GLib.DBusCallFlags.NONE, -1, cancellable + ); + } + + private void on_goa_account_added(Goa.Object account) { + debug("GOA account added: %s", account.get_account().id); + // XXX get a cancellable for this. + this.create_goa_account.begin(account, null); + } + + private void on_goa_account_changed(Goa.Object account) { + debug("GOA account changed: %s", account.get_account().id); + AccountState? state = this.accounts.get(to_geary_id(account)); + // XXX get a cancellable to these + if (state != null) { + // We already know about this account, so check that it is + // still valid. If not, the account should be disabled, + // not deleted, since it may be re-enabled at some point. + this.update_goa_account.begin( + state.account, + is_valid_goa_account(account), + null + ); + } else { + // We haven't created an account for this GOA account + // before, so try doing so now. + // + // XXX get a cancellable for this. + this.create_goa_account.begin(account, null); + } + } + + private void on_goa_account_removed(Goa.Object account) { + debug("GOA account removed: %s", account.get_account().id); + AccountState? state = this.accounts.get(to_geary_id(account)); + if (state != null) { + // Just disabled it for now in case the GOA daemon as just + // shutting down. + set_available(state.account, false); + } + } + + private void on_account_changed(Geary.AccountInformation account) { + this.save_account.begin( + account, null, + (obj, res) => { + try { + this.save_account.end(res); + } catch (GLib.Error err) { + report_problem( + new Geary.AccountProblemReport( + Geary.ProblemType.GENERIC_ERROR, + account, + err + ) + ); + } + } + ); + } + +} + +/** Objects that can be used to load/save account configuration. */ +public interface Accounts.AccountConfig : GLib.Object { + + /** Loads a supported account from a config file. */ + public abstract Geary.AccountInformation + load(Geary.ConfigFile config, + string id, + Geary.CredentialsMediator mediator, + Geary.ServiceProvider? default_provider, + string? default_name) + throws ConfigError, GLib.KeyFileError; + + /** Saves an account to a config file. */ + public abstract void save(Geary.AccountInformation account, + Geary.ConfigFile config); + +} + + +/** Objects that can be used to load/save service configuration. */ +public interface Accounts.ServiceConfig : GLib.Object { + + /** Loads a service from a config file. */ + public abstract void load(Geary.ConfigFile config, + Geary.AccountInformation account, + Geary.ServiceInformation service) + throws ConfigError, GLib.KeyFileError; + + /** Saves a service to a config file. */ + public abstract void save(Geary.AccountInformation account, + Geary.ServiceInformation service, + Geary.ConfigFile config); + +} + + +public errordomain Accounts.ConfigError { + IO, + MANAGEMENT, + SYNTAX, + VERSION, + UNAVAILABLE, + REMOVED; +} + + +/** + * Manages persistence for version 1 config files. + */ +public class Accounts.AccountConfigV1 : AccountConfig, GLib.Object { + + + private const string GROUP_ACCOUNT = "Account"; + private const string GROUP_FOLDERS = "Folders"; + + private const string ACCOUNT_LABEL = "label"; + private const string ACCOUNT_ORDINAL = "ordinal"; + private const string ACCOUNT_PREFETCH = "prefetch_days"; + private const string ACCOUNT_PROVIDER = "service_provider"; + private const string ACCOUNT_SAVE_DRAFTS = "save_drafts"; + private const string ACCOUNT_SAVE_SENT = "save_sent"; + private const string ACCOUNT_SENDERS = "sender_mailboxes"; + private const string ACCOUNT_SIG = "signature"; + private const string ACCOUNT_USE_SIG = "use_signature"; + + private const string FOLDER_ARCHIVE = "archive_folder"; + private const string FOLDER_DRAFTS = "drafts_folder"; + private const string FOLDER_SENT = "sent_folder"; + private const string FOLDER_SPAM = "spam_folder"; + private const string FOLDER_TRASH = "trash_folder"; + + + private bool is_managed; + + public AccountConfigV1(bool is_managed) { + this.is_managed = is_managed; + } + + public Geary.AccountInformation load(Geary.ConfigFile config, + string id, + Geary.CredentialsMediator mediator, + Geary.ServiceProvider? default_provider, + string? default_name) + throws ConfigError, GLib.KeyFileError { + Geary.ConfigFile.Group account_config = + config.get_group(GROUP_ACCOUNT); + + Gee.List senders = + new Gee.LinkedList(); + foreach (string sender in + account_config.get_required_string_list(ACCOUNT_SENDERS)) { + try { + senders.add( + new Geary.RFC822.MailboxAddress.from_rfc822_string(sender) + ); + } catch (Geary.RFC822Error err) { + throw new ConfigError.SYNTAX( + "%s: Invalid sender address: %s", id, sender + ); + } + } + + if (senders.is_empty) { + throw new ConfigError.SYNTAX("%s: No sender addresses found", id); + } + + Geary.ServiceProvider provider = ( + default_provider != null + ? default_provider + : account_config.parse_required_value( + ACCOUNT_PROVIDER, + (value) => { + try { + return Geary.ServiceProvider.for_value(value); + } catch (Geary.EngineError err) { + throw new GLib.KeyFileError.INVALID_VALUE(err.message); + } + } + ) + ); + + Geary.AccountInformation account = new Geary.AccountInformation( + id, provider, mediator, senders.remove_at(0) + ); + + account.ordinal = account_config.get_int( + ACCOUNT_ORDINAL, Geary.AccountInformation.next_ordinal++ + ); + account.label = account_config.get_string( + ACCOUNT_LABEL, account.label + ); + account.prefetch_period_days = account_config.get_int( + ACCOUNT_PREFETCH, account.prefetch_period_days + ); + account.save_drafts = account_config.get_bool( + ACCOUNT_SAVE_DRAFTS, account.save_drafts + ); + account.save_sent = account_config.get_bool( + ACCOUNT_SAVE_SENT, account.save_sent + ); + account.use_signature = account_config.get_bool( + ACCOUNT_USE_SIG, account.use_signature + ); + account.signature = account_config.get_string( + ACCOUNT_SIG, account.signature + ); + foreach (Geary.RFC822.MailboxAddress sender in senders) { + account.append_sender(sender); + } + + Geary.ConfigFile.Group folder_config = + config.get_group(GROUP_FOLDERS); + account.archive_folder_path = load_folder(folder_config, FOLDER_ARCHIVE); + account.drafts_folder_path = load_folder(folder_config, FOLDER_DRAFTS); + account.sent_folder_path = load_folder(folder_config, FOLDER_SENT); + account.spam_folder_path = load_folder(folder_config, FOLDER_SPAM); + account.trash_folder_path = load_folder(folder_config, FOLDER_TRASH); + + return account; + } + + /** Saves an account to a config file. */ + public void save(Geary.AccountInformation account, + Geary.ConfigFile config) { + Geary.ConfigFile.Group account_config = + config.get_group(GROUP_ACCOUNT); + account_config.set_int(ACCOUNT_ORDINAL, account.ordinal); + account_config.set_string(ACCOUNT_LABEL, account.label); + account_config.set_int(ACCOUNT_PREFETCH, account.prefetch_period_days); + account_config.set_bool(ACCOUNT_SAVE_DRAFTS, account.save_drafts); + account_config.set_bool(ACCOUNT_SAVE_SENT, account.save_sent); + account_config.set_bool(ACCOUNT_USE_SIG, account.use_signature); + account_config.set_string(ACCOUNT_SIG, account.signature); + account_config.set_string_list( + ACCOUNT_SENDERS, + Geary.traverse(account.sender_mailboxes) + .map((sender) => sender.to_rfc822_string()) + .to_array_list() + ); + + if (!is_managed) { + account_config.set_string( + ACCOUNT_PROVIDER, account.service_provider.to_value() + ); + } + + Geary.ConfigFile.Group folder_config = + config.get_group(GROUP_FOLDERS); + save_folder(folder_config, FOLDER_ARCHIVE, account.archive_folder_path); + save_folder(folder_config, FOLDER_DRAFTS, account.drafts_folder_path); + save_folder(folder_config, FOLDER_SENT, account.sent_folder_path); + save_folder(folder_config, FOLDER_SPAM, account.spam_folder_path); + save_folder(folder_config, FOLDER_TRASH, account.trash_folder_path); + } + + private void save_folder(Geary.ConfigFile.Group config, + string key, + Geary.FolderPath? path) { + if (path != null) { + config.set_string_list( + key, new Gee.ArrayList.wrap(path.as_array()) + ); + } + } + + private Geary.FolderPath? load_folder(Geary.ConfigFile.Group config, + string key) { + Geary.FolderPath? path = null; + Gee.List parts = config.get_string_list(key); + if (!parts.is_empty) { + path = Geary.AccountInformation.build_folder_path(parts); + } + return path; + } + +} + + +/** + * Manages persistence for un-versioned account configuration. + */ +public class Accounts.AccountConfigLegacy : AccountConfig, GLib.Object { + + internal const string GROUP = "AccountInformation"; + + private const string ALTERNATE_EMAILS_KEY = "alternate_emails"; + private const string ARCHIVE_FOLDER_KEY = "archive_folder"; + private const string CREDENTIALS_METHOD_KEY = "credentials_method"; + private const string CREDENTIALS_PROVIDER_KEY = "credentials_provider"; + private const string DRAFTS_FOLDER_KEY = "drafts_folder"; + private const string EMAIL_SIGNATURE_KEY = "email_signature"; + private const string NICKNAME_KEY = "nickname"; + private const string ORDINAL_KEY = "ordinal"; + private const string PREFETCH_PERIOD_DAYS_KEY = "prefetch_period_days"; + private const string PRIMARY_EMAIL_KEY = "primary_email"; + private const string REAL_NAME_KEY = "real_name"; + private const string SAVE_DRAFTS_KEY = "save_drafts"; + private const string SAVE_SENT_MAIL_KEY = "save_sent_mail"; + private const string SENT_MAIL_FOLDER_KEY = "sent_mail_folder"; + private const string SERVICE_PROVIDER_KEY = "service_provider"; + private const string SPAM_FOLDER_KEY = "spam_folder"; + private const string TRASH_FOLDER_KEY = "trash_folder"; + private const string USE_EMAIL_SIGNATURE_KEY = "use_email_signature"; + + + public Geary.AccountInformation load(Geary.ConfigFile config_file, + string id, + Geary.CredentialsMediator mediator, + Geary.ServiceProvider? default_provider, + string? default_name) + throws ConfigError, GLib.KeyFileError { + Geary.ConfigFile.Group config = config_file.get_group(GROUP); + + string primary_email = config.get_required_string(PRIMARY_EMAIL_KEY); + string real_name = config.get_string(REAL_NAME_KEY, default_name); + + Geary.ServiceProvider provider = ( + default_provider != null + ? default_provider + : config.parse_required_value( + SERVICE_PROVIDER_KEY, + (value) => { + try { + return Geary.ServiceProvider.for_value(value); + } catch (Geary.EngineError err) { + throw new GLib.KeyFileError.INVALID_VALUE(err.message); + } + } + ) + ); + + Geary.AccountInformation info = new Geary.AccountInformation( + id, provider, mediator, + new Geary.RFC822.MailboxAddress(real_name, primary_email) + ); + + info.ordinal = config.get_int(ORDINAL_KEY, info.ordinal); + if (info.ordinal >= Geary.AccountInformation.next_ordinal) { + Geary.AccountInformation.next_ordinal = info.ordinal + 1; + } + + info.append_sender(new Geary.RFC822.MailboxAddress( + config.get_string(REAL_NAME_KEY), primary_email + )); + + info.label = config.get_string(NICKNAME_KEY); + + // Store alternate emails in a list of case-insensitive strings + Gee.List alt_email_list = config.get_string_list( + ALTERNATE_EMAILS_KEY + ); + foreach (string alt_email in alt_email_list) { + Geary.RFC822.MailboxAddresses mailboxes = + new Geary.RFC822.MailboxAddresses.from_rfc822_string(alt_email); + foreach (Geary.RFC822.MailboxAddress mailbox in mailboxes.get_all()) { + info.append_sender(mailbox); + } + } + + info.prefetch_period_days = config.get_int( + PREFETCH_PERIOD_DAYS_KEY, info.prefetch_period_days + ); + info.save_sent = config.get_bool( + SAVE_SENT_MAIL_KEY, info.save_sent + ); + info.use_signature = config.get_bool( + USE_EMAIL_SIGNATURE_KEY, info.use_signature + ); + info.signature = config.get_string( + EMAIL_SIGNATURE_KEY, info.signature + ); + + info.drafts_folder_path = Geary.AccountInformation.build_folder_path( + config.get_string_list(DRAFTS_FOLDER_KEY) + ); + info.sent_folder_path = Geary.AccountInformation.build_folder_path( + config.get_string_list(SENT_MAIL_FOLDER_KEY) + ); + info.spam_folder_path = Geary.AccountInformation.build_folder_path( + config.get_string_list(SPAM_FOLDER_KEY) + ); + info.trash_folder_path = Geary.AccountInformation.build_folder_path( + config.get_string_list(TRASH_FOLDER_KEY) + ); + info.archive_folder_path = Geary.AccountInformation.build_folder_path( + config.get_string_list(ARCHIVE_FOLDER_KEY) + ); + + info.save_drafts = config.get_bool(SAVE_DRAFTS_KEY, true); + + return info; + } + + public void save(Geary.AccountInformation info, + Geary.ConfigFile config_file) { + + Geary.ConfigFile.Group config = config_file.get_group(GROUP); + + config.set_string(REAL_NAME_KEY, info.primary_mailbox.name ?? ""); + config.set_string(PRIMARY_EMAIL_KEY, info.primary_mailbox.address); + config.set_string(NICKNAME_KEY, info.label); + config.set_string(SERVICE_PROVIDER_KEY, info.service_provider.to_value()); + config.set_int(ORDINAL_KEY, info.ordinal); + config.set_int(PREFETCH_PERIOD_DAYS_KEY, info.prefetch_period_days); + config.set_bool(SAVE_SENT_MAIL_KEY, info.save_sent); + config.set_bool(USE_EMAIL_SIGNATURE_KEY, info.use_signature); + config.set_string(EMAIL_SIGNATURE_KEY, info.signature); + if (info.has_sender_aliases) { + Gee.List alts = info.sender_mailboxes; + // Don't include the primary in the list + alts.remove_at(0); + + config.set_string_list( + ALTERNATE_EMAILS_KEY, + Geary.traverse(alts) + .map((alt) => alt.to_rfc822_string()) + .to_array_list() + ); + } + + Gee.ArrayList empty = new Gee.ArrayList(); + config.set_string_list( + DRAFTS_FOLDER_KEY, + (info.drafts_folder_path != null + ? new Gee.ArrayList.wrap(info.drafts_folder_path.as_array()) + : empty) + ); + config.set_string_list( + SENT_MAIL_FOLDER_KEY, + (info.sent_folder_path != null + ? new Gee.ArrayList.wrap(info.sent_folder_path.as_array()) + : empty) + ); + config.set_string_list( + SPAM_FOLDER_KEY, + (info.spam_folder_path != null + ? new Gee.ArrayList.wrap(info.spam_folder_path.as_array()) + : empty) + ); + config.set_string_list( + TRASH_FOLDER_KEY, + (info.trash_folder_path != null + ? new Gee.ArrayList.wrap(info.trash_folder_path.as_array()) + : empty) + ); + config.set_string_list( + ARCHIVE_FOLDER_KEY, + (info.archive_folder_path != null + ? new Gee.ArrayList.wrap(info.archive_folder_path.as_array()) + : empty) + ); + + config.set_bool(SAVE_DRAFTS_KEY, info.save_drafts); + } + +} + + +/** + * Manages persistence for version 1 service configuration. + */ +public class Accounts.ServiceConfigV1 : ServiceConfig, GLib.Object { + + private const string GROUP_INCOMING = "Incoming"; + private const string GROUP_OUTGOING = "Outgoing"; + + private const string CREDENTIALS = "credentials"; + private const string HOST = "host"; + private const string LOGIN = "login"; + private const string PORT = "port"; + private const string REMEMBER_PASSWORD = "remember_password"; + private const string SECURITY = "transport_security"; + + + /** Loads a supported service from a config file. */ + public void load(Geary.ConfigFile config, + Geary.AccountInformation account, + Geary.ServiceInformation service) + throws ConfigError, GLib.KeyFileError { + Geary.ConfigFile.Group service_config = config.get_group( + service.protocol == IMAP ? GROUP_INCOMING : GROUP_OUTGOING + ); + + string? login = service_config.get_string(LOGIN, null); + if (login != null) { + service.credentials = new Geary.Credentials( + Geary.Credentials.Method.PASSWORD, login + ); + } + service.remember_password = service_config.get_bool( + REMEMBER_PASSWORD, service.remember_password + ); + + + if (account.service_provider == Geary.ServiceProvider.OTHER) { + service.host = service_config.get_required_string(HOST); + service.port = (uint16) service_config.get_int(PORT, service.port); + + service.transport_security = service_config.parse_required_value + ( + SECURITY, + (value) => { + try { + return Geary.TlsNegotiationMethod.for_value(value); + } catch (GLib.Error err) { + throw new GLib.KeyFileError.INVALID_VALUE(err.message); + } + } + ); + + service.credentials_requirement = service_config.parse_required_value + ( + CREDENTIALS, + (value) => { + try { + return Geary.Credentials.Requirement.for_value(value); + } catch (GLib.Error err) { + throw new GLib.KeyFileError.INVALID_VALUE(err.message); + } + } + ); + + if (service.port == 0) { + service.port = service.get_default_port(); + } + } + } + + /** Saves an service to a config file. */ + public void save(Geary.AccountInformation account, + Geary.ServiceInformation service, + Geary.ConfigFile config) { + Geary.ConfigFile.Group service_config = config.get_group( + service.protocol == IMAP ? GROUP_INCOMING : GROUP_OUTGOING + ); + + if (service.credentials != null) { + service_config.set_string(LOGIN, service.credentials.user); + } + service_config.set_bool(REMEMBER_PASSWORD, service.remember_password); + + if (account.service_provider == Geary.ServiceProvider.OTHER) { + service_config.set_string(HOST, service.host); + service_config.set_int(PORT, service.port); + service_config.set_string( + SECURITY, service.transport_security.to_value() + ); + + service_config.set_string( + CREDENTIALS, service.credentials_requirement.to_value() + ); + } + } + +} + + +/** + * Manages persistence for un-versioned service configuration. + */ +public class Accounts.ServiceConfigLegacy : ServiceConfig, GLib.Object { + + + private const string HOST = "host"; + private const string PORT = "port"; + private const string REMEMBER_PASSWORD = "remember_password"; + private const string SSL = "ssl"; + private const string STARTTLS = "starttls"; + private const string USERNAME = "username"; + + private const string SMTP_NOAUTH = "smtp_noauth"; + private const string SMTP_USE_IMAP_CREDENTIALS = "smtp_use_imap_credentials"; + + + /** Loads a supported service from a config file. */ + public void load(Geary.ConfigFile config, + Geary.AccountInformation account, + Geary.ServiceInformation service) + throws ConfigError, GLib.KeyFileError { + Geary.ConfigFile.Group service_config = + config.get_group(AccountConfigLegacy.GROUP); + + string prefix = service.protocol == Geary.Protocol.IMAP + ? "imap_" : "smtp_"; + + string? login = service_config.get_string( + prefix + USERNAME, account.primary_mailbox.address + ); + if (login != null) { + service.credentials = new Geary.Credentials( + Geary.Credentials.Method.PASSWORD, login + ); + } + service.remember_password = service_config.get_bool( + prefix + REMEMBER_PASSWORD, service.remember_password + ); + + if (account.service_provider == Geary.ServiceProvider.OTHER) { + service.host = service_config.get_string(prefix + HOST, service.host); + service.port = (uint16) service_config.get_int( + prefix + PORT, service.port + ); + + bool use_tls = service_config.get_bool( + prefix + SSL, service.protocol == Geary.Protocol.IMAP + ); + bool use_starttls = service_config.get_bool( + prefix + STARTTLS, true + ); + if (use_tls) { + service.transport_security = Geary.TlsNegotiationMethod.TRANSPORT; + } else if (use_starttls) { + service.transport_security = Geary.TlsNegotiationMethod.START_TLS; + } else { + service.transport_security = Geary.TlsNegotiationMethod.NONE; + } + + if (service.protocol == Geary.Protocol.SMTP) { + bool use_imap = service_config.get_bool( + SMTP_USE_IMAP_CREDENTIALS, service.credentials != null + ); + bool no_auth = service_config.get_bool( + SMTP_NOAUTH, false + ); + if (use_imap) { + service.credentials_requirement = + Geary.Credentials.Requirement.USE_INCOMING; + } else if (!no_auth) { + service.credentials_requirement = + Geary.Credentials.Requirement.CUSTOM; + } else { + service.credentials_requirement = + Geary.Credentials.Requirement.NONE; + } + } + } + } + + /** Saves an service to a config file. */ + public void save(Geary.AccountInformation account, + Geary.ServiceInformation service, + Geary.ConfigFile config) { + Geary.ConfigFile.Group service_config = + config.get_group(AccountConfigLegacy.GROUP); + + string prefix = service.protocol.to_value().ascii_down() + "_"; + + if (service.credentials != null) { + service_config.set_string( + prefix + USERNAME, service.credentials.user + ); + } + service_config.set_bool( + prefix + REMEMBER_PASSWORD, service.remember_password + ); + + if (account.service_provider == Geary.ServiceProvider.OTHER) { + service_config.set_string(prefix + HOST, service.host); + service_config.set_int(prefix + PORT, service.port); + + switch (service.transport_security) { + case NONE: + service_config.set_bool(prefix + SSL, false); + service_config.set_bool(prefix + STARTTLS, false); + break; + + case START_TLS: + service_config.set_bool(prefix + SSL, false); + service_config.set_bool(prefix + STARTTLS, true); + break; + + case TRANSPORT: + service_config.set_bool(prefix + SSL, true); + service_config.set_bool(prefix + STARTTLS, false); + break; + } + + if (service.protocol == Geary.Protocol.SMTP) { + switch (service.credentials_requirement) { + case NONE: + service_config.set_bool(SMTP_USE_IMAP_CREDENTIALS, false); + service_config.set_bool(SMTP_NOAUTH, true); + break; + + case USE_INCOMING: + service_config.set_bool(SMTP_USE_IMAP_CREDENTIALS, true); + service_config.set_bool(SMTP_NOAUTH, false); + break; + + case CUSTOM: + service_config.set_bool(SMTP_USE_IMAP_CREDENTIALS, false); + service_config.set_bool(SMTP_NOAUTH, false); + break; + } + } + } + } + +} diff -Nru geary-0.12.4/src/client/accounts/account-spinner-page.vala geary-3.32.0/src/client/accounts/account-spinner-page.vala --- geary-0.12.4/src/client/accounts/account-spinner-page.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/accounts/account-spinner-page.vala 1970-01-01 00:00:00.000000000 +0000 @@ -1,16 +0,0 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. - * - * This software is licensed under the GNU Lesser General Public License - * (version 2.1 or later). See the COPYING file in this distribution. - */ - -// Shows a simple spinner and a message indicating the account is being validated. -public class AccountSpinnerPage : Gtk.Box { - public AccountSpinnerPage() { - Object(orientation: Gtk.Orientation.VERTICAL, spacing: 4); - - Gtk.Builder builder = GearyApplication.instance.create_builder("account_spinner.glade"); - pack_end((Gtk.Box) builder.get_object("container")); - } -} - diff -Nru geary-0.12.4/src/client/accounts/accounts-signature-web-view.vala geary-3.32.0/src/client/accounts/accounts-signature-web-view.vala --- geary-0.12.4/src/client/accounts/accounts-signature-web-view.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/src/client/accounts/accounts-signature-web-view.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,29 @@ +/* + * Copyright 2019 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +/** + * A class for editing signatures in the accounts editor. + */ +public class Accounts.SignatureWebView : ClientWebView { + + + private static WebKit.UserScript? app_script = null; + + public static new void load_resources() + throws GLib.Error { + SignatureWebView.app_script = ClientWebView.load_app_script( + "signature-web-view.js" + ); + } + + + public SignatureWebView(Configuration config) { + base(config); + this.user_content_manager.add_script(SignatureWebView.app_script); + } + +} diff -Nru geary-0.12.4/src/client/accounts/add-edit-page.vala geary-3.32.0/src/client/accounts/add-edit-page.vala --- geary-0.12.4/src/client/accounts/add-edit-page.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/accounts/add-edit-page.vala 1970-01-01 00:00:00.000000000 +0000 @@ -1,908 +0,0 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. - * - * This software is licensed under the GNU Lesser General Public License - * (version 2.1 or later). See the COPYING file in this distribution. - */ - -// Page for adding or editing an account. -public class AddEditPage : Gtk.Box { - /// Placeholder text indicating that the user should type their first name and last name - private const string REAL_NAME_PLACEHOLDER = _("First Last"); - - public enum PageMode { - WELCOME, - ADD, - EDIT - } - - public string? id { get; private set; default = null; } - - public string real_name { - get { return entry_real_name.text; } - set { entry_real_name.text = value; } - } - - public string nickname { - get { return (mode == PageMode.WELCOME) ? email_address : entry_nickname.text; } - set { entry_nickname.text = value; } - } - - public string email_address { - get { return entry_email.text; } - set { entry_email.text = value; } - } - - public string password { - get { return entry_password.text; } - set { entry_password.text = value; } - } - - public string imap_username { - get { return entry_imap_username.text; } - set { entry_imap_username.text = value; } - } - - public string imap_password { - get { return entry_imap_password.text; } - set { entry_imap_password.text = value; } - } - - public bool remember_password { - get { return check_remember_password.active; } - set { check_remember_password.active = value; } - } - - public bool use_email_signature { - get { return check_use_email_signature.active; } - set { check_use_email_signature.active = value;} - } - - public string email_signature { - owned get { - return textview_email_signature.buffer.text; - } - set { - textview_email_signature.buffer.text = value ?? ""; - } - } - - public bool save_sent_mail { - get { return check_save_sent_mail.active; } - set { check_save_sent_mail.active = value; } - } - - public string smtp_username { - get { return entry_smtp_username.text; } - set { entry_smtp_username.text = value; } - } - - public string smtp_password { - get { return entry_smtp_password.text; } - set { entry_smtp_password.text = value; } - } - - public string imap_host { - get { return entry_imap_host.text; } - set { entry_imap_host.text = value; } - } - - public uint16 imap_port { - get { return (uint16) int.parse(entry_imap_port.text.strip()); } - set { entry_imap_port.text = value.to_string(); } - } - - public bool imap_ssl { - get { return combo_imap_encryption.active == Encryption.SSL; } - set { - if (value) - combo_imap_encryption.active = Encryption.SSL; - } - } - - public bool imap_starttls { - get { return combo_imap_encryption.active == Encryption.STARTTLS; } - set { - if (value) - combo_imap_encryption.active = Encryption.STARTTLS; - } - } - - public string smtp_host { - get { return entry_smtp_host.text; } - set { entry_smtp_host.text = value; } - } - - public uint16 smtp_port { - get { return (uint16) int.parse(entry_smtp_port.text.strip()); } - set { entry_smtp_port.text = value.to_string(); } - } - - public bool smtp_ssl { - get { return combo_smtp_encryption.active == Encryption.SSL; } - set { - if (value) - combo_smtp_encryption.active = Encryption.SSL; - } - } - - public bool smtp_starttls { - get { return combo_smtp_encryption.active == Encryption.STARTTLS; } - set { - if (value) - combo_smtp_encryption.active = Encryption.STARTTLS; - } - } - - public bool smtp_use_imap_credentials { - get { return check_smtp_use_imap_credentials.active; } - set { check_smtp_use_imap_credentials.active = value; } - } - - public bool smtp_noauth { - get { return check_smtp_noauth.active; } - set { check_smtp_noauth.active = value; } - } - - public bool save_drafts { - get { return check_save_drafts.active; } - set { check_save_drafts.active = value; } - } - - // these are tied to the values in the Glade file - private enum Encryption { - NONE = 0, - SSL = 1, - STARTTLS = 2 - } - - private PageMode mode = PageMode.WELCOME; - - private Gtk.Widget container_widget; - private Gtk.Box welcome_box; - - private Gtk.Label label_error; - - private Gtk.Entry entry_email; - private Gtk.Label label_password; - private Gtk.Entry entry_password; - private Gtk.Entry entry_real_name; - private Gtk.Label label_nickname; - private Gtk.Entry entry_nickname; - private Gtk.ComboBoxText combo_service; - private Gtk.CheckButton check_remember_password; - private Gtk.CheckButton check_save_sent_mail; - private Gtk.Button alternate_email_button; - - // Signature - private Gtk.Box composer_container; - private Gtk.CheckButton check_use_email_signature; - private Gtk.Stack signature_stack; - private Gtk.TextView textview_email_signature; - private ClientWebView preview_webview; - - private Gtk.Alignment other_info; - - // IMAP info widgets - private Gtk.Entry entry_imap_host; - private Gtk.Entry entry_imap_port; - private Gtk.Entry entry_imap_username; - private Gtk.Entry entry_imap_password; - private Gtk.ComboBox combo_imap_encryption; - - // SMTP info widgets - private Gtk.Entry entry_smtp_host; - private Gtk.Entry entry_smtp_port; - private Gtk.Entry entry_smtp_username; - private Gtk.Entry entry_smtp_password; - private Gtk.ComboBox combo_smtp_encryption; - private Gtk.CheckButton check_smtp_use_imap_credentials; - private Gtk.CheckButton check_smtp_noauth; - - private Gtk.CheckButton check_save_drafts; - - private string smtp_username_store; - private string smtp_password_store; - - // Storage options - private Gtk.Box storage_container; - private Gtk.ComboBoxText combo_storage_length; - - private bool edited_imap_port = false; - private bool edited_smtp_port = false; - - private Geary.Engine.ValidationResult last_validation_result = Geary.Engine.ValidationResult.OK; - - private bool first_ui_update = true; - - public signal void info_changed(); - - public signal void size_changed(); - - public signal void edit_alternate_emails(); - - public AddEditPage() { - Object(orientation: Gtk.Orientation.VERTICAL, spacing: 4); - - Gtk.Builder builder = GearyApplication.instance.create_builder("login.glade"); - - // Primary container. - container_widget = (Gtk.Widget) builder.get_object("container"); - pack_start(container_widget); - - welcome_box = (Gtk.Box) builder.get_object("welcome_box"); - Gtk.Label label_welcome = (Gtk.Label) builder.get_object("label-welcome"); - label_welcome.set_markup("%s\n%s".printf( - _("Welcome to Geary."), _("Enter your account information to get started."))); - - entry_real_name = (Gtk.Entry) builder.get_object("entry: real_name"); - entry_real_name.placeholder_text = REAL_NAME_PLACEHOLDER; - label_nickname = (Gtk.Label) builder.get_object("label: nickname"); - entry_nickname = (Gtk.Entry) builder.get_object("entry: nickname"); - combo_service = (Gtk.ComboBoxText) builder.get_object("combo: service"); - entry_email = (Gtk.Entry) builder.get_object("entry: email"); - label_password = (Gtk.Label) builder.get_object("label: password"); - entry_password = (Gtk.Entry) builder.get_object("entry: password"); - check_remember_password = (Gtk.CheckButton) builder.get_object("check: remember_password"); - check_save_sent_mail = (Gtk.CheckButton) builder.get_object("check: save_sent_mail"); - alternate_email_button = (Gtk.Button) builder.get_object("button: edit_alternate_email"); - label_error = (Gtk.Label) builder.get_object("label: error"); - other_info = (Gtk.Alignment) builder.get_object("container: other_info"); - - // Storage options. - storage_container = (Gtk.Box) builder.get_object("storage container"); - combo_storage_length = (Gtk.ComboBoxText) builder.get_object("combo: storage"); - combo_storage_length.set_row_separator_func(combo_storage_separator_delegate); - combo_storage_length.append("14", _("2 weeks back")); // IDs are # of days - combo_storage_length.append("30", _("1 month back")); - combo_storage_length.append("90", _("3 months back")); - combo_storage_length.append("180", _("6 months back")); - combo_storage_length.append("365", _("1 year back")); - combo_storage_length.append("730", _("2 years back")); - combo_storage_length.append("1461", _("4 years back")); - combo_storage_length.append(".", "."); // Separator - combo_storage_length.append("-1", _("Everything")); - - // composer options - composer_container = (Gtk.Box) builder.get_object("composer container"); - check_use_email_signature = (Gtk.CheckButton) builder.get_object("check: use_email_signature"); - - Gtk.ScrolledWindow edit_window = new Gtk.ScrolledWindow(null, null); - edit_window.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC); - edit_window.set_shadow_type(Gtk.ShadowType.IN); - textview_email_signature = new Gtk.TextView(); - edit_window.add(textview_email_signature); - - preview_webview = new ClientWebView(GearyApplication.instance.config); - - Gtk.ScrolledWindow preview_window = new Gtk.ScrolledWindow(null, null); - preview_window.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC); - preview_window.set_shadow_type(Gtk.ShadowType.IN); - preview_window.add(preview_webview); - - signature_stack = new Gtk.Stack(); - signature_stack.add_titled(edit_window, "edit_window", _("Edit")); - signature_stack.child_set_property(edit_window, "icon-name", "text-editor-symbolic"); - signature_stack.add_titled(preview_window, "preview_window", _("Preview")); - signature_stack.child_set_property(preview_window, "icon-name", "text-x-generic-symbolic"); - Gtk.StackSwitcher switcher = new Gtk.StackSwitcher(); - switcher.set_stack(signature_stack); - - Gtk.Box signature_box = (Gtk.Box) builder.get_object("signature box"); - signature_box.set_spacing(4); - signature_box.pack_start(signature_stack); - switcher.valign = Gtk.Align.START; - signature_box.pack_start(switcher, false, false); - - // IMAP info widgets. - entry_imap_host = (Gtk.Entry) builder.get_object("entry: imap host"); - entry_imap_port = (Gtk.Entry) builder.get_object("entry: imap port"); - entry_imap_username = (Gtk.Entry) builder.get_object("entry: imap username"); - entry_imap_password = (Gtk.Entry) builder.get_object("entry: imap password"); - combo_imap_encryption = (Gtk.ComboBox) builder.get_object("combo: imap encryption"); - - // SMTP info widgets. - entry_smtp_host = (Gtk.Entry) builder.get_object("entry: smtp host"); - entry_smtp_port = (Gtk.Entry) builder.get_object("entry: smtp port"); - entry_smtp_username = (Gtk.Entry) builder.get_object("entry: smtp username"); - entry_smtp_password = (Gtk.Entry) builder.get_object("entry: smtp password"); - combo_smtp_encryption = (Gtk.ComboBox) builder.get_object("combo: smtp encryption"); - check_smtp_use_imap_credentials = (Gtk.CheckButton) builder.get_object("check: use imap credentials"); - check_smtp_noauth = (Gtk.CheckButton) builder.get_object("check: smtp no authentication"); - check_save_drafts = (Gtk.CheckButton) builder.get_object("check: save_drafts"); - - // Build list of service providers. - foreach (Geary.ServiceProvider p in Geary.ServiceProvider.get_providers()) - combo_service.append_text(p.display_name()); - - reset_all(); - - combo_service.changed.connect(update_ui); - entry_email.changed.connect(on_changed); - entry_password.changed.connect(on_changed); - entry_real_name.changed.connect(on_changed); - entry_nickname.changed.connect(on_changed); - check_remember_password.toggled.connect(on_changed); - check_save_sent_mail.toggled.connect(on_changed); - combo_service.changed.connect(on_changed); - entry_imap_host.changed.connect(on_changed); - entry_imap_port.changed.connect(on_changed); - entry_imap_username.changed.connect(on_changed); - entry_imap_password.changed.connect(on_changed); - entry_smtp_host.changed.connect(on_changed); - entry_smtp_port.changed.connect(on_changed); - entry_smtp_username.changed.connect(on_changed); - entry_smtp_password.changed.connect(on_changed); - check_smtp_use_imap_credentials.toggled.connect(on_changed); - check_smtp_noauth.toggled.connect(on_changed); - check_save_drafts.toggled.connect(on_changed); - alternate_email_button.clicked.connect(on_alternate_email_button_clicked); - - entry_email.changed.connect(on_email_changed); - entry_password.changed.connect(on_password_changed); - - combo_imap_encryption.changed.connect(on_imap_encryption_changed); - combo_smtp_encryption.changed.connect(on_smtp_encryption_changed); - - check_smtp_use_imap_credentials.toggled.connect(() => on_smtp_auth_changed(true)); - check_smtp_noauth.toggled.connect(() => on_smtp_auth_changed(false)); - - entry_imap_port.insert_text.connect(on_port_insert_text); - entry_smtp_port.insert_text.connect(on_port_insert_text); - - entry_nickname.insert_text.connect(on_nickname_insert_text); - - check_use_email_signature.bind_property("active", signature_box, "sensitive"); - signature_stack.notify["visible-child-name"].connect(on_signature_stack_changed); - - // Reset the "first update" flag when the window is mapped. - map.connect(() => { first_ui_update = true; }); - } - - // Sets the account information to display on this page. - public void set_account_information(Geary.AccountInformation info, Geary.Engine.ValidationResult result) { - set_all_info( - info.id, - info.primary_mailbox.name, - info.nickname, - info.primary_mailbox.address, - info.imap_credentials.user, - info.imap_credentials.pass, - info.imap_remember_password && info.smtp_remember_password, - info.smtp_credentials != null ? info.smtp_credentials.user : null, - info.smtp_credentials != null ? info.smtp_credentials.pass : null, - info.service_provider, - info.save_sent_mail, - info.allow_save_sent_mail(), - info.default_imap_server_host, - info.default_imap_server_port, - info.default_imap_server_ssl, - info.default_imap_server_starttls, - info.default_smtp_server_host, - info.default_smtp_server_port, - info.default_smtp_server_ssl, - info.default_smtp_server_starttls, - info.default_smtp_use_imap_credentials, - info.default_smtp_server_noauth, - info.prefetch_period_days, - info.save_drafts, - info.use_email_signature, - info.email_signature, - result); - } - - // Can use this instead of set_account_information(), both do the same thing. - public void set_all_info( - string? initial_id = null, - string? initial_real_name = null, - string? initial_nickname = null, - string? initial_email = null, - string? initial_imap_username = null, - string? initial_imap_password = null, - bool initial_remember_password = true, - string? initial_smtp_username = null, - string? initial_smtp_password = null, - int initial_service_provider = Geary.ServiceProvider.GMAIL, - bool initial_save_sent_mail = true, - bool allow_save_sent_mail = true, - string? initial_default_imap_host = null, - uint16 initial_default_imap_port = Geary.Imap.ClientConnection.DEFAULT_PORT_SSL, - bool initial_default_imap_ssl = true, - bool initial_default_imap_starttls = false, - string? initial_default_smtp_host = null, - uint16 initial_default_smtp_port = Geary.Smtp.ClientConnection.DEFAULT_PORT_STARTTLS, - bool initial_default_smtp_ssl = false, - bool initial_default_smtp_starttls = true, - bool initial_default_smtp_use_imap_credentials = false, - bool initial_default_smtp_noauth = false, - int prefetch_period_days = Geary.AccountInformation.DEFAULT_PREFETCH_PERIOD_DAYS, - bool initial_save_drafts = true, - bool initial_use_email_signature = false, - string? initial_email_signature = null, - Geary.Engine.ValidationResult result = Geary.Engine.ValidationResult.OK) { - - // Set defaults - this.id = initial_id; - this.real_name = initial_real_name ?? ""; - this.nickname = initial_nickname ?? ""; - this.email_address = initial_email ?? ""; - this.password = initial_imap_password != null ? initial_imap_password : ""; - this.remember_password = initial_remember_password; - this.save_sent_mail = initial_save_sent_mail; - this.check_save_sent_mail.sensitive = allow_save_sent_mail; - this.set_service_provider((Geary.ServiceProvider) initial_service_provider); - this.combo_imap_encryption.active = Encryption.NONE; // Must be default; set to real value below. - this.combo_smtp_encryption.active = Encryption.NONE; - this.use_email_signature = initial_use_email_signature; - this.email_signature = initial_email_signature; - this.signature_stack.set_visible_child_name("edit_window"); - - // Set defaults for IMAP info - this.imap_host = initial_default_imap_host ?? ""; - this.imap_port = initial_default_imap_port; - this.imap_username = initial_imap_username ?? ""; - this.imap_password = initial_imap_password ?? ""; - this.imap_ssl = initial_default_imap_ssl; - this.imap_starttls = initial_default_imap_starttls; - - // Set defaults for SMTP info - this.smtp_host = initial_default_smtp_host ?? ""; - this.smtp_port = initial_default_smtp_port; - this.smtp_username = initial_smtp_username ?? ""; - this.smtp_password = initial_smtp_password ?? ""; - this.smtp_ssl = initial_default_smtp_ssl; - this.smtp_starttls = initial_default_smtp_starttls; - this.smtp_use_imap_credentials = initial_default_smtp_use_imap_credentials; - this.smtp_noauth = initial_default_smtp_noauth; - - this.save_drafts = initial_save_drafts; - - set_validation_result(result); - - set_storage_length(prefetch_period_days); - } - - public void set_validation_result(Geary.Engine.ValidationResult result) { - last_validation_result = result; - } - - // Resets all fields to their defaults. - public void reset_all() { - // Take advantage of set_all_info()'s defaults. - set_all_info(null, get_default_real_name()); - - edited_imap_port = false; - edited_smtp_port = false; - } - - /** Puts this page into one of three different modes: - * WELCOME: The first screen when Geary is started. - * ADD: Add account screen is like the Welcome screen, but without the welcome message. - * EDIT: This screen has only a few options that can be modified after creating an account. - */ - public void set_mode(PageMode m) { - mode = m; - update_ui(); - } - - public PageMode get_mode() { - return mode; - } - - // TODO: Only reset if not manually set by user. - private void on_email_changed() { - entry_imap_username.text = entry_email.text; - - if (entry_smtp_username.sensitive) - entry_smtp_username.text = entry_email.text; - } - - // TODO: Only reset if not manually set by user. - private void on_password_changed() { - entry_imap_password.text = entry_password.text; - - if (entry_password.sensitive) - entry_smtp_password.text = entry_password.text; - } - - private void on_changed() { - info_changed(); - } - - private void on_alternate_email_button_clicked() { - edit_alternate_emails(); - } - - // Prevent non-printable characters in nickname field. - private void on_nickname_insert_text(Gtk.Editable e, string text, int length, ref int position) { - unichar c; - int index = 0; - while (text.get_next_char(ref index, out c)) { - if (!c.isprint()) { - Signal.stop_emission_by_name(e, "insert-text"); - - return; - } - } - } - - private void on_port_insert_text(Gtk.Editable e, string text, int length, ref int position) { - // Prevent non-numerical characters and ensure port is <= uint16.MAX - if (!uint64.try_parse(text) || uint64.parse(((Gtk.Entry) e).text) > uint16.MAX) { - Signal.stop_emission_by_name(e, "insert-text"); - } else { - if (e == entry_imap_port) - edited_imap_port = true; - else if (e == entry_smtp_port) - edited_smtp_port = true; - } - } - - private void on_imap_encryption_changed() { - if (edited_imap_port) - return; - - imap_port = get_default_imap_port(); - edited_imap_port = false; - } - - private uint16 get_default_imap_port() { - switch (combo_imap_encryption.active) { - case Encryption.SSL: - return Geary.Imap.ClientConnection.DEFAULT_PORT_SSL; - - case Encryption.NONE: - case Encryption.STARTTLS: - default: - return Geary.Imap.ClientConnection.DEFAULT_PORT; - } - } - - private void on_smtp_encryption_changed() { - if (edited_smtp_port) - return; - - smtp_port = get_default_smtp_port(); - edited_smtp_port = false; - } - - private void on_smtp_auth_changed(bool use_imap_credentials_toggled) { - if (use_imap_credentials_toggled && check_smtp_use_imap_credentials.active) - check_smtp_noauth.active = false; - else if (!use_imap_credentials_toggled && check_smtp_noauth.active) - check_smtp_use_imap_credentials.active = false; - - if (check_smtp_use_imap_credentials.active || check_smtp_noauth.active) { - if (!Geary.String.is_empty_or_whitespace(entry_smtp_username.text)) - smtp_username_store = entry_smtp_username.text; - if (!Geary.String.is_empty_or_whitespace(entry_smtp_password.text)) - smtp_password_store = entry_smtp_password.text; - - entry_smtp_username.text = ""; - entry_smtp_password.text = ""; - - entry_smtp_username.sensitive = false; - entry_smtp_password.sensitive = false; - } else { - if (!Geary.String.is_empty_or_whitespace(smtp_username_store)) - entry_smtp_username.text = smtp_username_store; - smtp_username_store = ""; - if (!Geary.String.is_empty_or_whitespace(smtp_password_store)) - entry_smtp_password.text = smtp_password_store; - smtp_password_store = ""; - - entry_smtp_username.sensitive = true; - entry_smtp_password.sensitive = true; - } - } - - private void on_signature_stack_changed() { - if (signature_stack.visible_child_name == "preview_window") - preview_webview.load_html(Geary.HTML.smart_escape(email_signature, true), null); - } - - private uint16 get_default_smtp_port() { - switch (combo_smtp_encryption.active) { - case Encryption.SSL: - return Geary.Smtp.ClientConnection.DEFAULT_PORT_SSL; - - case Encryption.STARTTLS: - return Geary.Smtp.ClientConnection.DEFAULT_PORT_STARTTLS; - - case Encryption.NONE: - default: - return Geary.Smtp.ClientConnection.DEFAULT_PORT; - } - } - - public bool is_complete() { - if (Geary.String.is_empty_or_whitespace(email_address) || - !Geary.RFC822.MailboxAddress.is_valid_address(email_address)) { - return false; - } - - switch (get_service_provider()) { - case Geary.ServiceProvider.OTHER: - if (Geary.String.is_empty_or_whitespace(nickname) || - Geary.String.is_empty_or_whitespace(imap_host) || - Geary.String.is_empty_or_whitespace(imap_port.to_string()) || - Geary.String.is_empty_or_whitespace(imap_username) || - Geary.String.is_empty_or_whitespace(imap_password) || - Geary.String.is_empty_or_whitespace(smtp_host) || - Geary.String.is_empty_or_whitespace(smtp_port.to_string())) - return false; - if ((Geary.String.is_empty_or_whitespace(smtp_username) || - Geary.String.is_empty_or_whitespace(smtp_password)) && - !(check_smtp_noauth.active || check_smtp_use_imap_credentials.active)) - return false; - break; - - // GMAIL, YAHOO, and OUTLOOK - default: - if (Geary.String.is_empty_or_whitespace(nickname) || - Geary.String.is_empty_or_whitespace(password)) - return false; - break; - } - - return true; - } - - public Geary.AccountInformation? get_account_information() { - fix_credentials_for_supported_provider(); - - Geary.Credentials imap_credentials = new Geary.Credentials( - imap_username.strip(), imap_password.strip()); - Geary.Credentials smtp_credentials = new Geary.Credentials( - (smtp_use_imap_credentials ? imap_username.strip() : smtp_username.strip()), - (smtp_use_imap_credentials ? imap_password.strip() : smtp_password.strip())); - - Geary.AccountInformation? info = null; - if (this.id != null) { - // The id will be null in the case of adding a new - // account, but it won't be null and yet the account won't - // be accessible via Engine.get_account() in the case of - // an orphan account - i.e. when adding an account - // encountered validation errors. So we need to deal with - // both cases. - try { - info = Geary.Engine.instance.get_account(this.id); - } catch (Error err) { - // id was for an orphan account - } - } - - if (info == null) { - // New account - try { - info = Geary.Engine.instance.create_orphan_account(); - } catch (Error err) { - debug("Unable to create account %s for %s: %s", - this.id, this.email_address, err.message); - } - } else { - // Existing account: create a copy so we don't mess up the - // original. - info = new Geary.AccountInformation.temp_copy(info); - } - - if (info != null) { - info.primary_mailbox = new Geary.RFC822.MailboxAddress( - this.real_name.strip(), this.email_address.strip() - ); - info.nickname = this.nickname.strip(); - info.imap_credentials = imap_credentials; - info.smtp_credentials = smtp_credentials; - info.imap_remember_password = this.remember_password; - info.smtp_remember_password = this.remember_password; - info.service_provider = this.get_service_provider(); - info.save_sent_mail = this.save_sent_mail; - info.default_imap_server_host = this.imap_host; - info.default_imap_server_port = this.imap_port; - info.default_imap_server_ssl = this.imap_ssl; - info.default_imap_server_starttls = this.imap_starttls; - info.default_smtp_server_host = this.smtp_host.strip(); - info.default_smtp_server_port = this.smtp_port; - info.default_smtp_server_ssl = this.smtp_ssl; - info.default_smtp_server_starttls = this.smtp_starttls; - info.default_smtp_use_imap_credentials = this.smtp_use_imap_credentials; - info.default_smtp_server_noauth = this.smtp_noauth; - info.prefetch_period_days = get_storage_length(); - info.save_drafts = this.save_drafts; - info.use_email_signature = this.use_email_signature; - info.email_signature = this.email_signature; - - if (smtp_noauth) - info.smtp_credentials = null; - - on_changed(); - } - - return info; - } - - // Assembles credentials for supported providers. - private void fix_credentials_for_supported_provider() { - if (get_service_provider() != Geary.ServiceProvider.OTHER) { - imap_username = email_address; - smtp_username = email_address; - imap_password = password; - smtp_password = password; - } - } - - // Updates UI based on various options. - internal void update_ui() { - base.show_all(); - - welcome_box.visible = mode == PageMode.WELCOME; - entry_nickname.visible = label_nickname.visible = mode != PageMode.WELCOME; - storage_container.visible = mode == PageMode.EDIT; - check_save_sent_mail.visible = mode == PageMode.EDIT; - check_save_drafts.visible = mode == PageMode.EDIT; - composer_container.visible = mode == PageMode.EDIT; - alternate_email_button.visible = mode == PageMode.EDIT; - - if (get_service_provider() == Geary.ServiceProvider.OTHER) { - // Display all options for custom providers. - label_password.hide(); - entry_password.hide(); - other_info.show(); - set_other_info_sensitive(true); - check_remember_password.label = _("Remem_ber passwords"); // Plural - } else { - // For special-cased providers, only display necessary info. - label_password.show(); - entry_password.show(); - other_info.hide(); - set_other_info_sensitive(mode == PageMode.WELCOME); - check_remember_password.label = _("Remem_ber password"); - } - - // In edit mode, certain fields are not sensitive. - combo_service.sensitive = - entry_email.sensitive = - entry_imap_host.sensitive = - entry_imap_port.sensitive = - entry_imap_username.sensitive = - combo_imap_encryption.sensitive = - entry_smtp_host.sensitive = - entry_smtp_port.sensitive = - entry_smtp_username.sensitive = - combo_smtp_encryption.sensitive = - check_smtp_use_imap_credentials.sensitive = - check_smtp_noauth.sensitive = - mode != PageMode.EDIT; - - if (smtp_noauth) { - check_smtp_use_imap_credentials.sensitive = false; - entry_smtp_username.sensitive = false; - entry_smtp_password.sensitive = false; - } else if (smtp_use_imap_credentials) { - entry_smtp_username.sensitive = false; - entry_smtp_password.sensitive = false; - } - - // Update error text. - label_error.visible = false; - if (last_validation_result == Geary.Engine.ValidationResult.OK) { - label_error.visible = false; - } else { - label_error.visible = true; - - string error_string = _("Unable to validate:\n"); - if (last_validation_result.is_all_set(Geary.Engine.ValidationResult.INVALID_NICKNAME)) - error_string += _(" • Invalid account nickname.\n"); - - if (last_validation_result.is_all_set(Geary.Engine.ValidationResult.EMAIL_EXISTS)) - error_string += _(" • Email address already added to Geary.\n"); - - if (get_service_provider() == Geary.ServiceProvider.OTHER) { - if (last_validation_result.is_all_set(Geary.Engine.ValidationResult.IMAP_CONNECTION_FAILED)) - error_string += _(" • IMAP connection error.\n"); - - if (last_validation_result.is_all_set(Geary.Engine.ValidationResult.IMAP_CREDENTIALS_INVALID)) - error_string += _(" • IMAP username or password incorrect.\n"); - - if (last_validation_result.is_all_set(Geary.Engine.ValidationResult.SMTP_CONNECTION_FAILED)) - error_string += _(" • SMTP connection error.\n"); - - if (last_validation_result.is_all_set(Geary.Engine.ValidationResult.SMTP_CREDENTIALS_INVALID)) - error_string += _(" • SMTP username or password incorrect.\n"); - } else { - if (last_validation_result.is_all_set(Geary.Engine.ValidationResult.IMAP_CONNECTION_FAILED) || - last_validation_result.is_all_set(Geary.Engine.ValidationResult.SMTP_CONNECTION_FAILED)) - error_string += _(" • Connection error.\n"); - - if (last_validation_result.is_all_set(Geary.Engine.ValidationResult.IMAP_CREDENTIALS_INVALID) || - last_validation_result.is_all_set(Geary.Engine.ValidationResult.SMTP_CREDENTIALS_INVALID)) - error_string += _(" • Username or password incorrect.\n"); - } - - label_error.label = "" + error_string + ""; - } - - size_changed(); - - // Set initial field focus. - // This has to be done here because the window isn't completely setup until the first time - // this method runs. - if (first_ui_update && parent.get_visible()) { - if (mode == PageMode.EDIT) { - if (get_service_provider() != Geary.ServiceProvider.OTHER) - entry_password.grab_focus(); - else - entry_imap_password.grab_focus(); - } else { - if (Geary.String.is_empty(real_name)) - entry_real_name.grab_focus(); - else if (mode == PageMode.ADD) - entry_nickname.grab_focus(); - else - entry_email.grab_focus(); - } - - first_ui_update = false; - } - } - - public Geary.ServiceProvider get_service_provider() { - return (Geary.ServiceProvider) combo_service.get_active(); - } - - public void set_service_provider(Geary.ServiceProvider provider) { - foreach (Geary.ServiceProvider p in Geary.ServiceProvider.get_providers()) { - if (p == provider) - combo_service.set_active(p); - } - - if (combo_service.get_active() == -1) - combo_service.set_active(0); - } - - // Greys out "other info" (server settings, etc.) - public void set_other_info_sensitive(bool sensitive) { - entry_imap_host.sensitive = sensitive; - entry_imap_port.sensitive = sensitive; - entry_imap_username.sensitive = sensitive; - entry_imap_password.sensitive = sensitive; - combo_imap_encryption.sensitive = sensitive; - - entry_smtp_host.sensitive = sensitive; - entry_smtp_port.sensitive = sensitive; - check_smtp_use_imap_credentials.sensitive = sensitive; - entry_smtp_username.sensitive = sensitive; - entry_smtp_password.sensitive = sensitive; - combo_smtp_encryption.sensitive = sensitive; - } - - // Since users of this class embed it in a Gtk.Notebook, we're forced to override this method - // to prevent hidden UI elements from appearing. - public override void show_all() { - // Note that update_ui() calls base.show_all(), so no need to do that here. - update_ui(); - } - - private string get_default_real_name() { - string real_name = Environment.get_real_name(); - return real_name == "Unknown" ? "" : real_name; - } - - // Sets the storage length combo box. The days parameter should correspond to one of the pre-set - // values; arbitrary numbers will put the combo box into an undetermined state. - private void set_storage_length(int days) { - combo_storage_length.set_active_id(days.to_string()); - } - - // Returns number of days. - private int get_storage_length() { - return int.parse(combo_storage_length.get_active_id()); - } - - private bool combo_storage_separator_delegate(Gtk.TreeModel model, Gtk.TreeIter iter) { - GLib.Value v; - model.get_value(iter, 0, out v); - - return v.get_string() == "."; - } -} - diff -Nru geary-0.12.4/src/client/accounts/login-dialog.vala geary-3.32.0/src/client/accounts/login-dialog.vala --- geary-0.12.4/src/client/accounts/login-dialog.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/accounts/login-dialog.vala 1970-01-01 00:00:00.000000000 +0000 @@ -1,78 +0,0 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. - * - * This software is licensed under the GNU Lesser General Public License - * (version 2.1 or later). See the COPYING file in this distribution. - */ - -// Displays a dialog for collecting the user's login data. -public class LoginDialog : Gtk.Dialog { - private Gtk.Button ok_button; - private Gtk.Button cancel_button; - private AddEditPage page = new AddEditPage(); - private AccountSpinnerPage spinner_page = new AccountSpinnerPage(); - - public LoginDialog() { - Object(); - set_type_hint(Gdk.WindowTypeHint.DIALOG); - set_size_request(450, -1); // Sets min width. - - page.margin = 5; - spinner_page.margin = 5; - get_content_area().pack_start(page, true, true, 0); - get_content_area().pack_start(spinner_page, true, true, 0); - spinner_page.visible = false; - page.size_changed.connect(() => { resize(1, 1); }); - page.info_changed.connect(on_info_changed); - - cancel_button = new Gtk.Button.from_stock(Stock._CANCEL); - add_action_widget(cancel_button, Gtk.ResponseType.CANCEL); - ok_button = new Gtk.Button.from_stock(Stock._ADD); - ok_button.can_default = true; - add_action_widget(ok_button, Gtk.ResponseType.OK); - set_default_response(Gtk.ResponseType.OK); - get_action_area().show_all(); - - destroy.connect(() => { - debug("User closed login dialog, exiting..."); - GearyApplication.instance.exit(1); - }); - - on_info_changed(); - } - - public LoginDialog.from_account_information(Geary.AccountInformation initial_account_information) { - this(); - set_account_information(initial_account_information); - } - - public void set_account_information(Geary.AccountInformation info, - Geary.Engine.ValidationResult result = Geary.Engine.ValidationResult.OK) { - page.set_account_information(info, result); - page.update_ui(); - } - - public Geary.AccountInformation get_account_information() { - return page.get_account_information(); - } - - private void on_info_changed() { - if (!spinner_page.visible) - ok_button.sensitive = page.is_complete(); - else - ok_button.sensitive = false; - } - - // Switches between the account page and the busy spinner. - public void show_spinner(bool visible) { - spinner_page.visible = visible; - page.visible = !visible; - cancel_button.sensitive = !visible; - on_info_changed(); // sets OK button sensitivity - } - - public override void show() { - page.update_ui(); - base.show(); - } -} - diff -Nru geary-0.12.4/src/client/application/application-avatar-store.vala geary-3.32.0/src/client/application/application-avatar-store.vala --- geary-0.12.4/src/client/application/application-avatar-store.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/src/client/application/application-avatar-store.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,212 @@ +/* + * Copyright 2016-2018 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + + +/** + * Email address avatar loader and cache. + */ +public class Application.AvatarStore : Geary.BaseObject { + + + // Max age is low since we really only want to cache between + // conversation loads. + private const int64 MAX_CACHE_AGE_US = 5 * 1000 * 1000; + + // Max size is low since most conversations don't get above the + // low hundreds of messages, and those that do will likely get + // many repeated participants + private const uint MAX_CACHE_SIZE = 128; + + + private class CacheEntry { + + + public static string to_key(Geary.RFC822.MailboxAddress mailbox) { + // Use short name as the key, since it will use the name + // first, then the email address, which is especially + // important for things like GitLab email where the + // address is always the same, but the name changes. This + // ensures that each such user gets different initials. + return mailbox.to_short_display().normalize().casefold(); + } + + public static int lru_compare(CacheEntry a, CacheEntry b) { + return (a.key == b.key) + ? 0 : (int) (a.last_used - b.last_used); + } + + + public string key; + + public Geary.RFC822.MailboxAddress mailbox; + + // Store nulls so we can also cache avatars not found + public Folks.Individual? individual; + + public int64 last_used; + + private Gee.List pixbufs = new Gee.LinkedList(); + + public CacheEntry(Geary.RFC822.MailboxAddress mailbox, + Folks.Individual? individual, + int64 last_used) { + this.key = to_key(mailbox); + this.mailbox = mailbox; + this.individual = individual; + this.last_used = last_used; + } + + public async Gdk.Pixbuf? load(int pixel_size, + GLib.Cancellable cancellable) + throws GLib.Error { + Gdk.Pixbuf? pixbuf = null; + foreach (Gdk.Pixbuf cached in this.pixbufs) { + if ((cached.height == pixel_size && cached.width >= pixel_size) || + (cached.width == pixel_size && cached.height >= pixel_size)) { + pixbuf = cached; + break; + } + } + + if (pixbuf == null) { + Folks.Individual? individual = this.individual; + if (individual != null && individual.avatar != null) { + GLib.InputStream data = yield individual.avatar.load_async( + pixel_size, cancellable + ); + pixbuf = yield new Gdk.Pixbuf.from_stream_at_scale_async( + data, pixel_size, pixel_size, true, cancellable + ); + pixbuf = Util.Avatar.round_image(pixbuf); + this.pixbufs.add(pixbuf); + } + } + + if (pixbuf == null) { + string? name = null; + // XXX should really be using the folks display name + // here as below, but since we should the name from + // the email address if present in + // ConversationMessage, and since that might not match + // the folks display name, it is confusing when the + // initials are one thing and the name is + // another. Re-enable below when we start using the + // folks display name in ConversationEmail + name = this.mailbox.to_short_display(); + // if (this.individual != null) { + // name = this.individual.display_name; + // } else { + // // Use short display because it will clean up the + // // string, use the name if present and fall back + // // on the address if not. + // name = this.mailbox.to_short_display(); + // } + pixbuf = Util.Avatar.generate_user_picture(name, pixel_size); + pixbuf = Util.Avatar.round_image(pixbuf); + this.pixbufs.add(pixbuf); + } + + return pixbuf; + } + + } + + + private Folks.IndividualAggregator individuals; + private Gee.Map lru_cache = + new Gee.HashMap(); + private Gee.SortedSet lru_ordering = + new Gee.TreeSet(CacheEntry.lru_compare); + + + public AvatarStore(Folks.IndividualAggregator individuals) { + this.individuals = individuals; + } + + public void close() { + this.lru_cache.clear(); + this.lru_ordering.clear(); + } + + public async Gdk.Pixbuf? load(Geary.RFC822.MailboxAddress mailbox, + int pixel_size, + GLib.Cancellable cancellable) + throws GLib.Error { + // Normalise the address to improve caching + CacheEntry match = yield get_match(mailbox); + return yield match.load(pixel_size, cancellable); + } + + + private async CacheEntry get_match(Geary.RFC822.MailboxAddress mailbox) + throws GLib.Error { + string key = CacheEntry.to_key(mailbox); + int64 now = GLib.get_monotonic_time(); + CacheEntry? entry = this.lru_cache.get(key); + if (entry != null) { + if (entry.last_used + MAX_CACHE_AGE_US >= now) { + // Need to remove the entry from the ordering before + // updating the last used time since doing so changes + // the ordering + this.lru_ordering.remove(entry); + entry.last_used = now; + this.lru_ordering.add(entry); + } else { + this.lru_cache.unset(key); + this.lru_ordering.remove(entry); + entry = null; + } + } + + if (entry == null) { + Folks.Individual? match = yield search_match(mailbox.address); + entry = new CacheEntry(mailbox, match, now); + this.lru_cache.set(key, entry); + this.lru_ordering.add(entry); + + // Prune the cache if needed + if (this.lru_cache.size > MAX_CACHE_SIZE) { + CacheEntry oldest = this.lru_ordering.first(); + this.lru_cache.unset(oldest.key); + this.lru_ordering.remove(oldest); + } + } + + return entry; + } + + private async Folks.Individual? search_match(string address) + throws GLib.Error { + Folks.SearchView view = new Folks.SearchView( + this.individuals, + new Folks.SimpleQuery( + address, + new string[] { + Folks.PersonaStore.detail_key( + Folks.PersonaDetail.EMAIL_ADDRESSES + ) + } + ) + ); + + yield view.prepare(); + + Folks.Individual? match = null; + if (!view.individuals.is_empty) { + match = view.individuals.first(); + } + + try { + yield view.unprepare(); + } catch (GLib.Error err) { + warning("Error unpreparing Folks search: %s", err.message); + } + + return match; + } + +} diff -Nru geary-0.12.4/src/client/application/application-certificate-manager.vala geary-3.32.0/src/client/application/application-certificate-manager.vala --- geary-0.12.4/src/client/application/application-certificate-manager.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/src/client/application/application-certificate-manager.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,498 @@ +/* + * Copyright 2016 Software Freedom Conservancy Inc. + * Copyright 2019 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +// Required because GCR's VAPI is behind-the-times. See: +// https://gitlab.gnome.org/GNOME/gcr/merge_requests/7 +extern async bool gcr_trust_add_pinned_certificate_async( + Gcr.Certificate cert, + string purpose, + string peer, + Cancellable? cancellable +) throws Error; +extern bool gcr_trust_is_certificate_pinned( + Gcr.Certificate cert, + string purpose, + string peer, + Cancellable? cancellable +) throws Error; + + +// All of the below basically exists since cert pinning using GCR +// stopped working (GNOME/gcr#10) after gnome-keyring stopped +// advertising its PKCS11 module (GNOME/gnome-keyring#20). To work +// around, this piggy-backs off of the GIO infrastructure and adds a +// custom pinned cert store. + +/** Errors thrown by {@link CertificateManager}. */ +public errordomain Application.CertificateManagerError { + + /** The certificate was not trusted by the user. */ + UNTRUSTED, + + /** The certificate could not be saved. */ + STORE_FAILED; + +} + +/** + * Managing TLS certificate prompting and storage. + */ +public class Application.CertificateManager : GLib.Object { + + + // PCKS11 flag value lifted from pkcs11.h + private const ulong CKF_WRITE_PROTECTED = 1UL << 1; + + + private static async bool is_gcr_enabled(GLib.Cancellable? cancellable) { + // Use GCR if it looks like it should be able to be + // used. Specifically, if we can initialise the trust store + // must have both lookup and store PKCS11 slot URIs or else it + // won't be able to lookup or store pinned certs, secondly, + // there must be at least a read-write store slot available. + bool init_okay = false; + try { + init_okay = yield Gcr.pkcs11_initialize_async(cancellable); + } catch (GLib.Error err) { + warning("Failed to initialise GCR PCKS#11 modules: %s", err.message); + } + + bool has_uris = false; + if (init_okay) { + has_uris = ( + !Geary.String.is_empty(Gcr.pkcs11_get_trust_store_uri()) && + Gcr.pkcs11_get_trust_lookup_uris().length > 0 + ); + debug("GCR slot URIs found: %s", has_uris.to_string()); + } + + bool has_rw_store = false; + if (has_uris) { + Gck.Slot? store = Gcr.pkcs11_get_trust_store_slot(); + has_rw_store = !store.has_flags(CKF_WRITE_PROTECTED); + debug("GCR store is R/W: %s", has_rw_store.to_string()); + } + + return has_rw_store; + } + + + private TlsDatabase? pinning_database; + + + /** + * Constructs a new instance, globally installing the pinning database. + */ + public async CertificateManager(GLib.File store_dir, + GLib.Cancellable? cancellable) { + bool use_gcr = yield is_gcr_enabled(cancellable); + this.pinning_database = new TlsDatabase( + GLib.TlsBackend.get_default().get_default_database(), + store_dir, + use_gcr + ); + Geary.Endpoint.default_tls_database = this.pinning_database; + } + + /** + * Destroys an instance, de-installs the pinning database. + */ + ~CertificateManager() { + Geary.Endpoint.default_tls_database = null; + } + + + /** + * Prompts the user to trust the certificate for a service. + * + * Returns true if the user accepted the certificate. + */ + public async void prompt_pin_certificate(Gtk.Window parent, + Geary.AccountInformation account, + Geary.ServiceInformation service, + Geary.Endpoint endpoint, + bool is_validation, + GLib.Cancellable? cancellable) + throws CertificateManagerError { + CertificateWarningDialog dialog = new CertificateWarningDialog( + parent, account, service, endpoint, is_validation + ); + + bool save = false; + switch (dialog.run()) { + case CertificateWarningDialog.Result.TRUST: + // noop + break; + + case CertificateWarningDialog.Result.ALWAYS_TRUST: + save = true; + break; + + default: + throw new CertificateManagerError.UNTRUSTED("User declined"); + } + + debug("Pinning certificate for %s...", endpoint.remote.to_string()); + try { + yield this.pinning_database.pin_certificate( + endpoint.untrusted_certificate, + endpoint.remote, + save, + cancellable + ); + } catch (GLib.Error err) { + throw new CertificateManagerError.STORE_FAILED(err.message); + } + } + +} + + +/** TLS database that observes locally pinned certs. */ +private class Application.TlsDatabase : GLib.TlsDatabase { + + + /** A certificate and the identities it is trusted for. */ + private class TrustContext : Geary.BaseObject { + + + // Perform IO at high priority since UI and network + // connections depend on it + private const int IO_PRIO = GLib.Priority.HIGH; + private const GLib.ChecksumType ID_TYPE = GLib.ChecksumType.SHA384; + private const string FILENAME_FORMAT = "%s.pem"; + + + public string id; + public GLib.TlsCertificate certificate; + + + public TrustContext(GLib.TlsCertificate certificate) { + this.id = GLib.Checksum.compute_for_data( + ID_TYPE, certificate.certificate.data + ); + this.certificate = certificate; + } + + public TrustContext.lookup(GLib.File dir, + string identity, + GLib.Cancellable? cancellable) + throws GLib.Error { + // This isn't async so that we can support both + // verify_chain and verify_chain_async with the same call + GLib.File storage = dir.get_child(FILENAME_FORMAT.printf(identity)); + GLib.FileInputStream f_in = storage.read(cancellable); + GLib.BufferedInputStream buf = new GLib.BufferedInputStream(f_in); + GLib.ByteArray cert_pem = new GLib.ByteArray.sized(buf.buffer_size); + bool eof = false; + while (!eof) { + size_t filled = buf.fill(-1, cancellable); + if (filled > 0) { + cert_pem.append(buf.peek_buffer()); + buf.skip(filled, cancellable); + } else { + eof = true; + } + } + buf.close(cancellable); + + this(new GLib.TlsCertificate.from_pem((string) cert_pem.data, -1)); + } + + public async void save(GLib.File dir, + string identity, + GLib.Cancellable? cancellable) + throws GLib.Error { + yield Geary.Files.make_directory_with_parents(dir, cancellable); + GLib.File storage = dir.get_child(FILENAME_FORMAT.printf(identity)); + GLib.FileOutputStream f_out = yield storage.replace_async( + null, false, GLib.FileCreateFlags.NONE, IO_PRIO, cancellable + ); + GLib.BufferedOutputStream buf = new GLib.BufferedOutputStream(f_out); + + size_t written = 0; + yield buf.write_all_async( + this.certificate.certificate_pem.data, + IO_PRIO, + cancellable, + out written + ); + yield buf.close_async(IO_PRIO, cancellable); + } + + } + + + private static string to_name(GLib.SocketConnectable id) { + GLib.NetworkAddress? name = id as GLib.NetworkAddress; + if (name != null) { + return name.hostname; + } + + GLib.NetworkService? service = id as GLib.NetworkService; + if (service != null) { + return service.domain; + } + + GLib.InetSocketAddress? inet = id as GLib.InetSocketAddress; + if (inet != null) { + return inet.address.to_string(); + } + + return id.to_string(); + } + + + private GLib.TlsDatabase parent { get; private set; } + private GLib.File store_dir; + private bool use_gcr; + + private Gee.Map pinned_certs = + new Gee.HashMap(); + + + public TlsDatabase(GLib.TlsDatabase parent, + GLib.File store_dir, + bool use_gcr) { + this.parent = parent; + this.store_dir = store_dir; + this.use_gcr = use_gcr; + } + + public async void pin_certificate(GLib.TlsCertificate certificate, + GLib.SocketConnectable identity, + bool save, + GLib.Cancellable? cancellable = null) + throws GLib.Error { + string id = to_name(identity); + TrustContext context = new TrustContext(certificate); + lock (this.pinned_certs) { + this.pinned_certs.set(id, context); + } + if (save) { + if (this.use_gcr) { + yield gcr_trust_add_pinned_certificate_async( + new Gcr.SimpleCertificate(certificate.certificate.data), + GLib.TlsDatabase.PURPOSE_AUTHENTICATE_SERVER, + id, + cancellable + ); + } else { + yield context.save( + this.store_dir, to_name(identity), cancellable + ); + } + } + } + + public override string? + create_certificate_handle(GLib.TlsCertificate certificate) { + TrustContext? context = lookup_tls_certificate(certificate); + return (context != null) + ? context.id + : this.parent.create_certificate_handle(certificate); + } + + public override GLib.TlsCertificate? + lookup_certificate_for_handle(string handle, + GLib.TlsInteraction? interaction, + GLib.TlsDatabaseLookupFlags flags, + GLib.Cancellable? cancellable = null) + throws GLib.Error { + TrustContext? context = lookup_id(handle); + return (context != null) + ? context.certificate + : this.parent.lookup_certificate_for_handle( + handle, interaction, flags, cancellable + ); + } + + public override async GLib.TlsCertificate + lookup_certificate_for_handle_async(string handle, + GLib.TlsInteraction? interaction, + GLib.TlsDatabaseLookupFlags flags, + GLib.Cancellable? cancellable = null) + throws GLib.Error { + TrustContext? context = lookup_id(handle); + return (context != null) + ? context.certificate + : yield this.parent.lookup_certificate_for_handle_async( + handle, interaction, flags, cancellable + ); + } + + public override GLib.TlsCertificate + lookup_certificate_issuer(GLib.TlsCertificate certificate, + GLib.TlsInteraction? interaction, + GLib.TlsDatabaseLookupFlags flags, + GLib.Cancellable? cancellable = null) + throws GLib.Error { + return this.parent.lookup_certificate_issuer( + certificate, interaction, flags, cancellable + ); + } + + public override async GLib.TlsCertificate + lookup_certificate_issuer_async(GLib.TlsCertificate certificate, + GLib.TlsInteraction? interaction, + GLib.TlsDatabaseLookupFlags flags, + GLib.Cancellable? cancellable = null) + throws GLib.Error { + return yield this.parent.lookup_certificate_issuer_async( + certificate, interaction, flags, cancellable + ); + } + + public override GLib.List + lookup_certificates_issued_by(ByteArray issuer_raw_dn, + GLib.TlsInteraction? interaction, + GLib.TlsDatabaseLookupFlags flags, + GLib.Cancellable? cancellable = null) + throws GLib.Error { + return this.parent.lookup_certificates_issued_by( + issuer_raw_dn, interaction, flags, cancellable + ); + } + + public override async GLib.List + lookup_certificates_issued_by_async(GLib.ByteArray issuer_raw_dn, + GLib.TlsInteraction? interaction, + GLib.TlsDatabaseLookupFlags flags, + GLib.Cancellable? cancellable = null) + throws GLib.Error { + return yield this.parent.lookup_certificates_issued_by_async( + issuer_raw_dn, interaction, flags, cancellable + ); + } + + public override GLib.TlsCertificateFlags + verify_chain(GLib.TlsCertificate chain, + string purpose, + GLib.SocketConnectable? identity, + GLib.TlsInteraction? interaction, + GLib.TlsDatabaseVerifyFlags flags, + GLib.Cancellable? cancellable = null) + throws GLib.Error { + GLib.TlsCertificateFlags ret = this.parent.verify_chain( + chain, purpose, identity, interaction, flags, cancellable + ); + if (should_verify(ret, purpose, identity) && + verify(chain, identity, cancellable)) { + ret = 0; + } + return ret; + } + + public override async GLib.TlsCertificateFlags + verify_chain_async(GLib.TlsCertificate chain, + string purpose, + GLib.SocketConnectable? identity, + GLib.TlsInteraction? interaction, + GLib.TlsDatabaseVerifyFlags flags, + GLib.Cancellable? cancellable = null) + throws GLib.Error { + GLib.TlsCertificateFlags ret = yield this.parent.verify_chain_async( + chain, purpose, identity, interaction, flags, cancellable + ); + if (should_verify(ret, purpose, identity) && + yield verify_async(chain, identity, cancellable)) { + ret = 0; + } + return ret; + } + + private inline bool should_verify(GLib.TlsCertificateFlags parent_ret, + string purpose, + GLib.SocketConnectable? identity) { + // If the parent didn't verify, check for a locally pinned + // cert if it looks like we should, but always reject revoked + // certs + return ( + parent_ret != 0 && + !(GLib.TlsCertificateFlags.REVOKED in parent_ret) && + purpose == GLib.TlsDatabase.PURPOSE_AUTHENTICATE_SERVER && + identity != null + ); + } + + private bool verify(GLib.TlsCertificate chain, + GLib.SocketConnectable identity, + GLib.Cancellable? cancellable) + throws GLib.Error { + bool is_verified = false; + string id = to_name(identity); + TrustContext? context = null; + lock (this.pinned_certs) { + context = this.pinned_certs.get(id); + if (context != null) { + is_verified = true; + } else { + // Cert not found in memory, check with GCR if + // enabled. + if (this.use_gcr) { + is_verified = gcr_trust_is_certificate_pinned( + new Gcr.SimpleCertificate(chain.certificate.data), + GLib.TlsDatabase.PURPOSE_AUTHENTICATE_SERVER, + id, + cancellable + ); + } + + if (!is_verified) { + // Cert is not pinned in memory or in GCR, so look + // for it on disk. Do this even if GCR support is + // enabled, since if the cert was previously saved + // to disk, it should still be able to be used + try { + context = new TrustContext.lookup( + this.store_dir, id, cancellable + ); + this.pinned_certs.set(id, context); + is_verified = true; + } catch (GLib.IOError.NOT_FOUND err) { + // Cert was not found saved, so it not pinned + } catch (GLib.Error err) { + Geary.ErrorContext err_context = + new Geary.ErrorContext(err); + debug("Error loading pinned certificate: %s", + err_context.format_full_error()); + } + } + } + } + return is_verified; + } + + private async bool verify_async(GLib.TlsCertificate chain, + GLib.SocketConnectable identity, + GLib.Cancellable? cancellable) + throws GLib.Error { + bool is_valid = false; + yield Geary.Nonblocking.Concurrent.global.schedule_async(() => { + is_valid = verify(chain, identity, cancellable); + }, cancellable); + return is_valid; + } + + private TrustContext? lookup_id(string id) { + lock (this.pinned_certs) { + return Geary.traverse(this.pinned_certs.values).first_matching( + (ctx) => ctx.id == id + ); + } + } + + private TrustContext? lookup_tls_certificate(GLib.TlsCertificate cert) { + lock (this.pinned_certs) { + return Geary.traverse(this.pinned_certs.values).first_matching( + (ctx) => ctx.certificate.is_same(cert) + ); + } + } + +} diff -Nru geary-0.12.4/src/client/application/application-command.vala geary-3.32.0/src/client/application/application-command.vala --- geary-0.12.4/src/client/application/application-command.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/src/client/application/application-command.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,373 @@ +/* + * Copyright 2018 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + + +/** + * A generic application user command with undo and redo support. + */ +public abstract class Application.Command : GLib.Object { + + + /** + * A human-readable label describing the effect of calling {@link undo}. + * + * This can be used in a user interface, perhaps as a tooltip for + * an Undo button, to indicate what will happen if the command is + * un-done. For example, "Conversation restored from Trash". + */ + public string? undo_label { get; protected set; default = null; } + + /** + * A human-readable label describing the effect of calling {@link redo}. + * + * This can be used in a user interface, perhaps as a tooltip for + * a Redo button, to indicate what will happen if the command is + * re-done. For example, "Conversation restored from Trash". + */ + public string? redo_label { get; protected set; default = null; } + + /** + * A human-readable label describing the result of calling {@link execute}. + * + * This can be used in a user interface to indicate the effects of + * the action just executed. For example, "Conversation moved to + * Trash". + * + * Since the effects of re-doing a command should be identical to + * that of executing it, this string can also be used to describe + * the effects of {@link redo}. + */ + public string? executed_label { get; protected set; default = null; } + + /** + * A human-readable label describing the result of calling {@link undo}. + * + * This can be used in a user interface to indicate the effects of + * the action just executed. For example, "Conversation restored + * from Trash". + */ + public string? undone_label { get; protected set; default = null; } + + + /** + * Called by {@link CommandStack} to execute the command. + * + * Applications should not call this method directly, rather pass + * it to {@link CommandStack.execute}. + * + * Command implementations should apply the user command when this + * method is called. It will be called at most once when used sole + * with the command stack. + */ + public abstract async void execute(GLib.Cancellable? cancellable) + throws GLib.Error; + + /** + * Called by {@link CommandStack} to undo the executed command. + * + * Applications should not call this method directly, rather they + * should call {@link CommandStack.undo} so that it is managed + * correctly. + * + * Command implementations should reverse the user command carried + * out by the call to {@link execute}. It will be called zero or + * more times, but only ever after a call to either {@link + * execute} or {@link redo} when used sole with the command stack. + */ + public abstract async void undo(GLib.Cancellable? cancellable) + throws GLib.Error; + + /** + * Called by {@link CommandStack} to redo the executed command. + * + * Applications should not call this method directly, rather they + * should call {@link CommandStack.redo} so that it is managed + * correctly. + * + * Command implementations should re-apply a user command that has + * been un-done by a call to {@link undo}. By default, this method + * simply calls {@link execute}, but implementations with more + * complex requirements can override this. It will called zero or + * more times, but only ever after a call to {@link undo} when + * used sole with the command stack. + */ + public virtual async void redo(GLib.Cancellable? cancellable) + throws GLib.Error { + yield execute(cancellable); + } + + /** Returns a string representation of the command for debugging. */ + public virtual string to_string() { + return get_type().name(); + } + +} + + +/** + * A command that executes a sequence of other commands. + */ +public class Application.CommandSequence : Command { + + + public Gee.List commands { + get; private set; default = new Gee.LinkedList(); + } + + public CommandSequence(Command[]? commands = null) { + if (commands != null) { + this.commands.add_all_array(commands); + } + } + + + /** + * Executes all commands in the sequence, sequentially. + */ + public override async void execute(GLib.Cancellable? cancellable) + throws GLib.Error { + foreach (Command command in this.commands) { + yield command.execute(cancellable); + } + } + + /** + * Un-does all commands in the sequence, in reverse order. + */ + public override async void undo(GLib.Cancellable? cancellable) + throws GLib.Error { + Gee.LinkedList reversed = new Gee.LinkedList(); + foreach (Command command in this.commands) { + reversed.insert(0, command); + } + foreach (Command command in this.commands) { + yield command.undo(cancellable); + } + } + + /** + * Re-does all commands in the sequence, sequentially. + */ + public override async void redo(GLib.Cancellable? cancellable) + throws GLib.Error { + foreach (Command command in this.commands) { + yield command.redo(cancellable); + } + } + +} + + +/** + * A command that updates a GObject instance property. + * + * This command will save the existing property value on execution + * before updating it with the new given property, restore it on undo, + * and re-execute on redo. The type parameter T must be the same type + * as the property being updated and must be nullable if the property + * is nullable. + */ +public class Application.PropertyCommand : Application.Command { + + + private GLib.Object object; + private string property_name; + private T new_value; + private T old_value; + + + public PropertyCommand(GLib.Object object, + string property_name, + T new_value, + string? undo_label = null, + string? redo_label = null, + string? executed_label = null, + string? undone_label = null) { + this.object = object; + this.property_name = property_name; + this.new_value = new_value; + + this.object.get(this.property_name, ref this.old_value); + + if (undo_label != null) { + this.undo_label = undo_label.printf(this.old_value); + } + if (redo_label != null) { + this.redo_label = redo_label.printf(this.new_value); + } + if (executed_label != null) { + this.executed_label = executed_label.printf(this.new_value); + } + if (undone_label != null) { + this.undone_label = undone_label.printf(this.old_value); + } + } + + public async override void execute(GLib.Cancellable? cancellable) { + this.object.set(this.property_name, this.new_value); + } + + public async override void undo(GLib.Cancellable? cancellable) { + this.object.set(this.property_name, this.old_value); + } + + public override string to_string() { + return "%s(%s)".printf(base.to_string(), this.property_name); + } + +} + + +/** + * A stack of executed application commands. + * + * The command stack manages calling the {@link Command.execute}, + * {@link Command.undo}, and {@link Command.redo} methods on an + * application's user commands. It enforces the strict ordering of + * calls to those methods so that if a command is well implemented, + * then the application will be in the same state after executing and + * re-doing a command, and the application will return to the original + * state after being undone, both for individual commands and between + * after a number of commands have been executed. + * + * Applications should call {@link execute} to execute a command, + * which will push it on to an undo stack after executing it. The + * command at the top of the stack can be undone by calling {@link + * undo}, which undoes the command, pops it from the undo stack and + * pushes it on the redo stack. If a new command is executed when the + * redo stack is non-empty, it will be emptied first. + */ +public class Application.CommandStack : GLib.Object { + + + // The can_undo and can_redo are automatic properties so + // applications can get notified when they change. + + /** Determines if there are any commands able to be un-done. */ + public bool can_undo { get; private set; } + + /** Determines if there are any commands available to be re-done. */ + public bool can_redo { get; private set; } + + + private Gee.LinkedList undo_stack = new Gee.LinkedList(); + private Gee.LinkedList redo_stack = new Gee.LinkedList(); + + + /** Fired when a command is first executed */ + public signal void executed(Command command); + + /** Fired when a command is un-done */ + public signal void undone(Command command); + + /** Fired when a command is re-executed */ + public signal void redone(Command command); + + + /** + * Executes an command and pushes it onto the undo stack. + * + * This calls {@link Command.execute} and if no error is thrown, + * pushes the command onto the undo stack. + */ + public async void execute(Command target, GLib.Cancellable? cancellable) + throws GLib.Error { + debug("Executing: %s", target.to_string()); + yield target.execute(cancellable); + + this.undo_stack.insert(0, target); + this.can_undo = true; + + this.redo_stack.clear(); + this.can_redo = false; + + executed(target); + } + + /** + * Pops a command off the undo stack and un-does is. + * + * This calls {@link Command.undo} on the topmost command on the + * undo stack and if no error is thrown, pushes it on the redo + * stack. If an error is thrown, the command is discarded and the + * redo stack is emptied. + */ + public async void undo(GLib.Cancellable? cancellable) + throws GLib.Error { + if (!this.undo_stack.is_empty) { + Command target = this.undo_stack.remove_at(0); + + if (this.undo_stack.is_empty) { + this.can_undo = false; + } + + debug("Undoing: %s", target.to_string()); + try { + yield target.undo(cancellable); + } catch (Error err) { + this.redo_stack.clear(); + this.can_redo = false; + throw err; + } + + this.redo_stack.insert(0, target); + this.can_redo = true; + undone(target); + } + } + + /** + * Pops a command off the redo stack and re-applies it. + * + * This calls {@link Command.redo} on the topmost command on the + * redo stack and if no error is thrown, pushes it on the undo + * stack. If an error is thrown, the command is discarded and the + * redo stack is emptied. + */ + public async void redo(GLib.Cancellable? cancellable) + throws GLib.Error { + if (!this.redo_stack.is_empty) { + Command target = this.redo_stack.remove_at(0); + + if (this.redo_stack.is_empty) { + this.can_redo = false; + } + + debug("Redoing: %s", target.to_string()); + try { + yield target.redo(cancellable); + } catch (Error err) { + this.redo_stack.clear(); + this.can_redo = false; + throw err; + } + + this.undo_stack.insert(0, target); + this.can_undo = true; + redone(target); + } + } + + /** Returns the command at the top of the undo stack, if any. */ + public Command? peek_undo() { + return this.undo_stack.is_empty ? null : this.undo_stack[0]; + } + + /** Returns the command at the top of the redo stack, if any. */ + public Command? peek_redo() { + return this.redo_stack.is_empty ? null : this.redo_stack[0]; + } + + /** Clears all commands from both the undo and redo stacks. */ + public void clear() { + this.undo_stack.clear(); + this.can_undo = false; + this.redo_stack.clear(); + this.can_redo = false; + } + +} diff -Nru geary-0.12.4/src/client/application/autostart-manager.vala geary-3.32.0/src/client/application/autostart-manager.vala --- geary-0.12.4/src/client/application/autostart-manager.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/application/autostart-manager.vala 2019-03-17 13:39:29.000000000 +0000 @@ -9,17 +9,20 @@ * desktop file at $HOME/.config/autostart/geary.desktop */ public class AutostartManager : Object { + private const string AUTOSTART_FOLDER = "autostart"; private const string AUTOSTART_DESKTOP_FILE = "geary-autostart.desktop"; + private GearyApplication instance; private File startup_file; // Startup '.desktop' file - public AutostartManager() { - startup_file = File.new_for_path(Environment.get_user_config_dir()).get_child(AUTOSTART_FOLDER) + public AutostartManager(GearyApplication instance) { + this.instance = instance; + this.startup_file = File.new_for_path(Environment.get_user_config_dir()).get_child(AUTOSTART_FOLDER) .get_child(AUTOSTART_DESKTOP_FILE); - + // Connect startup-notifications option callback - GearyApplication.instance.config.settings.changed[Configuration.STARTUP_NOTIFICATIONS_KEY].connect( + this.instance.config.settings.changed[Configuration.STARTUP_NOTIFICATIONS_KEY].connect( on_startup_notification_change); } @@ -27,11 +30,11 @@ * Returns the system-wide autostart desktop file */ public File? get_autostart_desktop_file() { - File? install_dir = GearyApplication.instance.get_install_dir(); + File? install_dir = this.instance.get_install_dir(); File desktop_file = (install_dir != null) ? install_dir.get_child("share").get_child("applications").get_child(AUTOSTART_DESKTOP_FILE) : File.new_for_path(GearyApplication.SOURCE_ROOT_DIR).get_child("build").get_child("desktop").get_child(AUTOSTART_DESKTOP_FILE); - + return desktop_file.query_exists() ? desktop_file : null; } @@ -39,9 +42,9 @@ * Deletes the desktop file from autostart directory. */ public void delete_startup_file() { - if (startup_file.query_exists()) { + if (this.startup_file.query_exists()) { try { - startup_file.delete(); + this.startup_file.delete(); } catch (Error err) { message("Failed to delete startup file: %s", err.message); } @@ -52,39 +55,40 @@ * Creates .desktop file in autostart directory (usually '$HOME/.config/autostart/') if no one exists. */ public void create_startup_file() { - if (startup_file.query_exists()) + if (this.startup_file.query_exists()) return; - + try { - File autostart_dir = startup_file.get_parent(); + File autostart_dir = this.startup_file.get_parent(); if (!autostart_dir.query_exists()) autostart_dir.make_directory_with_parents(); File? autostart = get_autostart_desktop_file(); if (autostart == null) { message("Autostart file is not installed!"); } else { - autostart.copy(startup_file, 0); + autostart.copy(this.startup_file, 0); } } catch (Error err) { message("Failed to create startup file: %s", err.message); } } - + /** * Callback for startup notification option changes. */ public void on_startup_notification_change() { - if (GearyApplication.instance.config.startup_notifications) + if (this.instance.config.startup_notifications) create_startup_file(); else delete_startup_file(); } - + /* * A convenience method. The purpose of this method is to synchronize the state of startup notifications setting * with the actual state of the file, so it's not misleading for the user (the option is checked while the file doesn't exist) */ public void sync_with_config() { - GearyApplication.instance.config.startup_notifications = startup_file.query_exists(); + this.instance.config.startup_notifications = this.startup_file.query_exists(); } + } diff -Nru geary-0.12.4/src/client/application/geary-application.vala geary-3.32.0/src/client/application/geary-application.vala --- geary-0.12.4/src/client/application/geary-application.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/application/geary-application.vala 2019-03-17 13:39:29.000000000 +0000 @@ -7,6 +7,7 @@ // Defined by CMake build script. extern const string _INSTALL_PREFIX; extern const string _GSETTINGS_DIR; +extern const string _WEB_EXTENSIONS_DIR; extern const string _SOURCE_ROOT_DIR; extern const string _BUILD_ROOT_DIR; extern const string GETTEXT_PACKAGE; @@ -15,12 +16,13 @@ * The interface between Geary and the desktop environment. */ public class GearyApplication : Gtk.Application { + public const string NAME = "Geary"; public const string PRGNAME = "geary"; public const string APP_ID = "org.gnome.Geary"; public const string DESCRIPTION = _("Send and receive email"); public const string COPYRIGHT_1 = _("Copyright 2016 Software Freedom Conservancy Inc."); - public const string COPYRIGHT_2 = _("Copyright 2016-2017 Geary Development Team."); + public const string COPYRIGHT_2 = _("Copyright 2016-2019 Geary Development Team."); public const string WEBSITE = "https://wiki.gnome.org/Apps/Geary"; public const string WEBSITE_LABEL = _("Visit the Geary web site"); public const string BUGREPORT = "https://wiki.gnome.org/Apps/Geary/ReportingABug"; @@ -42,11 +44,19 @@ null }; + // Common window actions + public const string ACTION_CLOSE = "close"; + public const string ACTION_COPY = "copy"; + public const string ACTION_HELP_OVERLAY = "show-help-overlay"; + public const string ACTION_REDO = "redo"; + public const string ACTION_UNDO = "undo"; + + // App-wide actions private const string ACTION_ABOUT = "about"; private const string ACTION_ACCOUNTS = "accounts"; private const string ACTION_COMPOSE = "compose"; - private const string ACTION_MAILTO = "mailto"; private const string ACTION_HELP = "help"; + private const string ACTION_MAILTO = "mailto"; private const string ACTION_PREFERENCES = "preferences"; private const string ACTION_QUIT = "quit"; @@ -54,8 +64,8 @@ {ACTION_ABOUT, on_activate_about}, {ACTION_ACCOUNTS, on_activate_accounts}, {ACTION_COMPOSE, on_activate_compose}, - {ACTION_MAILTO, on_activate_mailto, "s"}, {ACTION_HELP, on_activate_help}, + {ACTION_MAILTO, on_activate_mailto, "s"}, {ACTION_PREFERENCES, on_activate_preferences}, {ACTION_QUIT, on_activate_quit}, }; @@ -64,7 +74,7 @@ private const int64 FORCE_SHUTDOWN_USEC = 5 * USEC_PER_SEC; - [Deprecated] + [Version (deprecated = true)] public static GearyApplication instance { get { return _instance; } private set { @@ -113,14 +123,6 @@ get { return Args.hidden_startup || this.config.startup_notifications; } } - public Gtk.ActionGroup actions { - get; private set; default = new Gtk.ActionGroup("GearyActionGroup"); - } - - public Gtk.UIManager ui_manager { - get; private set; default = new Gtk.UIManager(); - } - private string bin; private File exec_dir; private bool exiting_fired = false; @@ -150,13 +152,13 @@ public override bool local_command_line(ref unowned string[] args, out int exit_status) { bin = args[0]; exec_dir = (File.new_for_path(Posix.realpath(Environment.find_program_in_path(bin)))).get_parent(); - + try { register(); } catch (Error e) { error("Error registering GearyApplication: %s", e.message); } - + if (!Args.parse(args)) { exit_status = 1; return true; @@ -184,15 +186,18 @@ exit_status = 0; return true; } - + public override void startup() { Configuration.init(is_installed(), GSETTINGS_DIR); - + Environment.set_application_name(NAME); Environment.set_prgname(PRGNAME); International.init(GETTEXT_PACKAGE, bin); - + Geary.Logging.init(); + Geary.Logging.log_to(stderr); + GLib.Log.set_default_handler(Geary.Logging.default_handler); + Date.init(); // Calls Gtk.init(), amongst other things @@ -203,84 +208,96 @@ add_action_entries(action_entries, this); } - + public override void activate() { base.activate(); - + if (!present()) create_async.begin(); } - + public bool present() { - if (controller == null) + if (controller == null || controller.main_window == null) return false; - - // if LoginDialog (i.e. the opening dialog for creating the initial account) is present - // and visible, bring that to top (to prevent opening the hidden main window, which is - // empty) - if (controller.login_dialog != null && controller.login_dialog.visible) { - controller.login_dialog.present_with_time(Gdk.CURRENT_TIME); - - return true; - } - - if (controller.main_window == null) - return false; - - // When the app is started hidden, show_all() never gets - // called, do so here to prevent an empty window appearing. - controller.main_window.show_all(); // Use present_with_time and a synthesised time so the present // actually works, as a work around for Bug 766284 // . - // Subtract 10ms from the current time to avoid the main + // Subtract 1000ms from the current time to avoid the main // window stealing the focus when presented just before - // showing a dialog (issue #43). + // showing a dialog (issue #43, bgo 726282). this.controller.main_window.present_with_time( - (uint32) (get_monotonic_time() / 1000) - 10 + (uint32) (get_monotonic_time() / 1000) - 1000 ); return true; } - + private async void create_async() { // Manually keep the main loop around for the duration of this call. // Without this, the main loop will exit as soon as we hit the yield // below, before we create the main window. hold(); - + // do *after* parsing args, as they dicate where logging is sent to, if anywhere, and only // after activate (which means this is only logged for the one user-visible instance, not // the other instances called when sending commands to the app via the command-line) message("%s %s prefix=%s exec_dir=%s is_installed=%s", NAME, VERSION, INSTALL_PREFIX, exec_dir.get_path(), is_installed().to_string()); - + config = new Configuration(APP_ID); - yield controller.open_async(); - + + // Application accels + add_app_accelerators(ACTION_COMPOSE, { "N" }); + add_app_accelerators(ACTION_HELP, { "F1" }); + add_app_accelerators(ACTION_QUIT, { "Q" }); + + // Common window accels + add_window_accelerators(ACTION_CLOSE, { "W" }); + add_window_accelerators(ACTION_COPY, { "C" }); + add_window_accelerators(ACTION_HELP_OVERLAY, { "F1", "question" }); + add_window_accelerators(ACTION_REDO, { "Z" }); + add_window_accelerators(ACTION_UNDO, { "Z" }); + + ComposerWidget.add_window_accelerators(this); + + yield controller.open_async(null); + release(); } - + private async void destroy_async() { // see create_async() for reasoning hold/release is used hold(); - + yield controller.close_async(); - + release(); - + is_destroyed = true; } - // NOTE: This assert()'s if the Gtk.Action is not present in the default action group - public Gtk.Action get_action(string name) { - Gtk.Action? action = actions.get_action(name); - assert(action != null); - - return action; + public void add_window_accelerators(string action, + string[] accelerators, + Variant? param = null) { + string name = "win." + action; + string[] all_accel = get_accels_for_action(name); + foreach (string accel in accelerators) { + all_accel += accel; + } + set_accels_for_action(name, all_accel); + } + + public void show_accounts() { + activate(); + + Accounts.Editor editor = new Accounts.Editor(this, get_active_window()); + editor.run(); + editor.destroy(); + this.controller.expunge_accounts.begin(); } - + + public File get_user_data_directory() { return File.new_for_path(Environment.get_user_data_dir()).get_child("geary"); } @@ -292,7 +309,7 @@ public File get_user_config_directory() { return File.new_for_path(Environment.get_user_config_dir()).get_child("geary"); } - + /** * Returns the base directory that the application's various resource files are stored. If the * application is running from its installed directory, this will point to @@ -314,16 +331,14 @@ /** * Returns the directory containing the application's WebExtension libs. * - * If the application is installed, this will be - * `$INSTALL_PREFIX/lib/geary/web-extension`, else it will be + * When running from the installation prefix, this will be based + * on the Meson `libdir` option, and can be set by invoking `meson + * configure` as appropriate. */ public File get_web_extensions_dir() { - File? dir = get_install_dir(); - if (dir != null) - dir = dir.get_child("lib").get_child("geary").get_child("web-extensions"); - else - dir = File.new_for_path(BUILD_ROOT_DIR).get_child("src"); - return dir; + return (get_install_dir() != null) + ? File.new_for_path(_WEB_EXTENSIONS_DIR) + : File.new_for_path(BUILD_ROOT_DIR).get_child("src"); } public File? get_desktop_file() { @@ -331,96 +346,55 @@ File desktop_file = (install_dir != null) ? install_dir.get_child("share").get_child("applications").get_child("org.gnome.Geary.desktop") : File.new_for_path(SOURCE_ROOT_DIR).get_child("build").get_child("desktop").get_child("org.gnome.Geary.desktop"); - + return desktop_file.query_exists() ? desktop_file : null; } - + public bool is_installed() { return exec_dir.has_prefix(get_install_prefix_dir()); } - + // Returns the configure installation prefix directory, which does not imply Geary is installed // or that it's running from this directory. public File get_install_prefix_dir() { return File.new_for_path(INSTALL_PREFIX); } - + // Returns the installation directory, or null if we're running outside of the installation // directory. public File? get_install_dir() { File prefix_dir = get_install_prefix_dir(); - - return exec_dir.has_prefix(prefix_dir) ? prefix_dir : null; - } - /** - * Creates a GTK builder given the name of a GResource. - * - * @deprecated Use {@link GioUtil.create_builder} instead. - */ - [Deprecated] - public Gtk.Builder create_builder(string name) { - return GioUtil.create_builder(name); - } - - /** - * Loads a GResource as a string. - * - * @deprecated Use {@link GioUtil.read_resource} instead. - */ - [Deprecated] - public string read_resource(string name) throws Error { - return GioUtil.read_resource(name); - } - - /** - * Loads a UI GResource into the UI manager. - */ - [Deprecated] - public void load_ui_resource(string name) { - try { - this.ui_manager.add_ui_from_resource("/org/gnome/Geary/" + name); - } catch(GLib.Error error) { - critical("Unable to load \"%s\" for Gtk.UIManager: %s".printf( - name, error.message - )); - } + return exec_dir.has_prefix(prefix_dir) ? prefix_dir : null; } /** * Displays a URI on the current active window, if any. */ public void show_uri(string uri) throws Error { - Gtk.Window? window = get_active_window(); -#if !GTK_3_22 - bool success = Gtk.show_uri( - window != null ? window.get_screen() : null, uri, Gdk.CURRENT_TIME + bool success = Gtk.show_uri_on_window( + get_active_window(), uri, Gdk.CURRENT_TIME ); if (!success) { throw new IOError.FAILED("gtk_show_uri() returned false"); } -#else - if (!Gtk.show_uri_on_window(window, uri, Gdk.CURRENT_TIME)) { - throw new IOError.FAILED("gtk_show_uri_on_window() returned false"); - } -#endif } // This call will fire "exiting" only if it's not already been fired. public void exit(int exitcode = 0) { if (exiting_fired) return; - + this.exitcode = exitcode; - + exiting_fired = true; if (!exiting(false)) { exiting_fired = false; this.exitcode = 0; - + return; } - + // Give asynchronous destroy_async() a chance to complete, but to avoid bug(s) where // Geary hangs at exit, shut the whole thing down if destroy_async() takes too long to // complete @@ -428,7 +402,7 @@ destroy_async.begin(); while (!is_destroyed || Gtk.events_pending()) { Gtk.main_iteration(); - + int64 delta_usec = get_monotonic_time() - start_usec; if (delta_usec >= FORCE_SHUTDOWN_USEC) { debug("Forcing shutdown of Geary, %ss passed...", (delta_usec / USEC_PER_SEC).to_string()); @@ -448,7 +422,7 @@ Signal.stop_emission_by_name(this, "exiting"); return false; } - + // This call will fire "exiting" only if it's not already been fired and halt the application // in its tracks. public void panic() { @@ -456,10 +430,16 @@ exiting_fired = true; exiting(true); } - + Posix.exit(1); } + public void add_app_accelerators(string action, + string[] accelerators, + Variant? param = null) { + set_accels_for_action("app." + action, accelerators); + } + private void on_activate_about() { Gtk.show_about_dialog(get_active_window(), "program-name", NAME, @@ -480,10 +460,7 @@ } private void on_activate_accounts() { - AccountDialog dialog = new AccountDialog(get_active_window()); - dialog.show_all(); - dialog.run(); - dialog.destroy(); + show_accounts(); } private void on_activate_compose() { @@ -510,12 +487,12 @@ private void on_activate_help() { try { if (is_installed()) { - show_uri("ghelp:geary"); + show_uri("help:geary"); } else { Pid pid; File exec_dir = get_exec_dir(); string[] argv = new string[3]; - argv[0] = "gnome-help"; + argv[0] = "yelp"; argv[1] = GearyApplication.SOURCE_ROOT_DIR + "/help/C/"; argv[2] = null; if (!Process.spawn_async( diff -Nru geary-0.12.4/src/client/application/geary-args.vala geary-3.32.0/src/client/application/geary-args.vala --- geary-0.12.4/src/client/application/geary-args.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/application/geary-args.vala 2019-03-17 13:39:29.000000000 +0000 @@ -55,7 +55,7 @@ GearyApplication.COPYRIGHT_2, _("Please report comments, suggestions and bugs to:"), GearyApplication.BUGREPORT)); - + try { context.parse(ref args); } catch (OptionError error) { @@ -64,52 +64,57 @@ stdout.printf("\n%s", context.get_help(true, null)); return false; } - + // other than the OptionEntry command-line arguments, the only acceptable arguments are // mailto:'s for (int ctr = 1; ctr < args.length; ctr++) { string arg = args[ctr]; - + if (!arg.has_prefix(Geary.ComposedEmail.MAILTO_SCHEME)) { stdout.printf(_("Unrecognized command line option “%s”\n").printf(arg)); stdout.printf("\n%s", context.get_help(true, null)); - + return false; } } - + if (version) { stdout.printf("%s %s\n", GearyApplication.PRGNAME, GearyApplication.VERSION); Process.exit(0); } - + if (log_network) Geary.Logging.enable_flags(Geary.Logging.Flag.NETWORK); - + if (log_serializer) Geary.Logging.enable_flags(Geary.Logging.Flag.SERIALIZER); - + if (log_replay_queue) Geary.Logging.enable_flags(Geary.Logging.Flag.REPLAY); - + if (log_conversations) Geary.Logging.enable_flags(Geary.Logging.Flag.CONVERSATIONS); - + if (log_periodic) Geary.Logging.enable_flags(Geary.Logging.Flag.PERIODIC); - + if (log_sql) Geary.Logging.enable_flags(Geary.Logging.Flag.SQL); - + if (log_folder_normalization) Geary.Logging.enable_flags(Geary.Logging.Flag.FOLDER_NORMALIZATION); - + if (log_deserializer) Geary.Logging.enable_flags(Geary.Logging.Flag.DESERIALIZER); - - if (log_debug) + + if (log_debug) { Geary.Logging.log_to(stdout); - + } else { + // We'll be logging to stderror until this point, so stop + // that. + Geary.Logging.log_to(null); + } + return true; } diff -Nru geary-0.12.4/src/client/application/geary-config.vala geary-3.32.0/src/client/application/geary-config.vala --- geary-0.12.4/src/client/application/geary-config.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/application/geary-config.vala 2019-03-17 13:39:29.000000000 +0000 @@ -9,8 +9,6 @@ */ public class Configuration { - public const string ATTACHMENTS_DIR_KEY = "attachments-directory"; - public const string PRINT_DIR_KEY = "print-directory"; public const string WINDOW_WIDTH_KEY = "window-width"; public const string WINDOW_HEIGHT_KEY = "window-height"; public const string WINDOW_MAXIMIZE_KEY = "window-maximize"; @@ -30,6 +28,7 @@ public const string SPELL_CHECK_LANGUAGES = "spell-check-languages"; public const string SEARCH_STRATEGY_KEY = "search-strategy"; public const string CONVERSATION_VIEWER_ZOOM_KEY = "conversation-viewer-zoom"; + public const string COMPOSER_WINDOW_SIZE_KEY = "composer-window-size"; public enum DesktopEnvironment { @@ -64,54 +63,44 @@ } } - public string? attachments_dir { - owned get { return settings.get_string(ATTACHMENTS_DIR_KEY); } - set { settings.set_string(ATTACHMENTS_DIR_KEY, value); } - } - - public string? print_dir { - owned get { return settings.get_string(PRINT_DIR_KEY); } - set { settings.set_string(PRINT_DIR_KEY, value); } - } - public int window_width { get { return settings.get_int(WINDOW_WIDTH_KEY); } } - + public int window_height { get { return settings.get_int(WINDOW_HEIGHT_KEY); } } - + public bool window_maximize { get { return settings.get_boolean(WINDOW_MAXIMIZE_KEY); } } - + public int folder_list_pane_position_old { get { return settings.get_int(FOLDER_LIST_PANE_POSITION_KEY); } } - + public int folder_list_pane_position_horizontal { get { return settings.get_int(FOLDER_LIST_PANE_POSITION_HORIZONTAL_KEY); } set { settings.set_int(FOLDER_LIST_PANE_POSITION_HORIZONTAL_KEY, value); } } - + public int folder_list_pane_position_vertical { get { return settings.get_int(FOLDER_LIST_PANE_POSITION_VERTICAL_KEY); } } - + public bool folder_list_pane_horizontal { get { return settings.get_boolean(FOLDER_LIST_PANE_HORIZONTAL_KEY); } } - + public int messages_pane_position { get { return settings.get_int(MESSAGES_PANE_POSITION_KEY); } set { settings.set_int(MESSAGES_PANE_POSITION_KEY, value); } } - + public bool autoselect { get { return settings.get_boolean(AUTOSELECT_KEY); } } - + public bool display_preview { get { return settings.get_boolean(DISPLAY_PREVIEW_KEY); } } @@ -146,7 +135,7 @@ get { return settings.get_boolean(STARTUP_NOTIFICATIONS_KEY); } set { set_boolean(STARTUP_NOTIFICATIONS_KEY, value); } } - + private const string CLOCK_FORMAT_KEY = "clock-format"; public Date.ClockFormat clock_format { get { @@ -156,12 +145,12 @@ return Date.ClockFormat.TWENTY_FOUR_HOURS; } } - + public bool ask_open_attachment { get { return settings.get_boolean(ASK_OPEN_ATTACHMENT_KEY); } set { set_boolean(ASK_OPEN_ATTACHMENT_KEY, value); } } - + public bool compose_as_html { get { return settings.get_boolean(COMPOSE_AS_HTML_KEY); } set { set_boolean(COMPOSE_AS_HTML_KEY, value); } @@ -172,6 +161,21 @@ set { settings.set_double(CONVERSATION_VIEWER_ZOOM_KEY, value); } } + public int[] composer_window_size { + owned get { + int[] size = new int[2]; + var s = settings.get_value(COMPOSER_WINDOW_SIZE_KEY); + if (s.n_children () == 2) { + size = { (int) s.get_child_value(0), (int) s.get_child_value(1)}; + } else { + size = {-1,-1}; + } + return size; + } + set { + settings.set_value(COMPOSER_WINDOW_SIZE_KEY, value); + } + } // Creates a configuration object. public Configuration(string schema_id) { @@ -186,43 +190,43 @@ SettingsBindFlags flags = GLib.SettingsBindFlags.DEFAULT) { settings.bind(key, object, property, flags); } - + private void set_boolean(string name, bool value) { if (!settings.set_boolean(name, value)) message("Unable to set configuration value %s = %s", name, value.to_string()); } - + public Geary.SearchQuery.Strategy get_search_strategy() { switch (settings.get_string(SEARCH_STRATEGY_KEY).down()) { case "exact": return Geary.SearchQuery.Strategy.EXACT; - + case "aggressive": return Geary.SearchQuery.Strategy.AGGRESSIVE; - + case "horizon": return Geary.SearchQuery.Strategy.HORIZON; - + case "conservative": default: return Geary.SearchQuery.Strategy.CONSERVATIVE; } } - + public void set_search_strategy(Geary.SearchQuery.Strategy strategy) { switch (strategy) { case Geary.SearchQuery.Strategy.EXACT: settings.set_string(SEARCH_STRATEGY_KEY, "exact"); break; - + case Geary.SearchQuery.Strategy.AGGRESSIVE: settings.set_string(SEARCH_STRATEGY_KEY, "aggressive"); break; - + case Geary.SearchQuery.Strategy.HORIZON: settings.set_string(SEARCH_STRATEGY_KEY, "horizon"); break; - + case Geary.SearchQuery.Strategy.CONSERVATIVE: default: settings.set_string(SEARCH_STRATEGY_KEY, "conservative"); diff -Nru geary-0.12.4/src/client/application/geary-controller.vala geary-3.32.0/src/client/application/geary-controller.vala --- geary-0.12.4/src/client/application/geary-controller.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/application/geary-controller.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,114 +1,143 @@ /* * Copyright 2016 Software Freedom Conservancy Inc. - * Copyright 2016 Michael Gratton + * Copyright 2016-2018 Michael Gratton * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ -// Required because Gcr's VAPI is behind-the-times -// TODO: When bindings available, use async variants of these calls -extern const string GCR_PURPOSE_SERVER_AUTH; -extern bool gcr_trust_add_pinned_certificate(Gcr.Certificate cert, string purpose, string peer, - Cancellable? cancellable) throws Error; -extern bool gcr_trust_is_certificate_pinned(Gcr.Certificate cert, string purpose, string peer, - Cancellable? cancellable) throws Error; -extern bool gcr_trust_remove_pinned_certificate(Gcr.Certificate cert, string purpose, string peer, - Cancellable? cancellable) throws Error; /** * Primary controller for a Geary application instance. */ public class GearyController : Geary.BaseObject { + // Named actions. - // - // NOTE: Some actions with accelerators need to also be added to ui/accelerators.ui - public const string ACTION_NEW_MESSAGE = "GearyNewMessage"; - public const string ACTION_REPLY_TO_MESSAGE = "GearyReplyToMessage"; - public const string ACTION_REPLY_ALL_MESSAGE = "GearyReplyAllMessage"; - public const string ACTION_FORWARD_MESSAGE = "GearyForwardMessage"; - public const string ACTION_ARCHIVE_CONVERSATION = "GearyArchiveConversation"; - public const string ACTION_TRASH_CONVERSATION = "GearyTrashConversation"; - public const string ACTION_DELETE_CONVERSATION = "GearyDeleteConversation"; - public const string ACTION_EMPTY_SPAM = "GearyEmptySpam"; - public const string ACTION_EMPTY_TRASH = "GearyEmptyTrash"; - public const string ACTION_UNDO = "GearyUndo"; - public const string ACTION_FIND_IN_CONVERSATION = "GearyFindInConversation"; - public const string ACTION_ZOOM_IN = "GearyZoomIn"; - public const string ACTION_ZOOM_OUT = "GearyZoomOut"; - public const string ACTION_ZOOM_NORMAL = "GearyZoomNormal"; - public const string ACTION_MARK_AS_MENU = "GearyMarkAsMenuButton"; - public const string ACTION_MARK_AS_READ = "GearyMarkAsRead"; - public const string ACTION_MARK_AS_UNREAD = "GearyMarkAsUnread"; - public const string ACTION_MARK_AS_STARRED = "GearyMarkAsStarred"; - public const string ACTION_MARK_AS_UNSTARRED = "GearyMarkAsUnStarred"; - public const string ACTION_MARK_AS_SPAM = "GearyMarkAsSpam"; - public const string ACTION_COPY_MENU = "GearyCopyMenuButton"; - public const string ACTION_MOVE_MENU = "GearyMoveMenuButton"; - public const string ACTION_SEARCH = "GearySearch"; - public const string ACTION_CONVERSATION_LIST = "GearyConversationList"; - public const string ACTION_TOGGLE_SEARCH = "GearyToggleSearch"; - public const string ACTION_TOGGLE_FIND = "GearyToggleFind"; + public const string ACTION_REPLY_TO_MESSAGE = "reply-to-message"; + public const string ACTION_REPLY_ALL_MESSAGE = "reply-all-message"; + public const string ACTION_FORWARD_MESSAGE = "forward-message"; + public const string ACTION_ARCHIVE_CONVERSATION = "archive-conv"; + public const string ACTION_TRASH_CONVERSATION = "trash-conv"; + public const string ACTION_DELETE_CONVERSATION = "delete-conv"; + public const string ACTION_EMPTY_SPAM = "empty-spam"; + public const string ACTION_EMPTY_TRASH = "empty-trash"; + public const string ACTION_FIND_IN_CONVERSATION = "conv-find"; + public const string ACTION_ZOOM = "zoom"; + public const string ACTION_SHOW_MARK_MENU = "mark-message-menu"; + public const string ACTION_MARK_AS_READ = "mark-message-read"; + public const string ACTION_MARK_AS_UNREAD = "mark-message-unread"; + public const string ACTION_MARK_AS_STARRED = "mark-message-starred"; + public const string ACTION_MARK_AS_UNSTARRED = "mark-message-unstarred"; + public const string ACTION_MARK_AS_SPAM = "mark-message-spam"; + public const string ACTION_MARK_AS_NOT_SPAM = "mark-message-not-spam"; + public const string ACTION_COPY_MENU = "show-copy-menu"; + public const string ACTION_MOVE_MENU = "show-move-menu"; + public const string ACTION_SEARCH = "search-conv"; + public const string ACTION_CONVERSATION_LIST = "focus-conv-list"; + public const string ACTION_TOGGLE_SEARCH = "toggle-search"; + public const string ACTION_TOGGLE_FIND = "toggle-find"; + // Properties public const string PROP_CURRENT_CONVERSATION ="current-conversations"; - + public const string PROP_SELECTED_CONVERSATIONS ="selected-conversations"; + public const int MIN_CONVERSATION_COUNT = 50; - - private const string DELETE_CONVERSATION_LABEL = _("Delete conversation"); - private const string DELETE_CONVERSATION_TOOLTIP_SINGLE = _("Delete conversation (Shift+Delete)"); - private const string DELETE_CONVERSATION_TOOLTIP_MULTIPLE = _("Delete conversations (Shift+Delete)"); - private const string DELETE_CONVERSATION_ICON_NAME = "edit-delete-symbolic"; - - // This refers to the action ("move email to the trash"), not the Trash folder itself - private const string TRASH_CONVERSATION_TOOLTIP_SINGLE = _("Move conversation to Trash (Delete, Backspace)"); - private const string TRASH_CONVERSATION_TOOLTIP_MULTIPLE = _("Move conversations to Trash (Delete, Backspace)"); - private const string TRASH_CONVERSATION_ICON_NAME = "user-trash-symbolic"; - - // This refers to the action ("archive an email"), not the Archive folder itself - private const string ARCHIVE_CONVERSATION_LABEL = _("_Archive"); - private const string ARCHIVE_CONVERSATION_TOOLTIP_SINGLE = _("Archive conversation (A)"); - private const string ARCHIVE_CONVERSATION_TOOLTIP_MULTIPLE = _("Archive conversations (A)"); - private const string ARCHIVE_CONVERSATION_ICON_NAME = "mail-archive-symbolic"; - - private const string MARK_AS_SPAM_LABEL = _("Mark as S_pam"); - private const string MARK_AS_NOT_SPAM_LABEL = _("Mark as not S_pam"); - - private const string MARK_MESSAGE_MENU_TOOLTIP_SINGLE = _("Mark conversation"); - private const string MARK_MESSAGE_MENU_TOOLTIP_MULTIPLE = _("Mark conversations"); - private const string LABEL_MESSAGE_TOOLTIP_SINGLE = _("Add label to conversation"); - private const string LABEL_MESSAGE_TOOLTIP_MULTIPLE = _("Add label to conversations"); - private const string MOVE_MESSAGE_TOOLTIP_SINGLE = _("Move conversation"); - private const string MOVE_MESSAGE_TOOLTIP_MULTIPLE = _("Move conversations"); - + private const int SELECT_FOLDER_TIMEOUT_USEC = 100 * 1000; - + private const string PROP_ATTEMPT_OPEN_ACCOUNT = "attempt-open-account"; + private const uint MAX_AUTH_ATTEMPTS = 3; + + private static string untitled_file_name; + + + static construct { + // Translators: File name used in save chooser when saving + // attachments that do not otherwise have a name. + GearyController.untitled_file_name = _("Untitled"); + } + + + internal class AccountContext : Geary.BaseObject { + + public Geary.Account account { get; private set; } + public Geary.Folder? inbox = null; + public Geary.App.EmailStore store { get; private set; } + + public bool authentication_failed = false; + public bool authentication_prompting = false; + public uint authentication_attempts = 0; + + public bool tls_validation_failed = false; + public bool tls_validation_prompting = false; + + public Cancellable cancellable { get; private set; default = new Cancellable(); } + + public AccountContext(Geary.Account account) { + this.account = account; + this.store = new Geary.App.EmailStore(account); + } + + public Geary.Account.Status get_effective_status() { + Geary.Account.Status current = this.account.current_status; + Geary.Account.Status effective = 0; + if (current.is_online()) { + effective |= ONLINE; + } + if (current.has_service_problem()) { + // Only retain this flag if the problem isn't auth or + // cert related, that is handled elsewhere. + Geary.ClientService.Status incoming = + account.incoming.current_status; + Geary.ClientService.Status outgoing = + account.outgoing.current_status; + if (incoming != AUTHENTICATION_FAILED && + incoming != TLS_VALIDATION_FAILED && + outgoing != AUTHENTICATION_FAILED && + outgoing != TLS_VALIDATION_FAILED) { + effective |= SERVICE_PROBLEM; + } + } + return effective; + } + + } + + public weak GearyApplication application { get; private set; } // circular ref - public MainWindow main_window { get; private set; } - + public Accounts.Manager? account_manager { get; private set; default = null; } + + /** Application-wide {@link Application.CertificateManager} instance. */ + public Application.CertificateManager? certificate_manager { + get; private set; default = null; + } + + public MainWindow? main_window { get; private set; default = null; } + public Geary.App.ConversationMonitor? current_conversations { get; private set; default = null; } - + public AutostartManager? autostart_manager { get; private set; default = null; } - - public LoginDialog? login_dialog { get; private set; default = null; } - public Soup.Session? avatar_session { get; private set; default = null; } - private Soup.Cache? avatar_cache = null; + public Application.AvatarStore? avatar_store { + get; private set; default = null; + } private Geary.Account? current_account = null; - private Gee.HashMap email_stores - = new Gee.HashMap(); - private Gee.HashMap inboxes - = new Gee.HashMap(); + private Gee.Map accounts = + new Gee.HashMap(); + + // Created when controller is opened, cancelled and nulled out + // when closed. + private GLib.Cancellable? open_cancellable = null; + private Geary.Folder? current_folder = null; private Cancellable cancellable_folder = new Cancellable(); private Cancellable cancellable_search = new Cancellable(); private Cancellable cancellable_open_account = new Cancellable(); private Cancellable cancellable_context_dependent_buttons = new Cancellable(); - private Gee.HashMap inbox_cancellables - = new Gee.HashMap(); + private ContactListStoreCache contact_list_store_cache = new ContactListStoreCache(); private Gee.Set selected_conversations = new Gee.HashSet(); private Geary.App.Conversation? last_deleted_conversation = null; private Gee.LinkedList composer_widgets = new Gee.LinkedList(); @@ -119,32 +148,61 @@ private uint select_folder_timeout_id = 0; private int64 next_folder_select_allowed_usec = 0; private Geary.Nonblocking.Mutex select_folder_mutex = new Geary.Nonblocking.Mutex(); - private Geary.Account? account_to_select = null; private Geary.Folder? previous_non_search_folder = null; private UpgradeDialog upgrade_dialog; private Gee.List pending_mailtos = new Gee.ArrayList(); - private Geary.Nonblocking.Mutex untrusted_host_prompt_mutex = new Geary.Nonblocking.Mutex(); - private Gee.HashSet validating_endpoints = new Gee.HashSet(); + + private uint operation_count = 0; private Geary.Revokable? revokable = null; - + // List of windows we're waiting to close before Geary closes. private Gee.List waiting_to_close = new Gee.ArrayList(); - + + private const ActionEntry[] win_action_entries = { + {GearyApplication.ACTION_CLOSE, on_close }, + {GearyApplication.ACTION_UNDO, on_revoke }, + + {ACTION_CONVERSATION_LIST, on_conversation_list }, + {ACTION_FIND_IN_CONVERSATION, on_find_in_conversation_action }, + {ACTION_SEARCH, on_search_activated }, + {ACTION_EMPTY_SPAM, on_empty_spam }, + {ACTION_EMPTY_TRASH, on_empty_trash }, + // Message actions + {ACTION_REPLY_TO_MESSAGE, on_reply_to_message_action }, + {ACTION_REPLY_ALL_MESSAGE, on_reply_all_message_action }, + {ACTION_FORWARD_MESSAGE, on_forward_message_action }, + {ACTION_ARCHIVE_CONVERSATION, on_archive_conversation }, + {ACTION_TRASH_CONVERSATION, on_trash_conversation }, + {ACTION_DELETE_CONVERSATION, on_delete_conversation }, + {ACTION_COPY_MENU, on_show_copy_menu }, + {ACTION_MOVE_MENU, on_show_move_menu }, + // Message marking actions + {ACTION_SHOW_MARK_MENU, on_show_mark_menu }, + {ACTION_MARK_AS_READ, on_mark_as_read }, + {ACTION_MARK_AS_UNREAD, on_mark_as_unread }, + {ACTION_MARK_AS_STARRED, on_mark_as_starred }, + {ACTION_MARK_AS_UNSTARRED, on_mark_as_unstarred }, + {ACTION_MARK_AS_SPAM, on_mark_as_spam_toggle }, + {ACTION_MARK_AS_NOT_SPAM, on_mark_as_spam_toggle }, + // Message viewer + {ACTION_ZOOM, on_zoom, "s" }, + }; + /** * Fired when the currently selected account has changed. */ public signal void account_selected(Geary.Account? account); - + /** * Fired when the currently selected folder has changed. */ public signal void folder_selected(Geary.Folder? folder); - + /** * Fired when the number of conversations changes. */ public signal void conversation_count_changed(int count); - + /** * Fired when the search text is changed according to the controller. This accounts * for a brief typmatic delay. @@ -165,7 +223,9 @@ /** * Starts the controller and brings up Geary. */ - public async void open_async() { + public async void open_async(GLib.Cancellable? cancellable) { + Geary.Engine engine = this.application.engine; + // This initializes the IconFactory, important to do before // the actions are created (as they refer to some of Geary's // custom icons) @@ -173,9 +233,7 @@ apply_app_menu_fix(); - // Setup actions. - setup_actions(); - this.application.load_ui_resource("accelerators.ui"); + this.open_cancellable = new GLib.Cancellable(); // Listen for attempts to close the application. this.application.exiting.connect(on_application_exiting); @@ -192,42 +250,39 @@ Args.log_debug ); try { - ClientWebView.load_scripts(); - ComposerWebView.load_resources(); - ConversationWebView.load_resources( + ClientWebView.load_resources( this.application.get_user_config_directory() ); + ComposerWebView.load_resources(); + ConversationWebView.load_resources(); + Accounts.SignatureWebView.load_resources(); } catch (Error err) { error("Error loading web resources: %s", err.message); } - // Use a global avatar session because a cache must be used - // per-session, and we don't want to have to load the cache - // for each conversation load. - File avatar_cache_dir = this.application.get_user_cache_directory() - .get_child("avatars"); - this.avatar_cache = new Soup.Cache( - avatar_cache_dir.get_path(), - Soup.CacheType.SINGLE_USER - ); - this.avatar_cache.load(); - this.avatar_cache.set_max_size(10 * 1024 * 1024); // 4MB - this.avatar_session = new Soup.Session.with_options( - Soup.SESSION_USER_AGENT, "Geary/" + GearyApplication.VERSION - ); - this.avatar_session.add_feature(avatar_cache); + Folks.IndividualAggregator individuals = + Folks.IndividualAggregator.dup(); + if (!individuals.is_prepared) { + try { + yield individuals.prepare(); + } catch (GLib.Error err) { + error("Error preparing Folks: %s", err.message); + } + } + this.avatar_store = new Application.AvatarStore(individuals); // Create the main window (must be done after creating actions.) main_window = new MainWindow(this.application); + main_window.retry_service_problem.connect(on_retry_service_problem); main_window.on_shift_key.connect(on_shift_key); main_window.notify["has-toplevel-focus"].connect(on_has_toplevel_focus); - + + setup_actions(); + enable_message_buttons(false); - - Geary.Engine.instance.account_available.connect(on_account_available); - Geary.Engine.instance.account_unavailable.connect(on_account_unavailable); - Geary.Engine.instance.untrusted_host.connect(on_untrusted_host); - + + engine.account_available.connect(on_account_available); + // Connect to various UI signals. main_window.conversation_list_view.conversations_selected.connect(on_conversations_selected); main_window.conversation_list_view.conversation_activated.connect(on_conversation_activated); @@ -251,21 +306,19 @@ new_messages_indicator.application_activated.connect(on_indicator_activated_application); new_messages_indicator.composer_activated.connect(on_indicator_activated_composer); new_messages_indicator.inbox_activated.connect(on_indicator_activated_inbox); - + unity_launcher = new UnityLauncher(new_messages_monitor); - - // libnotify - libnotify = new Libnotify(new_messages_monitor); - libnotify.invoked.connect(on_libnotify_invoked); - - // This is fired after the accounts are ready. - Geary.Engine.instance.opened.connect(on_engine_opened); + + this.libnotify = new Libnotify( + this.new_messages_monitor, this.avatar_store + ); + this.libnotify.invoked.connect(on_libnotify_invoked); this.main_window.conversation_list_view.grab_focus(); // instantiate here to ensure that Config is initialized and ready - autostart_manager = new AutostartManager(); - + this.autostart_manager = new AutostartManager(this.application); + // initialize revokable save_revokable(null, null); @@ -277,30 +330,82 @@ error("Error migrating configuration directories: %s", e.message); } - // Start Geary. + // Hook up cert, accounts and credentials machinery + + this.certificate_manager = yield new Application.CertificateManager( + this.application.get_user_data_directory().get_child("pinned-certs"), + cancellable + ); + + SecretMediator? libsecret = null; + try { + libsecret = yield new SecretMediator(cancellable); + } catch (GLib.Error err) { + error("Error opening libsecret: %s", err.message); + } + + this.account_manager = new Accounts.Manager( + libsecret, + this.application.get_user_config_directory(), + this.application.get_user_data_directory() + ); + this.account_manager.account_added.connect( + on_account_added + ); + this.account_manager.account_status_changed.connect( + on_account_status_changed + ); + this.account_manager.account_removed.connect( + on_account_removed + ); + this.account_manager.report_problem.connect( + on_report_problem + ); + + try { + yield this.account_manager.connect_goa(cancellable); + } catch (GLib.Error err) { + warning("Error opening GOA: %s", err.message); + } + + // Start the engine and load our accounts try { - yield Geary.Engine.instance.open_async( - this.application.get_user_config_directory(), - this.application.get_user_data_directory(), - this.application.get_resource_directory(), - new SecretMediator() + yield engine.open_async( + this.application.get_resource_directory(), cancellable ); - if (Geary.Engine.instance.get_accounts().size == 0) { - create_account(); + yield this.account_manager.load_accounts(cancellable); + if (engine.get_accounts().size == 0) { + this.application.show_accounts(); + if (engine.get_accounts().size == 0) { + // User cancelled without creating an account, so + // nothing else to do but exit. + this.application.quit(); + } } } catch (Error e) { - error("Error opening Geary.Engine instance: %s", e.message); + warning("Error opening Geary.Engine instance: %s", e.message); } + + // Expunge any deleted accounts in the background, so we're + // not blocking the app continuing to open. + this.expunge_accounts.begin(); } - + /** * At the moment, this is non-reversible, i.e. once closed a GearyController cannot be * re-opened. */ public async void close_async() { + // Cancel internal processes early so they don't block + // shutdown + this.open_cancellable.cancel(); + this.open_cancellable = null; + Geary.Engine.instance.account_available.disconnect(on_account_available); - Geary.Engine.instance.account_unavailable.disconnect(on_account_unavailable); - Geary.Engine.instance.untrusted_host.disconnect(on_untrusted_host); + + // Release folder and conversations in the main window + on_conversations_selected(new Gee.HashSet()); + on_folder_selected(null); // Disconnect from various UI signals. main_window.conversation_list_view.conversations_selected.disconnect(on_conversations_selected); @@ -319,66 +424,81 @@ // hide window while shutting down, as this can take a few seconds under certain conditions main_window.hide(); - + + // Release monitoring early so held resources can be freed up + this.libnotify = null; + this.new_messages_indicator = null; + this.unity_launcher = null; + this.new_messages_monitor.clear_folders(); + this.new_messages_monitor = null; + // drop the Revokable, which will commit it if necessary save_revokable(null, null); - - // close the ConversationMonitor - try { - if (current_conversations != null) { - debug("Stopping conversation monitor for %s...", current_conversations.folder.to_string()); - - bool closing = yield current_conversations.stop_monitoring_async(null); - - // If not an Inbox, wait for it to close so all pending operations are flushed - if (closing) { - debug("Waiting for %s to close...", current_conversations.folder.to_string()); - yield current_conversations.folder.wait_for_close_async(null); - } - - debug("Stopped conversation monitor for %s", current_conversations.folder.to_string()); - } - } catch (Error err) { - message("Error closing conversation monitor %s at shutdown: %s", - current_conversations.folder.to_string(), err.message); - } finally { - current_conversations = null; - } - - // close all Inboxes - foreach (Geary.Folder inbox in inboxes.values) { + + this.cancellable_open_account.cancel(); + + // Close the ConversationMonitor + if (current_conversations != null) { + debug("Stopping conversation monitor for %s...", + this.current_conversations.base_folder.to_string()); try { - debug("Closing %s...", inbox.to_string()); - - // close and wait for all pending operations to be flushed - yield inbox.close_async(null); - - debug("Waiting for %s to close completely...", inbox.to_string()); - - yield inbox.wait_for_close_async(null); - - debug("Closed %s", inbox.to_string()); + yield this.current_conversations.stop_monitoring_async(null); } catch (Error err) { - message("Error closing Inbox %s at shutdown: %s", inbox.to_string(), err.message); + debug( + "Error closing conversation monitor %s at shutdown: %s", + this.current_conversations.base_folder.to_string(), + err.message + ); } + + this.current_conversations = null; } - - // close all Accounts - foreach (Geary.Account account in email_stores.keys) { - try { - debug("Closing account %s", account.to_string()); - yield account.close_async(null); - debug("Closed account %s", account.to_string()); - } catch (Error err) { - message("Error closing account %s at shutdown: %s", account.to_string(), err.message); + + // Create an array of known accounts so the loops below do not + // explode if accounts are removed while iterating. + AccountContext[] accounts = this.accounts.values.to_array(); + + // Close all inboxes. Launch these in parallel first so we're + // not wasting time waiting for each one to close. The account + // will wait around for them to actually close. + foreach (AccountContext context in accounts) { + Geary.Folder? inbox = context.inbox; + if (inbox != null) { + debug("Closing inbox: %s...", inbox.to_string()); + inbox.close_async.begin(null, (obj, ret) => { + try { + inbox.close_async.end(ret); + } catch (Error err) { + debug( + "Error closing Inbox %s at shutdown: %s", + inbox.to_string(), err.message + ); + } + }); + context.inbox = null; } } - - main_window.destroy(); - debug("Flushing avatar cache..."); - avatar_cache.flush(); - avatar_cache.dump(); + // Close all Accounts. Again, this is done in parallel to + // minimise time taken to close, but here use a barrier to + // wait for all to actually finish closing. + Geary.Nonblocking.CountingSemaphore close_barrier = + new Geary.Nonblocking.CountingSemaphore(null); + foreach (AccountContext context in accounts) { + close_barrier.acquire(); + this.close_account.begin( + context.account.information, + (obj, ret) => { + this.close_account.end(ret); + close_barrier.blind_notify(); + } + ); + } + try { + yield close_barrier.wait_async(); + } catch (Error err) { + debug("Error waiting at shutdown barrier: %s", err.message); + } // Turn off the lights and lock the door behind you try { @@ -388,6 +508,43 @@ } catch (Error err) { message("Error closing Geary Engine instance: %s", err.message); } + + this.account_manager.account_added.disconnect( + on_account_added + ); + this.account_manager.account_status_changed.disconnect( + on_account_status_changed + ); + this.account_manager.account_removed.disconnect( + on_account_removed + ); + this.account_manager = null; + + this.application.remove_window(this.main_window); + this.main_window.destroy(); + this.main_window = null; + + this.upgrade_dialog = null; + + this.current_account = null; + this.current_folder = null; + + this.previous_non_search_folder = null; + + this.selected_conversations = new Gee.HashSet(); + this.last_deleted_conversation = null; + + this.pending_mailtos.clear(); + this.composer_widgets.clear(); + this.waiting_to_close.clear(); + + this.autostart_manager = null; + + this.avatar_store.close(); + this.avatar_store = null; + + + debug("Closed GearyController"); } /** @@ -409,9 +566,15 @@ } } - private void add_accelerator(string accelerator, string action) { - GtkUtil.add_accelerator(this.application.ui_manager, this.application.actions, - accelerator, action); + /** Expunges removed accounts while the controller remains open. */ + internal async void expunge_accounts() { + try { + yield this.account_manager.expunge_accounts(this.open_cancellable); + } catch (GLib.Error err) { + report_problem( + new Geary.ProblemReport(Geary.ProblemType.GENERIC_ERROR, err) + ); + } } // Fix for clients having both: @@ -423,13 +586,10 @@ if (settings == null) { warning("Couldn't fetch Gtk default settings"); - return ; + return; } - string? decoration_layout = settings.gtk_decoration_layout; - - if (decoration_layout == null) decoration_layout = ""; - + string decoration_layout = settings.gtk_decoration_layout ?? ""; if (!decoration_layout.contains("menu")) { string prefix = "menu:"; if (decoration_layout.contains(":")) { @@ -439,655 +599,301 @@ } } - private Gtk.ActionEntry[] create_actions() { - Gtk.ActionEntry[] entries = new Gtk.ActionEntry[0]; + private void setup_actions() { + this.main_window.add_action_entries(win_action_entries, this); - Gtk.ActionEntry mark_menu = { ACTION_MARK_AS_MENU, null, TRANSLATABLE, null, _("Mark conversation"), - on_show_mark_menu }; - mark_menu.label = _("_Mark as…"); - mark_menu.tooltip = MARK_MESSAGE_MENU_TOOLTIP_SINGLE; - entries += mark_menu; - - Gtk.ActionEntry mark_read = { ACTION_MARK_AS_READ, "mail-mark-read", TRANSLATABLE, "I", - null, on_mark_as_read }; - mark_read.label = _("Mark as _Read"); - entries += mark_read; - add_accelerator("I", ACTION_MARK_AS_READ); - - Gtk.ActionEntry mark_unread = { ACTION_MARK_AS_UNREAD, "mail-mark-unread", TRANSLATABLE, - "U", null, on_mark_as_unread }; - mark_unread.label = _("Mark as _Unread"); - entries += mark_unread; - add_accelerator("U", ACTION_MARK_AS_UNREAD); - - Gtk.ActionEntry mark_starred = { ACTION_MARK_AS_STARRED, "star-symbolic", TRANSLATABLE, "S", null, - on_mark_as_starred }; - mark_starred.label = _("_Star"); - entries += mark_starred; - - Gtk.ActionEntry mark_unstarred = { ACTION_MARK_AS_UNSTARRED, "non-starred", TRANSLATABLE, "D", - null, on_mark_as_unstarred }; - mark_unstarred.label = _("U_nstar"); - entries += mark_unstarred; - - Gtk.ActionEntry mark_spam = { ACTION_MARK_AS_SPAM, null, TRANSLATABLE, "J", null, - on_mark_as_spam }; - mark_spam.label = MARK_AS_SPAM_LABEL; - entries += mark_spam; - add_accelerator("exclam", ACTION_MARK_AS_SPAM); // Exclamation mark (!) - - Gtk.ActionEntry copy_menu = { ACTION_COPY_MENU, null, TRANSLATABLE, "L", - _("Add label"), null }; - copy_menu.label = _("_Label"); - entries += copy_menu; - - Gtk.ActionEntry move_menu = { ACTION_MOVE_MENU, null, TRANSLATABLE, "M", _("Move conversation"), null }; - move_menu.label = _("_Move"); - entries += move_menu; - - Gtk.ActionEntry new_message = { ACTION_NEW_MESSAGE, null, null, "N", - _("Compose new message (Ctrl+N, N)"), on_new_message }; - entries += new_message; - add_accelerator("N", ACTION_NEW_MESSAGE); - - Gtk.ActionEntry reply_to_message = { ACTION_REPLY_TO_MESSAGE, null, _("_Reply"), "R", - _("Reply (Ctrl+R, R)"), on_reply_to_message_action }; - entries += reply_to_message; - add_accelerator("R", ACTION_REPLY_TO_MESSAGE); - - Gtk.ActionEntry reply_all_message = { ACTION_REPLY_ALL_MESSAGE, null, _("R_eply All"), - "R", _("Reply all (Ctrl+Shift+R, Shift+R)"), - on_reply_all_message_action }; - entries += reply_all_message; - add_accelerator("R", ACTION_REPLY_ALL_MESSAGE); - - Gtk.ActionEntry forward_message = { ACTION_FORWARD_MESSAGE, null, _("_Forward"), "L", - _("Forward (Ctrl+L, F)"), on_forward_message_action }; - entries += forward_message; - add_accelerator("F", ACTION_FORWARD_MESSAGE); - - Gtk.ActionEntry find_in_conversation = { ACTION_FIND_IN_CONVERSATION, null, null, "F", - null, on_find_in_conversation_action }; - entries += find_in_conversation; - add_accelerator("slash", ACTION_FIND_IN_CONVERSATION); - - Gtk.ActionEntry archive_conversation = { ACTION_ARCHIVE_CONVERSATION, ARCHIVE_CONVERSATION_ICON_NAME, - ARCHIVE_CONVERSATION_LABEL, "A", null, on_archive_conversation }; - archive_conversation.tooltip = ARCHIVE_CONVERSATION_TOOLTIP_SINGLE; - entries += archive_conversation; - - // although this action changes according to the account's capabilities, set to Delete - // until they're known so the "translatable" string doesn't first appear - Gtk.ActionEntry trash_conversation = { ACTION_TRASH_CONVERSATION, TRASH_CONVERSATION_ICON_NAME, - null, "Delete", null, on_trash_conversation }; - trash_conversation.tooltip = TRASH_CONVERSATION_TOOLTIP_SINGLE; - entries += trash_conversation; - add_accelerator("BackSpace", ACTION_TRASH_CONVERSATION); - - Gtk.ActionEntry delete_conversation = { ACTION_DELETE_CONVERSATION, DELETE_CONVERSATION_ICON_NAME, - null, "Delete", null, on_delete_conversation }; - delete_conversation.label = DELETE_CONVERSATION_LABEL; - delete_conversation.tooltip = DELETE_CONVERSATION_TOOLTIP_SINGLE; - entries += delete_conversation; - add_accelerator("BackSpace", ACTION_DELETE_CONVERSATION); - - Gtk.ActionEntry empty_spam = { ACTION_EMPTY_SPAM, null, null, null, null, on_empty_spam }; - empty_spam.label = _("Empty _Spam…"); - entries += empty_spam; - - Gtk.ActionEntry empty_trash = { ACTION_EMPTY_TRASH, null, null, null, null, on_empty_trash }; - empty_trash.label = _("Empty _Trash…"); - entries += empty_trash; - - Gtk.ActionEntry undo = { ACTION_UNDO, "edit-undo-symbolic", null, "Z", null, on_revoke }; - entries += undo; - - Gtk.ActionEntry zoom_in = { ACTION_ZOOM_IN, null, null, "equal", - null, on_zoom_in }; - entries += zoom_in; - add_accelerator("equal", ACTION_ZOOM_IN); - - Gtk.ActionEntry zoom_out = { ACTION_ZOOM_OUT, null, null, "minus", - null, on_zoom_out }; - entries += zoom_out; - add_accelerator("minus", ACTION_ZOOM_OUT); - - Gtk.ActionEntry zoom_normal = { ACTION_ZOOM_NORMAL, null, null, "0", - null, on_zoom_normal }; - entries += zoom_normal; - add_accelerator("0", ACTION_ZOOM_NORMAL); - - Gtk.ActionEntry search = { - ACTION_SEARCH, null, null, "S", null, - () => { show_search_bar(); } - }; - entries += search; - - Gtk.ActionEntry conversation_list = { ACTION_CONVERSATION_LIST, null, null, "B", null, on_conversation_list }; - entries += conversation_list; - - // No callback is connected, since we bind the toggle button to the search bar visibility - Gtk.ActionEntry toggle_search = { ACTION_TOGGLE_SEARCH, null, null, null, - _("Toggle search bar"), null }; - entries += toggle_search; - - // No callback is connected, since we bind the toggle button to the find bar visibility - Gtk.ActionEntry toggle_find = { ACTION_TOGGLE_FIND, null, null, null, - _("Toggle find bar"), null }; - entries += toggle_find; - - return entries; - } - - private Gtk.ToggleActionEntry[] create_toggle_actions() { - Gtk.ToggleActionEntry[] entries = new Gtk.ToggleActionEntry[0]; - - return entries; + add_window_accelerators(ACTION_MARK_AS_READ, { "I", "I" }); + add_window_accelerators(ACTION_MARK_AS_UNREAD, { "U", "U" }); + add_window_accelerators(ACTION_MARK_AS_STARRED, { "S" }); + add_window_accelerators(ACTION_MARK_AS_UNSTARRED, { "D" }); + add_window_accelerators(ACTION_MARK_AS_SPAM, { "J", "exclam" }); // Exclamation mark (!) + add_window_accelerators(ACTION_MARK_AS_NOT_SPAM, { "J", "exclam" }); + add_window_accelerators(ACTION_COPY_MENU, { "L" }); + add_window_accelerators(ACTION_MOVE_MENU, { "M" }); + add_window_accelerators(ACTION_REPLY_TO_MESSAGE, { "R", "R" }); + add_window_accelerators(ACTION_REPLY_ALL_MESSAGE, { "R", "R" }); + add_window_accelerators(ACTION_FORWARD_MESSAGE, { "L", "F" }); + add_window_accelerators(ACTION_FIND_IN_CONVERSATION, { "F", "slash" }); + add_window_accelerators(ACTION_ARCHIVE_CONVERSATION, { "A" }); + add_window_accelerators(ACTION_TRASH_CONVERSATION, { "Delete", "BackSpace" }); + add_window_accelerators(ACTION_DELETE_CONVERSATION, { "Delete", "BackSpace" }); + add_window_accelerators(ACTION_ZOOM+("('in')"), { "equal", "equal" }); + add_window_accelerators(ACTION_ZOOM+("('out')"), { "minus", "minus" }); + add_window_accelerators(ACTION_ZOOM+("('normal')"), { "0", "0" }); + add_window_accelerators(ACTION_SEARCH, { "S" }); + add_window_accelerators(ACTION_CONVERSATION_LIST, { "B" }); } - - private void setup_actions() { - const string[] important_actions = { - ACTION_NEW_MESSAGE, - ACTION_REPLY_TO_MESSAGE, - ACTION_REPLY_ALL_MESSAGE, - ACTION_FORWARD_MESSAGE, - ACTION_ARCHIVE_CONVERSATION, - ACTION_TRASH_CONVERSATION, - ACTION_DELETE_CONVERSATION, - }; - Gtk.ActionGroup action_group = this.application.actions; - - Gtk.ActionEntry[] action_entries = create_actions(); - action_group.add_actions(action_entries, this); - foreach (Gtk.ActionEntry e in action_entries) { - Gtk.Action action = action_group.get_action(e.name); - assert(action != null); - - if (e.name in important_actions) - action.is_important = true; - } - - Gtk.ToggleActionEntry[] toggle_action_entries = create_toggle_actions(); - action_group.add_toggle_actions(toggle_action_entries, this); - this.application.ui_manager.insert_action_group(action_group, 0); + + private void add_window_accelerators(string action, string[] accelerators, Variant? param = null) { + this.application.set_accels_for_action("win."+action, accelerators); } private void open_account(Geary.Account account) { + account.information.authentication_failure.connect( + on_authentication_failure + ); + account.information.untrusted_host.connect(on_untrusted_host); + account.notify["current-status"].connect( + on_account_status_notify + ); account.report_problem.connect(on_report_problem); - account.email_removed.connect(on_account_email_removed); connect_account_async.begin(account, cancellable_open_account); + + ContactListStore list_store = this.contact_list_store_cache.create(account.get_contact_store()); + account.contacts_loaded.connect(list_store.set_sort_function); } - - private void close_account(Geary.Account account) { - account.report_problem.disconnect(on_report_problem); - account.email_removed.disconnect(on_account_email_removed); - disconnect_account_async.begin(account); - } - - private Geary.Account get_account_instance(Geary.AccountInformation account_information) { - try { - return Geary.Engine.instance.get_account_instance(account_information); - } catch (Error e) { - error("Error creating account instance: %s", e.message); + + private async void close_account(Geary.AccountInformation config) { + AccountContext? context = this.accounts.get(config); + if (context != null) { + Geary.Account account = context.account; + Geary.ContactStore contact_store = account.get_contact_store(); + ContactListStore list_store = + this.contact_list_store_cache.get(contact_store); + + account.contacts_loaded.disconnect(list_store.set_sort_function); + this.contact_list_store_cache.unset(contact_store); + + if (this.current_account == account) { + this.current_account = null; + + previous_non_search_folder = null; + main_window.search_bar.set_search_text(""); // Reset search. + + cancel_folder(); + } + + // Stop updating status and showing errors when closing + // the account - the user doesn't care any more + account.report_problem.disconnect(on_report_problem); + account.information.authentication_failure.disconnect( + on_authentication_failure + ); + account.information.untrusted_host.disconnect(on_untrusted_host); + account.notify["current-status"].disconnect( + on_account_status_notify + ); + + yield disconnect_account_async(context); } } - - private void on_account_available(Geary.AccountInformation account_information) { - Geary.Account account = get_account_instance(account_information); - - upgrade_dialog.add_account(account, cancellable_open_account); - open_account(account); - } - - private void on_account_unavailable(Geary.AccountInformation account_information) { - close_account(get_account_instance(account_information)); - } - - private void on_untrusted_host(Geary.AccountInformation account_information, - Geary.Endpoint endpoint, Geary.Endpoint.SecurityType security, TlsConnection cx, - Geary.Service service) { - prompt_untrusted_host_async.begin(account_information, endpoint, security, cx, service); - } - - private async void prompt_untrusted_host_async(Geary.AccountInformation account_information, - Geary.Endpoint endpoint, Geary.Endpoint.SecurityType security, TlsConnection cx, - Geary.Service service) { - // use a mutex to prevent multiple dialogs popping up at the same time - int token = Geary.Nonblocking.Mutex.INVALID_TOKEN; - try { - token = yield untrusted_host_prompt_mutex.claim_async(); - } catch (Error err) { - message("Unable to lock mutex to prompt user about invalid certificate: %s", err.message); - - return; - } - - yield locked_prompt_untrusted_host_async(account_information, endpoint, security, cx, - service); - - try { - untrusted_host_prompt_mutex.release(ref token); - } catch (Error err) { - message("Unable to release mutex after prompting user about invalid certificate: %s", - err.message); + + private void report_problem(Geary.ProblemReport report) { + debug("Problem reported: %s", report.to_string()); + + if (report.error == null || + !(report.error.thrown is IOError.CANCELLED)) { + MainWindowInfoBar info_bar = new MainWindowInfoBar.for_problem(report); + info_bar.retry.connect(on_retry_problem); + this.main_window.show_infobar(info_bar); } } - - private static void get_gcr_params(Geary.Endpoint endpoint, out Gcr.Certificate cert, - out string peer) { - cert = new Gcr.SimpleCertificate(endpoint.untrusted_certificate.certificate.data); - peer = "%s:%u".printf(endpoint.remote_address.hostname, endpoint.remote_address.port); - } - - private async void locked_prompt_untrusted_host_async(Geary.AccountInformation account_information, - Geary.Endpoint endpoint, Geary.Endpoint.SecurityType security, TlsConnection cx, - Geary.Service service) { - // possible while waiting on mutex that this endpoint became trusted or untrusted - if (endpoint.trust_untrusted_host != Geary.Trillian.UNKNOWN) - return; - - // get GCR parameters - Gcr.Certificate cert; - string peer; - get_gcr_params(endpoint, out cert, out peer); - - // Geary allows for user to auto-revoke all questionable server certificates without - // digging around in a keyring/pk manager - if (Args.revoke_certs) { - debug("Auto-revoking certificate for %s...", peer); - - try { - gcr_trust_remove_pinned_certificate(cert, GCR_PURPOSE_SERVER_AUTH, peer, null); - } catch (Error err) { - message("Unable to auto-revoke server certificate for %s: %s", peer, err.message); - - // drop through, not absolutely valid to do this (might also mean certificate - // was never pinned) + + private void update_account_status() { + // Start off assuming all accounts are online and error free + // (i.e. no status issues to indicate) and proceed until + // proven incorrect. + Geary.Account.Status effective_status = ONLINE; + bool has_auth_error = false; + bool has_cert_error = false; + Geary.Account? service_problem_source = null; + foreach (AccountContext context in this.accounts.values) { + Geary.Account.Status status = context.get_effective_status(); + if (!status.is_online()) { + effective_status &= ~Geary.Account.Status.ONLINE; + } + if (status.has_service_problem()) { + effective_status |= SERVICE_PROBLEM; + if (service_problem_source == null) { + service_problem_source = context.account; + } } + has_auth_error |= context.authentication_failed; + has_cert_error |= context.tls_validation_failed; } - - // if pinned, the user has already made an exception for this server and its certificate, - // so go ahead w/o asking - try { - if (gcr_trust_is_certificate_pinned(cert, GCR_PURPOSE_SERVER_AUTH, peer, null)) { - debug("Certificate for %s is pinned, accepting connection...", peer); - - endpoint.trust_untrusted_host = Geary.Trillian.TRUE; - - return; + + foreach (Gtk.Window window in this.application.get_windows()) { + MainWindow? main = window as MainWindow; + if (main != null) { + main.update_account_status( + effective_status, + has_auth_error, + has_cert_error, + service_problem_source + ); } - } catch (Error err) { - message("Unable to check if server certificate for %s is pinned, assuming not: %s", - peer, err.message); } - - // if these are in validation, there are complex GTK and workflow issues from simply - // presenting the prompt now, so caller who connected will need to do it on their own dime - if (!validating_endpoints.contains(endpoint)) - prompt_for_untrusted_host(main_window, account_information, endpoint, service, false); - } - - private void prompt_for_untrusted_host(Gtk.Window? parent, Geary.AccountInformation account_information, - Geary.Endpoint endpoint, Geary.Service service, bool is_validation) { - CertificateWarningDialog dialog = new CertificateWarningDialog(parent, account_information, - service, endpoint.tls_validation_warnings, is_validation); - switch (dialog.run()) { - case CertificateWarningDialog.Result.TRUST: - endpoint.trust_untrusted_host = Geary.Trillian.TRUE; - break; - - case CertificateWarningDialog.Result.ALWAYS_TRUST: - endpoint.trust_untrusted_host = Geary.Trillian.TRUE; - - // get GCR parameters for pinning - Gcr.Certificate cert; - string peer; - get_gcr_params(endpoint, out cert, out peer); - - // pinning the certificate creates an exception for the next time a connection - // is attempted - debug("Pinning certificate for %s...", peer); - try { - gcr_trust_add_pinned_certificate(cert, GCR_PURPOSE_SERVER_AUTH, peer, null); - } catch (Error err) { - ErrorDialog error_dialog = new ErrorDialog(main_window, - _("Unable to store server trust exception"), err.message); - error_dialog.run(); + } + + private bool is_currently_prompting() { + return this.accounts.values.fold( + (ctx, seed) => ( + ctx.authentication_prompting | + ctx.tls_validation_prompting | + seed + ), + false + ); + } + + private async void prompt_for_password(AccountContext context, + Geary.ServiceInformation service) { + Geary.AccountInformation account = context.account.information; + bool is_incoming = (service == account.incoming); + Geary.Credentials credentials = is_incoming + ? account.incoming.credentials + : account.get_outgoing_credentials(); + + bool handled = true; + if (context.authentication_attempts > MAX_AUTH_ATTEMPTS || + credentials == null) { + // We have run out of authentication attempts or have + // been asked for creds but don't even have a login. So + // just bail out immediately and flag the account as + // needing attention. + handled = false; + } else if (this.account_manager.is_goa_account(account)) { + context.authentication_prompting = true; + try { + yield account.load_incoming_credentials(context.cancellable); + yield account.load_outgoing_credentials(context.cancellable); + } catch (GLib.Error err) { + // Bail out right away, but probably should be opening + // the GOA control panel. + handled = false; + report_problem( + new Geary.AccountProblemReport( + Geary.ProblemType.GENERIC_ERROR, + account, + err + ) + ); + } + context.authentication_prompting = false; + } else { + context.authentication_prompting = true; + this.application.present(); + PasswordDialog password_dialog = new PasswordDialog( + this.application.get_active_window(), + account, + service, + credentials + ); + if (password_dialog.run()) { + // The update the credentials for the service that the + // credentials actually came from + Geary.ServiceInformation creds_service = + (credentials == account.incoming.credentials) + ? account.incoming + : account.outgoing; + creds_service.credentials = credentials.copy_with_token( + password_dialog.password + ); + + // Update the remember password pref if changed + bool remember = password_dialog.remember_password; + if (creds_service.remember_password != remember) { + creds_service.remember_password = remember; + account.changed(); } - break; - - default: - endpoint.trust_untrusted_host = Geary.Trillian.FALSE; - - // close the account; can't go any further w/o offline mode + + SecretMediator libsecret = (SecretMediator) account.mediator; try { - if (Geary.Engine.instance.get_accounts().has_key(account_information.id)) { - Geary.Account account = Geary.Engine.instance.get_account_instance(account_information); - close_account(account); + // Update the secret using the service where the + // credentials originated, since the service forms + // part of the key's identity + if (creds_service.remember_password) { + yield libsecret.update_token( + account, creds_service, context.cancellable + ); + } else { + yield libsecret.clear_token( + account, creds_service, context.cancellable + ); } - } catch (Error err) { - message("Unable to close account due to user trust issues: %s", err.message); + } catch (GLib.IOError.CANCELLED err) { + // all good + } catch (GLib.Error err) { + report_problem( + new Geary.ServiceProblemReport( + Geary.ProblemType.GENERIC_ERROR, + account, + service, + err + ) + ); } - break; - } - } - - private void create_account() { - Geary.AccountInformation? account_information = request_account_information(null); - if (account_information != null) - do_validate_until_successful_async.begin(account_information); - } - - private async void do_validate_until_successful_async(Geary.AccountInformation account_information, - Cancellable? cancellable = null) { - Geary.AccountInformation? result = account_information; - do { - result = yield validate_or_retry_async(result, cancellable); - } while (result != null); - - if (login_dialog != null) - login_dialog.hide(); - } - - // Returns possibly modified validation results - private Geary.Engine.ValidationResult validation_check_endpoint_for_tls_warnings( - Geary.AccountInformation account_information, Geary.Service service, - Geary.Engine.ValidationResult validation_result, out bool prompted, out bool retry_required) { - prompted = false; - retry_required = false; - - // use LoginDialog for parent only if available and visible - Gtk.Window? parent; - if (login_dialog != null && login_dialog.visible) - parent = login_dialog; - else - parent = main_window; - - Geary.Endpoint endpoint = account_information.get_endpoint_for_service(service); - - // If Endpoint had unresolved TLS issues, prompt user about them - if (endpoint.tls_validation_warnings != 0 && endpoint.trust_untrusted_host != Geary.Trillian.TRUE) { - prompt_for_untrusted_host(parent, account_information, endpoint, service, true); - prompted = true; - } - - // If there are still TLS connection issues that caused the connection to fail (happens on the - // first attempt), clear those errors and retry - if (endpoint.tls_validation_warnings != 0 && endpoint.trust_untrusted_host == Geary.Trillian.TRUE) { - Geary.Engine.ValidationResult flag = (service == Geary.Service.IMAP) - ? Geary.Engine.ValidationResult.IMAP_CONNECTION_FAILED - : Geary.Engine.ValidationResult.SMTP_CONNECTION_FAILED; - - if ((validation_result & flag) != 0) { - validation_result &= ~flag; - retry_required = true; - } - } - - return validation_result; - } - - // Use after validating to see if TLS warnings were handled by the user and need to retry the - // validation; this will also modify the validation results to better indicate issues to the user - // - // Returns possibly modified validation results - public async Geary.Engine.ValidationResult validation_check_for_tls_warnings_async( - Geary.AccountInformation account_information, Geary.Engine.ValidationResult validation_result, - out bool retry_required) { - retry_required = false; - - // Because TLS warnings need cycles to process, sleep and give 'em a chance to do their - // thing ... note that the signal handler does *not* invoke the user prompt dialog when the - // login dialog is in play, so this sleep does not need to worry about user input - yield Geary.Scheduler.sleep_ms_async(100); - - // check each service for problems, prompting user each time for verification - bool imap_prompted, imap_retry_required; - validation_result = validation_check_endpoint_for_tls_warnings(account_information, - Geary.Service.IMAP, validation_result, out imap_prompted, out imap_retry_required); - - bool smtp_prompted, smtp_retry_required; - validation_result = validation_check_endpoint_for_tls_warnings(account_information, - Geary.Service.SMTP, validation_result, out smtp_prompted, out smtp_retry_required); - - // if prompted for user acceptance of bad certificates and they agreed to both, try again - if (imap_prompted && smtp_prompted - && account_information.get_imap_endpoint().is_trusted_or_never_connected - && account_information.get_smtp_endpoint().is_trusted_or_never_connected) { - retry_required = true; - } else if (validation_result == Geary.Engine.ValidationResult.OK) { - retry_required = true; - } else { - // if prompt requires retry or otherwise detected it, retry - retry_required = imap_retry_required && smtp_retry_required; - } - - return validation_result; - } - - // Returns null if we are done validating, or the revised account information if we should retry. - private async Geary.AccountInformation? validate_or_retry_async(Geary.AccountInformation account_information, - Cancellable? cancellable = null) { - Geary.Engine.ValidationResult result = yield validate_async(account_information, - Geary.Engine.ValidationOption.CHECK_CONNECTIONS, cancellable); - if (result == Geary.Engine.ValidationResult.OK) - return null; - - // check Endpoints for trust (TLS) issues - bool retry_required; - result = yield validation_check_for_tls_warnings_async(account_information, result, - out retry_required); - - // return for retry if required; check can also change validation results, in which case - // revalidate entirely to have them written out - if (retry_required) - return account_information; - - debug("Validation failed. Prompting user for revised account information"); - Geary.AccountInformation? new_account_information = - request_account_information(account_information, result); - - // If the user refused to enter account information. There is currently no way that we - // could see this--we exit in request_account_information, and the only way that an - // exit could be canceled is if there are unsaved composer windows open (which won't - // happen before an account is created). However, best to include this check for the - // future. - if (new_account_information == null) - return null; - - debug("User entered revised account information, retrying validation"); - return new_account_information; - } - - // Attempts to validate and add an account. Returns a result code indicating - // success or one or more errors. - public async Geary.Engine.ValidationResult validate_async( - Geary.AccountInformation account_information, Geary.Engine.ValidationOption options, - Cancellable? cancellable = null) { - // add Endpoints to set of validating endpoints to prevent the prompt from appearing - validating_endpoints.add(account_information.get_imap_endpoint()); - validating_endpoints.add(account_information.get_smtp_endpoint()); - - Geary.Engine.ValidationResult result = Geary.Engine.ValidationResult.OK; - try { - result = yield Geary.Engine.instance.validate_account_information_async(account_information, - options, cancellable); - } catch (Error err) { - debug("Error validating account: %s", err.message); - this.application.exit(-1); // Fatal error - return result; + context.authentication_attempts++; + } else { + // User cancelled, bail out unconditionally + handled = false; + } + context.authentication_prompting = false; } - - validating_endpoints.remove(account_information.get_imap_endpoint()); - validating_endpoints.remove(account_information.get_smtp_endpoint()); - - if (result == Geary.Engine.ValidationResult.OK) { - Geary.AccountInformation real_account_information = account_information; - if (account_information.is_copy()) { - // We have a temporary copy of the account. Find the "real" acct info object and - // copy the new data into it. - real_account_information = get_real_account_information(account_information); - real_account_information.copy_from(account_information); - } - - real_account_information.store_async.begin(cancellable); - do_update_stored_passwords_async.begin(Geary.ServiceFlag.IMAP | Geary.ServiceFlag.SMTP, - real_account_information); - - debug("Successfully validated account information"); - } - - return result; - } - - // Returns the "real" account info associated with a copy. If it's not a copy, null is returned. - public Geary.AccountInformation? get_real_account_information( - Geary.AccountInformation account_information) { - if (account_information.is_copy()) { + + if (handled) { try { - return Geary.Engine.instance.get_accounts().get(account_information.id); - } catch (Error e) { - error("Account information is out of sync: %s", e.message); + yield this.application.engine.update_account_service( + account, service, context.cancellable + ); + } catch (GLib.Error err) { + report_problem( + new Geary.ServiceProblemReport( + Geary.ProblemType.GENERIC_ERROR, + account, + service, + err + ) + ); } + } else { + context.authentication_attempts = 0; + context.authentication_failed = true; + update_account_status(); } - - return null; } - - // Prompt the user for a service, real name, username, and password, and try to start Geary. - private Geary.AccountInformation? request_account_information(Geary.AccountInformation? old_info, - Geary.Engine.ValidationResult result = Geary.Engine.ValidationResult.OK) { - Geary.AccountInformation? new_info = old_info; - if (login_dialog == null) { - // Create here so we know GTK is initialized. - login_dialog = new LoginDialog(); - } else if (!login_dialog.get_visible()) { - // If the dialog has been dismissed, exit here. - this.application.exit(); - return null; - } - - if (new_info != null) - login_dialog.set_account_information(new_info, result); - - login_dialog.present(); - for (;;) { - login_dialog.show_spinner(false); - if (login_dialog.run() != Gtk.ResponseType.OK) { - debug("User refused to enter account information. Exiting..."); - this.application.exit(1); - return null; - } - - login_dialog.show_spinner(true); - new_info = login_dialog.get_account_information(); - - if ((!new_info.default_imap_server_ssl && !new_info.default_imap_server_starttls) - || (!new_info.default_smtp_server_ssl && !new_info.default_smtp_server_starttls)) { - ConfirmationDialog security_dialog = new ConfirmationDialog(main_window, - _("Your settings are insecure"), - _("Your IMAP and/or SMTP settings do not specify SSL or TLS. This means your username and password could be read by another person on the network. Are you sure you want to do this?"), - _("Co_ntinue")); - if (security_dialog.run() != Gtk.ResponseType.OK) - continue; - } - - break; + + private async void prompt_untrusted_host(AccountContext context, + Geary.ServiceInformation service, + Geary.Endpoint endpoint, + GLib.TlsConnection cx) { + if (Args.revoke_certs) { + // XXX } - - return new_info; - } - - private async void do_update_stored_passwords_async(Geary.ServiceFlag services, - Geary.AccountInformation account_information) { + + context.tls_validation_prompting = true; try { - yield account_information.update_stored_passwords_async(services); - } catch (Error e) { - debug("Error updating stored passwords: %s", e.message); + yield this.certificate_manager.prompt_pin_certificate( + this.main_window, + context.account.information, + service, + endpoint, + false, + context.cancellable + ); + context.tls_validation_failed = false; + } catch (Application.CertificateManagerError.UNTRUSTED err) { + // Don't report an error here, the user simply declined. + context.tls_validation_failed = true; + } catch (Application.CertificateManagerError err) { + // Assume validation is now good, but report the error + // since the cert may not have been saved + context.tls_validation_failed = false; + report_problem( + new Geary.ServiceProblemReport( + Geary.ProblemType.UNTRUSTED, + context.account.information, + service, + err + ) + ); } - } - - private void on_report_problem(Geary.Account account, Geary.Account.Problem problem, Error? err) { - debug("Reported problem: %s Error: %s", problem.to_string(), err != null ? err.message : "(N/A)"); - - switch (problem) { - case Geary.Account.Problem.DATABASE_FAILURE: - case Geary.Account.Problem.HOST_UNREACHABLE: - case Geary.Account.Problem.NETWORK_UNAVAILABLE: - // TODO - break; - - case Geary.Account.Problem.RECV_EMAIL_LOGIN_FAILED: - case Geary.Account.Problem.SEND_EMAIL_LOGIN_FAILED: - // At this point, we've prompted them for the password and - // they've hit cancel, so there's not much for us to do here. - close_account(account); - break; - - case Geary.Account.Problem.EMAIL_DELIVERY_FAILURE: - handle_outbox_failure(StatusBar.Message.OUTBOX_SEND_FAILURE); - break; - - case Geary.Account.Problem.SAVE_SENT_MAIL_FAILED: - handle_outbox_failure(StatusBar.Message.OUTBOX_SAVE_SENT_MAIL_FAILED); - break; - - case Geary.Account.Problem.CONNECTION_FAILURE: - ErrorDialog dialog = new ErrorDialog(main_window, - _("Error connecting to the server"), - _("Geary encountered an error while connecting to the server. Please try again in a few moments.")); - dialog.run(); - break; - default: - assert_not_reached(); - } + context.tls_validation_prompting = false; + update_account_status(); } - - private void handle_outbox_failure(StatusBar.Message message) { - bool activate_message = false; - try { - // Due to a timing hole where it's possible to delete a message - // from the outbox after the SMTP queue has picked it up and is - // in the process of sending it, we only want to display a message - // telling the user there's a problem if there are any other - // messages waiting to be sent on any account. - foreach (Geary.AccountInformation info in Geary.Engine.instance.get_accounts().values) { - Geary.Account account = Geary.Engine.instance.get_account_instance(info); - if (account.is_open()) { - Geary.Folder? outbox = account.get_special_folder(Geary.SpecialFolderType.OUTBOX); - if (outbox != null && outbox.properties.email_total > 0) { - activate_message = true; - break; - } - } - } - } catch (Error e) { - debug("Error determining whether any outbox has messages: %s", e.message); - activate_message = true; - } - - if (activate_message) { - if (!main_window.status_bar.is_message_active(message)) - main_window.status_bar.activate_message(message); - switch (message) { - case StatusBar.Message.OUTBOX_SEND_FAILURE: - libnotify.set_error_notification(_("Error sending email"), - _("Geary encountered an error sending an email. If the problem persists, please manually delete the email from your Outbox folder.")); - break; - - case StatusBar.Message.OUTBOX_SAVE_SENT_MAIL_FAILED: - libnotify.set_error_notification(_("Error saving sent mail"), - _("Geary encountered an error saving a sent message to Sent Mail. The message will stay in your Outbox folder until you delete it.")); - break; - - default: - assert_not_reached(); - } - } - } - + private void on_account_email_removed(Geary.Folder folder, Gee.Collection ids) { if (folder.special_folder_type == Geary.SpecialFolderType.OUTBOX) { main_window.status_bar.deactivate_message(StatusBar.Message.OUTBOX_SEND_FAILURE); @@ -1095,31 +901,28 @@ libnotify.clear_error_notification(); } } - + private void on_sending_started() { main_window.status_bar.activate_message(StatusBar.Message.OUTBOX_SENDING); } - + private void on_sending_finished() { main_window.status_bar.deactivate_message(StatusBar.Message.OUTBOX_SENDING); } - - // Removes an existing account. - public async void remove_account_async(Geary.AccountInformation account, - Cancellable? cancellable = null) { - try { - yield get_account_instance(account).close_async(cancellable); - yield Geary.Engine.instance.remove_account_async(account, cancellable); - } catch (Error e) { - message("Error removing account: %s", e.message); - } - } - - public async void connect_account_async(Geary.Account account, Cancellable? cancellable = null) { + + private async void connect_account_async(Geary.Account account, Cancellable? cancellable = null) { + AccountContext context = new AccountContext(account); + + // XXX Need to set this early since + // on_folders_available_unavailable expects it to be there + this.accounts.set(account.information, context); + + account.email_sent.connect(on_sent); + account.email_removed.connect(on_account_email_removed); account.folders_available_unavailable.connect(on_folders_available_unavailable); account.sending_monitor.start.connect(on_sending_started); account.sending_monitor.finish.connect(on_sending_finished); - + bool retry = false; do { try { @@ -1128,34 +931,35 @@ retry = false; } catch (Error open_err) { debug("Unable to open account %s: %s", account.to_string(), open_err.message); - - if (open_err is Geary.EngineError.CORRUPT) + + if (open_err is Geary.EngineError.CORRUPT) { retry = yield account_database_error_async(account); - else if (open_err is Geary.EngineError.PERMISSIONS) - yield account_database_perms_async(account); - else if (open_err is Geary.EngineError.VERSION) - yield account_database_version_async(account); - else - yield account_general_error_async(account); - - if (!retry) - return; + } + + if (!retry) { + report_problem( + new Geary.AccountProblemReport( + Geary.ProblemType.GENERIC_ERROR, + account.information, + open_err + ) + ); + + this.account_manager.disable_account(account.information); + this.accounts.unset(account.information); + } } } while (retry); - - email_stores.set(account, new Geary.App.EmailStore(account)); - inbox_cancellables.set(account, new Cancellable()); - - account.email_sent.connect(on_sent); - + main_window.folder_list.set_user_folders_root_name(account, _("Labels")); display_main_window_if_ready(); + update_account_status(); } - + // Returns true if the caller should try opening the account again private async bool account_database_error_async(Geary.Account account) { bool retry = true; - + // give the user two options: reset the Account local store, or exit Geary. A third // could be done to leave the Account in an unopened state, but we don't currently // have provisions for that. @@ -1175,99 +979,59 @@ _("Unable to rebuild database for “%s”").printf(account.information.id), _("Error during rebuild:\n\n%s").printf(err.message)); errdialog.run(); - + retry = false; } break; - + default: retry = false; break; } - if (!retry) - this.application.exit(1); - return retry; } - - private async void account_database_perms_async(Geary.Account account) { - // some other problem opening the account ... as with other flow path, can't run - // Geary today with an account in unopened state, so have to exit - ErrorDialog dialog = new ErrorDialog(main_window, - _("Unable to open local mailbox for %s").printf(account.information.id), - _("There was an error opening the local mail database for this account. This is possibly due to a file permissions problem.\n\nPlease check that you have read/write permissions for all files in this directory:\n\n%s") - .printf(account.information.data_dir.get_path())); - dialog.run(); - - this.application.exit(1); - } - - private async void account_database_version_async(Geary.Account account) { - ErrorDialog dialog = new ErrorDialog(main_window, - _("Unable to open local mailbox for %s").printf(account.information.id), - _("The version number of the local mail database is formatted for a newer version of Geary. Unfortunately, the database cannot be “rolled back” to work with this version of Geary.\n\nPlease install the latest version of Geary and try again.")); - dialog.run(); - - this.application.exit(1); - } - - private async void account_general_error_async(Geary.Account account) { - // some other problem opening the account ... as with other flow path, can't run - // Geary today with an account in unopened state, so have to exit - ErrorDialog dialog = new ErrorDialog(main_window, - _("Unable to open local mailbox for %s").printf(account.information.id), - _("There was an error opening the local account. This is probably due to connectivity issues.\n\nPlease check your network connection and restart Geary.")); - dialog.run(); - - this.application.exit(1); - } - - public async void disconnect_account_async(Geary.Account account, Cancellable? cancellable = null) { - cancel_inbox(account); - - previous_non_search_folder = null; - main_window.search_bar.set_search_text(""); // Reset search. - if (current_account == account) { - cancel_folder(); - switch_to_first_inbox(); // Switch folder. - } - + + private async void disconnect_account_async(AccountContext context, Cancellable? cancellable = null) { + debug("Disconnecting account: %s", context.account.information.id); + + Geary.Account account = context.account; + + // Guard against trying to disconnect the account twice + this.accounts.unset(account.information); + + // Now the account is not in the accounts map, reset any + // status notifications for it + update_account_status(); + + account.email_sent.disconnect(on_sent); + account.email_removed.disconnect(on_account_email_removed); account.folders_available_unavailable.disconnect(on_folders_available_unavailable); account.sending_monitor.start.disconnect(on_sending_started); account.sending_monitor.finish.disconnect(on_sending_finished); - + main_window.folder_list.remove_account(account); - - if (inboxes.has_key(account)) { + + context.cancellable.cancel(); + Geary.Folder? inbox = context.inbox; + if (inbox != null) { try { - yield inboxes.get(account).close_async(cancellable); + yield inbox.close_async(cancellable); } catch (Error close_inbox_err) { debug("Unable to close monitored inbox: %s", close_inbox_err.message); } - - inboxes.unset(account); + context.inbox = null; } - + try { yield account.close_async(cancellable); } catch (Error close_err) { debug("Unable to close account %s: %s", account.to_string(), close_err.message); } - - inbox_cancellables.unset(account); - email_stores.unset(account); - - // If there are no accounts available, exit. (This can happen if the user declines to - // enter a password on their account.) - try { - if (get_num_open_accounts() == 0) - this.application.exit(); - } catch (Error e) { - message("Error enumerating accounts: %s", e.message); - } + + debug("Account closed: %s", account.to_string()); } - + /** * Returns true if we've attempted to open all accounts at this point. */ @@ -1281,10 +1045,10 @@ } catch(Error e) { error("Could not open accounts: %s", e.message); } - + return true; } - + /** * Displays the main window if we're ready. Otherwise does nothing. */ @@ -1293,9 +1057,9 @@ !upgrade_dialog.visible && !cancellable_open_account.is_cancelled() && !Args.hidden_startup) - main_window.show_all(); + main_window.show(); } - + /** * Returns the number of accounts that exist in Geary. Note that not all accounts may be * open. Zero is returned on an error. @@ -1306,30 +1070,33 @@ } catch (Error e) { debug("Error getting number of accounts: %s", e.message); } - + return 0; // on error } - - // Returns the number of open accounts. - private int get_num_open_accounts() throws Error { - int num = 0; - foreach (Geary.AccountInformation info in Geary.Engine.instance.get_accounts().values) { - Geary.Account a = Geary.Engine.instance.get_account_instance(info); - if (a.is_open()) - num++; + + private bool is_inbox_descendant(Geary.Folder target) { + bool is_descendent = false; + + Geary.Account account = target.account; + Geary.Folder? inbox = null; + try { + inbox = account.get_special_folder(Geary.SpecialFolderType.INBOX); + } catch (Error err) { + debug("Failed to get inbox for account %s", account.information.id); } - - return num; + + if (inbox != null) { + is_descendent = inbox.path.is_descendant(target.path); + } + return is_descendent; } // Update widgets and such to match capabilities of the current folder ... sensitivity is handled // by other utility methods private void update_ui() { - update_tooltips(); - main_window.main_toolbar.update_trash_button( - current_folder_supports_trash() || - !(current_folder is Geary.FolderSupport.Remove) - ); + main_window.main_toolbar.selected_conversations = this.selected_conversations.size; + main_window.main_toolbar.show_trash_button = current_folder_supports_trash() || + !(current_folder is Geary.FolderSupport.Remove); } private void on_folder_selected(Geary.Folder? folder) { @@ -1341,9 +1108,7 @@ folder_selected(null); } else if (folder != this.current_folder) { this.main_window.conversation_viewer.show_loading(); - this.application.get_action( - ACTION_FIND_IN_CONVERSATION - ).set_sensitive(false); + get_window_action(ACTION_FIND_IN_CONVERSATION).set_enabled(false); enable_message_buttons(false); // To prevent the user from selecting folders too quickly, @@ -1380,110 +1145,98 @@ private async void do_select_folder(Geary.Folder folder) throws Error { debug("Switching to %s...", folder.to_string()); - + closed_folder(); - + // This function is not reentrant. It should be, because it can be // called reentrant-ly if you select folders quickly enough. This // mutex lock is a bandaid solution to make the function safe to // reenter. int mutex_token = yield select_folder_mutex.claim_async(cancellable_folder); - - bool current_is_inbox = inboxes.values.contains(current_folder); - - Cancellable? conversation_cancellable = (current_is_inbox ? - inbox_cancellables.get(folder.account) : cancellable_folder); - + // clear Revokable, as Undo is only available while a folder is selected save_revokable(null, null); - + // stop monitoring for conversations and close the folder if (current_conversations != null) { yield current_conversations.stop_monitoring_async(null); current_conversations = null; } - + // re-enable copy/move to the last selected folder if (current_folder != null) { main_window.main_toolbar.copy_folder_menu.enable_disable_folder(current_folder, true); main_window.main_toolbar.move_folder_menu.enable_disable_folder(current_folder, true); } - - current_folder = folder; - - if (current_account != folder.account) { - current_account = folder.account; - account_selected(current_account); - + + this.current_folder = folder; + + if (this.current_account != folder.account) { + this.current_account = folder.account; + account_selected(this.current_account); + // If we were waiting for an account to be selected before issuing mailtos, do that now. if (pending_mailtos.size > 0) { foreach(string mailto in pending_mailtos) compose_mailto(mailto); - + pending_mailtos.clear(); } + + main_window.main_toolbar.copy_folder_menu.clear(); + main_window.main_toolbar.move_folder_menu.clear(); + foreach(Geary.Folder f in current_folder.account.list_folders()) { + main_window.main_toolbar.copy_folder_menu.add_folder(f); + main_window.main_toolbar.move_folder_menu.add_folder(f); + } } - + folder_selected(current_folder); - + if (!(current_folder is Geary.SearchFolder)) previous_non_search_folder = current_folder; - - main_window.main_toolbar.copy_folder_menu.clear(); - main_window.main_toolbar.move_folder_menu.clear(); - foreach(Geary.Folder f in current_folder.account.list_folders()) { - main_window.main_toolbar.copy_folder_menu.add_folder(f); - main_window.main_toolbar.move_folder_menu.add_folder(f); - } - + // disable copy/move to the new folder - if (current_folder != null) { - main_window.main_toolbar.copy_folder_menu.enable_disable_folder(current_folder, false); - main_window.main_toolbar.move_folder_menu.enable_disable_folder(current_folder, false); - } - + main_window.main_toolbar.copy_folder_menu.enable_disable_folder(current_folder, false); + main_window.main_toolbar.move_folder_menu.enable_disable_folder(current_folder, false); + update_ui(); - - current_conversations = new Geary.App.ConversationMonitor(current_folder, Geary.Folder.OpenFlags.NO_DELAY, - ConversationListStore.REQUIRED_FIELDS, MIN_CONVERSATION_COUNT); - - if (inboxes.values.contains(current_folder)) { - // Inbox selected, clear new messages if visible - clear_new_messages("do_select_folder (inbox)", null); - } - + + current_conversations = new Geary.App.ConversationMonitor( + current_folder, + Geary.Folder.OpenFlags.NO_DELAY, + // Include fields for the conversation viewer as well so + // conversations can be displayed without having to go + // back to the db + ConversationListStore.REQUIRED_FIELDS | + ConversationListBox.REQUIRED_FIELDS | + ConversationEmail.REQUIRED_FOR_CONSTRUCT, + MIN_CONVERSATION_COUNT + ); + + current_conversations.scan_completed.connect(on_scan_completed); current_conversations.scan_error.connect(on_scan_error); - current_conversations.seed_completed.connect(on_seed_completed); - current_conversations.seed_completed.connect(on_conversation_count_changed); + current_conversations.scan_completed.connect(on_conversation_count_changed); current_conversations.conversations_added.connect(on_conversation_count_changed); current_conversations.conversations_removed.connect(on_conversation_count_changed); - - if (!current_conversations.is_monitoring) - yield current_conversations.start_monitoring_async(conversation_cancellable); - + + clear_new_messages("do_select_folder", null); + + yield this.current_conversations.start_monitoring_async( + this.cancellable_folder + ); + select_folder_mutex.release(ref mutex_token); - - debug("Switched to %s", folder.to_string()); - } - private void on_scan_error(Error err) { - debug("Scan error: %s", err.message); - } - - private void on_seed_completed() { - // Done scanning. Check if we have enough messages to fill the conversation list; if not, - // trigger a load_more(); - if (!main_window.conversation_list_has_scrollbar()) { - debug("Not enough messages, loading more for folder %s", current_folder.to_string()); - on_load_more(); - } + debug("Switched to %s", folder.to_string()); } private void on_conversation_count_changed() { if (this.current_conversations != null) { + ConversationListView list = this.main_window.conversation_list_view; ConversationViewer viewer = this.main_window.conversation_viewer; - int count = this.current_conversations.get_conversation_count(); + int count = this.current_conversations.size; if (count == 0) { // Let the user know if there's no available conversations if (this.current_folder is Geary.SearchFolder) { @@ -1495,8 +1248,12 @@ } else { // When not doing autoselect, we never get // conversations_selected firing from the convo list, - // so we need to stop the loading spinner here - if (!this.application.config.autoselect) { + // so we need to stop the loading spinner here. Only + // do so if there isn't already a selection or a + // composer to avoid interrupting those. + if (!this.application.config.autoselect && + list.get_selection().count_selected_rows() == 0 && + !viewer.is_composer_visible) { viewer.show_none_selected(); enable_message_buttons(false); } @@ -1507,38 +1264,35 @@ private void on_libnotify_invoked(Geary.Folder? folder, Geary.Email? email) { new_messages_monitor.clear_all_new_messages(); - + if (folder == null || email == null || !can_switch_conversation_view()) return; - + main_window.folder_list.select_folder(folder); - Geary.App.Conversation? conversation = current_conversations.get_conversation_for_email(email.id); + Geary.App.Conversation? conversation = current_conversations.get_by_email_identifier(email.id); if (conversation != null) main_window.conversation_list_view.select_conversation(conversation); } private void on_indicator_activated_application(uint32 timestamp) { - // When the app is started hidden, show_all() never gets - // called, do so here to prevent an empty window appearing. - main_window.show_all(); this.application.present(); } private void on_indicator_activated_composer(uint32 timestamp) { on_indicator_activated_application(timestamp); - on_new_message(); + compose(); } - + private void on_indicator_activated_inbox(Geary.Folder folder, uint32 timestamp) { on_indicator_activated_application(timestamp); main_window.folder_list.select_folder(folder); } - + private void on_load_more() { debug("on_load_more"); current_conversations.min_window_count += MIN_CONVERSATION_COUNT; } - + private void on_select_folder_completed(Object? source, AsyncResult result) { try { do_select_folder.end(result); @@ -1549,9 +1303,7 @@ private void on_conversations_selected(Gee.Set selected) { this.selected_conversations = selected; - this.application.get_action( - ACTION_FIND_IN_CONVERSATION - ).set_sensitive(false); + get_window_action(ACTION_FIND_IN_CONVERSATION).set_enabled(false); ConversationViewer viewer = this.main_window.conversation_viewer; if (this.current_folder != null && !viewer.is_composer_visible) { switch(selected.size) { @@ -1563,24 +1315,39 @@ case 1: // Cancel existing avatar loads before loading new // convo since that will start loading more avatars - viewer.load_conversation.begin( - Geary.Collection.get_first(selected), - this.current_folder, - this.application.config, - this.avatar_session, - (obj, ret) => { - try { - viewer.load_conversation.end(ret); - enable_message_buttons(true); - this.application.get_action( - ACTION_FIND_IN_CONVERSATION - ).set_sensitive(true); - } catch (Error err) { - debug("Unable to load conversation: %s", - err.message); - } - } + Geary.App.Conversation convo = Geary.Collection.get_first( + selected + ); + Geary.App.EmailStore? store = get_store_for_folder( + convo.base_folder ); + + // It's possible for a conversation with zero email to + // be selected, when it has just evaporated after its + // last email was removed but the conversation monitor + // hasn't signalled its removal yet. In this case, + // just don't load it since it will soon disappear. + if (store != null && convo.get_count() > 0) { + viewer.load_conversation.begin( + convo, + store, + this.avatar_store, + (obj, ret) => { + try { + viewer.load_conversation.end(ret); + enable_message_buttons(true); + get_window_action( + ACTION_FIND_IN_CONVERSATION + ).set_enabled(true); + } catch (GLib.IOError.CANCELLED err) { + // All good + } catch (Error err) { + debug("Unable to load conversation: %s", + err.message); + } + } + ); + } break; default: @@ -1609,10 +1376,14 @@ private void on_special_folder_type_changed(Geary.Folder folder, Geary.SpecialFolderType old_type, Geary.SpecialFolderType new_type) { - main_window.folder_list.remove_folder(folder); - main_window.folder_list.add_folder(folder); - // Since removing the folder will also remove its children, we - // need to check for any and re-add them. See isssue #11. + Geary.AccountInformation info = folder.account.information; + + // Update the main window + this.main_window.folder_list.remove_folder(folder); + this.main_window.folder_list.add_folder(folder); + // Since removing the folder will also remove its children + // from the folder list, we need to check for any and re-add + // them. See isssue #11. try { foreach (Geary.Folder child in folder.account.list_matching_folders(folder.path)) { @@ -1621,44 +1392,30 @@ } catch (Error err) { // Oh well } - } - private void on_engine_opened() { - // Locate the first account so we can select its inbox when available. - try { - Gee.ArrayList all_accounts = - new Gee.ArrayList(); - all_accounts.add_all(Geary.Engine.instance.get_accounts().values); - if (all_accounts.size == 0) { - debug("No accounts found."); - return; - } - - all_accounts.sort(Geary.AccountInformation.compare_ascending); - account_to_select = Geary.Engine.instance.get_account_instance(all_accounts.get(0)); - } catch (Error e) { - debug("Error selecting first inbox: %s", e.message); + // Update notifications + this.new_messages_monitor.remove_folder(folder); + if (folder.special_folder_type == Geary.SpecialFolderType.INBOX || + (folder.special_folder_type == Geary.SpecialFolderType.NONE && + is_inbox_descendant(folder))) { + this.new_messages_monitor.add_folder( + folder, this.accounts.get(info).cancellable + ); } } - - // Meant to be called inside the available block of on_folders_available_unavailable, - // after we've located the first account. - private Geary.Folder? get_initial_selection_folder(Geary.Folder folder_being_added) { - if (folder_being_added.account == account_to_select && - !main_window.folder_list.is_any_selected() && inboxes.has_key(account_to_select)) { - return inboxes.get(account_to_select); - } else if (account_to_select == null) { - // This is the first account being added, so select the inbox. - return inboxes.get(folder_being_added.account); - } - - return null; - } - - private void on_folders_available_unavailable(Gee.List? available, - Gee.List? unavailable) { + + private void on_folders_available_unavailable( + Geary.Account account, + Gee.BidirSortedSet? available, + Gee.BidirSortedSet? unavailable) { + AccountContext context = this.accounts.get(account.information); + if (available != null && available.size > 0) { foreach (Geary.Folder folder in available) { + if (!should_add_folder(available, folder)) { + continue; + } + main_window.folder_list.add_folder(folder); if (folder.account == current_account) { if (!main_window.main_toolbar.copy_folder_menu.has_folder(folder)) @@ -1666,32 +1423,61 @@ if (!main_window.main_toolbar.move_folder_menu.has_folder(folder)) main_window.main_toolbar.move_folder_menu.add_folder(folder); } - - // monitor the Inbox for notifications - if (folder.special_folder_type == Geary.SpecialFolderType.INBOX && - !inboxes.has_key(folder.account)) { - inboxes.set(folder.account, folder); - Geary.Folder? select_folder = get_initial_selection_folder(folder); - - if (select_folder != null) { - // First we try to select the Inboxes branch inbox if - // it's there, falling back to the main folder list. - if (!main_window.folder_list.select_inbox(select_folder.account)) - main_window.folder_list.select_folder(select_folder); + + GLib.Cancellable cancellable = context.cancellable; + + switch (folder.special_folder_type) { + case Geary.SpecialFolderType.INBOX: + // Special case handling of inboxes + if (context.inbox == null) { + context.inbox = folder; + + // Select this inbox if there isn't an + // existing folder selected and it is the + // inbox for the first account + if (!main_window.folder_list.is_any_selected()) { + Geary.AccountInformation? first_account = null; + foreach (Geary.AccountInformation info in this.accounts.keys) { + if (first_account == null || + info.ordinal < first_account.ordinal) { + first_account = info; + } + } + if (folder.account.information == first_account) { + // First we try to select the Inboxes branch inbox if + // it's there, falling back to the main folder list. + if (!main_window.folder_list.select_inbox(folder.account)) + main_window.folder_list.select_folder(folder); + } + } + } + + folder.open_async.begin(Geary.Folder.OpenFlags.NO_DELAY, cancellable); + + // Always notify for new messages in the Inbox + this.new_messages_monitor.add_folder(folder, cancellable); + break; + + case Geary.SpecialFolderType.NONE: + // Only notify for new messages in non-special + // descendants of the Inbox + if (is_inbox_descendant(folder)) { + this.new_messages_monitor.add_folder(folder, cancellable); } - - folder.open_async.begin(Geary.Folder.OpenFlags.NONE, inbox_cancellables.get(folder.account)); - - new_messages_monitor.add_folder(folder, inbox_cancellables.get(folder.account)); + break; } - + folder.special_folder_type_changed.connect(on_special_folder_type_changed); } } - + if (unavailable != null) { - for (int i = (unavailable.size - 1); i >= 0; i--) { - Geary.Folder folder = unavailable[i]; + Gee.BidirIterator unavailable_iterator = + unavailable.bidir_iterator(); + unavailable_iterator.last(); + while (unavailable_iterator.previous()) { + Geary.Folder folder = unavailable_iterator.get(); + main_window.folder_list.remove_folder(folder); if (folder.account == current_account) { if (main_window.main_toolbar.copy_folder_menu.has_folder(folder)) @@ -1699,42 +1485,39 @@ if (main_window.main_toolbar.move_folder_menu.has_folder(folder)) main_window.main_toolbar.move_folder_menu.remove_folder(folder); } - - if (folder.special_folder_type == Geary.SpecialFolderType.INBOX && - inboxes.has_key(folder.account)) { - inboxes.unset(folder.account); + + switch (folder.special_folder_type) { + case Geary.SpecialFolderType.INBOX: + context.inbox = null; new_messages_monitor.remove_folder(folder); + break; + + case Geary.SpecialFolderType.NONE: + // Only notify for new messages in non-special + // descendants of the Inbox + if (is_inbox_descendant(folder)) { + this.new_messages_monitor.remove_folder(folder); + } + break; } - + folder.special_folder_type_changed.disconnect(on_special_folder_type_changed); } } } - + private void cancel_folder() { Cancellable old_cancellable = cancellable_folder; cancellable_folder = new Cancellable(); - + old_cancellable.cancel(); } - + // Like cancel_folder() but doesn't cancel outstanding operations, allowing them to complete // in the background private void closed_folder() { cancellable_folder = new Cancellable(); } - - private void cancel_inbox(Geary.Account account) { - if (!inbox_cancellables.has_key(account)) { - debug("Unable to cancel inbox operation for %s", account.to_string()); - return; - } - - Cancellable old_cancellable = inbox_cancellables.get(account); - inbox_cancellables.set(account, new Cancellable()); - - old_cancellable.cancel(); - } private void cancel_search() { Cancellable old_cancellable = this.cancellable_search; @@ -1746,24 +1529,25 @@ private void cancel_context_dependent_buttons() { Cancellable old_cancellable = cancellable_context_dependent_buttons; cancellable_context_dependent_buttons = new Cancellable(); - + old_cancellable.cancel(); } - + // We need to include the second parameter, or valac doesn't recognize the function as matching // GearyApplication.exiting's signature. private bool on_application_exiting(GearyApplication sender, bool panicked) { if (close_composition_windows()) return true; - + return sender.cancel_exit(); } private void on_shift_key(bool pressed) { if (main_window != null && main_window.main_toolbar != null && current_account != null && current_folder != null) { - main_window.main_toolbar.update_trash_button( - (!pressed && current_folder_supports_trash()) || !(current_folder is Geary.FolderSupport.Remove)); + main_window.main_toolbar.show_trash_button = + (!pressed && current_folder_supports_trash()) || + !(current_folder is Geary.FolderSupport.Remove); } } @@ -1773,48 +1557,78 @@ clear_new_messages("on_has_toplevel_focus", null); } - // latest_sent_only uses Email's Date: field, which corresponds to how they're sorted in the - // ConversationViewer - private Gee.ArrayList get_conversation_email_ids( - Geary.App.Conversation conversation, bool latest_sent_only, - Gee.ArrayList add_to) { - if (latest_sent_only) { - Geary.Email? latest = conversation.get_latest_sent_email( - Geary.App.Conversation.Location.IN_FOLDER_OUT_OF_FOLDER); - if (latest != null) - add_to.add(latest.id); - } else { - add_to.add_all(conversation.get_email_ids()); + // latest_sent_only uses Email's Date: field, which corresponds to + // how they're sorted in the ConversationViewer, not whether they + // are in the sent folder. + private Gee.Collection get_conversation_email_ids( + Gee.Collection conversations, + bool latest_sent_only) { + + Gee.Collection ids = + new Gee.ArrayList(); + + // Blacklist the Outbox unless that's currently selected since + // we don't want any operations to apply to messages there + // normally. + Gee.Collection? blacklist = null; + if (this.current_folder != null && + this.current_folder.special_folder_type != Geary.SpecialFolderType.OUTBOX) { + Geary.Folder? outbox = null; + try { + outbox = this.current_account.get_special_folder( + Geary.SpecialFolderType.OUTBOX + ); + + blacklist = new Gee.ArrayList(); + blacklist.add(outbox.path); + } catch (GLib.Error err) { + // Oh well + } } - - return add_to; - } - - private Gee.Collection get_conversation_collection_email_ids( - Gee.Collection conversations, bool latest_sent_only) { - Gee.ArrayList ret = new Gee.ArrayList(); - - foreach(Geary.App.Conversation c in conversations) - get_conversation_email_ids(c, latest_sent_only, ret); - - return ret; - } - - private Gee.ArrayList get_selected_email_ids(bool latest_sent_only) { - Gee.ArrayList ids = new Gee.ArrayList(); - foreach (Geary.App.Conversation conversation in selected_conversations) - get_conversation_email_ids(conversation, latest_sent_only, ids); + + foreach(Geary.App.Conversation conversation in conversations) { + if (latest_sent_only) { + Geary.Email? latest = conversation.get_latest_sent_email( + Geary.App.Conversation.Location.IN_FOLDER_OUT_OF_FOLDER, + blacklist + ); + if (latest != null) { + ids.add(latest.id); + } + } else { + Geary.traverse( + conversation.get_emails( + Geary.App.Conversation.Ordering.NONE, + Geary.App.Conversation.Location.ANYWHERE, + blacklist + ) + ).map(e => e.id) + .add_all_to(ids); + } + } + return ids; } - + + private Gee.Collection + get_selected_email_ids(bool latest_sent_only) { + return get_conversation_email_ids( + this.selected_conversations, latest_sent_only + ); + } + private void mark_email(Gee.Collection ids, Geary.EmailFlags? flags_to_add, Geary.EmailFlags? flags_to_remove) { if (ids.size > 0) { - email_stores.get(current_folder.account).mark_email_async.begin( - ids, flags_to_add, flags_to_remove, cancellable_folder); + Geary.App.EmailStore? store = get_store_for_folder(current_folder); + if (store != null) { + store.mark_email_async.begin( + ids, flags_to_add, flags_to_remove, cancellable_folder + ); + } } } - + private void on_show_mark_menu() { bool unread_selected = false; bool read_selected = false; @@ -1823,7 +1637,7 @@ foreach (Geary.App.Conversation conversation in selected_conversations) { if (conversation.is_unread()) unread_selected = true; - + // Only check the messages that "Mark as Unread" would mark, so we // don't add the menu option and have it not do anything. // @@ -1840,34 +1654,23 @@ unstarred_selected = true; } } - var actions = this.application.actions; - actions.get_action(ACTION_MARK_AS_READ).set_visible(unread_selected); - actions.get_action(ACTION_MARK_AS_UNREAD).set_visible(read_selected); - actions.get_action(ACTION_MARK_AS_STARRED).set_visible(unstarred_selected); - actions.get_action(ACTION_MARK_AS_UNSTARRED).set_visible(starred_selected); - - if (current_folder.special_folder_type != Geary.SpecialFolderType.DRAFTS && - current_folder.special_folder_type != Geary.SpecialFolderType.OUTBOX) { - if (current_folder.special_folder_type == Geary.SpecialFolderType.SPAM) { - // We're in the spam folder. - actions.get_action(ACTION_MARK_AS_SPAM).sensitive = true; - actions.get_action(ACTION_MARK_AS_SPAM).label = MARK_AS_NOT_SPAM_LABEL; - } else { - // We're not in the spam folder, but we are in a folder that allows mark-as-spam. - actions.get_action(ACTION_MARK_AS_SPAM).sensitive = true; - actions.get_action(ACTION_MARK_AS_SPAM).label = MARK_AS_SPAM_LABEL; - } - } else { - // We're in Drafts/Outbox, so gray-out the option. - actions.get_action(ACTION_MARK_AS_SPAM).sensitive = false; - actions.get_action(ACTION_MARK_AS_SPAM).label = MARK_AS_SPAM_LABEL; - } + get_window_action(ACTION_MARK_AS_READ).set_enabled(unread_selected); + get_window_action(ACTION_MARK_AS_UNREAD).set_enabled(read_selected); + get_window_action(ACTION_MARK_AS_STARRED).set_enabled(unstarred_selected); + get_window_action(ACTION_MARK_AS_UNSTARRED).set_enabled(starred_selected); + + bool in_spam_folder = current_folder.special_folder_type == Geary.SpecialFolderType.SPAM; + get_window_action(ACTION_MARK_AS_NOT_SPAM).set_enabled(in_spam_folder); + // If we're in Drafts/Outbox, we also shouldn't set a message as SPAM. + get_window_action(ACTION_MARK_AS_SPAM).set_enabled(!in_spam_folder && + current_folder.special_folder_type != Geary.SpecialFolderType.DRAFTS && + current_folder.special_folder_type != Geary.SpecialFolderType.OUTBOX); } - + private void on_visible_conversations_changed(Gee.Set visible) { clear_new_messages("on_visible_conversations_changed", visible); } - + private bool should_notify_new_messages(Geary.Folder folder) { // A monitored folder must be selected to squelch notifications; // if conversation list is at top of display, don't display @@ -1876,44 +1679,44 @@ || main_window.conversation_list_view.vadjustment.value != 0.0 || !main_window.has_toplevel_focus; } - + // Clears messages if conditions are true: anything in should_notify_new_messages() is // false and the supplied visible messages are visible in the conversation list view private void clear_new_messages(string caller, Gee.Set? supplied) { if (current_folder == null || !new_messages_monitor.get_folders().contains(current_folder) || should_notify_new_messages(current_folder)) return; - + Gee.Set visible = supplied ?? main_window.conversation_list_view.get_visible_conversations(); - + foreach (Geary.App.Conversation conversation in visible) { if (new_messages_monitor.are_any_new_messages(current_folder, conversation.get_email_ids())) { debug("Clearing new messages: %s", caller); new_messages_monitor.clear_new_messages(current_folder); - + break; } } } - + private void on_mark_conversations(Gee.Collection conversations, Geary.EmailFlags? flags_to_add, Geary.EmailFlags? flags_to_remove, bool latest_only = false) { - mark_email(get_conversation_collection_email_ids(conversations, latest_only), + mark_email(get_conversation_email_ids(conversations, latest_only), flags_to_add, flags_to_remove); } - + private void on_conversation_viewer_mark_emails(Gee.Collection emails, Geary.EmailFlags? flags_to_add, Geary.EmailFlags? flags_to_remove) { mark_email(emails, flags_to_add, flags_to_remove); } - - private void on_mark_as_read() { + + private void on_mark_as_read(SimpleAction action) { Geary.EmailFlags flags = new Geary.EmailFlags(); flags.add(Geary.EmailFlags.UNREAD); - - Gee.ArrayList ids = get_selected_email_ids(false); + + Gee.Collection ids = get_selected_email_ids(false); mark_email(ids, null, flags); ConversationListBox? list = @@ -1924,11 +1727,11 @@ } } - private void on_mark_as_unread() { + private void on_mark_as_unread(SimpleAction action) { Geary.EmailFlags flags = new Geary.EmailFlags(); flags.add(Geary.EmailFlags.UNREAD); - - Gee.ArrayList ids = get_selected_email_ids(true); + + Gee.Collection ids = get_selected_email_ids(true); mark_email(ids, flags, null); ConversationListBox? list = @@ -1939,19 +1742,27 @@ } } - private void on_mark_as_starred() { + private void on_mark_as_starred(SimpleAction action) { Geary.EmailFlags flags = new Geary.EmailFlags(); flags.add(Geary.EmailFlags.FLAGGED); mark_email(get_selected_email_ids(true), flags, null); } - private void on_mark_as_unstarred() { + private void on_mark_as_unstarred(SimpleAction action) { Geary.EmailFlags flags = new Geary.EmailFlags(); flags.add(Geary.EmailFlags.FLAGGED); mark_email(get_selected_email_ids(false), null, flags); } - - private async void mark_as_spam_async(Cancellable? cancellable) { + + private void on_show_move_menu(SimpleAction? action) { + this.main_window.main_toolbar.copy_message_button.clicked(); + } + + private void on_show_copy_menu(SimpleAction? action) { + this.main_window.main_toolbar.move_message_button.clicked(); + } + + private async void mark_as_spam_toggle_async(Cancellable? cancellable) { Geary.Folder? destination_folder = null; if (current_folder.special_folder_type != Geary.SpecialFolderType.SPAM) { // Move to spam folder. @@ -1969,37 +1780,41 @@ debug("Error getting inbox folder: %s", e.message); } } - + if (destination_folder != null) on_move_conversation(destination_folder); } - - private void on_mark_as_spam() { - mark_as_spam_async.begin(null); + + private void on_mark_as_spam_toggle(SimpleAction action) { + mark_as_spam_toggle_async.begin(null); } - + private void copy_email(Gee.Collection ids, Geary.FolderPath destination) { if (ids.size > 0) { - email_stores.get(current_folder.account).copy_email_async.begin( - ids, destination, cancellable_folder); + Geary.App.EmailStore? store = get_store_for_folder(current_folder); + if (store != null) { + store.copy_email_async.begin( + ids, destination, cancellable_folder + ); + } } } - + private void on_copy_conversation(Geary.Folder destination) { copy_email(get_selected_email_ids(false), destination.path); } - + private void on_move_conversation(Geary.Folder destination) { // Nothing to do if nothing selected. if (selected_conversations == null || selected_conversations.size == 0) return; - - Gee.List ids = get_selected_email_ids(false); + + Gee.Collection ids = get_selected_email_ids(false); if (ids.size == 0) return; - this.main_window.conversation_list_view.set_changing_selection(true); + selection_operation_started(); Geary.FolderSupport.Move? supports_move = current_folder as Geary.FolderSupport.Move; if (supports_move != null) @@ -2007,12 +1822,14 @@ supports_move, ids, destination.path, cancellable_folder, (obj, ret) => { move_conversation_async.end(ret); - this.main_window.conversation_list_view.set_changing_selection(false); + selection_operation_finished(); }); } private async void move_conversation_async(Geary.FolderSupport.Move source_folder, - Gee.List ids, Geary.FolderPath destination, Cancellable? cancellable) { + Gee.Collection ids, + Geary.FolderPath destination, + Cancellable? cancellable) { try { save_revokable(yield source_folder.move_email_async(ids, destination, cancellable), _("Undo move (Ctrl+Z)")); @@ -2046,102 +1863,166 @@ } private async void save_attachment_to_file(Geary.Attachment attachment, - string? alt_text) { - string file_name = yield attachment.get_safe_file_name(alt_text); + string? alt_text, + GLib.Cancellable cancellable) { + string alt_display_name = Geary.String.is_empty_or_whitespace(alt_text) + ? GearyController.untitled_file_name : alt_text; + string display_name = yield attachment.get_safe_file_name( + alt_display_name + ); + + Geary.Memory.FileBuffer? content = null; try { - yield this.prompt_save_buffer( - file_name, new Geary.Memory.FileBuffer(attachment.file, true) + content = new Geary.Memory.FileBuffer(attachment.file, true); + } catch (GLib.Error err) { + warning( + "Error opening attachment file \"%s\": %s", + attachment.file.get_uri(), err.message + ); + report_problem( + new Geary.ProblemReport(Geary.ProblemType.GENERIC_ERROR, err) ); - } catch (Error err) { - message("Unable to save buffer to \"%s\": %s", file_name, err.message); } + + yield this.prompt_save_buffer(display_name, content, cancellable); } - private async void save_attachments_to_file(Gee.Collection attachments) { -#if GTK_3_20 + private async void + save_attachments_to_file(Gee.Collection attachments, + GLib.Cancellable? cancellable) { Gtk.FileChooserNative dialog = new_save_chooser(Gtk.FileChooserAction.SELECT_FOLDER); -#else - Gtk.FileChooserDialog dialog = new_save_chooser(Gtk.FileChooserAction.SELECT_FOLDER); -#endif bool accepted = (dialog.run() == Gtk.ResponseType.ACCEPT); string? filename = dialog.get_filename(); - dialog.destroy(); - if (!accepted || Geary.String.is_empty(filename)) return; - - File dest_dir = File.new_for_path(filename); - this.application.config.attachments_dir = dest_dir.get_path(); - - debug("Saving attachments to %s", dest_dir.get_path()); + File dest_dir = File.new_for_path(filename); foreach (Geary.Attachment attachment in attachments) { - File source_file = attachment.file; - File dest_file = dest_dir.get_child(yield attachment.get_safe_file_name()); - if (dest_file.query_exists() && !do_overwrite_confirmation(dest_file)) - return; - + Geary.Memory.FileBuffer? content = null; + GLib.File? dest = null; try { - yield write_buffer_to_file( - new Geary.Memory.FileBuffer(source_file, true), dest_file + content = new Geary.Memory.FileBuffer(attachment.file, true); + dest = dest_dir.get_child_for_display_name( + yield attachment.get_safe_file_name( + GearyController.untitled_file_name + ) ); - } catch (Error error) { - message( - "Failed to copy attachment %s to destination: %s", - source_file.get_path(), error.message + } catch (GLib.Error err) { + warning( + "Error opening attachment files \"%s\": %s", + attachment.file.get_uri(), err.message + ); + report_problem( + new Geary.ProblemReport( + Geary.ProblemType.GENERIC_ERROR, err + ) ); } + + if (content != null && + dest != null && + yield check_overwrite(dest, cancellable)) { + yield write_buffer_to_file(content, dest, cancellable); + } } } - private async void prompt_save_buffer(string? filename, Geary.Memory.Buffer buffer) - throws Error { -#if GTK_3_20 - Gtk.FileChooserNative dialog = new_save_chooser(Gtk.FileChooserAction.SAVE); -#else - Gtk.FileChooserDialog dialog = new_save_chooser(Gtk.FileChooserAction.SAVE); -#endif - if (!Geary.String.is_empty(filename)) - dialog.set_current_name(filename); - bool accepted = (dialog.run() == Gtk.ResponseType.ACCEPT); - string? accepted_filename = dialog.get_filename(); + private async void prompt_save_buffer(string display_name, + Geary.Memory.Buffer buffer, + GLib.Cancellable? cancellable) { + Gtk.FileChooserNative dialog = new_save_chooser( + Gtk.FileChooserAction.SAVE + ); + dialog.set_current_name(display_name); + string? accepted_path = null; + if (dialog.run() == Gtk.ResponseType.ACCEPT) { + accepted_path = dialog.get_filename(); + } dialog.destroy(); - if (accepted && !Geary.String.is_empty(accepted_filename)) { - File destination = File.new_for_path(accepted_filename); - this.application.config.attachments_dir = destination.get_parent().get_path(); - yield write_buffer_to_file(buffer, destination); + if (!Geary.String.is_empty_or_whitespace(accepted_path)) { + GLib.File dest_file = File.new_for_path(accepted_path); + if (yield check_overwrite(dest_file, cancellable)) { + yield write_buffer_to_file(buffer, dest_file, cancellable); + } } } - private async void write_buffer_to_file(Geary.Memory.Buffer buffer, File dest) - throws Error { - debug("Saving buffer to: %s", dest.get_path()); - FileOutputStream outs = dest.replace( - null, false, FileCreateFlags.REPLACE_DESTINATION, null - ); - yield outs.splice_async( - buffer.get_input_stream(), - OutputStreamSpliceFlags.CLOSE_SOURCE | OutputStreamSpliceFlags.CLOSE_TARGET, - Priority.DEFAULT, null - ); - } + private async bool check_overwrite(GLib.File to_overwrite, + GLib.Cancellable? cancellable) { + bool overwrite = true; + try { + GLib.FileInfo file_info = yield to_overwrite.query_info_async( + GLib.FileAttribute.STANDARD_DISPLAY_NAME, + GLib.FileQueryInfoFlags.NONE, + GLib.Priority.DEFAULT, + cancellable + ); + GLib.FileInfo parent_info = yield to_overwrite.get_parent() + .query_info_async( + GLib.FileAttribute.STANDARD_DISPLAY_NAME, + GLib.FileQueryInfoFlags.NONE, + GLib.Priority.DEFAULT, + cancellable + ); - private bool do_overwrite_confirmation(File to_overwrite) { - string primary = _("A file named “%s” already exists. Do you want to replace it?").printf( - to_overwrite.get_basename()); - string secondary = _("The file already exists in “%s”. Replacing it will overwrite its contents.").printf( - to_overwrite.get_parent().get_basename()); + // Translators: Dialog primary label when prompting to + // overwrite a file. The string substitution is the file'sx + // name. + string primary = _( + "A file named “%s” already exists. Do you want to replace it?" + ).printf(file_info.get_display_name()); + + // Translators: Dialog secondary label when prompting to + // overwrite a file. The string substitution is the parent + // folder's name. + string secondary = _( + "The file already exists in “%s”. Replacing it will overwrite its contents." + ).printf(parent_info.get_display_name()); - ConfirmationDialog dialog = new ConfirmationDialog(main_window, primary, secondary, _("_Replace"), "destructive-action"); + ConfirmationDialog dialog = new ConfirmationDialog( + main_window, primary, secondary, _("_Replace"), "destructive-action" + ); + overwrite = (dialog.run() == Gtk.ResponseType.OK); + } catch (GLib.Error err) { + // Oh well + } + return overwrite; + } - return (dialog.run() == Gtk.ResponseType.OK); + private async void write_buffer_to_file(Geary.Memory.Buffer buffer, + File dest, + GLib.Cancellable? cancellable) { + try { + FileOutputStream outs = dest.replace( + null, false, FileCreateFlags.REPLACE_DESTINATION, cancellable + ); + yield outs.splice_async( + buffer.get_input_stream(), + OutputStreamSpliceFlags.CLOSE_SOURCE | OutputStreamSpliceFlags.CLOSE_TARGET, + Priority.DEFAULT, + cancellable + ); + } catch (GLib.IOError.CANCELLED err) { + try { + yield dest.delete_async(GLib.Priority.HIGH, null); + } catch (GLib.Error err) { + // Oh well + } + } catch (GLib.Error err) { + warning( + "Error writing buffer \"%s\": %s", + dest.get_uri(), err.message + ); + report_problem( + new Geary.ProblemReport(Geary.ProblemType.GENERIC_ERROR, err) + ); + } } -#if GTK_3_20 private inline Gtk.FileChooserNative new_save_chooser(Gtk.FileChooserAction action) { Gtk.FileChooserNative dialog = new Gtk.FileChooserNative( null, @@ -2150,21 +2031,6 @@ Stock._SAVE, Stock._CANCEL ); -#else - private inline Gtk.FileChooserDialog new_save_chooser(Gtk.FileChooserAction action) { - Gtk.FileChooserDialog dialog = new Gtk.FileChooserDialog( - null, - this.main_window, - action, - Stock._CANCEL, Gtk.ResponseType.CANCEL, - Stock._SAVE, Gtk.ResponseType.ACCEPT, - null - ); -#endif - string? dir = this.application.config.attachments_dir; - if (!Geary.String.is_empty(dir)) - dialog.set_current_folder(dir); - dialog.set_create_folders(true); dialog.set_local_only(false); return dialog; } @@ -2221,21 +2087,21 @@ // Safely destroy windows. foreach(ComposerWidget cw in composers_to_destroy) ((ComposerContainer) cw.parent).close_container(); - + // If we cancelled the quit we can bail here. if (quit_cancelled) { waiting_to_close.clear(); - + return false; } - + // If there's still windows saving, we can't exit just yet. Hide the main window and wait. if (waiting_to_close.size > 0) { main_window.hide(); - + return false; } - + // If we deleted all composer windows without the user cancelling, we can exit. return true; } @@ -2283,16 +2149,17 @@ bool is_draft = false) { if (current_account == null) return; - + bool inline; if (!should_create_new_composer(compose_type, referred, quote, is_draft, out inline)) return; ComposerWidget widget; if (mailto != null) { - widget = new ComposerWidget.from_mailto(current_account, mailto, application.config); + widget = new ComposerWidget.from_mailto(current_account, contact_list_store_cache, + mailto, application.config); } else { - widget = new ComposerWidget(current_account, compose_type, application.config); + widget = new ComposerWidget(current_account, contact_list_store_cache, compose_type, application.config); } widget.destroy.connect(on_composer_widget_destroy); widget.link_activated.connect((uri) => { open_uri(uri); }); @@ -2303,12 +2170,9 @@ debug(@"Creating composer of type $(widget.compose_type); $(composer_widgets.size) composers total"); if (inline) { - if (widget.state == ComposerWidget.ComposerState.NEW || - widget.state == ComposerWidget.ComposerState.PANED) { + if (widget.state == ComposerWidget.ComposerState.PANED) { main_window.conversation_viewer.do_compose(widget); - this.application.get_action( - ACTION_FIND_IN_CONVERSATION - ).set_sensitive(false); + get_window_action(ACTION_FIND_IN_CONVERSATION).set_enabled(false); } else { main_window.conversation_viewer.do_compose_embedded( widget, @@ -2324,12 +2188,18 @@ // Load the widget's content Geary.Email? full = null; if (referred != null) { - try { - full = yield email_stores.get(current_folder.account).fetch_email_async( - referred.id, Geary.ComposedEmail.REQUIRED_REPLY_FIELDS, - Geary.Folder.ListFlags.NONE, cancellable_folder); - } catch (Error e) { - message("Could not load full message: %s", e.message); + Geary.App.EmailStore? store = get_store_for_folder(current_folder); + if (store != null) { + try { + full = yield store.fetch_email_async( + referred.id, + Geary.ComposedEmail.REQUIRED_REPLY_FIELDS, + Geary.Folder.ListFlags.NONE, + cancellable_folder + ); + } catch (Error e) { + message("Could not load full message: %s", e.message); + } } } yield widget.load(full, quote, is_draft); @@ -2340,7 +2210,7 @@ private bool should_create_new_composer(ComposerWidget.ComposeType? compose_type, Geary.Email? referred, string? quote, bool is_draft, out bool inline) { inline = true; - + // In we're replying, see whether we already have a reply for that message. if (compose_type != null && compose_type != ComposerWidget.ComposeType.NEW_MESSAGE) { foreach (ComposerWidget cw in composer_widgets) { @@ -2354,22 +2224,22 @@ inline = !any_inline_composers(); return true; } - + // If there are no inline composers, go ahead! if (!any_inline_composers()) return true; - + // If we're resuming a draft with open composers, open in a new window. if (is_draft) { inline = false; return true; } - + // If we're creating a new message, and there's already a new message open, focus on // it if it hasn't been modified; otherwise open a new composer in a new window. if (compose_type == ComposerWidget.ComposeType.NEW_MESSAGE) { foreach (ComposerWidget cw in composer_widgets) { - if (cw.state == ComposerWidget.ComposerState.NEW) { + if (cw.state == ComposerWidget.ComposerState.PANED) { if (!cw.is_blank) { inline = false; return true; @@ -2380,58 +2250,64 @@ } } } - + // Find out what to do with the inline composers. // TODO: Remove this in favor of automatically saving drafts this.application.present(); - ConfirmationDialog dialog = new ConfirmationDialog(main_window, _("Close open draft messages?"), - null, Stock._CLOSE, "destructive-action"); + Gee.List composers_to_destroy = new Gee.ArrayList(); + foreach (ComposerWidget cw in composer_widgets) { + if (cw.state != ComposerWidget.ComposerState.DETACHED) + composers_to_destroy.add(cw); + } + string message = ngettext( + "Close the draft message?", + "Close all draft messages?", + composers_to_destroy.size + ); + ConfirmationDialog dialog = new ConfirmationDialog( + main_window, message, null, Stock._CLOSE, "destructive-action" + ); if (dialog.run() == Gtk.ResponseType.OK) { - Gee.List composers_to_destroy = new Gee.ArrayList(); - foreach (ComposerWidget cw in composer_widgets) { - if (cw.state != ComposerWidget.ComposerState.DETACHED) - composers_to_destroy.add(cw); - } foreach(ComposerWidget cw in composers_to_destroy) ((ComposerContainer) cw.parent).close_container(); return true; } return false; } - + public bool can_switch_conversation_view() { bool inline; return should_create_new_composer(null, null, null, false, out inline); } - + public bool any_inline_composers() { foreach (ComposerWidget cw in composer_widgets) if (cw.state != ComposerWidget.ComposerState.DETACHED) return true; return false; } - + private void on_composer_widget_destroy(Gtk.Widget sender) { composer_widgets.remove((ComposerWidget) sender); debug(@"Destroying composer of type $(((ComposerWidget) sender).compose_type); " + @"$(composer_widgets.size) composers remaining"); - + if (waiting_to_close.remove((ComposerWidget) sender)) { // If we just removed the last window in the waiting to close list, it's time to exit! if (waiting_to_close.size == 0) this.application.exit(); } } - - private void on_new_message() { - create_compose_widget(ComposerWidget.ComposeType.NEW_MESSAGE); + + private void on_close() { + this.application.exit(); } private void on_reply_to_message(ConversationEmail target_view) { create_reply_forward_widget(ComposerWidget.ComposeType.REPLY, target_view); } - private void on_reply_to_message_action() { + private void on_reply_to_message_action(SimpleAction action) { create_reply_forward_widget(ComposerWidget.ComposeType.REPLY, null); } @@ -2439,7 +2315,7 @@ create_reply_forward_widget(ComposerWidget.ComposeType.REPLY_ALL, target_view); } - private void on_reply_all_message_action() { + private void on_reply_all_message_action(SimpleAction action) { create_reply_forward_widget(ComposerWidget.ComposeType.REPLY_ALL, null); } @@ -2447,64 +2323,68 @@ create_reply_forward_widget(ComposerWidget.ComposeType.FORWARD, target_view); } - private void on_forward_message_action() { + private void on_forward_message_action(SimpleAction action) { create_reply_forward_widget(ComposerWidget.ComposeType.FORWARD, null); } - private void on_find_in_conversation_action() { - this.main_window.conversation_viewer.conversation_find_bar.set_search_mode(true); + private void on_find_in_conversation_action(SimpleAction action) { + this.main_window.conversation_viewer.enable_find(); + } + + private void on_search_activated(SimpleAction action) { + show_search_bar(); } - private void on_archive_conversation() { + private void on_archive_conversation(SimpleAction action) { archive_or_delete_selection_async.begin(true, false, cancellable_folder, on_archive_or_delete_selection_finished); } - - private void on_trash_conversation() { + + private void on_trash_conversation(SimpleAction action) { archive_or_delete_selection_async.begin(false, true, cancellable_folder, on_archive_or_delete_selection_finished); } - - private void on_delete_conversation() { + + private void on_delete_conversation(SimpleAction action) { archive_or_delete_selection_async.begin(false, false, cancellable_folder, on_archive_or_delete_selection_finished); } - - private void on_empty_spam() { + + private void on_empty_spam(SimpleAction action) { on_empty_trash_or_spam(Geary.SpecialFolderType.SPAM); } - - private void on_empty_trash() { + + private void on_empty_trash(SimpleAction action) { on_empty_trash_or_spam(Geary.SpecialFolderType.TRASH); } - + private void on_empty_trash_or_spam(Geary.SpecialFolderType special_folder_type) { // Account must be in place, must have the specified special folder type, and that folder // must support Empty in order for this command to proceed if (current_account == null) return; - + Geary.Folder? folder = null; try { folder = current_account.get_special_folder(special_folder_type); } catch (Error err) { debug("%s: Unable to get special folder %s: %s", current_account.to_string(), special_folder_type.to_string(), err.message); - + // fall through } - + if (folder == null) return; - + Geary.FolderSupport.Empty? emptyable = folder as Geary.FolderSupport.Empty; if (emptyable == null) { debug("%s: Special folder %s (%s) does not support emptying", current_account.to_string(), folder.path.to_string(), special_folder_type.to_string()); - + return; } - + ConfirmationDialog dialog = new ConfirmationDialog(main_window, _("Empty all email from your %s folder?").printf(special_folder_type.get_display_name()), _("This removes the email from Geary and your email server.") @@ -2512,48 +2392,50 @@ _("Empty %s").printf(special_folder_type.get_display_name()), "destructive-action"); dialog.use_secondary_markup(true); dialog.set_focus_response(Gtk.ResponseType.CANCEL); - + if (dialog.run() == Gtk.ResponseType.OK) empty_folder_async.begin(emptyable, cancellable_folder); } - + private async void empty_folder_async(Geary.FolderSupport.Empty emptyable, Cancellable? cancellable) { try { yield do_empty_folder_async(emptyable, cancellable); } catch (Error err) { // don't report to user if cancelled - if (cancellable is IOError.CANCELLED) + if (err is IOError.CANCELLED) return; - + ErrorDialog dialog = new ErrorDialog(main_window, _("Error emptying %s").printf(emptyable.get_display_name()), err.message); dialog.run(); } } - + private async void do_empty_folder_async(Geary.FolderSupport.Empty emptyable, Cancellable? cancellable) throws Error { - yield emptyable.open_async(Geary.Folder.OpenFlags.NONE, cancellable); - - // be sure to close in all code paths + bool open = false; try { + yield emptyable.open_async(Geary.Folder.OpenFlags.NO_DELAY, cancellable); + open = true; yield emptyable.empty_folder_async(cancellable); } finally { - try { - yield emptyable.close_async(null); - } catch (Error err) { - // ignored + if (open) { + try { + yield emptyable.close_async(null); + } catch (Error err) { + // ignored + } } } } - + private bool current_folder_supports_trash() { return (current_folder != null && current_folder.special_folder_type != Geary.SpecialFolderType.TRASH && !current_folder.properties.is_local_only && current_account != null && (current_folder as Geary.FolderSupport.Move) != null); } - public bool confirm_delete(int num_messages) { + private bool confirm_delete(int num_messages) { this.application.present(); ConfirmationDialog dialog = new ConfirmationDialog(main_window, ngettext( "Do you want to permanently delete this message?", @@ -2563,6 +2445,38 @@ return (dialog.run() == Gtk.ResponseType.OK); } + private async void trash_messages_async(Gee.Collection ids, Cancellable? cancellable) + throws Error { + debug("Trashing selected messages"); + + Geary.FolderSupport.Move? supports_move = current_folder as Geary.FolderSupport.Move; + if (current_folder_supports_trash() && supports_move != null) { + Geary.FolderPath trash_path = (yield current_account.get_required_special_folder_async( + Geary.SpecialFolderType.TRASH, cancellable)).path; + save_revokable(yield supports_move.move_email_async(ids, trash_path, cancellable), + _("Undo trash (Ctrl+Z)")); + } else { + debug("Folder %s doesn't support move or account %s doesn't have a trash folder", + current_folder.to_string(), current_account.to_string()); + } + } + + private async void delete_messages_async(Gee.Collection ids, Cancellable? cancellable) + throws Error { + debug("Deleting selected messages"); + + Geary.FolderSupport.Remove? supports_remove = current_folder as Geary.FolderSupport.Remove; + if (supports_remove != null) { + if (confirm_delete(ids.size)) { + yield supports_remove.remove_email_async(ids, cancellable); + } else { + last_deleted_conversation = null; + } + } else { + debug("Folder %s doesn't support remove", current_folder.to_string()); + } + } + private async void archive_or_delete_selection_async(bool archive, bool trash, Cancellable? cancellable) throws Error { if (!can_switch_conversation_view()) @@ -2576,15 +2490,15 @@ return; } + selection_operation_started(); + last_deleted_conversation = selected_conversations.size > 0 ? Geary.traverse(selected_conversations).first() : null; - this.main_window.conversation_list_view.set_changing_selection(true); - - Gee.List ids = get_selected_email_ids(false); + Gee.Collection ids = get_selected_email_ids(false); if (archive) { debug("Archiving selected messages"); - + Geary.FolderSupport.Archive? supports_archive = current_folder as Geary.FolderSupport.Archive; if (supports_archive == null) { debug("Folder %s doesn't support archive", current_folder.to_string()); @@ -2592,65 +2506,39 @@ save_revokable(yield supports_archive.archive_email_async(ids, cancellable), _("Undo archive (Ctrl+Z)")); } - + return; } - + if (trash) { - debug("Trashing selected messages"); - - if (current_folder_supports_trash()) { - Geary.FolderPath trash_path = (yield current_account.get_required_special_folder_async( - Geary.SpecialFolderType.TRASH, cancellable)).path; - Geary.FolderSupport.Move? supports_move = current_folder as Geary.FolderSupport.Move; - if (supports_move != null) { - save_revokable(yield supports_move.move_email_async(ids, trash_path, cancellable), - _("Undo trash (Ctrl+Z)")); - - return; - } - } - - debug("Folder %s doesn't support move or account %s doesn't have a trash folder", - current_folder.to_string(), current_account.to_string()); - return; - } - - debug("Deleting selected messages"); - - Geary.FolderSupport.Remove? supports_remove = current_folder as Geary.FolderSupport.Remove; - if (supports_remove == null) { - debug("Folder %s doesn't support remove", current_folder.to_string()); + yield trash_messages_async(ids, cancellable); } else { - if (confirm_delete(ids.size)) - yield supports_remove.remove_email_async(ids, cancellable); - else - last_deleted_conversation = null; + yield delete_messages_async(ids, cancellable); } } - + private void on_archive_or_delete_selection_finished(Object? source, AsyncResult result) { try { archive_or_delete_selection_async.end(result); } catch (Error e) { debug("Unable to archive/trash/delete messages: %s", e.message); } - this.main_window.conversation_list_view.set_changing_selection(false); + selection_operation_finished(); } - + private void save_revokable(Geary.Revokable? new_revokable, string? description) { // disconnect old revokable & blindly commit it if (revokable != null) { revokable.notify[Geary.Revokable.PROP_VALID].disconnect(on_revokable_valid_changed); revokable.notify[Geary.Revokable.PROP_IN_PROCESS].disconnect(update_revokable_action); revokable.committed.disconnect(on_revokable_committed); - + revokable.commit_async.begin(); } - + // store new revokable revokable = new_revokable; - + // connect to new revokable if (revokable != null) { revokable.notify[Geary.Revokable.PROP_VALID].connect(on_revokable_valid_changed); @@ -2658,44 +2546,48 @@ revokable.committed.connect(on_revokable_committed); } - Gtk.Action undo_action = this.application.get_action(ACTION_UNDO); - undo_action.tooltip = (revokable != null && description != null) ? description : _("Undo (Ctrl+Z)"); + if (revokable != null && description != null) + this.main_window.main_toolbar.undo_tooltip = description; + else + this.main_window.main_toolbar.undo_tooltip = _("Undo (Ctrl+Z)"); update_revokable_action(); } - + private void update_revokable_action() { - Gtk.Action undo_action = this.application.get_action(ACTION_UNDO); - undo_action.sensitive = revokable != null && revokable.valid && !revokable.in_process; + get_window_action(GearyApplication.ACTION_UNDO).set_enabled( + this.revokable != null && + this.revokable.valid && + !this.revokable.in_process + ); } - + private void on_revokable_valid_changed() { // remove revokable if it goes invalid if (revokable != null && !revokable.valid) save_revokable(null, null); } - + private void on_revokable_committed(Geary.Revokable? committed_revokable) { if (committed_revokable == null) return; // use existing description - Gtk.Action undo_action = this.application.get_action(ACTION_UNDO); - save_revokable(committed_revokable, undo_action.tooltip); + save_revokable(committed_revokable, this.main_window.main_toolbar.undo_tooltip); } - + private void on_revoke() { if (revokable != null && revokable.valid) revokable.revoke_async.begin(null, on_revoke_completed); } - + private void on_revoke_completed(Object? object, AsyncResult result) { // Don't use the "revokable" instance because it might have gone null before this callback // was reached Geary.Revokable? origin = object as Geary.Revokable; if (origin == null) return; - + try { origin.revoke_async.end(result); } catch (Error err) { @@ -2703,27 +2595,30 @@ } } - private void on_zoom_in() { - ConversationListBox? view = - main_window.conversation_viewer.current_list; - if (view != null) { - view.zoom_in(); + private void selection_operation_started() { + this.operation_count += 1; + if (this.operation_count == 1) { + this.main_window.conversation_list_view.set_changing_selection(true); } } - private void on_zoom_out() { - ConversationListBox? view = - main_window.conversation_viewer.current_list; - if (view != null) { - view.zoom_out(); + private void selection_operation_finished() { + this.operation_count -= 1; + if (this.operation_count == 0) { + this.main_window.conversation_list_view.set_changing_selection(false); } } - private void on_zoom_normal() { - ConversationListBox? view = - main_window.conversation_viewer.current_list; - if (view != null) { - view.zoom_reset(); + private void on_zoom(SimpleAction action, Variant? parameter) { + ConversationListBox? view = main_window.conversation_viewer.current_list; + if (view != null && parameter != null) { + string zoom_action = parameter.get_string(); + if (zoom_action == "in") + view.zoom_in(); + else if (zoom_action == "out") + view.zoom_out(); + else + view.zoom_reset(); } } @@ -2732,6 +2627,13 @@ } private void on_sent(Geary.RFC822.Message rfc822) { + // Translators: The label for an in-app notification. The + // string substitution is a list of recipients of the email. + string message = _( + "Successfully sent mail to %s." + ).printf(Util.Email.to_short_recipient_display(rfc822.to)); + InAppNotification notification = new InAppNotification(message); + this.main_window.add_notification(notification); Libnotify.play_sound("message-sent-email"); } @@ -2742,9 +2644,21 @@ private void on_conversation_viewer_email_added(ConversationEmail view) { view.attachments_activated.connect(on_attachments_activated); + view.forward_message.connect(on_forward_message); + view.load_error.connect(on_email_load_error); view.reply_to_message.connect(on_reply_to_message); view.reply_all_message.connect(on_reply_all_message); - view.forward_message.connect(on_forward_message); + + Geary.App.Conversation conversation = main_window.conversation_viewer.current_list.conversation; + bool in_current_folder = (conversation.is_in_base_folder(view.email.id) && + conversation.base_folder == current_folder); + bool supports_trash = in_current_folder && current_folder_supports_trash(); + bool supports_delete = in_current_folder && current_folder is Geary.FolderSupport.Remove; + view.trash_message.connect(on_trash_message); + view.delete_message.connect(on_delete_message); + view.set_folder_actions_enabled(supports_trash, supports_delete); + main_window.on_shift_key.connect(view.shift_key_changed); + view.edit_draft.connect((draft_view) => { create_compose_widget( ComposerWidget.ComposeType.NEW_MESSAGE, @@ -2765,6 +2679,20 @@ view.view_source.connect(on_view_source); } + private void on_trash_message(ConversationEmail target_view) { + Gee.Collection ids = + new Gee.ArrayList(); + ids.add(target_view.email.id); + trash_messages_async.begin(ids, cancellable_folder); + } + + private void on_delete_message(ConversationEmail target_view) { + Gee.Collection ids = + new Gee.ArrayList(); + ids.add(target_view.email.id); + delete_messages_async.begin(ids, cancellable_folder); + } + private void on_view_source(ConversationEmail email_view) { string source = (email_view.email.header.buffer.to_string() + email_view.email.body.buffer.to_string()); @@ -2791,24 +2719,24 @@ } } + private SimpleAction get_window_action(string action_name) { + return (SimpleAction) this.main_window.lookup_action(action_name); + } + // Disables all single-message buttons and enables all multi-message buttons. public void enable_multiple_message_buttons() { - update_tooltips(); + main_window.main_toolbar.selected_conversations = this.selected_conversations.size; // Single message only buttons. - this.application.actions.get_action(ACTION_REPLY_TO_MESSAGE).sensitive = false; - this.application.actions.get_action(ACTION_REPLY_ALL_MESSAGE).sensitive = false; - this.application.actions.get_action(ACTION_FORWARD_MESSAGE).sensitive = false; + get_window_action(ACTION_REPLY_TO_MESSAGE).set_enabled(false); + get_window_action(ACTION_REPLY_ALL_MESSAGE).set_enabled(false); + get_window_action(ACTION_FORWARD_MESSAGE).set_enabled(false); // Mutliple message buttons. - this.application.actions.get_action(ACTION_MOVE_MENU).sensitive = - (current_folder is Geary.FolderSupport.Move); - this.application.actions.get_action(ACTION_ARCHIVE_CONVERSATION).sensitive = - (current_folder is Geary.FolderSupport.Archive); - this.application.actions.get_action(ACTION_TRASH_CONVERSATION).sensitive = - current_folder_supports_trash(); - this.application.actions.get_action(ACTION_DELETE_CONVERSATION).sensitive = - (current_folder is Geary.FolderSupport.Remove); + get_window_action(ACTION_MOVE_MENU).set_enabled(current_folder is Geary.FolderSupport.Move); + get_window_action(ACTION_ARCHIVE_CONVERSATION).set_enabled(current_folder is Geary.FolderSupport.Archive); + get_window_action(ACTION_TRASH_CONVERSATION).set_enabled(current_folder_supports_trash()); + get_window_action(ACTION_DELETE_CONVERSATION).set_enabled(current_folder is Geary.FolderSupport.Remove); cancel_context_dependent_buttons(); enable_context_dependent_buttons_async.begin(true, cancellable_context_dependent_buttons); @@ -2816,34 +2744,30 @@ // Enables or disables the message buttons on the toolbar. public void enable_message_buttons(bool sensitive) { - update_tooltips(); - + main_window.main_toolbar.selected_conversations = this.selected_conversations.size; + // No reply/forward in drafts folder. bool respond_sensitive = sensitive; if (current_folder != null && current_folder.special_folder_type == Geary.SpecialFolderType.DRAFTS) respond_sensitive = false; - this.application.actions.get_action(ACTION_REPLY_TO_MESSAGE).sensitive = respond_sensitive; - this.application.actions.get_action(ACTION_REPLY_ALL_MESSAGE).sensitive = respond_sensitive; - this.application.actions.get_action(ACTION_FORWARD_MESSAGE).sensitive = respond_sensitive; - this.application.actions.get_action(ACTION_MOVE_MENU).sensitive = - sensitive && (current_folder is Geary.FolderSupport.Move); - this.application.actions.get_action(ACTION_ARCHIVE_CONVERSATION).sensitive = sensitive - && (current_folder is Geary.FolderSupport.Archive); - this.application.actions.get_action(ACTION_TRASH_CONVERSATION).sensitive = sensitive - && current_folder_supports_trash(); - this.application.actions.get_action(ACTION_DELETE_CONVERSATION).sensitive = sensitive - && (current_folder is Geary.FolderSupport.Remove); + get_window_action(ACTION_REPLY_TO_MESSAGE).set_enabled(respond_sensitive); + get_window_action(ACTION_REPLY_ALL_MESSAGE).set_enabled(respond_sensitive); + get_window_action(ACTION_FORWARD_MESSAGE).set_enabled(respond_sensitive); + get_window_action(ACTION_MOVE_MENU).set_enabled(sensitive && (current_folder is Geary.FolderSupport.Move)); + get_window_action(ACTION_ARCHIVE_CONVERSATION).set_enabled(sensitive && (current_folder is Geary.FolderSupport.Archive)); + get_window_action(ACTION_TRASH_CONVERSATION).set_enabled(sensitive && current_folder_supports_trash()); + get_window_action(ACTION_DELETE_CONVERSATION).set_enabled(sensitive && (current_folder is Geary.FolderSupport.Remove)); cancel_context_dependent_buttons(); enable_context_dependent_buttons_async.begin(sensitive, cancellable_context_dependent_buttons); } - + private async void enable_context_dependent_buttons_async(bool sensitive, Cancellable? cancellable) { Gee.MultiMap? selected_operations = null; try { if (current_folder != null) { - Geary.App.EmailStore? store = email_stores.get(current_folder.account); + Geary.App.EmailStore? store = get_store_for_folder(current_folder); if (store != null) { selected_operations = yield store .get_supported_operations_async(get_selected_email_ids(false), cancellable); @@ -2853,38 +2777,17 @@ debug("Error checking for what operations are supported in the selected conversations: %s", e.message); } - + // Exit here if the user has cancelled. if (cancellable != null && cancellable.is_cancelled()) return; - + Gee.HashSet supported_operations = new Gee.HashSet(); if (selected_operations != null) supported_operations.add_all(selected_operations.get_values()); - this.application.actions.get_action(ACTION_MARK_AS_MENU).sensitive = - sensitive && (supported_operations.contains(typeof(Geary.FolderSupport.Mark))); - this.application.actions.get_action(ACTION_COPY_MENU).sensitive = - sensitive && (supported_operations.contains(typeof(Geary.FolderSupport.Copy))); - } - - // Updates tooltip text depending on number of conversations selected. - private void update_tooltips() { - bool single = selected_conversations.size == 1; - - this.application.actions.get_action(ACTION_MARK_AS_MENU).tooltip = single ? - MARK_MESSAGE_MENU_TOOLTIP_SINGLE : MARK_MESSAGE_MENU_TOOLTIP_MULTIPLE; - this.application.actions.get_action(ACTION_COPY_MENU).tooltip = single ? - LABEL_MESSAGE_TOOLTIP_SINGLE : LABEL_MESSAGE_TOOLTIP_MULTIPLE; - this.application.actions.get_action(ACTION_MOVE_MENU).tooltip = single ? - MOVE_MESSAGE_TOOLTIP_SINGLE : MOVE_MESSAGE_TOOLTIP_MULTIPLE; - - this.application.actions.get_action(ACTION_ARCHIVE_CONVERSATION).tooltip = single ? - ARCHIVE_CONVERSATION_TOOLTIP_SINGLE : ARCHIVE_CONVERSATION_TOOLTIP_MULTIPLE; - this.application.actions.get_action(ACTION_TRASH_CONVERSATION).tooltip = single ? - TRASH_CONVERSATION_TOOLTIP_SINGLE : TRASH_CONVERSATION_TOOLTIP_MULTIPLE; - this.application.actions.get_action(ACTION_DELETE_CONVERSATION).tooltip = single ? - DELETE_CONVERSATION_TOOLTIP_SINGLE : DELETE_CONVERSATION_TOOLTIP_MULTIPLE; + get_window_action(ACTION_SHOW_MARK_MENU).set_enabled(sensitive && (typeof(Geary.FolderSupport.Mark) in supported_operations)); + get_window_action(ACTION_COPY_MENU).set_enabled(sensitive && (supported_operations.contains(typeof(Geary.FolderSupport.Copy)))); } // Returns a list of composer windows for an account, or null if none. @@ -2892,7 +2795,7 @@ Gee.LinkedList ret = Geary.traverse(composer_widgets) .filter(w => w.account.information == account) .to_linked_list(); - + return ret.size >= 1 ? ret : null; } @@ -2904,37 +2807,44 @@ } private void do_search(string search_text) { - Geary.SearchFolder? folder = null; - try { - folder = (Geary.SearchFolder) current_account.get_special_folder( - Geary.SpecialFolderType.SEARCH); - } catch (Error e) { - debug("Could not get search folder: %s", e.message); - - return; - } - - if (search_text == "") { - if (previous_non_search_folder != null && current_folder is Geary.SearchFolder) - main_window.folder_list.select_folder(previous_non_search_folder); - - main_window.folder_list.remove_search(); - search_text_changed(""); - folder.clear(); - - return; + Geary.SearchFolder? search_folder = null; + if (this.current_account != null) { + try { + search_folder = + this.current_account.get_special_folder( + Geary.SpecialFolderType.SEARCH + ) as Geary.SearchFolder; + } catch (Error e) { + debug("Could not get search folder: %s", e.message); + } } - - if (current_account == null) - return; - - cancel_search(); // Stop any search in progress. - folder.search(search_text, this.application.config.get_search_strategy(), - this.cancellable_search); + if (Geary.String.is_empty_or_whitespace(search_text)) { + if (this.previous_non_search_folder != null && + this.current_folder is Geary.SearchFolder) { + this.main_window.folder_list.select_folder( + this.previous_non_search_folder + ); + } + + this.main_window.folder_list.remove_search(); + + if (search_folder != null) { + search_folder.clear(); + } + } else if (search_folder != null) { + cancel_search(); // Stop any search in progress + + search_folder.search( + search_text, + this.application.config.get_search_strategy(), + this.cancellable_search + ); + + this.main_window.folder_list.set_search(search_folder); + } - main_window.folder_list.set_search(folder); - search_text_changed(main_window.search_bar.search_text); + search_text_changed(search_text); } /** @@ -2943,39 +2853,265 @@ public Gee.Set get_selected_conversations() { return selected_conversations.read_only_view; } - - // Find the first inbox we know about and switch to it. - private void switch_to_first_inbox() { - try { - if (Geary.Engine.instance.get_accounts().values.size == 0) - return; // No account! - - // Look through our accounts, grab the first inbox we can find. - Geary.Folder? first_inbox = null; - - foreach(Geary.AccountInformation info in Geary.Engine.instance.get_accounts().values) { - first_inbox = get_account_instance(info).get_special_folder(Geary.SpecialFolderType.INBOX); - - if (first_inbox != null) + + private inline Geary.App.EmailStore? get_store_for_folder(Geary.Folder target) { + AccountContext? context = this.accounts.get(target.account.information); + return context != null ? context.store : null; + } + + private bool should_add_folder(Gee.Collection? all, + Geary.Folder folder) { + // if folder is openable, add it + if (folder.properties.is_openable != Geary.Trillian.FALSE) + return true; + else if (folder.properties.has_children == Geary.Trillian.FALSE) + return false; + + // if folder contains children, we must ensure that there is at least one of the same type + Geary.SpecialFolderType type = folder.special_folder_type; + foreach (Geary.Folder other in all) { + if (other.special_folder_type == type && other.path.parent == folder.path) + return true; + } + + return false; + } + + private void on_account_available(Geary.AccountInformation info) { + Geary.Account? account = null; + try { + account = Geary.Engine.instance.get_account_instance(info); + } catch (Error e) { + error("Error creating account instance: %s", e.message); + } + + if (account != null) { + upgrade_dialog.add_account(account, cancellable_open_account); + open_account(account); + } + } + + private void on_account_added(Geary.AccountInformation added, + Accounts.Manager.Status status) { + if (status == Accounts.Manager.Status.ENABLED) { + try { + this.application.engine.add_account(added); + } catch (GLib.Error err) { + report_problem( + new Geary.AccountProblemReport( + Geary.ProblemType.GENERIC_ERROR, added, err + ) + ); + } + } + } + + private void on_account_status_changed(Geary.AccountInformation changed, + Accounts.Manager.Status status) { + switch (status) { + case Accounts.Manager.Status.ENABLED: + if (!this.application.engine.has_account(changed.id)) { + try { + this.application.engine.add_account(changed); + } catch (GLib.Error err) { + report_problem( + new Geary.AccountProblemReport( + Geary.ProblemType.GENERIC_ERROR, changed, err + ) + ); + } + } + break; + + case Accounts.Manager.Status.UNAVAILABLE: + case Accounts.Manager.Status.DISABLED: + if (this.application.engine.has_account(changed.id)) { + this.close_account.begin( + changed, + (obj, res) => { + this.close_account.end(res); + try { + this.application.engine.remove_account(changed); + } catch (GLib.Error err) { + report_problem( + new Geary.AccountProblemReport( + Geary.ProblemType.GENERIC_ERROR, + changed, + err + ) + ); + } + } + ); + } + break; + } + } + + private void on_account_removed(Geary.AccountInformation removed) { + debug("%s: Closing account for removal", removed.id); + this.close_account.begin( + removed, + (obj, res) => { + this.close_account.end(res); + debug("%s: Account closed", removed.id); + try { + this.application.engine.remove_account(removed); + debug("%s: Account removed from engine", removed.id); + } catch (GLib.Error err) { + report_problem( + new Geary.AccountProblemReport( + Geary.ProblemType.GENERIC_ERROR, + removed, + err + ) + ); + } + } + ); + } + + private void on_report_problem(Geary.ProblemReport problem) { + report_problem(problem); + } + + private void on_retry_problem(MainWindowInfoBar info_bar) { + Geary.ServiceProblemReport? service_report = + info_bar.report as Geary.ServiceProblemReport; + if (service_report != null) { + AccountContext? context = this.accounts.get(service_report.account); + if (context != null && context.account.is_open()) { + switch (service_report.service.protocol) { + case Geary.Protocol.IMAP: + context.account.incoming.restart.begin(context.cancellable); break; + + case Geary.Protocol.SMTP: + context.account.outgoing.restart.begin(context.cancellable); + break; + } } - - if (first_inbox == null) - return; - - // Attempt the selection. Try the inboxes branch first. - if (!main_window.folder_list.select_inbox(first_inbox.account)) - main_window.folder_list.select_folder(first_inbox); - } catch (Error e) { - debug("Could not locate inbox: %s", e.message); } } + private void on_account_status_notify() { + update_account_status(); + } + + private void on_authentication_failure(Geary.AccountInformation account, + Geary.ServiceInformation service) { + AccountContext? context = this.accounts.get(account); + if (context != null && !is_currently_prompting()) { + this.prompt_for_password.begin(context, service); + } + } + + private void on_untrusted_host(Geary.AccountInformation account, + Geary.ServiceInformation service, + Geary.Endpoint endpoint, + TlsConnection cx) { + AccountContext? context = this.accounts.get(account); + if (context != null && !is_currently_prompting()) { + this.prompt_untrusted_host.begin(context, service, endpoint, cx); + } + } + + private void on_retry_service_problem(Geary.ClientService.Status type) { + bool has_restarted = false; + foreach (AccountContext context in this.accounts.values) { + Geary.Account account = context.account; + if (account.current_status.has_service_problem() && + (account.incoming.current_status == type || + account.outgoing.current_status == type)) { + + Geary.ClientService service = + (account.incoming.current_status == type) + ? account.incoming + : account.outgoing; + + bool do_restart = true; + switch (type) { + case AUTHENTICATION_FAILED: + if (has_restarted) { + // Only restart at most one at a time, so we + // don't attempt to re-auth multiple bad + // accounts at once. + do_restart = false; + } else { + // Reset so the infobar does not show up again + context.authentication_failed = false; + } + break; + + case TLS_VALIDATION_FAILED: + if (has_restarted) { + // Only restart at most one at a time, so we + // don't attempt to re-pin multiple bad + // accounts at once. + do_restart = false; + } else { + // Reset so the infobar does not show up again + context.tls_validation_failed = false; + } + break; + } + + if (do_restart) { + has_restarted = true; + service.restart.begin(context.cancellable); + } + } + } + } + + private void on_scan_completed() { + // Done scanning. Check if we have enough messages to fill + // the conversation list; if not, trigger a load_more(); + if (!main_window.conversation_list_has_scrollbar()) { + debug("Not enough messages, loading more for folder %s", current_folder.to_string()); + on_load_more(); + } + } + + private void on_scan_error(Geary.App.ConversationMonitor monitor, Error err) { + // XXX determine the problem better here + Geary.AccountInformation account = + monitor.base_folder.account.information; + report_problem( + new Geary.ServiceProblemReport( + Geary.ProblemType.GENERIC_ERROR, + account, + account.incoming, + err + ) + ); + } + + private void on_email_load_error(ConversationEmail view, GLib.Error err) { + // XXX determine the problem better here + report_problem( + new Geary.ServiceProblemReport( + Geary.ProblemType.GENERIC_ERROR, + this.current_account.information, + this.current_account.information.incoming, + err + ) + ); + } + private void on_save_attachments(Gee.Collection attachments) { + GLib.Cancellable? cancellable = null; + if (this.current_account != null) { + cancellable = this.accounts.get( + this.current_account.information + ).cancellable; + } if (attachments.size == 1) { - this.save_attachment_to_file.begin(attachments.to_array()[0], null); + this.save_attachment_to_file.begin( + attachments.to_array()[0], null, cancellable + ); } else { - this.save_attachments_to_file.begin(attachments); + this.save_attachments_to_file.begin(attachments, cancellable); } } @@ -2991,10 +3127,16 @@ string url, string? alt_text, Geary.Memory.Buffer resource_buf) { + GLib.Cancellable? cancellable = null; + if (this.current_account != null) { + cancellable = this.accounts.get( + this.current_account.information + ).cancellable; + } + // This is going to be either an inline image, or a remote // image, so either treat it as an attachment ot assume we'll // have a valid filename in the URL - bool handled = false; if (url.has_prefix(ClientWebView.CID_URL_PREFIX)) { string cid = url.substring(ClientWebView.CID_URL_PREFIX.length); @@ -3005,24 +3147,28 @@ debug("Could not get attachment \"%s\": %s", cid, err.message); } if (attachment != null) { - this.save_attachment_to_file.begin(attachment, alt_text); + this.save_attachment_to_file.begin( + attachment, alt_text, cancellable + ); handled = true; } } if (!handled) { - File source = File.new_for_uri(url); - string filename = source.get_basename(); + GLib.File source = GLib.File.new_for_uri(url); + // Querying the URL-based file for the display name + // results in it being looked up, so just get the basename + // from it directly. GIO seems to decode any %-encoded + // chars anyway. + string? display_name = source.get_basename(); + if (Geary.String.is_empty_or_whitespace(display_name)) { + display_name = GearyController.untitled_file_name; + } + this.prompt_save_buffer.begin( - filename, resource_buf, - (obj, res) => { - try { - this.prompt_save_buffer.end(res); - } catch (Error err) { - message("Unable to save buffer to \"%s\": %s", filename, err.message); - } - }); + display_name, resource_buf, cancellable + ); } } -} +} diff -Nru geary-0.12.4/src/client/application/goa-mediator.vala geary-3.32.0/src/client/application/goa-mediator.vala --- geary-0.12.4/src/client/application/goa-mediator.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/src/client/application/goa-mediator.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,191 @@ +/* + * Copyright 2017 Software Freedom Conservancy Inc. + * Copyright 2018 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +/** GNOME Online Accounts token adapter. */ +public class GoaMediator : Geary.CredentialsMediator, Object { + + + private Goa.Object handle; + + + public GoaMediator(Goa.Object handle) { + this.handle = handle; + } + + public Geary.ServiceProvider get_service_provider() { + Geary.ServiceProvider provider = Geary.ServiceProvider.OTHER; + switch (this.handle.get_account().provider_type) { + case "google": + provider = Geary.ServiceProvider.GMAIL; + break; + + case "windows_live": + provider = Geary.ServiceProvider.OUTLOOK; + break; + } + return provider; + } + + public string get_service_label() { + return this.handle.get_account().provider_name; + } + + public async void update(Geary.AccountInformation geary_account, + GLib.Cancellable? cancellable) + throws GLib.Error { + // Call this to get the exception thrown if no auth method is + // supported. + get_auth_method(); + + update_imap_config(geary_account.incoming); + update_smtp_config(geary_account.outgoing); + } + + public virtual async bool load_token(Geary.AccountInformation account, + Geary.ServiceInformation service, + Cancellable? cancellable) + throws GLib.Error { + // XXX have to call the sync version of this since the async + // version seems to be broken. See + // https://gitlab.gnome.org/GNOME/vala/issues/709 + this.handle.get_account().call_ensure_credentials_sync( + null, cancellable + ); + + bool loaded = false; + string? token = null; + + switch (get_auth_method()) { + case OAUTH2: + this.handle.get_oauth2_based().call_get_access_token_sync( + out token, null, cancellable + ); + break; + + case PASSWORD: + switch (service.protocol) { + case Geary.Protocol.IMAP: + this.handle.get_password_based().call_get_password_sync( + "imap-password", out token, cancellable + ); + break; + + case Geary.Protocol.SMTP: + this.handle.get_password_based().call_get_password_sync( + "smtp-password", out token, cancellable + ); + break; + + default: + return false; + } + break; + } + + if (token != null) { + service.credentials = service.credentials.copy_with_token(token); + loaded = true; + } + return loaded; + } + + private Geary.Credentials.Method get_auth_method() throws GLib.Error { + if (this.handle.get_oauth2_based() != null) { + return Geary.Credentials.Method.OAUTH2; + } + if (this.handle.get_password_based() != null) { + return Geary.Credentials.Method.PASSWORD; + } + throw new Geary.EngineError.UNSUPPORTED( + "GOA account supports neither password or OAuth2 auth" + ); + } + + private void update_imap_config(Geary.ServiceInformation service) + throws GLib.Error { + Goa.Mail? mail = this.handle.get_mail(); + if (mail != null) { + parse_host_name(service, mail.imap_host); + + if (mail.imap_use_ssl) { + service.transport_security = Geary.TlsNegotiationMethod.TRANSPORT; + } else if (mail.imap_use_tls) { + service.transport_security = Geary.TlsNegotiationMethod.START_TLS; + } else { + service.transport_security = Geary.TlsNegotiationMethod.NONE; + } + + service.credentials = new Geary.Credentials( + get_auth_method(), mail.imap_user_name + ); + + if (service.port == 0) { + service.port = service.get_default_port(); + } + } + } + + private void update_smtp_config(Geary.ServiceInformation service) + throws GLib.Error { + Goa.Mail? mail = this.handle.get_mail(); + if (mail != null) { + parse_host_name(service, mail.smtp_host); + + if (mail.smtp_use_ssl) { + service.transport_security = Geary.TlsNegotiationMethod.TRANSPORT; + } else if (mail.smtp_use_tls) { + service.transport_security = Geary.TlsNegotiationMethod.START_TLS; + } else { + service.transport_security = Geary.TlsNegotiationMethod.NONE; + } + + if (mail.smtp_use_auth) { + service.credentials_requirement = Geary.Credentials.Requirement.CUSTOM; + } else { + service.credentials_requirement = Geary.Credentials.Requirement.NONE; + } + + if (mail.smtp_use_auth) { + service.credentials = new Geary.Credentials( + get_auth_method(), mail.smtp_user_name + ); + } + + if (service.port == 0) { + service.port = service.get_default_port(); + } + } + } + + private void parse_host_name(Geary.ServiceInformation service, + string host_name) { + // Fall back to trying to use the host name as-is. + // At least the user can see it in the settings if + // they look. + service.host = host_name; + service.port = 0; + + try { + GLib.NetworkAddress address = GLib.NetworkAddress.parse( + host_name, service.port + ); + + service.host = address.hostname; + service.port = (uint16) address.port; + } catch (GLib.Error err) { + warning( + "GOA account \"%s\" %s hostname \"%s\": %", + this.handle.get_account().id, + service.protocol.to_value(), + host_name, + err.message + ); + } + } + +} diff -Nru geary-0.12.4/src/client/application/secret-mediator.vala geary-3.32.0/src/client/application/secret-mediator.vala --- geary-0.12.4/src/client/application/secret-mediator.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/application/secret-mediator.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,4 +1,5 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. @@ -34,125 +35,81 @@ null ); - private Geary.Nonblocking.Mutex dialog_mutex = new Geary.Nonblocking.Mutex(); - - public virtual async string? get_password_async(Geary.Service service, - Geary.AccountInformation account, - Cancellable? cancellable = null) - throws Error { + public async SecretMediator(GLib.Cancellable? cancellable) + throws GLib.Error { yield check_unlocked(cancellable); + } - string? password = yield Secret.password_lookupv( - SecretMediator.schema, new_attrs(service, account), cancellable - ); + public virtual async bool load_token(Geary.AccountInformation account, + Geary.ServiceInformation service, + Cancellable? cancellable) + throws GLib.Error { + bool loaded = false; + if (service.credentials != null) { + if (service.remember_password) { + string? password = yield Secret.password_lookupv( + SecretMediator.schema, new_attrs(service), cancellable + ); - if (password == null) { - password = yield migrate_old_password(service, account, cancellable); + if (password == null) { + password = yield migrate_old_password(service, cancellable); + } + + if (password != null) { + service.credentials = + service.credentials.copy_with_token(password); + loaded = true; + } + } else { + // Not remembering the password, so just make sure it + // has been filled in + loaded = service.credentials.is_complete(); + } } - if (password == null) - debug("Unable to fetch password in libsecret keyring for %s", account.id); - - return password; + return loaded; } - public virtual async void set_password_async(Geary.Service service, - Geary.AccountInformation account, - Cancellable? cancellable = null) - throws Error { - yield check_unlocked(cancellable); - - Geary.Credentials credentials = get_credentials(service, account); - try { - yield do_store(service, account, credentials.pass, cancellable); - } catch (Error e) { - debug("Unable to store password for \"%s\" %s in libsecret keyring: %s", - account.id, service.name(), e.message); + public async void update_token(Geary.AccountInformation account, + Geary.ServiceInformation service, + Cancellable? cancellable) + throws GLib.Error { + if (service.credentials != null) { + yield do_store(service, service.credentials.token, cancellable); } } - public virtual async void clear_password_async(Geary.Service service, - Geary.AccountInformation account, - Cancellable? cancellable = null) - throws Error { - yield check_unlocked(cancellable); - - Geary.Credentials credentials = get_credentials(service, account); - yield Secret.password_clearv(SecretMediator.schema, - new_attrs(service, account), - cancellable); - - // Remove legacy formats - // <= 0.11 - yield Secret.password_clear( - compat_schema, - cancellable, - "user", get_legacy_user(service, account.primary_mailbox.address) - ); - // <= 0.6 - yield Secret.password_clear( - compat_schema, - cancellable, - "user", get_legacy_user(service, credentials.user) - ); - } - - public virtual async bool prompt_passwords_async(Geary.ServiceFlag services, - Geary.AccountInformation account_information, - out string? imap_password, out string? smtp_password, - out bool imap_remember_password, out bool smtp_remember_password) throws Error { - // Our dialog doesn't support asking for both at once, even though this - // API would indicate it does. We need to revamp the API. - assert(!services.has_imap() || !services.has_smtp()); - - // to prevent multiple dialogs from popping up at the same time, use a nonblocking mutex - // to serialize the code - int token = yield dialog_mutex.claim_async(null); - - // If the main window is hidden, make it visible now and present to user as transient parent - Gtk.Window? main_window = GearyApplication.instance.controller.main_window; - if (main_window != null && !main_window.visible) { - main_window.show_all(); - GearyApplication.instance.present(); - } - - PasswordDialog password_dialog = new PasswordDialog(main_window, services.has_smtp(), - account_information, services); - bool result = password_dialog.run(); - - dialog_mutex.release(ref token); - - if (!result) { - // user cancelled the dialog - imap_password = null; - smtp_password = null; - imap_remember_password = false; - smtp_remember_password = false; - return false; - } - - // password_dialog.password should never be null at this point. It will only be null when - // password_dialog.run() returns false, in which case we have already returned. - if (services.has_smtp()) { - imap_password = null; - imap_remember_password = false; - smtp_password = password_dialog.password; - smtp_remember_password = password_dialog.remember_password; - } else { - imap_password = password_dialog.password; - imap_remember_password = password_dialog.remember_password; - smtp_password = null; - smtp_remember_password = false; + public async void clear_token(Geary.AccountInformation account, + Geary.ServiceInformation service, + Cancellable? cancellable) + throws Error { + if (service.credentials != null) { + yield Secret.password_clearv(SecretMediator.schema, + new_attrs(service), + cancellable); + + // Remove legacy formats + // <= 0.11 + yield Secret.password_clear( + compat_schema, + cancellable, + "user", get_legacy_user(service, account.primary_mailbox.address) + ); + // <= 0.6 + yield Secret.password_clear( + compat_schema, + cancellable, + "user", get_legacy_user(service, service.credentials.user) + ); } - return true; } // Ensure the default collection unlocked. Try to unlock it since // the user may be running in a limited environment and it would // prevent us from prompting the user multiple times in one // session. See Bug 784300. - private async void check_unlocked(Cancellable? cancellable = null) + private async void check_unlocked(Cancellable? cancellable) throws Error { Secret.Service service = yield Secret.Service.get( Secret.ServiceFlags.OPEN_SESSION, cancellable @@ -180,104 +137,65 @@ } } - private async void do_store(Geary.Service service, - Geary.AccountInformation account, + private async void do_store(Geary.ServiceInformation service, string password, Cancellable? cancellable) - throws Error { + throws Error { yield Secret.password_storev( SecretMediator.schema, - new_attrs(service, account), + new_attrs(service), Secret.COLLECTION_DEFAULT, - "Geary %s password".printf(service.name()), + "Geary %s password".printf(to_proto_value(service.protocol)), password, cancellable ); } - private HashTable new_attrs(Geary.Service service, - Geary.AccountInformation account, - Cancellable? cancellable = null) { - string login = ""; - string host = ""; - switch (service) { - case Geary.Service.IMAP: - login = account.imap_credentials.user; - host = account.get_imap_endpoint().remote_address.get_hostname(); - break; - - case Geary.Service.SMTP: - login = account.smtp_credentials.user; - host = account.get_smtp_endpoint().remote_address.get_hostname(); - break; - - default: - warning("Unknown service type"); - break; - } - - HashTable table = new HashTable(str_hash, str_equal); - table.insert(ATTR_PROTO, service.name()); - table.insert(ATTR_HOST, host); - table.insert(ATTR_LOGIN, login); + private HashTable new_attrs(Geary.ServiceInformation service) { + HashTable table = new HashTable( + str_hash, str_equal + ); + table.insert(ATTR_PROTO, to_proto_value(service.protocol)); + table.insert(ATTR_HOST, service.host); + table.insert(ATTR_LOGIN, service.credentials.user); return table; } - private Geary.Credentials get_credentials(Geary.Service service, Geary.AccountInformation account) { - switch (service) { - case Geary.Service.IMAP: - return account.imap_credentials; - - case Geary.Service.SMTP: - return account.smtp_credentials; - - default: - assert_not_reached(); - } + private inline string to_proto_value(Geary.Protocol protocol) { + return protocol.to_value().ascii_up(); } - private async string? migrate_old_password(Geary.Service service, - Geary.AccountInformation account, - Cancellable? cancellable = null) - throws Error { + private async string? migrate_old_password(Geary.ServiceInformation service, + GLib.Cancellable? cancellable) + throws GLib.Error { // <= 0.11 + string user = get_legacy_user(service, service.credentials.user); string? password = yield Secret.password_lookup( compat_schema, cancellable, - "user", get_legacy_user(service, account.primary_mailbox.address) + "user", user ); - // <= 0.6 - if (password == null) { - Geary.Credentials creds = get_credentials(service, account); - string user = get_legacy_user(service, creds.user); - password = yield Secret.password_lookup( + if (password != null) { + // Clear the old password + yield Secret.password_clear( compat_schema, cancellable, "user", user ); - // Clear the old password - if (password != null) { - yield Secret.password_clear( - compat_schema, - cancellable, - "user", user - ); - } + // Store it in the new format + yield do_store(service, password, cancellable); } - if (password != null) - yield do_store(service, account, password, cancellable); - return password; } - private string get_legacy_user(Geary.Service service, string user) { - switch (service) { - case Geary.Service.IMAP: + private string get_legacy_user(Geary.ServiceInformation service, string user) { + switch (service.protocol) { + case Geary.Protocol.IMAP: return "org.yorba.geary imap_username:" + user; - case Geary.Service.SMTP: + case Geary.Protocol.SMTP: return "org.yorba.geary smtp_username:" + user; default: warning("Unknown service type"); diff -Nru geary-0.12.4/src/client/components/client-web-view.vala geary-3.32.0/src/client/components/client-web-view.vala --- geary-0.12.4/src/client/components/client-web-view.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/components/client-web-view.vala 2019-03-17 13:39:29.000000000 +0000 @@ -14,7 +14,7 @@ * integration, Inspector support, and remote and inline image * handling. */ -public class ClientWebView : WebKit.WebView, Geary.BaseInterface { +public abstract class ClientWebView : WebKit.WebView, Geary.BaseInterface { /** URI Scheme and delimiter for internal resource loads. */ @@ -26,13 +26,22 @@ /** URI Scheme and delimiter for images loaded by Content-ID. */ public const string CID_URL_PREFIX = "cid:"; + // WebKit message handler names + private const string COMMAND_STACK_CHANGED = "commandStackChanged"; private const string CONTENT_LOADED = "contentLoaded"; + private const string DOCUMENT_MODIFIED = "documentModified"; private const string PREFERRED_HEIGHT_CHANGED = "preferredHeightChanged"; private const string REMOTE_IMAGE_LOAD_BLOCKED = "remoteImageLoadBlocked"; private const string SELECTION_CHANGED = "selectionChanged"; private const double ZOOM_DEFAULT = 1.0; private const double ZOOM_FACTOR = 0.1; + private const double ZOOM_MAX = 2.0; + private const double ZOOM_MIN = 0.5; + + private const string USER_CSS = "user-style.css"; + private const string USER_CSS_LEGACY = "user-message.css"; + // Workaround WK binding ctor not accepting any args @@ -53,9 +62,12 @@ private static WebKit.WebContext? default_context = null; + private static WebKit.UserStyleSheet? user_stylesheet = null; + private static WebKit.UserScript? script = null; private static WebKit.UserScript? allow_remote_images = null; + /** * Initialises WebKit.WebContext for use by the client. */ @@ -65,8 +77,12 @@ bool enable_logging) { WebsiteDataManager data_manager = new WebsiteDataManager(cache_dir.get_path()); WebKit.WebContext context = new WebKit.WebContext.with_website_data_manager(data_manager); + // Use a shared process so we don't spawn N WebProcess instances + // when showing N messages in a conversation. context.set_process_model(WebKit.ProcessModel.SHARED_SECONDARY_PROCESS); - context.set_cache_model(WebKit.CacheModel.DOCUMENT_BROWSER); + // Use the doc viewer model since each web view instance only + // ever shows a single HTML document. + context.set_cache_model(WebKit.CacheModel.DOCUMENT_VIEWER); context.register_uri_scheme("cid", (req) => { ClientWebView? view = req.get_web_view() as ClientWebView; @@ -100,19 +116,35 @@ /** * Loads static resources used by ClientWebView. */ - public static void load_scripts() - throws Error { + public static void load_resources(GLib.File user_dir) + throws GLib.Error { ClientWebView.script = load_app_script( "client-web-view.js" ); ClientWebView.allow_remote_images = load_app_script( "client-web-view-allow-remote-images.js" ); + + foreach (string name in new string[] { USER_CSS, USER_CSS_LEGACY }) { + GLib.File stylesheet = user_dir.get_child(name); + try { + ClientWebView.user_stylesheet = load_user_stylesheet(stylesheet); + break; + } catch (GLib.IOError.NOT_FOUND err) { + // All good, try the next one or just exit + } catch (GLib.FileError.NOENT err) { + // Ditto + } catch (GLib.Error err) { + warning( + "Could not load %s: %s", stylesheet.get_path(), err.message + ); + } + } } /** Loads an application-specific WebKit stylesheet. */ protected static WebKit.UserStyleSheet load_app_stylesheet(string name) - throws Error { + throws GLib.Error { return new WebKit.UserStyleSheet( GioUtil.read_resource(name), WebKit.UserContentInjectedFrames.TOP_FRAME, @@ -122,24 +154,17 @@ ); } - /** Loads a user stylesheet, if any. */ - protected static WebKit.UserStyleSheet? load_user_stylesheet(File name) { - WebKit.UserStyleSheet? user_stylesheet = null; - try { - Geary.Memory.FileBuffer buf = new Geary.Memory.FileBuffer(name, true); - user_stylesheet = new WebKit.UserStyleSheet( - buf.get_valid_utf8(), - WebKit.UserContentInjectedFrames.ALL_FRAMES, - WebKit.UserStyleLevel.USER, - null, - null - ); - } catch (IOError.NOT_FOUND err) { - debug("User CSS file does not exist: %s", err.message); - } catch (Error err) { - debug("Failed to load user CSS file: %s", err.message); - } - return user_stylesheet; + /** Loads a user stylesheet from disk. */ + protected static WebKit.UserStyleSheet? load_user_stylesheet(GLib.File name) + throws GLib.Error { + Geary.Memory.FileBuffer buf = new Geary.Memory.FileBuffer(name, true); + return new WebKit.UserStyleSheet( + buf.get_valid_utf8(), + WebKit.UserContentInjectedFrames.ALL_FRAMES, + WebKit.UserStyleLevel.USER, + null, + null + ); } /** Loads an application-specific WebKit JavaScript script. */ @@ -193,11 +218,14 @@ /** Determines if the view has any selected text */ public bool has_selection { get; private set; default = false; } - /** Determines if the view has started rendering the HTML */ - public bool has_valid_height { get; private set; default = false; } - - /** The HTML content's current preferred height. */ - public int preferred_height { get; private set; default = 0; } + /** The HTML content's current preferred height in window pixels. */ + public int preferred_height { + get { + return (int) GLib.Math.round( + this.webkit_reported_height * this.zoom_level + ); + } + } public string document_font { get { @@ -237,6 +265,8 @@ private Gee.List registered_message_handlers = new Gee.LinkedList(); + private double webkit_reported_height = 0; + /** * Emitted when the view's content has finished loaded. @@ -246,6 +276,12 @@ */ public signal void content_loaded(); + /** Emitted when the web view's undo/redo stack state changes. */ + public signal void command_stack_changed(bool can_undo, bool can_redo); + + /** Emitted when the web view's content has changed. */ + public signal void document_modified(); + /** Emitted when the view's selection has changed. */ public signal void selection_changed(bool has_selection); @@ -259,8 +295,8 @@ public signal void remote_image_load_blocked(); - public ClientWebView(Configuration config, - WebKit.UserContentManager? custom_manager = null) { + protected ClientWebView(Configuration config, + WebKit.UserContentManager? custom_manager = null) { WebKit.Settings setts = new WebKit.Settings(); setts.allow_modal_dialogs = false; setts.default_charset = "UTF-8"; @@ -279,6 +315,9 @@ WebKit.UserContentManager content_manager = custom_manager ?? new WebKit.UserContentManager(); content_manager.add_script(ClientWebView.script); + if (ClientWebView.user_stylesheet != null) { + content_manager.add_style_sheet(ClientWebView.user_stylesheet); + } Object( web_context: ClientWebView.default_context, @@ -290,15 +329,20 @@ // XXX get the allow prefix from the extension somehow this.decide_policy.connect(on_decide_policy); - this.web_process_crashed.connect(() => { - debug("Web process crashed"); - return Gdk.EVENT_PROPAGATE; + this.web_process_terminated.connect((reason) => { + warning("Web process crashed: %s", reason.to_string()); }); register_message_handler( + COMMAND_STACK_CHANGED, on_command_stack_changed + ); + register_message_handler( CONTENT_LOADED, on_content_loaded ); register_message_handler( + DOCUMENT_MODIFIED, on_document_modified + ); + register_message_handler( PREFERRED_HEIGHT_CHANGED, on_preferred_height_changed ); register_message_handler( @@ -308,8 +352,13 @@ SELECTION_CHANGED, on_selection_changed ); - // Manage zoom level + // Manage zoom level, ensure it's sane config.bind(Configuration.CONVERSATION_VIEWER_ZOOM_KEY, this, "zoom_level"); + if (this.zoom_level < ZOOM_MIN) { + this.zoom_level = ZOOM_MIN; + } else if (this.zoom_level > ZOOM_MAX) { + this.zoom_level = ZOOM_MAX; + } this.scroll_event.connect(on_scroll_event); // Watch desktop font settings @@ -341,6 +390,15 @@ } /** + * Returns the view's content as an HTML string. + */ + public async string? get_html() throws Error { + return WebKitUtil.to_string( + yield call(Geary.JS.callable("geary.getHtml"), null) + ); + } + + /** * Adds an resource that may be accessed from the view via a URL. * * Internal resources may be access via both the internal `geary` @@ -398,16 +456,37 @@ execute_editing_command(WebKit.EDITING_COMMAND_COPY); } - public void reset_zoom() { - this.zoom_level == ZOOM_DEFAULT; + public void zoom_reset() { + this.zoom_level = ZOOM_DEFAULT; + // Notify the preferred height has changed since it depends on + // the zoom level. Same for zoom in and out below. + notify_property("preferred-height"); } public void zoom_in() { - this.zoom_level += (this.zoom_level * ZOOM_FACTOR); + double new_zoom = this.zoom_level += (this.zoom_level * ZOOM_FACTOR); + if (new_zoom > ZOOM_MAX) { + new_zoom = ZOOM_MAX; + } + this.zoom_level = new_zoom; + notify_property("preferred-height"); } public void zoom_out() { - this.zoom_level -= (this.zoom_level * ZOOM_FACTOR); + double new_zoom = this.zoom_level -= (this.zoom_level * ZOOM_FACTOR); + if (new_zoom < ZOOM_MIN) { + new_zoom = ZOOM_MIN; + } + this.zoom_level = new_zoom; + notify_property("preferred-height"); + } + + public new async void set_editable(bool enabled, + Cancellable? cancellable) + throws Error { + yield call( + Geary.JS.callable("geary.setEditable").bool(enabled), cancellable + ); } /** @@ -478,9 +557,11 @@ type == WebKit.PolicyDecisionType.NEW_WINDOW_ACTION) { WebKit.NavigationPolicyDecision nav_policy = (WebKit.NavigationPolicyDecision) policy; - switch (nav_policy.get_navigation_type()) { + WebKit.NavigationAction nav_action = + nav_policy.get_navigation_action(); + switch (nav_action.get_navigation_type()) { case WebKit.NavigationType.OTHER: - if (nav_policy.request.uri == INTERNAL_URL_BODY) { + if (nav_action.get_request().uri == INTERNAL_URL_BODY) { policy.use(); } else { policy.ignore(); @@ -500,7 +581,7 @@ // and WebKitGTK Bug 182528 // policy.ignore(); - link_activated(nav_policy.request.uri); + link_activated(nav_action.get_request().uri); break; default: @@ -535,24 +616,30 @@ } private void on_preferred_height_changed(WebKit.JavascriptResult result) { + double height = this.webkit_reported_height; try { - int height = (int) WebKitUtil.to_number(result); - // Avoid notifying if the values have not changed - if (this.preferred_height != height) { - // value has changed - this.preferred_height = height; - if (height >= 1) { - // value is valid - if (!this.has_valid_height) { - // validity has changed - this.has_valid_height = true; - } - queue_resize(); - } - } + height = WebKitUtil.to_number(result); } catch (Geary.JS.Error err) { debug("Could not get preferred height: %s", err.message); } + + if (this.webkit_reported_height != height) { + this.webkit_reported_height = height; + notify_property("preferred-height"); + } + } + + private void on_command_stack_changed(WebKit.JavascriptResult result) { + try { + string[] values = WebKitUtil.to_string(result).split(","); + command_stack_changed(values[0] == "true", values[1] == "true"); + } catch (Geary.JS.Error err) { + debug("Could not get command stack state: %s", err.message); + } + } + + private void on_document_modified(WebKit.JavascriptResult result) { + document_modified(); } private void on_remote_image_load_blocked(WebKit.JavascriptResult result) { diff -Nru geary-0.12.4/src/client/components/components-placeholder-pane.vala geary-3.32.0/src/client/components/components-placeholder-pane.vala --- geary-0.12.4/src/client/components/components-placeholder-pane.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/src/client/components/components-placeholder-pane.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,64 @@ +/* + * Copyright 2016 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +/** + * A placeholder image and message for empty views. + */ +[GtkTemplate (ui = "/org/gnome/Geary/components-placeholder-pane.ui")] +public class Components.PlaceholderPane : Gtk.Grid { + + + public const string CLASS_HAS_TEXT = "geary-has-text"; + + + /** The icon name of the pane's image. */ + public string icon_name { + owned get { return this.placeholder_image.icon_name; } + set { this.placeholder_image.icon_name = value; } + } + + /** The text of the pane's title label. */ + public string title { + get { return this.title_label.get_text(); } + set { + this.title_label.set_text(value); + update(); + } + } + + /** The text of the pane's sub-title label. */ + public string subtitle { + get { return this.subtitle_label.get_text(); } + set { + this.subtitle_label.set_text(value); + update(); + } + } + + [GtkChild] + private Gtk.Image placeholder_image; + + [GtkChild] + private Gtk.Label title_label; + + [GtkChild] + private Gtk.Label subtitle_label; + + + private void update() { + if (Geary.String.is_empty_or_whitespace(this.title_label.get_text())) { + this.title_label.hide(); + } + if (Geary.String.is_empty_or_whitespace(this.subtitle_label.get_text())) { + this.subtitle_label.hide(); + } + if (this.title_label.visible || this.subtitle_label.visible) { + get_style_context().add_class(CLASS_HAS_TEXT); + } + } + +} diff -Nru geary-0.12.4/src/client/components/components-validator.vala geary-3.32.0/src/client/components/components-validator.vala --- geary-0.12.4/src/client/components/components-validator.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/src/client/components/components-validator.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,490 @@ +/* + * Copyright 2018 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +/** + * Validates the contents of a Gtk Entry as they are entered. + * + * This class may be used to validate required, but otherwise free + * form entries. Subclasses may perform more complex and task-specific + * validation. + */ +public class Components.Validator : GLib.Object { + + + private const Gtk.EntryIconPosition ICON_POS = + Gtk.EntryIconPosition.SECONDARY; + + + /** + * The state of the entry monitored by this validator. + * + * Only {@link VALID} can be considered strictly valid, all other + * states should be treated as being invalid. + */ + public enum Validity { + /** The contents of the entry have not been validated. */ + INDETERMINATE, + + /** The contents of the entry is valid. */ + VALID, + + /** + * The contents of the entry is being checked. + * + * See {@link validate} for the use of this value. + */ + IN_PROGRESS, + + /** The contents of the entry is required but not present. */ + EMPTY, + + /** The contents of the entry is not valid. */ + INVALID; + } + + /** The cause of a validity check being required. */ + public enum Trigger { + /** The entry's contents changed */ + CHANGED, + /** The entry lost the keyboard focus. */ + LOST_FOCUS, + /** The user activated the entry. */ + ACTIVATED; + } + + /** Defines the UI state for a specific validity. */ + protected struct UiState { + public string? icon_name; + public string? icon_tooltip_text; + } + + /** The entry being monitored */ + public Gtk.Entry target { get; private set; } + + /** Determines if the current state indicates the entry is valid. */ + public bool is_valid { + get { return (this.state == Validity.VALID); } + } + + /** + * Determines how empty entries are treated. + * + * If true, an empty entry is considered {@link Validity.EMPTY} + * (i.e. invalid) else it is considered to be {@link + * Validity.INDETERMINATE}. + */ + public bool is_required { get; set; default = true; } + + /** The current validation state of the entry. */ + public Validity state { + get; private set; default = Validity.INDETERMINATE; + } + + /** The UI state to use when indeterminate. */ + public UiState indeterminate_state; + + /** The UI state to use when valid. */ + public UiState valid_state; + + /** The UI state to use when in progress. */ + public UiState in_progress_state; + + /** The UI state to use when empty. */ + public UiState empty_state; + + /** The UI state to use when invalid. */ + public UiState invalid_state; + + // Determines if the value has changed since last validation + private bool target_changed = false; + + private Geary.TimeoutManager ui_update_timer; + + private Geary.TimeoutManager pulse_timer; + bool did_pulse = false; + + + /** Fired when the validation state changes. */ + public signal void state_changed(Trigger reason, Validity prev_state); + + /** Fired when validation completes after the target has changed. */ + public signal void changed(); + + /** Fired when validation completes after the target was activated. */ + public signal void activated(); + + /** Fired when validation completes after the target lost focus. */ + public signal void focus_lost(); + + + public Validator(Gtk.Entry target) { + this.target = target; + + this.ui_update_timer = new Geary.TimeoutManager.seconds( + 2, on_update_ui + ); + + this.pulse_timer = new Geary.TimeoutManager.milliseconds( + 200, on_pulse + ); + this.pulse_timer.repetition = FOREVER; + + this.indeterminate_state = { + target.get_icon_name(ICON_POS), + target.get_icon_tooltip_text(ICON_POS) + }; + this.valid_state = { + target.get_icon_name(ICON_POS), + target.get_icon_tooltip_text(ICON_POS) + }; + this.in_progress_state = { + target.get_icon_name(ICON_POS), + null + }; + this.empty_state = { "dialog-warning-symbolic", null }; + this.invalid_state = { "dialog-error-symbolic", null }; + + this.target.add_events(Gdk.EventMask.FOCUS_CHANGE_MASK); + this.target.activate.connect(on_activate); + this.target.changed.connect(on_changed); + this.target.focus_out_event.connect(on_focus_out); + } + + ~Validator() { + this.target.focus_out_event.disconnect(on_focus_out); + this.target.changed.disconnect(on_changed); + this.target.activate.disconnect(on_activate); + this.ui_update_timer.reset(); + this.pulse_timer.reset(); + } + + /** + * Called to validate the target entry's value. + * + * This method will be called repeatedly as the user edits the + * value of the target entry to set the new validation {@link + * state} given the updated value. It will *not* be called if the + * entry is changed to be empty, instead the validity state will + * be set based on {@link is_required}. + * + * Subclasses may override this method to implement custom + * validation routines. Since this call is made repeatedly as the + * user is typing, it should not perform a CPU-intensive or + * long-running routine. Subclasses that do perform such + * validation should launch the routine in the background (either + * asynchronously or in a thread) and return {@link + * Validity.IN_PROGRESS} as soon as possible. Then, when it is + * complete, call {@link update_state} to update the validity state + * with the actual result. + * + * The given reason specifies which user action was taken to cause + * the entry's value to be validated. + * + * By default, this always returns {@link Validity.VALID}, making + * it useful for required, but otherwise free-form fields only. + */ + protected virtual Validity validate(string value, Trigger reason) { + return Validity.VALID; + } + + /** + * Updates the current validation state and the entry's UI. + * + * This should only be called by subclasses that implement a + * CPU-intensive or long-running validation routine and it has + * completed validating a value. See {@link validate} for details. + */ + protected void update_state(Validity new_state, Trigger reason) { + if (this.state != new_state) { + Validity old_state = this.state; + + // Fire the signal after updating the state but before + // updating the UI so listeners can update UI settings + // first if needed. + this.state = new_state; + state_changed(reason, old_state); + + if (new_state == Validity.VALID || reason != Trigger.CHANGED) { + // Update the UI straight away when going valid or + // when editing is complete to provide instant + // feedback + update_ui(new_state); + } else { + if (old_state == Validity.EMPTY) { + // Technically this is a lie, but when going from + // empty to non-empty we also want to provide + // instant feedback, and going to indeterminate + // when the user is in the middle of editing is + // better than going to invalid. + update_ui(Validity.INDETERMINATE); + } + // Start the a timer running to update the UI to give + // the timer running since they might still be editing + // it. + this.ui_update_timer.start(); + } + } + + if (new_state != Validity.IN_PROGRESS) { + this.target_changed = false; + + switch (reason) { + case Trigger.CHANGED: + changed(); + break; + + case Trigger.ACTIVATED: + activated(); + break; + + case Trigger.LOST_FOCUS: + focus_lost(); + break; + } + } else if (!this.pulse_timer.is_running) { + this.pulse_timer.start(); + } + } + + private void validate_entry(Trigger reason) { + string value = this.target.get_text(); + Validity new_state = this.state; + if (Geary.String.is_empty_or_whitespace(value)) { + new_state = this.is_required + ? Validity.EMPTY : Validity.INDETERMINATE; + } else { + new_state = validate(value, reason); + } + + update_state(new_state, reason); + } + + private void update_ui(Validity state) { + this.ui_update_timer.reset(); + + Gtk.StyleContext style = this.target.get_style_context(); + style.remove_class(Gtk.STYLE_CLASS_ERROR); + style.remove_class(Gtk.STYLE_CLASS_WARNING); + + UiState ui = { null, null }; + bool in_progress = false; + switch (state) { + case Validity.INDETERMINATE: + ui = this.indeterminate_state; + break; + + case Validity.VALID: + ui = this.valid_state; + break; + + case Validity.IN_PROGRESS: + in_progress = true; + ui = this.in_progress_state; + break; + + case Validity.EMPTY: + style.add_class(Gtk.STYLE_CLASS_WARNING); + ui = this.empty_state; + break; + + case Validity.INVALID: + style.add_class(Gtk.STYLE_CLASS_ERROR); + ui = this.invalid_state; + break; + } + + if (in_progress) { + if (!this.pulse_timer.is_running) { + this.pulse_timer.start(); + } + } else { + this.pulse_timer.reset(); + // If a pulse hasn't been performed (and hence the + // progress bar is not visible), setting the fraction here + // to reset it will actually cause the progress bar to + // become visible. So only reset if needed. + if (this.did_pulse) { + this.target.progress_fraction = 0.0; + this.did_pulse = false; + } + } + + this.target.set_icon_from_icon_name(ICON_POS, ui.icon_name); + this.target.set_icon_tooltip_text( + ICON_POS, + // Setting the tooltip to null or the empty string can + // cause GTK+ to setfult. See GTK+ issue #1160. + Geary.String.is_empty(ui.icon_tooltip_text) + ? " " : ui.icon_tooltip_text + ); + } + + private void on_activate() { + if (this.target_changed) { + validate_entry(Trigger.ACTIVATED); + } else { + activated(); + } + } + + private void on_update_ui() { + update_ui(this.state); + } + + private void on_pulse() { + this.target.progress_pulse(); + this.did_pulse = true; + } + + private void on_changed() { + this.target_changed = true; + validate_entry(Trigger.CHANGED); + // Restart the UI timer if running to give the user some + // breathing room while they are still editing. + this.ui_update_timer.start(); + } + + private bool on_focus_out() { + if (this.target_changed) { + // Only update if the widget has lost focus due to not being + // the focused widget any more, rather than the whole window + // having lost focus. + if (!this.target.is_focus) { + validate_entry(Trigger.LOST_FOCUS); + } + } else { + focus_lost(); + } + return Gdk.EVENT_PROPAGATE; + } + +} + + +/** + * A validator for GTK Entry widgets that contain an email address. + */ +public class Components.EmailValidator : Validator { + + public EmailValidator(Gtk.Entry target) { + base(target); + + // Translators: Tooltip used when an entry requires a valid + // email address to be entered, but one is not provided. + this.empty_state.icon_tooltip_text = _("An email address is required"); + + // Translators: Tooltip used when an entry requires a valid + // email address to be entered, but the address is invalid. + this.invalid_state.icon_tooltip_text = _("Not a valid email address"); + } + + + protected override Validator.Validity validate(string value, + Validator.Trigger reason) { + return Geary.RFC822.MailboxAddress.is_valid_address(value) + ? Validator.Validity.VALID : Validator.Validity.INVALID; + } + +} + + +/** + * A validator for GTK Entry widgets that contain a network address. + * + * This attempts parse the entry value as a host name or IP address + * with an optional port, then resolve the host name if + * needed. Parsing is performed by {@link GLib.NetworkAddress.parse} + * to parse the user input, hence it may be specified in any form + * supported by that method. + */ +public class Components.NetworkAddressValidator : Validator { + + + /** The validated network address, if any. */ + public GLib.NetworkAddress? validated_address { + get; private set; default = null; + } + + /** The default port used when parsing the address. */ + public uint16 default_port { get; private set; } + + private GLib.Resolver resolver; + private GLib.Cancellable? cancellable = null; + + + public NetworkAddressValidator(Gtk.Entry target, uint16 default_port = 0) { + base(target); + this.default_port = default_port; + + this.resolver = GLib.Resolver.get_default(); + + // Translators: Tooltip used when an entry requires a valid, + // resolvable server name to be entered, but one is not + // provided. + this.empty_state.icon_tooltip_text = _("A server name is required"); + + // Translators: Tooltip used when an entry requires a valid + // server name to be entered, but it was unable to be + // looked-up in the DNS. + this.invalid_state.icon_tooltip_text = _("Could not look up server name"); + } + + + public override Validator.Validity validate(string value, + Validator.Trigger reason) { + if (this.cancellable != null) { + this.cancellable.cancel(); + } + + Validator.Validity ret = this.state; + + GLib.NetworkAddress? address = null; + try { + address = GLib.NetworkAddress.parse( + value.strip(), this.default_port + ); + } catch (GLib.Error err) { + this.validated_address = null; + ret = Validator.Validity.INVALID; + debug("Error parsing host name \"%s\": %s", value, err.message); + } + + if (address != null) { + // Re-validate if previously invalid or the host has + // changed + if (this.validated_address == null || + this.validated_address.hostname != address.hostname) { + this.cancellable = new GLib.Cancellable(); + this.resolver.lookup_by_name_async.begin( + address.hostname, this.cancellable, + (obj, res) => { + try { + this.resolver.lookup_by_name_async.end(res); + this.validated_address = address; + update_state(Validator.Validity.VALID, reason); + } catch (GLib.IOError.CANCELLED err) { + this.validated_address = null; + } catch (GLib.Error err) { + this.validated_address = null; + update_state(Validator.Validity.INVALID, reason); + } + this.cancellable = null; + } + ); + ret = Validator.Validity.IN_PROGRESS; + } else { + // Update the validated address in case the port + // number is being edited and has changed + this.validated_address = address; + ret = Validator.Validity.VALID; + } + } + + return ret; + } + +} diff -Nru geary-0.12.4/src/client/components/count-badge.vala geary-3.32.0/src/client/components/count-badge.vala --- geary-0.12.4/src/client/components/count-badge.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/components/count-badge.vala 2019-03-17 13:39:29.000000000 +0000 @@ -9,13 +9,13 @@ */ public class CountBadge : Geary.BaseObject { public const string UNREAD_BG_COLOR = "#888888"; - + private const int FONT_SIZE_MESSAGE_COUNT = 8; - + public int count { get; set; default = 0; } - + private int min = 0; - + /** * Creates a count badge. * @param min Minimum count to draw. @@ -23,38 +23,38 @@ public CountBadge(int min) { this.min = min; } - + public int get_width(Gtk.Widget widget) { int width = 0; render_internal(widget, null, 0, 0, false, out width, null); - + return width; } - + public int get_height(Gtk.Widget widget) { int height = 0; render_internal(widget, null, 0, 0, false, null, out height); - + return height; } - + public void render(Gtk.Widget widget, Cairo.Context? ctx, int x, int y, bool selected) { render_internal(widget, ctx, x, y, selected, null, null); } - + private void render_internal(Gtk.Widget widget, Cairo.Context? ctx, int x, int y, bool selected, out int? width, out int? height) { if (count < min) { width = 0; height = 0; - + return; } - - string mails = + + string mails = " %d " .printf(FONT_SIZE_MESSAGE_COUNT, count); - + Pango.Layout layout_num = widget.create_pango_layout(null); layout_num.set_markup(mails, -1); layout_num.set_alignment(Pango.Alignment.RIGHT); @@ -67,7 +67,7 @@ double bg_height = logical_rect.height; double radius = bg_height / 2.0; double degrees = Math.PI / 180.0; - + // Create rounded rect. ctx.new_sub_path(); ctx.arc(x + bg_width - radius, y + radius, radius, -90 * degrees, 0 * degrees); @@ -75,18 +75,18 @@ ctx.arc(x + radius, y + bg_height - radius, radius, 90 * degrees, 180 * degrees); ctx.arc(x + radius, y + radius, radius, 180 * degrees, 270 * degrees); ctx.close_path(); - + // Colorize our shape. GtkUtil.set_source_color_from_string(ctx, UNREAD_BG_COLOR); ctx.fill_preserve(); ctx.set_line_width(2.0); ctx.stroke(); - + // Center the text. ctx.move_to(x + (bg_width / 2) - logical_rect.width / 2, y); Pango.cairo_show_layout(ctx, layout_num); } - + width = logical_rect.width + FormattedConversationData.LINE_SPACING; height = logical_rect.height; } diff -Nru geary-0.12.4/src/client/components/empty-placeholder.vala geary-3.32.0/src/client/components/empty-placeholder.vala --- geary-0.12.4/src/client/components/empty-placeholder.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/components/empty-placeholder.vala 1970-01-01 00:00:00.000000000 +0000 @@ -1,38 +0,0 @@ -/* - * Copyright 2016 Michael Gratton - * - * This software is licensed under the GNU Lesser General Public License - * (version 2.1 or later). See the COPYING file in this distribution. - */ - -/** - * A placeholder image and message for empty views. - */ -[GtkTemplate (ui = "/org/gnome/Geary/empty-placeholder.ui")] -public class EmptyPlaceholder : Gtk.Grid { - - public string image_name { - owned get { return this.placeholder_image.icon_name; } - set { this.placeholder_image.icon_name = value; } - } - - public string title { - get { return this.title_label.get_text(); } - set { this.title_label.set_text(value); } - } - - public string subtitle { - get { return this.subtitle_label.get_text(); } - set { this.subtitle_label.set_text(value); } - } - - [GtkChild] - private Gtk.Image placeholder_image; - - [GtkChild] - private Gtk.Label title_label; - - [GtkChild] - private Gtk.Label subtitle_label; - -} diff -Nru geary-0.12.4/src/client/components/folder-popover.vala geary-3.32.0/src/client/components/folder-popover.vala --- geary-0.12.4/src/client/components/folder-popover.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/components/folder-popover.vala 2019-03-17 13:39:29.000000000 +0000 @@ -79,6 +79,8 @@ label.set_halign(Gtk.Align.START); row.add(label); + row.show_all(); + return row; } diff -Nru geary-0.12.4/src/client/components/icon-factory.vala geary-3.32.0/src/client/components/icon-factory.vala --- geary-0.12.4/src/client/components/icon-factory.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/components/icon-factory.vala 2019-03-17 13:39:29.000000000 +0000 @@ -8,26 +8,26 @@ public class IconFactory { public const Gtk.IconSize ICON_TOOLBAR = Gtk.IconSize.LARGE_TOOLBAR; public const Gtk.IconSize ICON_SIDEBAR = Gtk.IconSize.MENU; - + private static IconFactory? _instance = null; public static IconFactory instance { get { if (_instance == null) _instance = new IconFactory(); - + return _instance; } - + private set { _instance = value; } } public const int UNREAD_ICON_SIZE = 16; public const int STAR_ICON_SIZE = 16; - + private Gtk.IconTheme icon_theme { get; private set; } - + private File icons_dir; - + // Creates the icon factory. private IconFactory() { icons_dir = GearyApplication.instance.get_resource_directory().get_child("icons"); @@ -39,33 +39,33 @@ // perform any additional initialization here; at this time, everything is done in the // constructor } - + private int icon_size_to_pixels(Gtk.IconSize icon_size) { switch (icon_size) { case ICON_SIDEBAR: return 16; - + case ICON_TOOLBAR: default: return 24; } } - + public Icon get_theme_icon(string name) { return new ThemedIcon(name); } - + public Icon get_custom_icon(string name, Gtk.IconSize size) { int pixels = icon_size_to_pixels(size); - + // Try sized icon first. File icon_file = icons_dir.get_child("%dx%d".printf(pixels, pixels)).get_child( "%s.svg".printf(name)); - + // If that wasn't found, try a non-sized icon. if (!icon_file.query_exists()) icon_file = icons_dir.get_child("%s.svg".printf(name)); - + return new FileIcon(icon_file); } @@ -76,23 +76,25 @@ } catch (Error err) { warning("Couldn't load image-missing icon: %s", err.message); } - + // If that fails... well they're out of luck. return null; } - + public Gtk.IconInfo? lookup_icon(string icon_name, int size, Gtk.IconLookupFlags flags = 0) { Gtk.IconInfo? icon_info = icon_theme.lookup_icon(icon_name, size, flags); - return icon_info != null ? icon_info.copy() : - icon_theme.lookup_icon("text-x-generic-symbolic", size, flags); + if (icon_info == null) { + icon_info = icon_theme.lookup_icon("text-x-generic-symbolic", size, flags); + } + return icon_info; } - + // GTK+ 3.14 no longer scales icons via the IconInfo, so perform manually until we // properly install the icons as per 3.14's expectations. private Gdk.Pixbuf aspect_scale_down_pixbuf(Gdk.Pixbuf pixbuf, int size) { if (pixbuf.width <= size && pixbuf.height <= size) return pixbuf; - + int scaled_width, scaled_height; if (pixbuf.width >= pixbuf.height) { double aspect = (double) size / (double) pixbuf.width; @@ -103,14 +105,14 @@ scaled_width = (int) Math.round((double) pixbuf.width * aspect); scaled_height = size; } - + return pixbuf.scale_simple(scaled_width, scaled_height, Gdk.InterpType.BILINEAR); } - + public Gdk.Pixbuf? load_symbolic(string icon_name, int size, Gtk.StyleContext style, Gtk.IconLookupFlags flags = 0) { Gtk.IconInfo? icon_info = icon_theme.lookup_icon(icon_name, size, flags); - + // Attempt to load as a symbolic icon. if (icon_info != null) { try { @@ -119,11 +121,11 @@ message("Couldn't load icon: %s", e.message); } } - + // Default: missing image icon. return get_missing_icon(size, flags); } - + /** * Loads a symbolic icon into a pixbuf, where the color-key has been switched to the provided * color. @@ -131,7 +133,7 @@ public Gdk.Pixbuf? load_symbolic_colored(string icon_name, int size, Gdk.RGBA color, Gtk.IconLookupFlags flags = 0) { Gtk.IconInfo? icon_info = icon_theme.lookup_icon(icon_name, size, flags); - + // Attempt to load as a symbolic icon. if (icon_info != null) { try { @@ -143,6 +145,6 @@ // Default: missing image icon. return get_missing_icon(size, flags); } - + } diff -Nru geary-0.12.4/src/client/components/main-toolbar.vala geary-3.32.0/src/client/components/main-toolbar.vala --- geary-0.12.4/src/client/components/main-toolbar.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/components/main-toolbar.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,4 +1,4 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* Copyright 2017 Software Freedom Conservancy Inc. * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. @@ -7,117 +7,110 @@ // Draws the main toolbar. [GtkTemplate (ui = "/org/gnome/Geary/main-toolbar.ui")] public class MainToolbar : Gtk.Box { - private Gtk.ActionGroup action_group; - public FolderPopover copy_folder_menu { get; private set; default = new FolderPopover(); } - public FolderPopover move_folder_menu { get; private set; default = new FolderPopover(); } + // How wide the left pane should be. Auto-synced with our settings + public int left_pane_width { get; set; } + // Used to form the title of the folder header public string account { get; set; } public string folder { get; set; } + // Close button settings public bool show_close_button { get; set; default = false; } public bool show_close_button_left { get; private set; default = true; } public bool show_close_button_right { get; private set; default = true; } + // Search and find bar public bool search_open { get; set; default = false; } public bool find_open { get; set; default = false; } - public int left_pane_width { get; set; } + // Copy and Move popovers + public FolderPopover copy_folder_menu { get; private set; default = new FolderPopover(); } + public FolderPopover move_folder_menu { get; private set; default = new FolderPopover(); } + // How many conversations are selected right now. Should automatically be updated. + public int selected_conversations { get; set; } + // Whether to show the trash or the delete button + public bool show_trash_button { get; set; default = true; } + // The tooltip of the Undo-button + public string undo_tooltip { + owned get { return this.undo_button.tooltip_text; } + set { this.undo_button.tooltip_text = value; } + } // Folder header elements [GtkChild] private Gtk.HeaderBar folder_header; [GtkChild] - private Gtk.Button compose_new_message_button; - [GtkChild] - private Gtk.MenuButton empty_menu_button; - [GtkChild] private Gtk.ToggleButton search_conversations_button; + [GtkChild] + private Gtk.MenuButton main_menu_button; private Binding guest_header_binding; // Conversation header elements [GtkChild] private Gtk.HeaderBar conversation_header; [GtkChild] - private Gtk.Button reply_sender_button; - [GtkChild] - private Gtk.Button reply_all_button; - [GtkChild] - private Gtk.Button forward_button; - [GtkChild] private Gtk.MenuButton mark_message_button; [GtkChild] - private Gtk.MenuButton copy_message_button; + public Gtk.MenuButton copy_message_button; [GtkChild] - private Gtk.MenuButton move_message_button; + public Gtk.MenuButton move_message_button; [GtkChild] private Gtk.Button archive_button; [GtkChild] private Gtk.Button trash_delete_button; [GtkChild] - private Gtk.Button undo_button; - [GtkChild] private Gtk.ToggleButton find_button; - public MainToolbar(Configuration config) { - this.action_group = GearyApplication.instance.actions; + // Other + [GtkChild] + private Gtk.Button undo_button; + + // Load these at construction time + private Gtk.Image trash_image = new Gtk.Image.from_icon_name("user-trash-symbolic", Gtk.IconSize.MENU); + private Gtk.Image delete_image = new Gtk.Image.from_icon_name("edit-delete-symbolic", Gtk.IconSize.MENU); + + public MainToolbar(Configuration config) { // Instead of putting a separator between the two headerbars, as other applications do, // we put a separator at the right end of the left headerbar. This greatly improves // the appearance under the Ambiance theme (see bug #746171). To get this separator to // line up with the handle of the pane, we need to extend the width of the left-hand // headerbar a bit. Six pixels is right both for Adwaita and Ambiance. - GearyApplication.instance.config.bind(Configuration.MESSAGES_PANE_POSITION_KEY, - this, "left-pane-width", SettingsBindFlags.GET); - this.bind_property("left-pane-width", folder_header, "width-request", + config.bind(Configuration.MESSAGES_PANE_POSITION_KEY, this, "left-pane-width", + SettingsBindFlags.GET); + this.bind_property("left-pane-width", this.folder_header, "width-request", BindingFlags.SYNC_CREATE, (binding, source_value, ref target_value) => { target_value = left_pane_width + 6; return true; }); if (config.desktop_environment != Configuration.DesktopEnvironment.UNITY) { - this.bind_property("account", folder_header, "title", BindingFlags.SYNC_CREATE); - this.bind_property("folder", folder_header, "subtitle", BindingFlags.SYNC_CREATE); + this.bind_property("account", this.folder_header, "title", BindingFlags.SYNC_CREATE); + this.bind_property("folder", this.folder_header, "subtitle", BindingFlags.SYNC_CREATE); } - this.bind_property("show-close-button-left", folder_header, "show-close-button", + this.bind_property("show-close-button-left", this.folder_header, "show-close-button", BindingFlags.SYNC_CREATE); - this.bind_property("show-close-button-right", conversation_header, "show-close-button", + this.bind_property("show-close-button-right", this.conversation_header, "show-close-button", BindingFlags.SYNC_CREATE); - // Assemble the empty/mark menus - GearyApplication.instance.load_ui_resource("toolbar_empty_menu.ui"); - Gtk.Menu empty_menu = (Gtk.Menu) GearyApplication.instance.ui_manager.get_widget("/ui/ToolbarEmptyMenu"); - GearyApplication.instance.load_ui_resource("toolbar_mark_menu.ui"); - Gtk.Menu mark_menu = (Gtk.Menu) GearyApplication.instance.ui_manager.get_widget("/ui/ToolbarMarkMenu"); + // Assemble the main/mark menus + Gtk.Builder builder = new Gtk.Builder.from_resource("/org/gnome/Geary/main-toolbar-menus.ui"); + MenuModel main_menu = (MenuModel) builder.get_object("main_menu"); + MenuModel mark_menu = (MenuModel) builder.get_object("mark_message_menu"); // Setup folder header elements - setup_button(compose_new_message_button, GearyController.ACTION_NEW_MESSAGE); - empty_menu_button.popup = empty_menu; - - setup_button(search_conversations_button, GearyController.ACTION_TOGGLE_SEARCH); - this.bind_property("search-open", search_conversations_button, "active", + this.main_menu_button.popover = new Gtk.Popover.from_model(null, main_menu); + this.bind_property("search-open", this.search_conversations_button, "active", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL); // Setup conversation header elements - setup_button(reply_sender_button, GearyController.ACTION_REPLY_TO_MESSAGE); - setup_button(reply_all_button, GearyController.ACTION_REPLY_ALL_MESSAGE); - setup_button(forward_button, GearyController.ACTION_FORWARD_MESSAGE); - - setup_menu_button(mark_message_button, mark_menu, GearyController.ACTION_MARK_AS_MENU); - setup_popover_button(copy_message_button, copy_folder_menu, GearyController.ACTION_COPY_MENU); - setup_popover_button(move_message_button, move_folder_menu, GearyController.ACTION_MOVE_MENU); - - setup_button(archive_button, GearyController.ACTION_ARCHIVE_CONVERSATION, true); - setup_button(trash_delete_button, GearyController.ACTION_TRASH_CONVERSATION); - setup_button(undo_button, GearyController.ACTION_UNDO); + this.notify["selected-conversations"].connect(() => update_conversation_buttons()); + this.notify["show-trash-button"].connect(() => update_conversation_buttons()); + this.mark_message_button.popover = new Gtk.Popover.from_model(null, mark_menu); + this.copy_message_button.popover = copy_folder_menu; + this.move_message_button.popover = move_folder_menu; - setup_button(find_button, GearyController.ACTION_TOGGLE_FIND); - this.bind_property("find-open", find_button, "active", + this.bind_property("find-open", this.find_button, "active", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL); Gtk.Settings.get_default().notify["gtk-decoration-layout"].connect(set_window_buttons); - realize.connect(set_window_buttons); - } - - public void update_trash_button(bool is_trash) { - string action_name = (is_trash ? GearyController.ACTION_TRASH_CONVERSATION - : GearyController.ACTION_DELETE_CONVERSATION); - setup_button(trash_delete_button, action_name, false); + this.realize.connect(set_window_buttons); } public void set_conversation_header(Gtk.HeaderBar header) { @@ -152,75 +145,45 @@ conversation_header.decoration_layout = ":" + buttons[1]; } - private void setup_button(Gtk.Button b, string action_name, bool show_label = false) { - Gtk.Action related_action = action_group.get_action(action_name); - // Explicitly call set_focus_on_click() here to work around - // linker error for gtk_widget_set_focus_on_click that would - // otherwise get generated when compiling vala >= 0.34 against - // GTK+ < 3.20 - b.set_focus_on_click(false); - b.use_underline = true; - b.tooltip_text = related_action.tooltip; - related_action.notify["tooltip"].connect(() => { b.tooltip_text = related_action.tooltip; }); - b.related_action = related_action; - - // Load icon by name with this fallback order: specified icon name, the action's icon name, - // the action's stock ID ... although stock IDs are being deprecated, that's how we specify - // the icon in the GtkActionEntry (also being deprecated) and GTK+ 3.14 doesn't support that - // any longer - string? icon_to_load = b.related_action.icon_name; - if (icon_to_load == null) - icon_to_load = b.related_action.stock_id; - - // set pixel size to force GTK+ to load our images from our installed directory, not the theme - // directory - if (icon_to_load != null) { - Gtk.Image image = new Gtk.Image.from_icon_name(icon_to_load, Gtk.IconSize.MENU); - image.set_pixel_size(16); - b.image = image; - } - - b.always_show_image = true; - - if (show_label) - b.label = related_action.label; - else - b.label = null; - } - - /** - * Given an icon, menu, and action, creates a button that triggers the menu and the action. - */ - private void setup_menu_button(Gtk.MenuButton b, Gtk.Menu menu, string action_name) { - setup_button(b, action_name); - menu.foreach(GtkUtil.show_menuitem_accel_labels); - b.popup = menu; - - if (b.related_action != null) { - b.related_action.activate.connect(() => { - b.clicked(); - }); - // Null out the action since by connecting it to clicked - // above, invoking would cause an infinite loop otherwise. - b.related_action = null; - } - } - - /** - * Given an icon, popover, and action, creates a button that triggers the popover and the action. - */ - private void setup_popover_button(Gtk.MenuButton b, Gtk.Popover popover, string action_name) { - setup_button(b, action_name); - b.popover = popover; - b.clicked.connect(() => popover.show_all()); - - if (b.related_action != null) { - b.related_action.activate.connect(() => { - b.clicked(); - }); - // Null out the action since by connecting it to clicked - // above, invoking would cause an infinite loop otherwise. - b.related_action = null; + // Updates tooltip text depending on number of conversations selected. + private void update_conversation_buttons() { + this.mark_message_button.tooltip_text = ngettext( + "Mark conversation", + "Mark conversations", + this.selected_conversations + ); + this.copy_message_button.tooltip_text = ngettext( + "Add label to conversation", + "Add label to conversations", + this.selected_conversations + ); + this.move_message_button.tooltip_text = ngettext( + "Move conversation", + "Move conversations", + this.selected_conversations + ); + this.archive_button.tooltip_text = ngettext( + "Archive conversation (A)", + "Archive conversations (A)", + this.selected_conversations + ); + + if (this.show_trash_button) { + this.trash_delete_button.action_name = "win."+GearyController.ACTION_TRASH_CONVERSATION; + this.trash_delete_button.image = trash_image; + this.trash_delete_button.tooltip_text = ngettext( + "Move conversation to Trash (Delete, Backspace)", + "Move conversations to Trash (Delete, Backspace)", + this.selected_conversations + ); + } else { + this.trash_delete_button.action_name = "win."+GearyController.ACTION_DELETE_CONVERSATION; + this.trash_delete_button.image = delete_image; + this.trash_delete_button.tooltip_text = ngettext( + "Delete conversation (Shift+Delete)", + "Delete conversations (Shift+Delete)", + this.selected_conversations + ); } } } diff -Nru geary-0.12.4/src/client/components/main-window-info-bar.vala geary-3.32.0/src/client/components/main-window-info-bar.vala --- geary-0.12.4/src/client/components/main-window-info-bar.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/src/client/components/main-window-info-bar.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,236 @@ +/* + * Copyright 2017 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +/** + * Displays application-wide or important account-related messages. + */ +[GtkTemplate (ui = "/org/gnome/Geary/main-window-info-bar.ui")] +public class MainWindowInfoBar : Gtk.InfoBar { + + + private enum ResponseType { DETAILS, RETRY; } + + /** If reporting a problem, returns the problem report else null. */ + public Geary.ProblemReport? report { get; private set; default = null; } + + /** Emitted when the user clicks the Retry button, if any. */ + public signal void retry(); + + + [GtkChild] + private Gtk.Label title; + + [GtkChild] + private Gtk.Label description; + + + public MainWindowInfoBar.for_problem(Geary.ProblemReport report) { + Gtk.MessageType type = Gtk.MessageType.WARNING; + string title = ""; + string descr = ""; + string? retry = null; + bool show_generic = false; + bool show_close = false; + + if (report is Geary.ServiceProblemReport) { + Geary.ServiceProblemReport service_report = (Geary.ServiceProblemReport) report; + string account = service_report.account.display_name; + string server = service_report.service.host; + + if (report.problem_type == Geary.ProblemType.CONNECTION_ERROR && + service_report.service.protocol == Geary.Protocol.IMAP) { + // Translators: String substitution is the account name + title = _("Problem connecting to incoming server for %s".printf(account)); + // Translators: String substitution is the server name + descr = _("Could not connect to %s, check your Internet access and the server name and try again").printf(server); + // Translators: Tooltip label for Retry button + retry = _("Try reconnecting"); + + } else if (report.problem_type == Geary.ProblemType.CONNECTION_ERROR && + service_report.service.protocol == Geary.Protocol.SMTP) { + // Translators: String substitution is the account name + title = _("Problem connecting to outgoing server for %s".printf(account)); + // Translators: String substitution is the server name + descr = _("Could not connect to %s, check your Internet access and the server name and try again").printf(server); + // Translators: Tooltip label for Retry button + retry = _("Try reconnecting"); + + } else if (report.problem_type == Geary.ProblemType.NETWORK_ERROR && + service_report.service.protocol == Geary.Protocol.IMAP) { + // Translators: String substitution is the account name + title = _("Problem communicating with incoming server for %s").printf(account); + // Translators: String substitution is the server name + descr = _("Network error talking to %s, check your Internet access and try again").printf(server); + // Translators: Tooltip label for Retry button + retry = _("Try reconnecting"); + + } else if (report.problem_type == Geary.ProblemType.NETWORK_ERROR && + service_report.service.protocol == Geary.Protocol.SMTP) { + // Translators: String substitution is the account name + title = _("Problem communicating with outgoing mail server"); + // Translators: String substitution is the server name + descr = _("Network error talking to %s, check your Internet access and try again").printf(server); + // Translators: Tooltip label for Retry button + retry = _("Try reconnecting"); + + } else if (report.problem_type == Geary.ProblemType.SERVER_ERROR && + service_report.service.protocol == Geary.Protocol.IMAP) { + // Translators: String substitution is the account name + title = _("Problem communicating with incoming server for %s").printf(account); + // Translators: String substitution is the server name + descr = _("Geary did not understand a message from %s or vice versa, please file a bug report").printf(server); + // Translators: Tooltip label for Retry button + retry = _("Try reconnecting"); + + } else if (report.problem_type == Geary.ProblemType.SERVER_ERROR && + service_report.service.protocol == Geary.Protocol.SMTP) { + title = _("Problem communicating with outgoing mail server"); + // Translators: First string substitution is the server + // name, second is the account name + descr = _("Could not communicate with %s for %s, check the server name and try again in a moment").printf(server, account); + // Translators: Tooltip label for Retry button + retry = _("Try reconnecting"); + + } else if (report.problem_type == Geary.ProblemType.AUTHENTICATION && + service_report.service.protocol == Geary.Protocol.IMAP) { + // Translators: String substitution is the account name + title = _("Incoming mail server password required for %s").printf(account); + descr = _("Messages cannot be received without the correct password."); + // Translators: Tooltip label for Retry button + retry = _("Retry receiving email, you will be prompted for a password"); + + } else if (report.problem_type == Geary.ProblemType.AUTHENTICATION && + service_report.service.protocol == Geary.Protocol.SMTP) { + // Translators: String substitution is the account name + title = _("Outgoing mail server password required for %s").printf(account); + descr = _("Messages cannot be sent without the correct password."); + // Translators: Tooltip label for Retry button + retry = _("Retry sending queued messages, you will be prompted for a password"); + + } else if (report.problem_type == Geary.ProblemType.UNTRUSTED && + service_report.service.protocol == Geary.Protocol.IMAP) { + // Translators: String substitution is the account name + title = _("Incoming mail server security is not trusted for %s").printf(account); + descr = _("Messages will not be received until checked."); + // Translators: Tooltip label for Retry button + retry = _("Check security details"); + + } else if (report.problem_type == Geary.ProblemType.UNTRUSTED && + service_report.service.protocol == Geary.Protocol.SMTP) { + // Translators: String substitution is the account name + title = _("Outgoing mail server security is not trusted for %s").printf(account); + descr = _("Messages cannot be sent until checked."); + // Translators: Tooltip label for Retry button + retry = _("Check security details"); + + } else if (report.problem_type == Geary.ProblemType.GENERIC_ERROR && + service_report.service.protocol == Geary.Protocol.IMAP) { + // Translators: String substitution is the account name + title = _("A problem occurred checking mail for %s").printf(account); + descr = _("Something went wrong, please file a bug report if the problem persists"); + // Translators: Tooltip label for Retry button + retry = _("Try reconnecting"); + + } else if (report.problem_type == Geary.ProblemType.GENERIC_ERROR && + service_report.service.protocol == Geary.Protocol.SMTP) { + // Translators: String substitution is the account name + title = _("A problem occurred sending mail for %s").printf(account); + descr = _("Something went wrong, please file a bug report if the problem persists"); + // Translators: Tooltip label for Retry button + retry = _("Retry sending queued messages"); + + } else { + debug("Un-handled service problem report: %s".printf(report.to_string())); + show_generic = true; + } + } else if (report is Geary.AccountProblemReport) { + Geary.AccountProblemReport account_report = (Geary.AccountProblemReport) report; + string account = account_report.account.display_name; + if (report.problem_type == Geary.ProblemType.DATABASE_FAILURE) { + type = Gtk.MessageType.ERROR; + title = _("A database problem has occurred"); + // Translators: String substitution is the account name + descr = _("Messages for %s must be downloaded again.").printf(account); + show_close = true; + + } else { + debug("Un-handled account problem report: %s".printf(report.to_string())); + show_generic = true; + } + } else { + debug("Un-handled generic problem report: %s".printf(report.to_string())); + show_generic = true; + } + + if (show_generic) { + title = _("Geary has encountered a problem"); + descr = _("Please check the technical details and report the problem if it persists."); + show_close = true; + } + + this(type, title, descr, show_close); + this.report = report; + + if (this.report.error != null) { + Gtk.Button details = add_button(_("_Details"), ResponseType.DETAILS); + details.tooltip_text = _("View technical details about the error"); + } + + if (retry != null) { + Gtk.Button retry_btn = add_button(_("_Retry"), ResponseType.RETRY); + retry_btn.tooltip_text = retry; + } + } + + protected MainWindowInfoBar(Gtk.MessageType type, + string title, + string description, + bool show_close) { + this.message_type = type; + this.title.label = title; + + // Set the label and tooltip for the description in case it is + // long enough to be ellipsized + this.description.label = description; + this.description.tooltip_text = description; + + this.show_close_button = show_close; + } + + private void show_details() { + Dialogs.ProblemDetailsDialog dialog = + new Dialogs.ProblemDetailsDialog.for_problem_report( + get_toplevel() as Gtk.Window, this.report + ); + dialog.run(); + dialog.destroy(); + } + + [GtkCallback] + private void on_info_bar_response(int response) { + switch(response) { + case ResponseType.DETAILS: + show_details(); + break; + + case ResponseType.RETRY: + retry(); + this.hide(); + break; + + default: + this.hide(); + break; + } + } + + [GtkCallback] + private void on_hide() { + this.parent.remove(this); + } + +} diff -Nru geary-0.12.4/src/client/components/main-window.vala geary-3.32.0/src/client/components/main-window.vala --- geary-0.12.4/src/client/components/main-window.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/components/main-window.vala 2019-03-17 13:39:29.000000000 +0000 @@ -29,10 +29,11 @@ public FolderList.Tree folder_list { get; private set; default = new FolderList.Tree(); } public MainToolbar main_toolbar { get; private set; } public SearchBar search_bar { get; private set; default = new SearchBar(); } - public ConversationListView conversation_list_view { get; private set; default = new ConversationListView(); } - public ConversationViewer conversation_viewer { get; private set; default = new ConversationViewer(); } + public ConversationListView conversation_list_view { get; private set; } + public ConversationViewer conversation_viewer { get; private set; } public StatusBar status_bar { get; private set; default = new StatusBar(); } private MonitoredSpinner spinner = new MonitoredSpinner(); + [GtkChild] private Gtk.Box main_layout; [GtkChild] @@ -49,26 +50,50 @@ private Gtk.Box conversation_box; [GtkChild] private Gtk.ScrolledWindow conversation_list_scrolled; + [GtkChild] + private Gtk.Overlay overlay; + + // This is a frame so users can use F6/Shift-F6 to get to it + [GtkChild] + private Gtk.Frame info_bar_frame; + + [GtkChild] + private Gtk.Grid info_bar_container; + + [GtkChild] + private Gtk.InfoBar offline_infobar; + + [GtkChild] + private Gtk.InfoBar service_problem_infobar; + + [GtkChild] + private Gtk.Button service_problem_details; + + [GtkChild] + private Gtk.InfoBar cert_problem_infobar; + + [GtkChild] + private Gtk.InfoBar auth_problem_infobar; + + private Geary.Account? service_problem_account = null; + /** Fired when the user requests an account status be retried. */ + public signal void retry_service_problem(Geary.ClientService.Status problem); /** Fired when the shift key is pressed or released. */ public signal void on_shift_key(bool pressed); public MainWindow(GearyApplication application) { - Object(application: application); + Object( + application: application, + show_menubar: false + ); base_ref(); - // GTK+ 3.14 (and others?) ignores this property in the UI - // file, so set it explicitly here to avoid having a menubar - // appear when the desktop shell does not show the app menu - this.show_menubar = false; - load_config(application.config); restore_saved_window_state(); - add_accel_group(application.ui_manager.get_accel_group()); - application.controller.notify[GearyController.PROP_CURRENT_CONVERSATION] .connect(on_conversation_monitor_changed); application.controller.folder_selected.connect(on_folder_selected); @@ -78,12 +103,54 @@ set_styling(); setup_layout(application.config); on_change_orientation(); + + this.main_layout.show_all(); } ~MainWindow() { base_unref(); } + /** Updates the window's account status info bars. */ + public void update_account_status(Geary.Account.Status status, + bool has_auth_error, + bool has_cert_error, + Geary.Account? service_problem) { + // Only ever show one at a time. Offline is primary since + // nothing else can happen when offline. Service problems are + // secondary since auth and cert problems can't be resolved + // when the service isn't talking to the server. Cert problems + // are tertiary since you can't auth if you can't connect. + bool show_offline = false; + bool show_service = false; + bool show_cert = false; + bool show_auth = false; + + if (!status.is_online()) { + show_offline = true; + } else if (status.has_service_problem()) { + show_service = true; + } else if (has_cert_error) { + show_cert = true; + } else if (has_auth_error) { + show_auth = true; + } + + this.service_problem_account = service_problem; + + this.offline_infobar.set_visible(show_offline); + this.service_problem_infobar.set_visible(show_service); + this.service_problem_details.set_visible(get_problem_service() != null); + this.cert_problem_infobar.set_visible(show_cert); + this.auth_problem_infobar.set_visible(show_auth); + update_infobar_frame(); + } + + public void show_infobar(MainWindowInfoBar info_bar) { + this.info_bar_container.add(info_bar); + this.info_bar_frame.show(); + } + private void load_config(Configuration config) { // This code both loads AND saves the pane positions with live updating. This is more // resilient against crashes because the value in dconf changes *immediately*, and @@ -102,16 +169,22 @@ } private void restore_saved_window_state() { - Gdk.Screen? screen = get_screen(); - if (screen != null && - this.window_width <= screen.get_width() && - this.window_height <= screen.get_height()) { - set_default_size(this.window_width, this.window_height); + Gdk.Display? display = Gdk.Display.get_default(); + if (display != null) { + Gdk.Monitor? monitor = display.get_primary_monitor(); + if (monitor == null) { + monitor = display.get_monitor_at_point(1, 1); + } + if (monitor != null && + this.window_width <= monitor.geometry.width && + this.window_height <= monitor.geometry.height) { + set_default_size(this.window_width, this.window_height); + } } + this.window_position = Gtk.WindowPosition.CENTER; if (this.window_maximized) { maximize(); } - this.window_position = Gtk.WindowPosition.CENTER; } // Called on [un]maximize and possibly others. Save maximized state @@ -132,25 +205,37 @@ public override void size_allocate(Gtk.Allocation allocation) { base.size_allocate(allocation); - Gdk.Screen? screen = get_screen(); - if (screen != null && !this.window_maximized) { - // Get the size via ::get_size instead of the allocation - // so that the window isn't ever-expanding. - int width = 0; - int height = 0; - get_size(out width, out height); - - // Only store if the values have changed and are - // reasonable-looking. - if (this.window_width != width && - width > 0 && width <= screen.get_width()) - this.window_width = width; - if (this.window_height != height && - height > 0 && height <= screen.get_height()) - this.window_height = height; + if (!this.window_maximized) { + Gdk.Display? display = get_display(); + Gdk.Window? window = get_window(); + if (display != null && window != null) { + Gdk.Monitor monitor = display.get_monitor_at_window(window); + + // Get the size via ::get_size instead of the + // allocation so that the window isn't ever-expanding. + int width = 0; + int height = 0; + get_size(out width, out height); + + // Only store if the values have changed and are + // reasonable-looking. + if (this.window_width != width && + width > 0 && width <= monitor.geometry.width) { + this.window_width = width; + } + if (this.window_height != height && + height > 0 && height <= monitor.geometry.height) { + this.window_height = height; + } + } } } + public void add_notification(InAppNotification notification) { + this.overlay.add_overlay(notification); + notification.show(); + } + private void set_styling() { Gtk.CssProvider provider = new Gtk.CssProvider(); Gtk.StyleContext.add_provider_for_screen(Gdk.Display.get_default().get_default_screen(), @@ -173,7 +258,12 @@ } private void setup_layout(Configuration config) { - // Toolbar + this.conversation_list_view = new ConversationListView(this); + + this.conversation_viewer = new ConversationViewer( + this.application.config + ); + this.main_toolbar = new MainToolbar(config); this.main_toolbar.bind_property("search-open", this.search_bar, "search-mode-enabled", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL); @@ -188,8 +278,8 @@ return true; }; - bind_property("current-folder", this, "title", BindingFlags.SYNC_CREATE, title_func); - main_toolbar.bind_property("account", this, "title", BindingFlags.SYNC_CREATE, title_func); + bind_property("current-folder", this, "title", BindingFlags.SYNC_CREATE, (owned) title_func); + main_toolbar.bind_property("account", this, "title", BindingFlags.SYNC_CREATE, (owned) title_func); main_layout.pack_start(main_toolbar, false, true, 0); } else { main_toolbar.show_close_button = true; @@ -220,6 +310,7 @@ return scrollbar != null && scrollbar.get_visible(); } + /** {@inheritDoc} */ public override bool key_press_event(Gdk.EventKey event) { check_shift_event(event); @@ -258,16 +349,38 @@ * [0] - */ - bool handled = propagate_key_event(event); - if (!handled) { - handled = activate_key(event); - } - if (!handled) { - handled = Gtk.bindings_activate_event(this, event); + bool handled = false; + Gdk.ModifierType state = ( + event.state & Gtk.accelerator_get_default_mod_mask() + ); + if (state > 0 && state != Gdk.ModifierType.SHIFT_MASK) { + // Have a modifier held down (Ctrl, Alt, etc) that is used + // as an accelerator so we don't need to worry about SKCs, + // and the key press can be handled normally. Can't do + // this with Shift though since that will stop chars being + // typed in the composer that conflict with accels, like + // `!`. + handled = base.key_press_event(event); + } else { + // No modifier used as an accelerator is down, so kluge + // input handling to make SKCs work per the above. + handled = propagate_key_event(event); + if (!handled) { + handled = activate_key(event); + } + if (!handled) { + handled = Gtk.bindings_activate_event(this, event); + } } return handled; } + /** {@inheritDoc} */ + public override bool key_release_event(Gdk.EventKey event) { + check_shift_event(event); + return base.key_release_event(event); + } + private void on_conversation_monitor_changed() { ConversationListStore? old_model = this.conversation_list_view.get_model(); if (old_model != null) { @@ -285,11 +398,6 @@ this.progress_monitor.add(conversations.progress_monitor); this.conversation_list_view.set_model(new_model); } - - if (old_model != null) { - // Must be destroyed, but only after it has been replaced. - old_model.destroy(); - } } private void on_folder_selected(Geary.Folder? folder) { @@ -374,7 +482,8 @@ return; } - this.main_toolbar.account = this.current_folder.account.information.nickname; + this.main_toolbar.account = + this.current_folder.account.information.display_name; /// Current folder's name followed by its unread count, i.e. "Inbox (42)" // except for Drafts and Outbox, where we show total count @@ -396,6 +505,18 @@ this.main_toolbar.folder = this.current_folder.get_display_name(); } + private void update_infobar_frame() { + // Ensure the info bar frame is shown only when it has visible + // children + bool show_frame = false; + info_bar_container.foreach((child) => { + if (child.visible) { + show_frame = true; + } + }); + this.info_bar_frame.set_visible(show_frame); + } + private inline void check_shift_event(Gdk.EventKey event) { // FIXME: it's possible the user will press two shift keys. We want // the shift key to report as released when they release ALL of them. @@ -409,10 +530,16 @@ } } - [GtkCallback] - private bool on_key_release_event(Gdk.EventKey event) { - check_shift_event(event); - return Gdk.EVENT_PROPAGATE; + private Geary.ClientService? get_problem_service() { + Geary.ClientService? service = null; + if (this.service_problem_account != null) { + if (this.service_problem_account.incoming.last_error != null) { + service = this.service_problem_account.incoming; + } else if (this.service_problem_account.outgoing.last_error != null) { + service = this.service_problem_account.outgoing; + } + } + return service; } [GtkCallback] @@ -432,4 +559,53 @@ } return Gdk.EVENT_STOP; } + + [GtkCallback] + private void on_offline_infobar_response() { + this.offline_infobar.hide(); + update_infobar_frame(); + } + + [GtkCallback] + private void on_service_problem_retry() { + this.service_problem_infobar.hide(); + update_infobar_frame(); + retry_service_problem(Geary.ClientService.Status.CONNECTION_FAILED); + } + + [GtkCallback] + private void on_service_problem_details() { + Geary.ClientService? service = get_problem_service(); + if (service != null) { + Dialogs.ProblemDetailsDialog dialog = + new Dialogs.ProblemDetailsDialog( + this, + service.last_error, + this.service_problem_account.information, + service.configuration + ); + dialog.run(); + dialog.destroy(); + } + } + + [GtkCallback] + private void on_cert_problem_retry() { + this.cert_problem_infobar.hide(); + update_infobar_frame(); + retry_service_problem(Geary.ClientService.Status.TLS_VALIDATION_FAILED); + } + + [GtkCallback] + private void on_auth_problem_retry() { + this.auth_problem_infobar.hide(); + update_infobar_frame(); + retry_service_problem(Geary.ClientService.Status.AUTHENTICATION_FAILED); + } + + [GtkCallback] + private void on_info_bar_container_remove() { + update_infobar_frame(); + } + } diff -Nru geary-0.12.4/src/client/components/monitored-progress-bar.vala geary-3.32.0/src/client/components/monitored-progress-bar.vala --- geary-0.12.4/src/client/components/monitored-progress-bar.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/components/monitored-progress-bar.vala 2019-03-17 13:39:29.000000000 +0000 @@ -9,24 +9,24 @@ */ public class MonitoredProgressBar : Gtk.ProgressBar { private Geary.ProgressMonitor? monitor = null; - + public void set_progress_monitor(Geary.ProgressMonitor monitor) { this.monitor = monitor; monitor.start.connect(on_start); monitor.finish.connect(on_finish); monitor.update.connect(on_update); - + fraction = monitor.progress; } - + private void on_start() { fraction = 0.0; } - + private void on_update(double total_progress, double change, Geary.ProgressMonitor monitor) { fraction = total_progress; } - + private void on_finish() { fraction = 1.0; } diff -Nru geary-0.12.4/src/client/components/monitored-spinner.vala geary-3.32.0/src/client/components/monitored-spinner.vala --- geary-0.12.4/src/client/components/monitored-spinner.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/components/monitored-spinner.vala 2019-03-17 13:39:29.000000000 +0000 @@ -9,7 +9,7 @@ */ public class MonitoredSpinner : Gtk.Spinner { private Geary.ProgressMonitor? monitor = null; - + public void set_progress_monitor(Geary.ProgressMonitor? monitor) { if (monitor != null) { this.monitor = monitor; @@ -21,17 +21,17 @@ hide(); } } - + public override void show() { if (monitor != null && monitor.is_in_progress) base.show(); } - + private void on_start() { start(); show(); } - + private void on_stop() { stop(); hide(); diff -Nru geary-0.12.4/src/client/components/search-bar.vala geary-3.32.0/src/client/components/search-bar.vala --- geary-0.12.4/src/client/components/search-bar.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/components/search-bar.vala 2019-03-17 13:39:29.000000000 +0000 @@ -6,17 +6,17 @@ public class SearchBar : Gtk.SearchBar { private const string DEFAULT_SEARCH_TEXT = _("Search"); - + public string search_text { get { return search_entry.text; } } public bool search_entry_has_focus { get { return search_entry.has_focus; } } - + private Gtk.SearchEntry search_entry = new Gtk.SearchEntry(); private Geary.ProgressMonitor? search_upgrade_progress_monitor = null; private MonitoredProgressBar search_upgrade_progress_bar = new MonitoredProgressBar(); private Geary.Account? current_account = null; - + public signal void search_text_changed(string search_text); - + public SearchBar() { // Search entry. search_entry.width_chars = 28; @@ -28,86 +28,88 @@ search_text_changed(search_entry.text); }); search_entry.has_focus = true; - + // Search upgrade progress bar. search_upgrade_progress_bar.show_text = true; search_upgrade_progress_bar.visible = false; search_upgrade_progress_bar.no_show_all = true; - + add(search_upgrade_progress_bar); add(search_entry); - + set_search_placeholder_text(DEFAULT_SEARCH_TEXT); - + GearyApplication.instance.controller.account_selected.connect(on_account_changed); } - + public void set_search_text(string text) { search_entry.text = text; } - + public void give_search_focus() { set_search_mode(true); search_entry.grab_focus(); } - + public void set_search_placeholder_text(string placeholder) { search_entry.placeholder_text = placeholder; } - + private void on_search_upgrade_start() { // Set the progress bar's width to match the search entry's width. int minimum_width = 0; int natural_width = 0; search_entry.get_preferred_width(out minimum_width, out natural_width); search_upgrade_progress_bar.width_request = minimum_width; - + search_entry.hide(); search_upgrade_progress_bar.show(); } - + private void on_search_upgrade_finished() { search_entry.show(); search_upgrade_progress_bar.hide(); } - + private void on_account_changed(Geary.Account? account) { on_search_upgrade_finished(); // Reset search box. - + if (search_upgrade_progress_monitor != null) { search_upgrade_progress_monitor.start.disconnect(on_search_upgrade_start); search_upgrade_progress_monitor.finish.disconnect(on_search_upgrade_finished); search_upgrade_progress_monitor = null; } - + if (current_account != null) { - current_account.information.notify[Geary.AccountInformation.PROP_NICKNAME].disconnect( - on_nickname_changed); + current_account.information.changed.disconnect( + on_information_changed); } - + if (account != null) { search_upgrade_progress_monitor = account.search_upgrade_monitor; search_upgrade_progress_bar.set_progress_monitor(search_upgrade_progress_monitor); - + search_upgrade_progress_monitor.start.connect(on_search_upgrade_start); search_upgrade_progress_monitor.finish.connect(on_search_upgrade_finished); if (search_upgrade_progress_monitor.is_in_progress) on_search_upgrade_start(); // Remove search box, we're already in progress. - - account.information.notify[Geary.AccountInformation.PROP_NICKNAME].connect( - on_nickname_changed); - - search_upgrade_progress_bar.text = _("Indexing %s account").printf(account.information.nickname); + + account.information.changed.connect( + on_information_changed); + + search_upgrade_progress_bar.text = + _("Indexing %s account").printf(account.information.display_name); } - + current_account = account; - - on_nickname_changed(); // Set new account name. + + on_information_changed(); // Set new account name. } - - private void on_nickname_changed() { + + private void on_information_changed() { set_search_placeholder_text(current_account == null || GearyApplication.instance.controller.get_num_accounts() == 1 ? DEFAULT_SEARCH_TEXT : - _("Search %s account").printf(current_account.information.nickname)); + _("Search %s account").printf(current_account.information.display_name)); } + } diff -Nru geary-0.12.4/src/client/components/status-bar.vala geary-3.32.0/src/client/components/status-bar.vala --- geary-0.12.4/src/client/components/status-bar.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/components/status-bar.vala 2019-03-17 13:39:29.000000000 +0000 @@ -18,7 +18,7 @@ OUTBOX_SENDING, OUTBOX_SEND_FAILURE, OUTBOX_SAVE_SENT_MAIL_FAILED; - + internal string get_text() { switch (this) { case Message.OUTBOX_SENDING: @@ -35,7 +35,7 @@ assert_not_reached(); } } - + internal Context get_context() { switch (this) { case Message.OUTBOX_SENDING: @@ -49,36 +49,36 @@ } } } - + internal enum Context { OUTBOX, } - + private Gee.HashMap context_ids = new Gee.HashMap(); private Gee.HashMap message_ids = new Gee.HashMap(); private Gee.HashMap message_counts = new Gee.HashMap(); - + public StatusBar() { set_context_id(Context.OUTBOX); } - + private void set_context_id(Context context) { context_ids.set(context, get_context_id(context.to_string())); } - + private int get_count(Message message) { return (message_counts.has_key(message) ? message_counts.get(message) : 0); } - + private void push_message(Message message) { message_ids.set(message, push(context_ids.get(message.get_context()), message.get_text())); } - + private void remove_message(Message message) { remove(context_ids.get(message.get_context()), message_ids.get(message)); message_ids.unset(message); } - + /** * Return whether the message has been activated more times than it has * been deactivated. @@ -86,19 +86,19 @@ public bool is_message_active(Message message) { return message_ids.has_key(message); } - + public void activate_message(Message message) { if (is_message_active(message)) remove_message(message); - + push_message(message); message_counts.set(message, get_count(message) + 1); } - + public void deactivate_message(Message message) { if (!is_message_active(message)) return; - + int count = get_count(message); if (count == 1) remove_message(message); diff -Nru geary-0.12.4/src/client/composer/composer-box.vala geary-3.32.0/src/client/composer/composer-box.vala --- geary-0.12.4/src/client/composer/composer-box.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/composer/composer-box.vala 2019-03-17 13:39:29.000000000 +0000 @@ -37,17 +37,10 @@ add(this.composer); this.main_toolbar.set_conversation_header(composer.header); - this.composer.editor.focus_in_event.connect(on_focus_in); - this.composer.editor.focus_out_event.connect(on_focus_out); show(); } public void remove_composer() { - if (this.composer.editor.has_focus) - on_focus_out(); - this.composer.editor.focus_in_event.disconnect(on_focus_in); - this.composer.editor.focus_out_event.disconnect(on_focus_out); - remove(this.composer); close_container(); } @@ -56,8 +49,6 @@ hide(); this.main_toolbar.remove_conversation_header(composer.header); this.composer.state = ComposerWidget.ComposerState.DETACHED; - this.composer.editor.focus_in_event.disconnect(on_focus_in); - this.composer.editor.focus_out_event.disconnect(on_focus_out); vanished(); } @@ -66,5 +57,5 @@ vanish(); destroy(); } -} +} diff -Nru geary-0.12.4/src/client/composer/composer-container.vala geary-3.32.0/src/client/composer/composer-container.vala --- geary-0.12.4/src/client/composer/composer-container.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/composer/composer-container.vala 2019-03-17 13:39:29.000000000 +0000 @@ -12,12 +12,6 @@ // The ComposerWidget-child. internal abstract ComposerWidget composer { get; set; } - // Workaround to retrieve all Gtk.Actions with conflicting accelerators - protected const string[] conflicting_actions = { - GearyController.ACTION_MARK_AS_UNREAD, - GearyController.ACTION_FORWARD_MESSAGE - }; - // We use old_accelerators to keep track of the accelerators we temporarily disabled. protected abstract Gee.MultiMap? old_accelerators { get; set; } @@ -50,89 +44,4 @@ */ public abstract void remove_composer(); - protected virtual bool on_focus_in() { - if (this.old_accelerators == null) { - this.old_accelerators = new Gee.HashMultiMap(); - add_accelerators(); - } - return false; - } - - protected virtual bool on_focus_out() { - if (this.old_accelerators != null) { - remove_accelerators(); - this.old_accelerators = null; - } - return false; - } - - /** - * Adds the accelerators for the child composer, and temporarily removes conflicting - * accelerators from existing actions. - */ - protected virtual void add_accelerators() { - GearyApplication app = GearyApplication.instance; - - // Check for actions with conflicting accelerators - foreach (string action in ComposerWidget.action_accelerators.get_keys()) { - foreach (string accelerator in ComposerWidget.action_accelerators[action]) { - string[] actions = app.get_actions_for_accel(accelerator); - - foreach (string conflicting_action in actions) { - remove_conflicting_accelerator(conflicting_action, accelerator); - this.old_accelerators[conflicting_action] = accelerator; - } - } - } - - // Very stupid workaround while we still use Gtk.Actions in the GearyController - foreach (string conflicting_action in conflicting_actions) - app.actions.get_action(conflicting_action).disconnect_accelerator(); - - // Now add our actions to the window and their accelerators - foreach (string action in ComposerWidget.action_accelerators.get_keys()) { - this.top_window.add_action(composer.get_action(action)); - app.set_accels_for_action("win." + action, - ComposerWidget.action_accelerators[action].to_array()); - } - } - - /** - * Removes the accelerators for the child composer, and restores previously removed accelerators. - */ - protected virtual void remove_accelerators() { - foreach (string action in ComposerWidget.action_accelerators.get_keys()) - GearyApplication.instance.set_accels_for_action("win." + action, {}); - - // Very stupid workaround while we still use Gtk.Actions in the GearyController - foreach (string conflicting_action in conflicting_actions) - GearyApplication.instance.actions.get_action(conflicting_action).connect_accelerator(); - - foreach (string action in old_accelerators.get_keys()) - foreach (string accelerator in this.old_accelerators[action]) - restore_conflicting_accelerator(action, accelerator); - } - - // Helper method. Removes the given conflicting accelerator from the action's accelerators. - private void remove_conflicting_accelerator(string action, string accelerator) { - GearyApplication app = GearyApplication.instance; - string[] accelerators = app.get_accels_for_action(action); - if (accelerators.length == 0) - return; - - string[] without_accel = new string[accelerators.length - 1]; - foreach (string a in accelerators) - if (a != accelerator) - without_accel += a; - - app.set_accels_for_action(action, without_accel); - } - - // Helper method. Adds the given accelerator back to the action's accelerators. - private void restore_conflicting_accelerator(string action, string accelerator) { - GearyApplication app = GearyApplication.instance; - string[] accelerators = app.get_accels_for_action(action); - accelerators += accelerator; - app.set_accels_for_action(action, accelerators); - } } diff -Nru geary-0.12.4/src/client/composer/composer-embed.vala geary-3.32.0/src/client/composer/composer-embed.vala --- geary-0.12.4/src/client/composer/composer-embed.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/composer/composer-embed.vala 2019-03-17 13:39:29.000000000 +0000 @@ -42,8 +42,6 @@ add(composer); realize.connect(on_realize); - this.composer.editor.focus_in_event.connect(on_focus_in); - this.composer.editor.focus_out_event.connect(on_focus_out); show(); } @@ -71,14 +69,7 @@ } public void remove_composer() { - if (this.composer.editor.has_focus) - on_focus_out(); - - this.composer.editor.focus_in_event.disconnect(on_focus_in); - this.composer.editor.focus_out_event.disconnect(on_focus_out); - disable_scroll_reroute(this); - remove(this.composer); close_container(); } @@ -189,8 +180,6 @@ public void vanish() { hide(); this.composer.state = ComposerWidget.ComposerState.DETACHED; - this.composer.editor.focus_in_event.disconnect(on_focus_in); - this.composer.editor.focus_out_event.disconnect(on_focus_out); vanished(); } diff -Nru geary-0.12.4/src/client/composer/composer-link-popover.vala geary-3.32.0/src/client/composer/composer-link-popover.vala --- geary-0.12.4/src/client/composer/composer-link-popover.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/composer/composer-link-popover.vala 2019-03-17 13:39:29.000000000 +0000 @@ -72,8 +72,7 @@ public ComposerLinkPopover(Type type) { - // GTK_ 3.18: Re-enable this - //set_default_widget(this.url); + set_default_widget(this.url); set_focus_child(this.url); switch (type) { case Type.NEW_LINK: @@ -174,15 +173,13 @@ [GtkCallback] private void on_activate_popover() { link_activate(); - // GTK+ 3.22/4: Change change this to popdown() - hide(); + popdown(); } [GtkCallback] private void on_delete_clicked() { link_delete(); - // GTK+ 3.22/4: Change change this to popdown() - hide(); + popdown(); } [GtkCallback] diff -Nru geary-0.12.4/src/client/composer/composer-web-view.vala geary-3.32.0/src/client/composer/composer-web-view.vala --- geary-0.12.4/src/client/composer/composer-web-view.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/composer/composer-web-view.vala 2019-03-17 13:39:29.000000000 +0000 @@ -12,9 +12,9 @@ public class ComposerWebView : ClientWebView { - private const string COMMAND_STACK_CHANGED = "commandStackChanged"; + // WebKit message handler names private const string CURSOR_CONTEXT_CHANGED = "cursorContextChanged"; - private const string DOCUMENT_MODIFIED = "documentModified"; + /** * Encapsulates editing-related state for a specific DOM node. @@ -81,7 +81,7 @@ private static WebKit.UserStyleSheet? app_style = null; private static WebKit.UserScript? app_script = null; - public static void load_resources() + public static new void load_resources() throws Error { ComposerWebView.app_style = ClientWebView.load_app_stylesheet( "composer-web-view.css" @@ -104,17 +104,6 @@ /** Determines if the view is in rich text mode. */ public bool is_rich_text { get; private set; default = true; } - // Determines if signals should be sent, useful for e.g. stopping - // document_modified being sent when the editor content is being - // updated before sending. - private bool signals_enabled = true; - - - /** Emitted when the web view's content has changed. */ - public signal void document_modified(); - - /** Emitted when the web view's undo/redo stack state changes. */ - public signal void command_stack_changed(bool can_undo, bool can_redo); /** Emitted when the cursor's edit context has changed. */ public signal void cursor_context_changed(EditContext cursor_context); @@ -131,27 +120,28 @@ this.user_content_manager.add_style_sheet(ComposerWebView.app_style); this.user_content_manager.add_script(ComposerWebView.app_script); - register_message_handler(COMMAND_STACK_CHANGED, on_command_stack_changed); register_message_handler(CURSOR_CONTEXT_CHANGED, on_cursor_context_changed); - register_message_handler(DOCUMENT_MODIFIED, on_document_modified); + + // XXX this is a bit of a hack given the docs for is_empty, + // above + this.command_stack_changed.connect((can_undo, can_redo) => { + this.is_empty = !can_undo; + }); } /** * Loads a message HTML body into the view. */ public new void load_html(string body, - string signature, string quote, bool top_posting, bool is_draft) { - const string HTML_PRE = """"""; + const string HTML_PRE = """"""; const string HTML_POST = """"""; const string BODY_PRE = """
"""; const string BODY_POST = """
-"""; - const string SIGNATURE = """ -
%s
+
"""; const string QUOTE = """

%s
@@ -160,7 +150,8 @@ const string SPACER = "

"; StringBuilder html = new StringBuilder(); - html.append(HTML_PRE); + string body_class = (this.is_rich_text) ? "" : "plain"; + html.append(HTML_PRE.printf(body_class)); if (!is_draft) { html.append(BODY_PRE); bool have_body = !Geary.String.is_empty(body); @@ -177,10 +168,6 @@ html.append(CURSOR); html.append(BODY_POST); - if (!Geary.String.is_empty(signature)) { - html.append_printf(SIGNATURE, signature); - } - if (top_posting && !Geary.String.is_empty(quote)) { html.append_printf(QUOTE, quote); } @@ -196,7 +183,6 @@ */ public void disable() { set_sensitive(false); - this.signals_enabled = false; } /** @@ -204,7 +190,9 @@ */ public void set_rich_text(bool enabled) { this.is_rich_text = enabled; - this.call.begin(Geary.JS.callable("geary.setRichText").bool(enabled), null); + if (this.is_content_loaded) { + this.call.begin(Geary.JS.callable("geary.setRichText").bool(enabled), null); + } } /** @@ -376,6 +364,14 @@ this.call.begin(Geary.JS.callable("geary.indentLine"), null); } + public void insert_olist() { + this.call.begin(Geary.JS.callable("geary.insertOrderedList"), null); + } + + public void insert_ulist() { + this.call.begin(Geary.JS.callable("geary.insertUnorderedList"), null); + } + /** * Updates the signature block if it has not been deleted. */ @@ -419,15 +415,6 @@ } /** - * Returns the editor content as an HTML string. - */ - public async string? get_html() throws Error { - return WebKitUtil.to_string( - yield call(Geary.JS.callable("geary.getHtml"), null) - ); - } - - /** * Returns the editor text as RFC 3676 format=flowed text. */ public async string? get_text() throws Error { @@ -504,41 +491,16 @@ // to show a link popopver after the view has processed one, // we need to emit our own. bool ret = base.button_release_event(event); - if (this.signals_enabled) { - button_release_event_done(event); - } + button_release_event_done(event); return ret; } - private void on_command_stack_changed(WebKit.JavascriptResult result) { - try { - if (this.signals_enabled) { - string[] values = WebKitUtil.to_string(result).split(","); - command_stack_changed(values[0] == "true", values[1] == "true"); - } - } catch (Geary.JS.Error err) { - debug("Could not get command stack state: %s", err.message); - } - } - private void on_cursor_context_changed(WebKit.JavascriptResult result) { try { - if (this.signals_enabled) { - cursor_context_changed(new EditContext(WebKitUtil.to_string(result))); - } + cursor_context_changed(new EditContext(WebKitUtil.to_string(result))); } catch (Geary.JS.Error err) { debug("Could not get text cursor style: %s", err.message); } } - private void on_document_modified(WebKit.JavascriptResult result) { - // Only modify actually changed to avoid excessive notify - // signals being fired. - if (this.signals_enabled) { - if (this.is_empty) { - this.is_empty = false; - } - document_modified(); - } - } } diff -Nru geary-0.12.4/src/client/composer/composer-widget.vala geary-3.32.0/src/client/composer/composer-widget.vala --- geary-0.12.4/src/client/composer/composer-widget.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/composer/composer-widget.vala 2019-03-17 13:39:29.000000000 +0000 @@ -13,7 +13,7 @@ // The actual widget for sending messages. Should be put in a ComposerContainer [GtkTemplate (ui = "/org/gnome/Geary/composer-widget.ui")] -public class ComposerWidget : Gtk.EventBox { +public class ComposerWidget : Gtk.EventBox, Geary.BaseInterface { public enum ComposeType { @@ -22,17 +22,16 @@ REPLY_ALL, FORWARD } - + public enum CloseStatus { DO_CLOSE, PENDING_CLOSE, CANCEL_CLOSE } - + public enum ComposerState { DETACHED, PANED, - NEW, INLINE, INLINE_COMPACT } @@ -48,15 +47,17 @@ } } - private SimpleActionGroup actions = new SimpleActionGroup(); - - private const string ACTION_UNDO = "undo"; - private const string ACTION_REDO = "redo"; + // XXX need separate composer close action in addition to the + // default window close action so we can bind Esc to it without + // also binding the default window close action to Esc as + // well. This could probably be fixed by pulling both the main + // window's and composer's actions out of the 'win' action + // namespace, leaving only common window actions there. + private const string ACTION_CLOSE = "composer-close"; private const string ACTION_CUT = "cut"; - private const string ACTION_COPY = "copy"; private const string ACTION_COPY_LINK = "copy-link"; private const string ACTION_PASTE = "paste"; - private const string ACTION_PASTE_WITH_FORMATTING = "paste-with-formatting"; + private const string ACTION_PASTE_WITHOUT_FORMATTING = "paste-without-formatting"; private const string ACTION_SELECT_ALL = "select-all"; private const string ACTION_BOLD = "bold"; private const string ACTION_ITALIC = "italic"; @@ -67,13 +68,14 @@ private const string ACTION_REMOVE_FORMAT = "remove-format"; private const string ACTION_INDENT = "indent"; private const string ACTION_OUTDENT = "outdent"; + private const string ACTION_OLIST = "olist"; + private const string ACTION_ULIST = "ulist"; private const string ACTION_JUSTIFY = "justify"; private const string ACTION_COLOR = "color"; private const string ACTION_INSERT_IMAGE = "insert-image"; private const string ACTION_INSERT_LINK = "insert-link"; private const string ACTION_COMPOSE_AS_HTML = "compose-as-html"; private const string ACTION_SHOW_EXTENDED = "show-extended"; - private const string ACTION_CLOSE = "close"; private const string ACTION_CLOSE_AND_SAVE = "close-and-save"; private const string ACTION_CLOSE_AND_DISCARD = "close-and-discard"; private const string ACTION_DETACH = "detach"; @@ -88,18 +90,18 @@ private const string[] html_actions = { ACTION_BOLD, ACTION_ITALIC, ACTION_UNDERLINE, ACTION_STRIKETHROUGH, ACTION_FONT_SIZE, ACTION_FONT_FAMILY, ACTION_COLOR, ACTION_JUSTIFY, - ACTION_INSERT_IMAGE, ACTION_COPY_LINK, ACTION_PASTE_WITH_FORMATTING + ACTION_INSERT_IMAGE, ACTION_COPY_LINK, + ACTION_OLIST, ACTION_ULIST }; - private const ActionEntry[] action_entries = { - // Editor commands - {ACTION_UNDO, on_undo }, - {ACTION_REDO, on_redo }, + private const ActionEntry[] editor_action_entries = { + {GearyApplication.ACTION_UNDO, on_undo }, + {GearyApplication.ACTION_REDO, on_redo }, + {GearyApplication.ACTION_COPY, on_copy }, {ACTION_CUT, on_cut }, - {ACTION_COPY, on_copy }, {ACTION_COPY_LINK, on_copy_link }, {ACTION_PASTE, on_paste }, - {ACTION_PASTE_WITH_FORMATTING, on_paste_with_formatting }, + {ACTION_PASTE_WITHOUT_FORMATTING, on_paste_without_formatting }, {ACTION_SELECT_ALL, on_select_all }, {ACTION_BOLD, on_action, null, "false" }, {ACTION_ITALIC, on_action, null, "false" }, @@ -109,53 +111,54 @@ {ACTION_FONT_FAMILY, on_font_family, "s", "'sans'" }, {ACTION_REMOVE_FORMAT, on_remove_format, null, "false" }, {ACTION_INDENT, on_indent }, + {ACTION_OLIST, on_olist }, + {ACTION_ULIST, on_ulist }, {ACTION_OUTDENT, on_action }, {ACTION_JUSTIFY, on_justify, "s", "'left'" }, {ACTION_COLOR, on_select_color }, {ACTION_INSERT_IMAGE, on_insert_image }, {ACTION_INSERT_LINK, on_insert_link }, - // Composer commands - {ACTION_COMPOSE_AS_HTML, on_toggle_action, null, "true", on_compose_as_html_toggled }, - {ACTION_SHOW_EXTENDED, on_toggle_action, null, "false", on_show_extended_toggled }, + {ACTION_OPEN_INSPECTOR, on_open_inspector }, + }; + + private const ActionEntry[] composer_action_entries = { + {GearyApplication.ACTION_CLOSE, on_close }, {ACTION_CLOSE, on_close }, - {ACTION_CLOSE_AND_SAVE, on_close_and_save }, - {ACTION_CLOSE_AND_DISCARD, on_close_and_discard }, - {ACTION_DETACH, on_detach }, - {ACTION_SEND, on_send }, {ACTION_ADD_ATTACHMENT, on_add_attachment }, {ACTION_ADD_ORIGINAL_ATTACHMENTS, on_pending_attachments }, + {ACTION_CLOSE_AND_DISCARD, on_close_and_discard }, + {ACTION_CLOSE_AND_SAVE, on_close_and_save }, + {ACTION_COMPOSE_AS_HTML, on_toggle_action, null, "true", on_compose_as_html_toggled }, + {ACTION_DETACH, on_detach }, {ACTION_SELECT_DICTIONARY, on_select_dictionary }, - {ACTION_OPEN_INSPECTOR, on_open_inspector }, + {ACTION_SEND, on_send }, + {ACTION_SHOW_EXTENDED, on_toggle_action, null, "false", on_show_extended_toggled }, }; public static Gee.MultiMap action_accelerators = new Gee.HashMultiMap(); - static construct { - action_accelerators.set(ACTION_UNDO, "z"); - action_accelerators.set(ACTION_REDO, "z"); - action_accelerators.set(ACTION_CUT, "x"); - action_accelerators.set(ACTION_COPY, "c"); - action_accelerators.set(ACTION_PASTE, "v"); - action_accelerators.set(ACTION_PASTE_WITH_FORMATTING, "v"); - action_accelerators.set(ACTION_INSERT_IMAGE, "g"); - action_accelerators.set(ACTION_INSERT_LINK, "l"); - action_accelerators.set(ACTION_INDENT, "bracketright"); - action_accelerators.set(ACTION_OUTDENT, "bracketleft"); - action_accelerators.set(ACTION_REMOVE_FORMAT, "space"); - action_accelerators.set(ACTION_BOLD, "b"); - action_accelerators.set(ACTION_ITALIC, "i"); - action_accelerators.set(ACTION_UNDERLINE, "u"); - action_accelerators.set(ACTION_STRIKETHROUGH, "k"); - action_accelerators.set(ACTION_CLOSE, "w"); - action_accelerators.set(ACTION_CLOSE, "Escape"); - action_accelerators.set(ACTION_ADD_ATTACHMENT, "t"); - action_accelerators.set(ACTION_DETACH, "d"); + + public static void add_window_accelerators(GearyApplication application) { + application.add_window_accelerators(ACTION_CLOSE, { "Escape" } ); + application.add_window_accelerators(ACTION_CUT, { "x" } ); + application.add_window_accelerators(ACTION_PASTE, { "v" } ); + application.add_window_accelerators(ACTION_PASTE_WITHOUT_FORMATTING, { "v" } ); + application.add_window_accelerators(ACTION_INSERT_IMAGE, { "g" } ); + application.add_window_accelerators(ACTION_INSERT_LINK, { "l" } ); + application.add_window_accelerators(ACTION_INDENT, { "bracketright" } ); + application.add_window_accelerators(ACTION_OUTDENT, { "bracketleft" } ); + application.add_window_accelerators(ACTION_REMOVE_FORMAT, { "space" } ); + application.add_window_accelerators(ACTION_BOLD, { "b" } ); + application.add_window_accelerators(ACTION_ITALIC, { "i" } ); + application.add_window_accelerators(ACTION_UNDERLINE, { "u" } ); + application.add_window_accelerators(ACTION_STRIKETHROUGH, { "k" } ); + application.add_window_accelerators(ACTION_ADD_ATTACHMENT, { "t" } ); + application.add_window_accelerators(ACTION_DETACH, { "d" } ); } private const string DRAFT_SAVED_TEXT = _("Saved"); private const string DRAFT_SAVING_TEXT = _("Saving"); private const string DRAFT_ERROR_TEXT = _("Error saving"); private const string BACKSPACE_TEXT = _("Press Backspace to delete quote"); - private const string DEFAULT_TITLE = _("New Message"); private const string URI_LIST_MIME_TYPE = "text/uri-list"; private const string FILE_URI_PREFIX = "file://"; @@ -235,12 +238,6 @@ public ComposerWebView editor { get; private set; } - public string draft_save_text { get; private set; } - - public bool can_delete_quote { get; private set; default = false; } - - public string toolbar_text { get; set; } - public string window_title { get; set; } public Configuration config { get; set; } @@ -256,6 +253,9 @@ internal Gtk.Grid editor_container; [GtkChild] + internal Gtk.Grid body_container; + + [GtkChild] private Gtk.Label from_label; [GtkChild] private Gtk.Label from_single; @@ -310,6 +310,8 @@ [GtkChild] private Gtk.Box font_style_buttons; [GtkChild] + private Gtk.Box list_buttons; + [GtkChild] private Gtk.Button insert_link_button; [GtkChild] private Gtk.Button remove_format_button; @@ -323,6 +325,9 @@ [GtkChild] private Gtk.Box message_area; + private SimpleActionGroup composer_actions = new SimpleActionGroup(); + private SimpleActionGroup editor_actions = new SimpleActionGroup(); + private Menu html_menu; private Menu plain_menu; @@ -356,10 +361,29 @@ private Geary.EmailFlags draft_flags = new Geary.EmailFlags.with(Geary.EmailFlags.DRAFT); private Geary.TimeoutManager draft_timer; private bool is_draft_saved = false; + private string draft_status_text { + get { return this._draft_status_text; } + set { + this._draft_status_text = value; + update_info_label(); + } + } + private string _draft_status_text = ""; + + private bool can_delete_quote { + get { return this._can_delete_quote; } + set { + this._can_delete_quote = value; + update_info_label(); + } + } + private bool _can_delete_quote = false; // Is the composer closing (e.g. saving a draft or sending)? private bool is_closing = false; + private ContactListStoreCache contact_list_store_cache; + private ComposerContainer container { get { return (ComposerContainer) parent; } } @@ -371,14 +395,23 @@ /** Fired when the user opens a link in the composer. */ public signal void link_activated(string url); + /** Fired when the user has changed the composer's subject. */ + public signal void subject_changed(string new_subject); + - public ComposerWidget(Geary.Account account, ComposeType compose_type, Configuration config) { + public ComposerWidget(Geary.Account account, + ContactListStoreCache contact_list_store_cache, + ComposeType compose_type, + Configuration config) { + base_ref(); this.account = account; + this.contact_list_store_cache = contact_list_store_cache; this.config = config; this.compose_type = compose_type; if (this.compose_type == ComposeType.NEW_MESSAGE) - this.state = ComposerState.NEW; - else if (this.compose_type == ComposeType.FORWARD) + this.state = ComposerState.PANED; + else if (this.compose_type == ComposeType.FORWARD || + this.account.information.has_sender_aliases) this.state = ComposerState.INLINE; else this.state = ComposerState.INLINE_COMPACT; @@ -401,19 +434,10 @@ add_events(Gdk.EventMask.KEY_PRESS_MASK | Gdk.EventMask.KEY_RELEASE_MASK); - this.visible_on_attachment_drag_over.remove(this.visible_on_attachment_drag_over_child); + this.visible_on_attachment_drag_over.remove( + this.visible_on_attachment_drag_over_child + ); - BindingTransformFunc set_toolbar_text = (binding, source_value, ref target_value) => { - if (draft_save_text == "" && can_delete_quote) - target_value = BACKSPACE_TEXT; - else - target_value = draft_save_text; - return true; - }; - bind_property("draft-save-text", this, "toolbar-text", BindingFlags.SYNC_CREATE, - set_toolbar_text); - bind_property("can-delete-quote", this, "toolbar-text", BindingFlags.SYNC_CREATE, - set_toolbar_text); this.to_entry = new EmailEntry(this); this.to_entry.changed.connect(on_envelope_changed); this.to_box.add(to_entry); @@ -437,9 +461,10 @@ this.editor = new ComposerWebView(config); this.editor.set_hexpand(true); this.editor.set_vexpand(true); + this.editor.content_loaded.connect(on_editor_content_loaded); this.editor.show(); - this.editor_container.add(this.editor); + this.body_container.add(this.editor); // Initialize menus Gtk.Builder builder = new Gtk.Builder.from_resource( @@ -454,13 +479,6 @@ this.context_menu_webkit_spelling = (Menu) builder.get_object("context_menu_webkit_spelling"); this.context_menu_webkit_text_entry = (Menu) builder.get_object("context_menu_webkit_text_entry"); - this.subject_entry.bind_property("text", this, "window-title", BindingFlags.SYNC_CREATE, - (binding, source_value, ref target_value) => { - target_value = Geary.String.is_empty_or_whitespace(this.subject_entry.text) - ? DEFAULT_TITLE : this.subject_entry.text.strip(); - return true; - }); - embed_header(); // Listen to account signals to update from menu. @@ -474,9 +492,6 @@ }); // TODO: also listen for account updates to allow adding identities while writing an email - bind_property("toolbar-text", this.info_label, "label", BindingFlags.SYNC_CREATE); - bind_property("toolbar-text", this.info_label, "tooltip-text", BindingFlags.SYNC_CREATE); - this.from = new Geary.RFC822.MailboxAddresses.single(account.information.primary_mailbox); this.draft_timer = new Geary.TimeoutManager.seconds( @@ -500,7 +515,7 @@ this.editor.document_modified.connect(() => { draft_changed(); }); this.editor.get_editor_state().notify["typing-attributes"].connect(on_typing_attributes_changed); this.editor.key_press_event.connect(on_editor_key_press_event); - this.editor.load_changed.connect(on_load_changed); + this.editor.content_loaded.connect(on_content_loaded); this.editor.mouse_target_changed.connect(on_mouse_target_changed); this.editor.selection_changed.connect(on_selection_changed); @@ -515,6 +530,11 @@ this.composer_container.set_focus_chain(chain); update_composer_view(); + load_entry_completions(); + } + + ~ComposerWidget() { + base_unref(); } public override void destroy() { @@ -528,9 +548,11 @@ base.destroy(); } - public ComposerWidget.from_mailto(Geary.Account account, string mailto, Configuration config) { - this(account, ComposeType.NEW_MESSAGE, config); - + public ComposerWidget.from_mailto(Geary.Account account, + ContactListStoreCache contact_list_store_cache, string mailto, Configuration config) { + + this(account, contact_list_store_cache, ComposeType.NEW_MESSAGE, config); + Gee.HashMultiMap headers = new Gee.HashMultiMap(); if (mailto.length > Geary.ComposedEmail.MAILTO_SCHEME.length) { // Parse the mailto link. @@ -607,10 +629,8 @@ update_attachments_view(); update_pending_attachments(this.pending_include, true); - string signature = yield load_signature(cancellable); this.editor.load_html( this.body_html, - signature, referred_quote, this.top_posting, is_referred_draft @@ -621,34 +641,27 @@ } catch (Error e) { debug("Could not open draft manager: %s", e.message); } - - // For accounts with large numbers of contacts, loading the - // entry completions can some time, so do it after the UI has - // been shown - yield load_entry_completions(); } /** * Loads and sets contact auto-complete data for the current account. */ - private async void load_entry_completions() { - // XXX Since ContactListStore hooks into ContactStore to - // listen for contacts being added and removed, - // GearyController or some composer-related controller should - // construct an instance per account and keep it around for - // the lifetime of the app, since there can be tens of - // thousands of contacts for large accounts. + private void load_entry_completions() { Geary.ContactStore contacts = this.account.get_contact_store(); if (this.contact_list_store == null || this.contact_list_store.contact_store != contacts) { - ContactListStore store = new ContactListStore(contacts); - this.contact_list_store = store; - yield store.load(); - - this.to_entry.completion = new ContactEntryCompletion(store); - this.cc_entry.completion = new ContactEntryCompletion(store); - this.bcc_entry.completion = new ContactEntryCompletion(store); - this.reply_to_entry.completion = new ContactEntryCompletion(store); + ContactListStore? store = this.contact_list_store_cache.get(contacts); + + if (store == null) { + error("Error loading contact_list_store from cache"); + } else { + this.contact_list_store = store; + + this.to_entry.completion = new ContactEntryCompletion(store); + this.cc_entry.completion = new ContactEntryCompletion(store); + this.bcc_entry.completion = new ContactEntryCompletion(store); + this.reply_to_entry.completion = new ContactEntryCompletion(store); + } } } @@ -712,7 +725,8 @@ if (in_reply_to.size > 1) { this.state = ComposerState.PANED; } else if (this.compose_type == ComposeType.FORWARD || this.to_entry.modified - || this.cc_entry.modified || this.bcc_entry.modified) { + || this.cc_entry.modified || this.bcc_entry.modified + || this.account.information.has_sender_aliases) { this.state = ComposerState.INLINE; } else { this.state = ComposerState.INLINE_COMPACT; @@ -763,10 +777,11 @@ this.references = Geary.RFC822.Utils.reply_references(referred); referred_quote = Geary.RFC822.Utils.quote_email_for_reply(referred, quote, Geary.RFC822.TextFormat.HTML); - if (!Geary.String.is_empty(quote)) + if (!Geary.String.is_empty(quote)) { this.top_posting = false; - else + } else { this.can_delete_quote = true; + } break; case ComposeType.FORWARD: @@ -797,16 +812,39 @@ // Initializes all actions and adds them to the action group private void initialize_actions() { - this.actions.add_action_entries(action_entries, this); + // Composer actions + this.composer_actions.add_action_entries( + ComposerWidget.composer_action_entries, this + ); + // Main actions use 'win' prefix so they override main window + // action. But for some reason, we can't use the same prefix + // for the headerbar. + insert_action_group("win", this.composer_actions); + this.header.insert_action_group("cmh", this.composer_actions); + + // Editor actions - scoped to the editor only. Need to include + // composer actions however since if not found in this group, + // ancestors (including the composer's) will not be consulted. + this.editor_actions.add_action_entries( + ComposerWidget.composer_action_entries, this + ); + this.editor_actions.add_action_entries( + ComposerWidget.editor_action_entries, this + ); + this.editor_container.insert_action_group("win", this.editor_actions); - // for some reason, we can't use the same prefix. - insert_action_group("cmp", this.actions); - this.header.insert_action_group("cmh", this.actions); + SimpleActionGroup[] composer_action_entries_users + = {this.editor_actions, this.composer_actions}; + foreach (SimpleActionGroup entries_users in composer_action_entries_users) { + entries_users.change_action_state(ACTION_SHOW_EXTENDED, false); + entries_users.change_action_state( + ACTION_COMPOSE_AS_HTML, this.config.compose_as_html + ); + } get_action(ACTION_CLOSE_AND_SAVE).set_enabled(false); - - get_action(ACTION_UNDO).set_enabled(false); - get_action(ACTION_REDO).set_enabled(false); + get_action(GearyApplication.ACTION_UNDO).set_enabled(false); + get_action(GearyApplication.ACTION_REDO).set_enabled(false); update_cursor_actions(); } @@ -814,7 +852,7 @@ private void update_cursor_actions() { bool has_selection = this.editor.has_selection; get_action(ACTION_CUT).set_enabled(has_selection); - get_action(ACTION_COPY).set_enabled(has_selection); + get_action(GearyApplication.ACTION_COPY).set_enabled(has_selection); get_action(ACTION_INSERT_LINK).set_enabled( this.editor.is_rich_text && (has_selection || this.cursor_url != null) @@ -837,30 +875,14 @@ return false; } - private void on_load_changed(WebKit.WebView view, WebKit.LoadEvent event) { - if (event == WebKit.LoadEvent.FINISHED) { - if (get_realized()) - on_load_finished_and_realized(); - else - realize.connect(on_load_finished_and_realized); - } - } - - private void on_load_finished_and_realized() { - // This is safe to call even when this connection hasn't been made. - realize.disconnect(on_load_finished_and_realized); - - this.actions.change_action_state( - ACTION_SHOW_EXTENDED, false - ); - this.actions.change_action_state( - ACTION_COMPOSE_AS_HTML, this.config.compose_as_html - ); - - if (can_delete_quote) + private void on_content_loaded() { + if (this.can_delete_quote) { this.editor.selection_changed.connect( - () => { this.can_delete_quote = false; } + () => { + this.can_delete_quote = false; + } ); + } } private void show_attachment_overlay(bool visible) { @@ -897,11 +919,11 @@ [GtkCallback] private void on_drag_data_received(Gtk.Widget sender, Gdk.DragContext context, int x, int y, Gtk.SelectionData selection_data, uint info, uint time_) { - + bool dnd_success = false; if (selection_data.get_length() >= 0) { dnd_success = true; - + string uri_list = (string) selection_data.get_data(); string[] uris = uri_list.strip().split("\n"); foreach (string uri in uris) { @@ -910,12 +932,13 @@ try { add_attachment_part(File.new_for_uri(uri.strip())); + draft_changed(); } catch (Error err) { attachment_failed(err.message); } } } - + Gtk.drag_finish(context, dnd_success, false, time_); } @@ -923,7 +946,7 @@ private bool on_drag_drop(Gtk.Widget sender, Gdk.DragContext context, int x, int y, uint time_) { if (context.list_targets() == null) return false; - + uint length = context.list_targets().length(); Gdk.Atom? target_type = null; for (uint i = 0; i < length; i++) { @@ -931,10 +954,10 @@ if (target.name() == URI_LIST_MIME_TYPE) target_type = target; } - + if (target_type == null) return false; - + Gtk.drag_get_data(sender, context, target_type, time_); return true; } @@ -1044,7 +1067,7 @@ private void add_recipients_and_ids(ComposeType type, Geary.Email referred, bool modify_headers = true) { Gee.List sender_addresses = - account.information.get_all_mailboxes(); + account.information.sender_mailboxes; // Set the preferred from address. New messages should retain // the account default and drafts should retain the draft's @@ -1069,7 +1092,7 @@ if (!modify_headers) return; - + bool recipients_modified = this.to_entry.modified || this.cc_entry.modified || this.bcc_entry.modified; if (!recipients_modified) { if (type == ComposeType.REPLY || type == ComposeType.REPLY_ALL) @@ -1099,12 +1122,21 @@ CloseStatus status = CloseStatus.PENDING_CLOSE; if (this.can_save) { - AlertDialog dialog = new TernaryConfirmationDialog(container.top_window, - _("Do you want to discard this message?"), null, Stock._KEEP, Stock._DISCARD, - Gtk.ResponseType.CLOSE, "suggested-action"); + AlertDialog dialog = new TernaryConfirmationDialog( + container.top_window, + // Translators: This dialog text is displayed to the + // user when closing a composer where the options are + // Keep, Discard or Cancel. + _("Do you want to keep or discard this draft message?"), + null, + Stock._KEEP, + Stock._DISCARD, Gtk.ResponseType.CLOSE, + "suggested-action" + ); Gtk.ResponseType response = dialog.run(); if (response == Gtk.ResponseType.CANCEL || response == Gtk.ResponseType.DELETE_EVENT) { + // Cancel status = CloseStatus.CANCEL_CLOSE; } else if (response == Gtk.ResponseType.OK) { // Keep @@ -1118,8 +1150,16 @@ discard_and_exit_async.begin(); } } else { - AlertDialog dialog = new ConfirmationDialog(container.top_window, - _("Do you want to discard this message?"), null, Stock._DISCARD, "destructive-action"); + AlertDialog dialog = new ConfirmationDialog( + container.top_window, + // Translators: This dialog text is displayed to the + // user when closing a composer where the options are + // only Discard or Cancel. + _("Do you want to discard this draft message?"), + null, + Stock._DISCARD, + "destructive-action" + ); Gtk.ResponseType response = dialog.run(); if (response == Gtk.ResponseType.OK) { discard_and_exit_async.begin(); @@ -1163,7 +1203,7 @@ // conversation back in the main window. The workaround here // sets a new menu model and hence the menu_button constructs // a new popover. - this.actions.change_action_state(ACTION_COMPOSE_AS_HTML, + this.composer_actions.change_action_state(ACTION_COMPOSE_AS_HTML, GearyApplication.instance.config.compose_as_html); this.state = ComposerWidget.ComposerState.DETACHED; @@ -1345,7 +1385,8 @@ private async void close_draft_manager_async(Cancellable? cancellable) throws Error { - this.draft_save_text = ""; + this.draft_status_text = ""; + get_action(ACTION_CLOSE_AND_SAVE).set_enabled(false); Geary.App.DraftManager old_manager = this.draft_manager; @@ -1369,22 +1410,22 @@ private void update_draft_state() { switch (this.draft_manager.draft_state) { case Geary.App.DraftManager.DraftState.STORED: - this.draft_save_text = DRAFT_SAVED_TEXT; + this.draft_status_text = DRAFT_SAVED_TEXT; this.is_draft_saved = true; break; case Geary.App.DraftManager.DraftState.STORING: - this.draft_save_text = DRAFT_SAVING_TEXT; + this.draft_status_text = DRAFT_SAVING_TEXT; this.is_draft_saved = true; break; case Geary.App.DraftManager.DraftState.NOT_STORED: - this.draft_save_text = ""; + this.draft_status_text = ""; this.is_draft_saved = false; break; case Geary.App.DraftManager.DraftState.ERROR: - this.draft_save_text = DRAFT_ERROR_TEXT; + this.draft_status_text = DRAFT_ERROR_TEXT; this.is_draft_saved = false; break; @@ -1394,10 +1435,10 @@ } private inline void draft_changed() { - if (this.can_save) { + if (this.should_save) { this.draft_timer.start(); } - this.draft_save_text = ""; + this.draft_status_text = ""; // can_save depends on the value of this, so reset it after // the if test above this.is_draft_saved = false; @@ -1430,7 +1471,7 @@ } catch (Error err) { GLib.message("Unable to discard draft: %s", err.message); } - + return null; } @@ -1481,7 +1522,8 @@ // Both adds pending attachments and updates the UI if there are // any that were left out, that could have been added manually. - private void update_pending_attachments(AttachPending include, bool do_add) { + private bool update_pending_attachments(AttachPending include, bool do_add) { + bool have_added = false; bool manual_enabled = false; if (this.pending_attachments != null) { foreach(Geary.Attachment part in this.pending_attachments) { @@ -1514,13 +1556,14 @@ // automatically, so add it if asked to and it // hasn't already been added if (do_add && - !(file in this.attached_files) && - !(content_id in this.inline_files)) { + !this.attached_files.contains(file) && + !this.inline_files.has_key(content_id)) { if (type == Geary.Mime.DispositionType.INLINE) { add_inline_part(file, content_id); } else { add_attachment_part(file); } + have_added = true; } } else { // The pending attachment should only be added @@ -1533,6 +1576,7 @@ } } this.header.show_pending_attachments = manual_enabled; + return have_added; } private void add_attachment_part(File target) @@ -1636,6 +1680,7 @@ update_attachments_view(); update_pending_attachments(this.pending_include, false); + draft_changed(); } private bool check_send_on_return(Gdk.EventKey event) { @@ -1647,7 +1692,7 @@ // the Enter leaking through to the controls, but only // send if send is available if ((event.state & Gdk.ModifierType.CONTROL_MASK) != 0) { - this.actions.activate_action(ACTION_SEND, null); + this.composer_actions.activate_action(ACTION_SEND, null); ret = Gdk.EVENT_STOP; } break; @@ -1655,11 +1700,6 @@ return ret; } - [GtkCallback] - private void on_envelope_changed() { - draft_changed(); - } - private void validate_send_button() { get_action(ACTION_SEND).set_enabled(this.to_entry.valid || this.cc_entry.valid || this.bcc_entry.valid); } @@ -1670,18 +1710,30 @@ string label = this.to_entry.buffer.text + (tocc ? ", " : "") + this.cc_entry.buffer.text + (ccbcc ? ", " : "") + this.bcc_entry.buffer.text; StringBuilder tooltip = new StringBuilder(); - if (to_entry.addresses != null) - foreach(Geary.RFC822.MailboxAddress addr in this.to_entry.addresses) - tooltip.append(_("To: ") + addr.get_full_address() + "\n"); - if (cc_entry.addresses != null) - foreach(Geary.RFC822.MailboxAddress addr in this.cc_entry.addresses) - tooltip.append(_("Cc: ") + addr.get_full_address() + "\n"); - if (bcc_entry.addresses != null) - foreach(Geary.RFC822.MailboxAddress addr in this.bcc_entry.addresses) - tooltip.append(_("Bcc: ") + addr.get_full_address() + "\n"); - if (reply_to_entry.addresses != null) - foreach(Geary.RFC822.MailboxAddress addr in this.reply_to_entry.addresses) - tooltip.append(_("Reply-To: ") + addr.get_full_address() + "\n"); + if (to_entry.addresses != null) { + foreach(Geary.RFC822.MailboxAddress addr in this.to_entry.addresses) { + // Translators: Human-readable version of the RFC 822 To header + tooltip.append("%s %s\n".printf(_("To:"), addr.to_full_display())); + } + } + if (cc_entry.addresses != null) { + foreach(Geary.RFC822.MailboxAddress addr in this.cc_entry.addresses) { + // Translators: Human-readable version of the RFC 822 CC header + tooltip.append("%s %s\n".printf(_("Cc:"), addr.to_full_display())); + } + } + if (bcc_entry.addresses != null) { + foreach(Geary.RFC822.MailboxAddress addr in this.bcc_entry.addresses) { + // Translators: Human-readable version of the RFC 822 BCC header + tooltip.append("%s %s\n".printf(_("Bcc:"), addr.to_full_display())); + } + } + if (reply_to_entry.addresses != null) { + foreach(Geary.RFC822.MailboxAddress addr in this.reply_to_entry.addresses) { + // Translators: Human-readable version of the RFC 822 Reply-To header + tooltip.append("%s%s\n".printf(_("Reply-To: "), addr.to_full_display())); + } + } this.header.set_recipients(label, tooltip.str.slice(0, -1)); // Remove trailing \n } @@ -1730,15 +1782,20 @@ } private void on_paste(SimpleAction action, Variant? param) { - if (this.container.get_focus() == this.editor) - this.editor.paste_plain_text(); - else if (this.container.get_focus() is Gtk.Editable) + if (this.container.get_focus() == this.editor) { + if (this.editor.is_rich_text) { + this.editor.paste_rich_text(); + } else { + this.editor.paste_plain_text(); + } + } else if (this.container.get_focus() is Gtk.Editable) { ((Gtk.Editable) this.container.get_focus()).paste_clipboard(); + } } - private void on_paste_with_formatting(SimpleAction action, Variant? param) { + private void on_paste_without_formatting(SimpleAction action, Variant? param) { if (this.container.get_focus() == this.editor) - this.editor.paste_rich_text(); + this.editor.paste_plain_text(); } private void on_select_all(SimpleAction action, Variant? param) { @@ -1769,6 +1826,7 @@ this.insert_buttons.visible = compose_as_html; this.font_style_buttons.visible = compose_as_html; + this.list_buttons.visible = compose_as_html; this.remove_format_button.visible = compose_as_html; this.menu_button.menu_model = (compose_as_html) ? this.html_menu : this.plain_menu; @@ -1827,40 +1885,24 @@ this.editor.indent_line(); } + private void on_olist(SimpleAction action, Variant? param) { + this.editor.insert_olist(); + } + + private void on_ulist(SimpleAction action, Variant? param) { + this.editor.insert_ulist(); + } + private void on_mouse_target_changed(WebKit.WebView web_view, WebKit.HitTestResult hit_test, uint modifiers) { bool copy_link_enabled = hit_test.context_is_link(); this.pointer_url = copy_link_enabled ? hit_test.get_link_uri() : null; this.message_overlay_label.label = this.pointer_url ?? ""; + this.message_overlay_label.set_visible(copy_link_enabled); get_action(ACTION_COPY_LINK).set_enabled(copy_link_enabled); } - private void update_message_overlay_label_style() { - Gdk.RGBA window_background = container.top_window.get_style_context() - .get_background_color(Gtk.StateFlags.NORMAL); - Gdk.RGBA label_background = message_overlay_label.get_style_context() - .get_background_color(Gtk.StateFlags.NORMAL); - - if (label_background == window_background) - return; - - message_overlay_label.get_style_context().changed.disconnect( - on_message_overlay_label_style_changed); - message_overlay_label.override_background_color(Gtk.StateFlags.NORMAL, window_background); - message_overlay_label.get_style_context().changed.connect( - on_message_overlay_label_style_changed); - } - - [GtkCallback] - private void on_message_overlay_label_realize() { - update_message_overlay_label_style(); - } - - private void on_message_overlay_label_style_changed() { - update_message_overlay_label_style(); - } - private bool on_context_menu(WebKit.WebView view, WebKit.ContextMenu context_menu, Gdk.Event event, @@ -1942,15 +1984,21 @@ private inline void append_menu_section(WebKit.ContextMenu context_menu, Menu section) { GtkUtil.menu_foreach(section, (label, name, target, section) => { - if ("." in name) - name = name.split(".")[1]; + string simple_name = name; + if ("." in simple_name) { + simple_name = simple_name.split(".")[1]; + } - Gtk.Action action = new Gtk.Action(name, label, null, null); - action.set_sensitive(get_action(name).enabled); - action.activate.connect((action) => { - this.actions.activate_action(name, target); - }); - context_menu.append(new WebKit.ContextMenuItem(action)); + GLib.SimpleAction? action = get_action(simple_name); + if (action != null) { + context_menu.append( + new WebKit.ContextMenuItem.from_gaction( + action, label, target + ) + ); + } else { + warning("Unknown action: %s/%s", name, label); + } }); } @@ -1991,41 +2039,57 @@ * Helper method, returns a composer action. * @param action_name - The name of the action (as found in action_entries) */ - public SimpleAction? get_action(string action_name) { - return this.actions.lookup_action(action_name) as SimpleAction; + public GLib.SimpleAction? get_action(string action_name) { + GLib.Action? action = this.composer_actions.lookup_action(action_name); + if (action == null) { + action = this.editor_actions.lookup_action(action_name); + } + return action as SimpleAction; } private bool add_account_emails_to_from_list(Geary.Account other_account, bool set_active = false) { - Geary.RFC822.MailboxAddresses primary_address = new Geary.RFC822.MailboxAddresses.single( - other_account.information.primary_mailbox); - this.from_multiple.append_text(primary_address.to_rfc822_string()); - this.from_list.add(new FromAddressMap(other_account, primary_address)); - if (!set_active && this.from.equal_to(primary_address)) { - this.from_multiple.set_active(this.from_list.size - 1); - set_active = true; - } - - if (other_account.information.alternate_mailboxes != null) { - foreach (Geary.RFC822.MailboxAddress alternate_mailbox in other_account.information.alternate_mailboxes) { - Geary.RFC822.MailboxAddresses addresses = new Geary.RFC822.MailboxAddresses.single( - alternate_mailbox); - - // Displayed in the From dropdown to indicate an "alternate email address" - // for an account. The first printf argument will be the alternate email - // address, and the second will be the account's primary email address. - string display = _("%1$s via %2$s").printf(addresses.to_rfc822_string(), other_account.information.display_name); - this.from_multiple.append_text(display); - this.from_list.add(new FromAddressMap(other_account, addresses)); - - if (!set_active && this.from.equal_to(addresses)) { - this.from_multiple.set_active(this.from_list.size - 1); - set_active = true; - } + bool is_primary = true; + foreach (Geary.RFC822.MailboxAddress mailbox in + other_account.information.sender_mailboxes) { + Geary.RFC822.MailboxAddresses addresses = + new Geary.RFC822.MailboxAddresses.single(mailbox); + + string display = mailbox.to_full_display(); + if (!is_primary) { + // Displayed in the From dropdown to indicate an + // "alternate email address" for an account. The first + // printf argument will be the alternate email address, + // and the second will be the account's primary email + // address. + display = _("%1$s via %2$s").printf( + display, other_account.information.display_name + ); + } + is_primary = false; + + this.from_multiple.append_text(display); + this.from_list.add(new FromAddressMap(other_account, addresses)); + + if (!set_active && this.from.equal_to(addresses)) { + this.from_multiple.set_active(this.from_list.size - 1); + set_active = true; } } return set_active; } + private void update_info_label() { + string text = ""; + if (this.can_delete_quote) { + text = BACKSPACE_TEXT; + } else { + text = this.draft_status_text; + } + + this.info_label.set_text(text); + this.info_label.set_tooltip_text(text); + } + // Updates from combobox contents and visibility, returns true if // the from address had to be set private bool update_from_field() { @@ -2036,19 +2100,22 @@ try { accounts = Geary.Engine.instance.get_accounts(); } catch (Error e) { - debug("Could not fetch account info: %s", e.message); - + warning("Could not fetch account info: %s", e.message); return false; } - // Don't show in inline, compact, or paned modes. - if (this.state == ComposerState.INLINE || this.state == ComposerState.INLINE_COMPACT || - this.state == ComposerState.PANED) + // Don't show in inline unless the current account has + // multiple emails, since these will be replies to a + // conversation + if ((this.state == ComposerState.INLINE || + this.state == ComposerState.INLINE_COMPACT) && + !this.account.information.has_sender_aliases) return false; - // If there's only one account, show nothing. (From fields are hidden above.) - if (accounts.size < 1 || (accounts.size == 1 && Geary.traverse( - accounts.values).first().alternate_mailboxes == null)) + // If there's only one account and it not have any aliases, + // show nothing. + if (accounts.size < 1 || (accounts.size == 1 && !Geary.traverse( + accounts.values).first().has_sender_aliases)) return false; this.from_label.visible = true; @@ -2088,7 +2155,7 @@ return !set_active; } - private void update_from_account() throws Error { + private void update_from() throws Error { int index = this.from_multiple.get_active(); if (index >= 0) { FromAddressMap selected = this.from_list.get(index); @@ -2096,41 +2163,43 @@ if (selected.account != this.account) { this.account = selected.account; - this.load_signature.begin(null, (obj, res) => { - this.editor.update_signature(this.load_signature.end(res)); - }); - load_entry_completions.begin(); - reopen_draft_manager_async.begin(); + this.update_signature.begin(null); + load_entry_completions(); + this.reopen_draft_manager_async.begin(); } } } - private async string load_signature(Cancellable? cancellable = null) { - string account_sig = ""; - - if (this.account.information.use_email_signature) { - account_sig = account.information.email_signature ?? ""; - if (Geary.String.is_empty_or_whitespace(account_sig)) { + private async void update_signature(Cancellable? cancellable = null) { + string sig = ""; + if (this.account.information.use_signature) { + sig = account.information.signature; + if (Geary.String.is_empty_or_whitespace(sig)) { // No signature is specified in the settings, so use // ~/.signature File signature_file = File.new_for_path(Environment.get_home_dir()).get_child(".signature"); try { uint8[] data; yield signature_file.load_contents_async(cancellable, out data, null); - account_sig = (string) data; + sig = (string) data; } catch (Error error) { if (!(error is IOError.NOT_FOUND)) { debug("Error reading signature file %s: %s", signature_file.get_path(), error.message); } } } - - account_sig = (!Geary.String.is_empty_or_whitespace(account_sig)) - ? Geary.HTML.smart_escape(account_sig, true) - : ""; } - return account_sig; + // Still want to update the signature even if it is empty, + // since when changing the selected from account, if the + // previously selected account had a sig but the newly + // selected account does not, the old sig gets cleared out. + if (Geary.String.is_empty_or_whitespace(sig)) { + // Clear out multiple spaces etc so smart_escape + // doesn't create  's + sig = ""; + } + this.editor.update_signature(Geary.HTML.smart_escape(sig)); } private async ComposerLinkPopover new_link_popover(ComposerLinkPopover.Type type, @@ -2158,8 +2227,12 @@ } private void on_command_state_changed(bool can_undo, bool can_redo) { - get_action(ACTION_UNDO).set_enabled(can_undo); - get_action(ACTION_REDO).set_enabled(can_redo); + get_action(GearyApplication.ACTION_UNDO).set_enabled(can_undo); + get_action(GearyApplication.ACTION_REDO).set_enabled(can_redo); + } + + private void on_editor_content_loaded() { + this.update_signature.begin(null); } private void on_draft_id_changed() { @@ -2167,18 +2240,29 @@ } private void on_draft_manager_fatal(Error err) { - this.draft_save_text = DRAFT_ERROR_TEXT; + this.draft_status_text = DRAFT_ERROR_TEXT; } private void on_draft_state_changed() { update_draft_state(); } + [GtkCallback] + private void on_subject_changed() { + draft_changed(); + subject_changed(this.subject); + } + + [GtkCallback] + private void on_envelope_changed() { + draft_changed(); + } + private void on_from_changed() { try { - update_from_account(); + update_from(); } catch (Error err) { - debug("Unable to update From: Account in composer: %s", err.message); + debug("Error updating from address: %s", err.message); } } @@ -2187,10 +2271,9 @@ // so the user can still select text with a link in it, // without the popover immediately appearing and raining on // their text selection parade. - if (this.pointer_url != null && - this.actions.get_action_state(ACTION_COMPOSE_AS_HTML).get_boolean()) { + if (this.pointer_url != null && this.config.compose_as_html) { Gdk.EventButton? button = (Gdk.EventButton) event; - Gdk.Rectangle location = new Gdk.Rectangle(); + Gdk.Rectangle location = Gdk.Rectangle(); location.x = (int) button.x; location.y = (int) button.y; @@ -2210,31 +2293,33 @@ this.cursor_url = context.is_link ? context.link_url : null; update_cursor_actions(); - this.actions.change_action_state(ACTION_FONT_FAMILY, context.font_family); + this.editor_actions.change_action_state( + ACTION_FONT_FAMILY, context.font_family + ); if (context.font_size < 11) - this.actions.change_action_state(ACTION_FONT_SIZE, "small"); + this.editor_actions.change_action_state(ACTION_FONT_SIZE, "small"); else if (context.font_size > 20) - this.actions.change_action_state(ACTION_FONT_SIZE, "large"); + this.editor_actions.change_action_state(ACTION_FONT_SIZE, "large"); else - this.actions.change_action_state(ACTION_FONT_SIZE, "medium"); + this.editor_actions.change_action_state(ACTION_FONT_SIZE, "medium"); } private void on_typing_attributes_changed() { uint mask = this.editor.get_editor_state().get_typing_attributes(); - this.actions.change_action_state( + this.editor_actions.change_action_state( ACTION_BOLD, (mask & WebKit.EditorTypingAttributes.BOLD) == WebKit.EditorTypingAttributes.BOLD ); - this.actions.change_action_state( + this.editor_actions.change_action_state( ACTION_ITALIC, (mask & WebKit.EditorTypingAttributes.ITALIC) == WebKit.EditorTypingAttributes.ITALIC ); - this.actions.change_action_state( + this.editor_actions.change_action_state( ACTION_UNDERLINE, (mask & WebKit.EditorTypingAttributes.UNDERLINE) == WebKit.EditorTypingAttributes.UNDERLINE ); - this.actions.change_action_state( + this.editor_actions.change_action_state( ACTION_STRIKETHROUGH, (mask & WebKit.EditorTypingAttributes.STRIKETHROUGH) == WebKit.EditorTypingAttributes.STRIKETHROUGH ); @@ -2247,6 +2332,7 @@ foreach (File file in dialog.get_files()) { try { add_attachment_part(file); + draft_changed(); } catch (Error err) { attachment_failed(err.message); break; @@ -2258,7 +2344,9 @@ } private void on_pending_attachments() { - update_pending_attachments(AttachPending.ALL, true); + if (update_pending_attachments(AttachPending.ALL, true)) { + draft_changed(); + } } private void on_insert_image(SimpleAction action, Variant? param) { @@ -2289,7 +2377,7 @@ private void on_insert_link(SimpleAction action, Variant? param) { ComposerLinkPopover.Type type = ComposerLinkPopover.Type.NEW_LINK; - string url = "http://"; + string url = "https://"; if (this.cursor_url != null) { type = ComposerLinkPopover.Type.EXISTING_LINK; url = this.cursor_url; diff -Nru geary-0.12.4/src/client/composer/composer-window.vala geary-3.32.0/src/client/composer/composer-window.vala --- geary-0.12.4/src/client/composer/composer-window.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/composer/composer-window.vala 2019-03-17 13:39:29.000000000 +0000 @@ -10,6 +10,10 @@ */ public class ComposerWindow : Gtk.ApplicationWindow, ComposerContainer { + + private const string DEFAULT_TITLE = _("New Message"); + + public Gtk.ApplicationWindow top_window { get { return this; } } @@ -20,7 +24,6 @@ private bool closing = false; - public ComposerWindow(ComposerWidget composer) { Object(type: Gtk.WindowType.TOPLEVEL); this.composer = composer; @@ -33,34 +36,73 @@ set_property("name", "GearyComposerWindow"); add(this.composer); - focus_in_event.connect(on_focus_in); - focus_out_event.connect(on_focus_out); if (composer.config.desktop_environment == Configuration.DesktopEnvironment.UNITY) { composer.embed_header(); - composer.bind_property("window-title", this, "title", BindingFlags.SYNC_CREATE); } else { - this.composer.header.show_close_button = true; - this.composer.free_header(); + composer.header.show_close_button = true; + composer.free_header(); set_titlebar(this.composer.header); - composer.bind_property("window-title", this.composer.header, "title", - BindingFlags.SYNC_CREATE); } + composer.subject_changed.connect(() => { update_title(); } ); + update_title(); + show(); set_position(Gtk.WindowPosition.CENTER); } public override void show() { - set_default_size(680, 600); + Gdk.Display? display = Gdk.Display.get_default(); + if (display != null) { + Gdk.Monitor? monitor = display.get_primary_monitor(); + if (monitor == null) { + monitor = display.get_monitor_at_point(1, 1); + } + int[] size = GearyApplication.instance.config.composer_window_size; + //check if stored values are reasonable + if (monitor != null && + size[0] >= 0 && size[0] <= monitor.geometry.width && + size[1] >= 0 && size[1] <= monitor.geometry.height) { + set_default_size(size[0], size[1]); + } else { + set_default_size(680, 600); + } + } + base.show(); } - public void close_container() { - on_focus_out(); - this.composer.editor.focus_in_event.disconnect(on_focus_in); - this.composer.editor.focus_out_event.disconnect(on_focus_out); + private void save_window_geometry () { + if (!this.is_maximized) { + Gdk.Display? display = get_display(); + Gdk.Window? window = get_window(); + if (display != null && window != null) { + Gdk.Monitor monitor = display.get_monitor_at_window(window); + + int width = 0; + int height = 0; + get_size(out width, out height); + + // Only store if the values are reasonable-looking. + if (width > 0 && width <= monitor.geometry.width && + height > 0 && height <= monitor.geometry.height) { + GearyApplication.instance.config.composer_window_size = { + width, height + }; + } + } + } + } + + // Fired on window resize. Save window size for the next start. + public override void size_allocate(Gtk.Allocation allocation) { + base.size_allocate(allocation); + this.save_window_geometry(); + } + + public void close_container() { this.closing = true; destroy(); } @@ -77,5 +119,22 @@ public void remove_composer() { warning("Detached composer received remove"); } -} + private void update_title() { + string subject = this.composer.subject.strip(); + if (Geary.String.is_empty_or_whitespace(subject)) { + subject = DEFAULT_TITLE; + } + + switch (this.composer.config.desktop_environment) { + case Configuration.DesktopEnvironment.UNITY: + this.title = subject; + break; + + default: + this.composer.header.title = subject; + break; + } + } + +} diff -Nru geary-0.12.4/src/client/composer/contact-entry-completion.vala geary-3.32.0/src/client/composer/contact-entry-completion.vala --- geary-0.12.4/src/client/composer/contact-entry-completion.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/composer/contact-entry-completion.vala 2019-03-17 13:39:29.000000000 +0000 @@ -23,8 +23,6 @@ if (!contacts.match_prefix_contact(current_address_key, contact, out highlighted_result)) return false; - contacts.list_store.set_highlighted_result(iter, highlighted_result, current_address_key); - return true; } @@ -39,20 +37,40 @@ Gtk.CellRendererText text_renderer = new Gtk.CellRendererText(); pack_start(text_renderer, true); - add_attribute(text_renderer, "markup", ContactListStore.Column.CONTACT_MARKUP_NAME); - + set_cell_data_func(text_renderer, cell_layout_data_func); + set_inline_selection(true); match_selected.connect(on_match_selected); cursor_on_match.connect(on_cursor_on_match); } - + + private void cell_layout_data_func(Gtk.CellLayout cell_layout, Gtk.CellRenderer cell, + Gtk.TreeModel tree_model, Gtk.TreeIter iter) { + string highlighted_result = ""; + + GLib.Value contact_value; + tree_model.get_value(iter, ContactListStore.Column.CONTACT_OBJECT, out contact_value); + + Geary.Contact? contact = (Geary.Contact) contact_value.get_object(); + + if (contact != null) { + string current_address_key; + this.get_addresses(this, null, out current_address_key); + + this.match_prefix_contact(current_address_key, contact, out highlighted_result); + } + + Gtk.CellRendererText text_renderer = (Gtk.CellRendererText) cell; + text_renderer.markup = highlighted_result; + } + private bool on_match_selected(Gtk.EntryCompletion sender, Gtk.TreeModel model, Gtk.TreeIter iter) { - string full_address = list_store.get_rfc822_string(iter); - + string full_address = list_store.to_full_address(iter); + Gtk.Entry? entry = sender.get_entry() as Gtk.Entry; if (entry == null) return false; - + int current_address_index; string current_address_remainder; Gee.List addresses = get_addresses(sender, out current_address_index, null, @@ -62,32 +80,32 @@ addresses.insert(current_address_index + 1, current_address_remainder); string delimiter = ", "; entry.text = concat_strings(addresses, delimiter); - + int characters_seen_so_far = 0; for (int i = 0; i <= current_address_index; i++) characters_seen_so_far += addresses[i].char_count() + delimiter.char_count(); - + entry.set_position(characters_seen_so_far); - + return true; } - + private bool on_cursor_on_match(Gtk.EntryCompletion sender, Gtk.TreeModel model, Gtk.TreeIter iter) { last_iter = iter; return true; } - + public void trigger_selection() { if (last_iter != null) { on_match_selected(this, model, last_iter); last_iter = null; } } - + public void reset_selection() { last_iter = null; } - + private Gee.List get_addresses(Gtk.EntryCompletion completion, out int current_address_index = null, out string current_address_key = null, out string current_address_remainder = null) { @@ -99,25 +117,25 @@ empty_addresses.add(""); if (entry == null) return empty_addresses; - + string? original_text = entry.get_text(); if (original_text == null) return empty_addresses; - + int cursor_position = entry.cursor_position; int cursor_offset = original_text.index_of_nth_char(cursor_position); if (cursor_offset < 0) return empty_addresses; - + Gee.List addresses = new Gee.ArrayList(); string delimiter = ","; string[] addresses_array = original_text.split(delimiter); foreach (string address in addresses_array) addresses.add(address); - + if (addresses.size < 1) return empty_addresses; - + int bytes_seen_so_far = 0; current_address_index = addresses.size - 1; for (int i = 0; i < addresses.size; i++) { @@ -127,7 +145,7 @@ current_address_key = addresses[i] .substring(0, cursor_offset - bytes_seen_so_far) .strip().normalize().casefold(); - + current_address_remainder = addresses[i] .substring(cursor_offset - bytes_seen_so_far).strip(); break; @@ -137,10 +155,10 @@ for (int i = 0; i < addresses.size; i++) { addresses[i] = addresses[i].strip(); } - + return addresses; } - + // We could only add the delimiter *between* each string (i.e., don't add it after the last // string). But it's easier for the user if they don't have to manually type a comma after // adding each address. So we add the delimiter after every string. @@ -150,36 +168,36 @@ builder.append(strings[i]); builder.append(delimiter); } - + return builder.str; } - + private bool match_prefix_contact(string needle, Geary.Contact contact, out string highlighted_result = null) { string email_result; bool email_match = match_prefix_string(needle, contact.email, out email_result); - + string real_name_result; bool real_name_match = match_prefix_string(needle, contact.real_name, out real_name_result); - + // email_result and real_name_result were already escaped, then tags were added to // highlight matches. We don't want to escape them again. highlighted_result = contact.real_name == null ? email_result : real_name_result + Markup.escape_text(" <") + email_result + Markup.escape_text(">"); - + return email_match || real_name_match; } private bool match_prefix_string(string needle, string? haystack = null, out string highlighted_result = null) { highlighted_result = ""; - + if (Geary.String.is_empty(haystack) || Geary.String.is_empty(needle)) return false; - + // Default result if there is no match or we encounter an error. highlighted_result = haystack; - + bool matched = false; try { string escaped_needle = Regex.escape_string(needle.normalize()); @@ -192,10 +210,10 @@ } catch (RegexError err) { debug("Error matching regex: %s", err.message); } - + highlighted_result = Markup.escape_text(highlighted_result) .replace("‘", "").replace("’", ""); - + return matched; } @@ -205,7 +223,7 @@ result.append("\xc2\x91%s\xc2\x92".printf(match)); // This is UTF-8 encoding of U+0091 and U+0092 } - + return false; } } diff -Nru geary-0.12.4/src/client/composer/contact-list-store-cache.vala geary-3.32.0/src/client/composer/contact-list-store-cache.vala --- geary-0.12.4/src/client/composer/contact-list-store-cache.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/src/client/composer/contact-list-store-cache.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,29 @@ +/* Copyright 2016 Software Freedom Conservancy Inc. + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +public class ContactListStoreCache { + + private Gee.HashMap cache = + new Gee.HashMap(); + + public ContactListStore create(Geary.ContactStore contact_store) { + ContactListStore list_store = new ContactListStore(contact_store); + + this.cache.set(contact_store, list_store); + + list_store.load.begin(); + + return list_store; + } + + public ContactListStore? get(Geary.ContactStore contact_store) { + return this.cache.get(contact_store); + } + + public void unset(Geary.ContactStore contact_store) { + this.cache.unset(contact_store); + } +} diff -Nru geary-0.12.4/src/client/composer/contact-list-store.vala geary-3.32.0/src/client/composer/contact-list-store.vala --- geary-0.12.4/src/client/composer/contact-list-store.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/composer/contact-list-store.vala 2019-03-17 13:39:29.000000000 +0000 @@ -54,32 +54,30 @@ public enum Column { CONTACT_OBJECT, - CONTACT_MARKUP_NAME, PRIOR_KEYS; - + public static Type[] get_types() { return { typeof (Geary.Contact), // CONTACT_OBJECT - typeof (string), // CONTACT_MARKUP_NAME typeof (Gee.HashSet) // PRIOR_KEYS }; } } - + public Geary.ContactStore contact_store { get; private set; } public ContactListStore(Geary.ContactStore contact_store) { base_ref(); set_column_types(Column.get_types()); this.contact_store = contact_store; - contact_store.contact_added.connect(on_contact_added); - contact_store.contact_updated.connect(on_contact_updated); + contact_store.contacts_added.connect(on_contacts_added); + contact_store.contacts_updated.connect(on_contacts_updated); } ~ContactListStore() { - this.contact_store.contact_added.disconnect(on_contact_added); - this.contact_store.contact_updated.disconnect(on_contact_updated); base_unref(); + this.contact_store.contacts_added.disconnect(on_contacts_added); + this.contact_store.contacts_updated.disconnect(on_contacts_updated); } /** @@ -95,8 +93,9 @@ yield; } } + } - // set sort function *after* adding all the contacts + public void set_sort_function() { set_sort_func(Column.CONTACT_OBJECT, ContactListStore.sort_func); set_sort_column_id(Column.CONTACT_OBJECT, Gtk.SortType.ASCENDING); } @@ -104,40 +103,20 @@ public Geary.Contact get_contact(Gtk.TreeIter iter) { GLib.Value contact_value; get_value(iter, Column.CONTACT_OBJECT, out contact_value); - + return (Geary.Contact) contact_value.get_object(); } - - public string get_rfc822_string(Gtk.TreeIter iter) { - return get_contact(iter).get_rfc822_address().to_rfc822_string(); - } - - // Highlighted result should be Markup.escaped for presentation to the user - public void set_highlighted_result(Gtk.TreeIter iter, string highlighted_result, - string current_address_key) { - // get the previous keys for this row for comparison - GLib.Value prior_keys_value; - get_value(iter, Column.PRIOR_KEYS, out prior_keys_value); - Gee.HashSet prior_keys = (Gee.HashSet) prior_keys_value.get_object(); - - // Changing a row in the list store causes Gtk.EntryCompletion to re-evaluate - // completion_match_func for that row. Thus we need to make sure the key has - // actually changed before settings the highlighting--otherwise we will cause - // an infinite loop. - if (!(current_address_key in prior_keys)) { - prior_keys.add(current_address_key); - set(iter, Column.CONTACT_MARKUP_NAME, highlighted_result, -1); - } + + public string to_full_address(Gtk.TreeIter iter) { + return get_contact(iter).get_rfc822_address().to_full_display(); } private inline void add_contact(Geary.Contact contact) { if (contact.highest_importance >= CONTACT_VISIBILITY_THRESHOLD) { - string full_address = contact.get_rfc822_address().to_rfc822_string(); Gtk.TreeIter iter; append(out iter); set(iter, Column.CONTACT_OBJECT, contact, - Column.CONTACT_MARKUP_NAME, Markup.escape_text(full_address), Column.PRIOR_KEYS, new Gee.HashSet()); } } @@ -146,25 +125,27 @@ Gtk.TreeIter iter; if (!get_iter_first(out iter)) return; - + do { if (get_contact(iter) != updated_contact) continue; - + Gtk.TreePath? path = get_path(iter); if (path != null) row_changed(path, iter); - + return; } while (iter_next(ref iter)); } - - private void on_contact_added(Geary.Contact contact) { - add_contact(contact); - } - - private void on_contact_updated(Geary.Contact contact) { - update_contact(contact); + + private void on_contacts_added(Gee.Collection contacts) { + foreach (Geary.Contact contact in contacts) + add_contact(contact); + } + + private void on_contacts_updated(Gee.Collection contacts) { + foreach (Geary.Contact contact in contacts) + update_contact(contact); } } diff -Nru geary-0.12.4/src/client/composer/email-entry.vala geary-3.32.0/src/client/composer/email-entry.vala --- geary-0.12.4/src/client/composer/email-entry.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/composer/email-entry.vala 2019-03-17 13:39:29.000000000 +0000 @@ -15,24 +15,24 @@ // null or valid addresses public Geary.RFC822.MailboxAddresses? addresses { get; set; default = null; } - + private weak ComposerWidget composer; - + private bool updating = false; public EmailEntry(ComposerWidget composer) { changed.connect(on_changed); key_press_event.connect(on_key_press); this.composer = composer; - + notify["addresses"].connect(() => { validate_addresses(); if (updating) return; - + updating = true; modified = true; - text = (addresses == null) ? "" : addresses.to_rfc822_string(); + text = (addresses == null) ? "" : addresses.to_full_display(); updating = false; }); @@ -62,7 +62,7 @@ addresses = new Geary.RFC822.MailboxAddresses.from_rfc822_string(text); updating = false; } - + private void validate_addresses() { if (addresses == null || addresses.size == 0) { valid = false; @@ -70,7 +70,7 @@ return; } empty = false; - + foreach (Geary.RFC822.MailboxAddress address in addresses) { if (!address.is_valid()) { valid = false; @@ -79,14 +79,14 @@ } valid = true; } - + private bool on_key_press(Gtk.Widget widget, Gdk.EventKey event) { if (event.keyval == Gdk.Key.Tab) { ((ContactEntryCompletion) get_completion()).trigger_selection(); composer.child_focus(Gtk.DirectionType.TAB_FORWARD); return true; } - + return false; } } diff -Nru geary-0.12.4/src/client/composer/spell-check-popover.vala geary-3.32.0/src/client/composer/spell-check-popover.vala --- geary-0.12.4/src/client/composer/spell-check-popover.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/composer/spell-check-popover.vala 2019-03-17 13:39:29.000000000 +0000 @@ -66,7 +66,7 @@ if (country_name != null) label_text += " (" + country_name + ")"; Gtk.Label label = new Gtk.Label(label_text); - GtkUtil.set_label_xalign(label, 0.0f); + label.set_halign(Gtk.Align.START); label.set_size_request(-1, 24); box.pack_start(label, false, false); diff -Nru geary-0.12.4/src/client/conversation-list/conversation-list-cell-renderer.vala geary-3.32.0/src/client/conversation-list/conversation-list-cell-renderer.vala --- geary-0.12.4/src/client/conversation-list/conversation-list-cell-renderer.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/conversation-list/conversation-list-cell-renderer.vala 2019-03-17 13:39:29.000000000 +0000 @@ -7,13 +7,17 @@ public class ConversationListCellRenderer : Gtk.CellRenderer { private static FormattedConversationData? example_data = null; private static bool hover_selected = false; - + // Mail message data. public FormattedConversationData data { get; set; } - + public ConversationListCellRenderer() { } + ~ConversationListCellRenderer() { + example_data = null; + } + public override void get_preferred_height(Gtk.Widget widget, out int minimum_size, out int natural_size) { @@ -33,29 +37,29 @@ minimum_size = natural_size = 1; } - public override void render(Cairo.Context ctx, Gtk.Widget widget, Gdk.Rectangle background_area, + public override void render(Cairo.Context ctx, Gtk.Widget widget, Gdk.Rectangle background_area, Gdk.Rectangle cell_area, Gtk.CellRendererState flags) { if (data != null) data.render(ctx, widget, background_area, cell_area, flags, hover_selected); } - + // Recalculates size when the style changed. // Note: this must be called by the parent TreeView. public static void style_changed(Gtk.Widget widget) { if (example_data == null) { example_data = new FormattedConversationData.create_example(); } - + example_data.calculate_sizes(widget); } - + // Shows hover effect on all selected cells. public static void set_hover_selected(bool hover) { hover_selected = hover; } // This is implemented because it's required; ignore it and look at get_preferred_height() instead. - public override void get_size(Gtk.Widget widget, Gdk.Rectangle? cell_area, out int x_offset, + public override void get_size(Gtk.Widget widget, Gdk.Rectangle? cell_area, out int x_offset, out int y_offset, out int width, out int height) { // Set values to avoid compiler warning. x_offset = 0; diff -Nru geary-0.12.4/src/client/conversation-list/conversation-list-store.vala geary-3.32.0/src/client/conversation-list/conversation-list-store.vala --- geary-0.12.4/src/client/conversation-list/conversation-list-store.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/conversation-list/conversation-list-store.vala 2019-03-17 13:39:29.000000000 +0000 @@ -14,19 +14,25 @@ */ public class ConversationListStore : Gtk.ListStore { - public const Geary.Email.Field REQUIRED_FIELDS = - Geary.Email.Field.ENVELOPE | Geary.Email.Field.FLAGS | Geary.Email.Field.PROPERTIES; - // XXX Remove ALL and NONE when PREVIEW has been fixed. See Bug 714317. - public const Geary.Email.Field WITH_PREVIEW_FIELDS = - Geary.Email.Field.ENVELOPE | Geary.Email.Field.FLAGS | Geary.Email.Field.PROPERTIES | Geary.Email.Field.PREVIEW | - Geary.Email.Field.ALL | Geary.Email.Field.NONE; + public const Geary.Email.Field REQUIRED_FIELDS = ( + Geary.Email.Field.ENVELOPE | + Geary.Email.Field.FLAGS | + Geary.Email.Field.PROPERTIES + ); + + // XXX Remove REQUIRED_FOR_BODY when PREVIEW has been fixed. See Bug 714317. + public const Geary.Email.Field WITH_PREVIEW_FIELDS = ( + REQUIRED_FIELDS | + Geary.Email.Field.PREVIEW | + Geary.Email.REQUIRED_FOR_MESSAGE + ); public enum Column { CONVERSATION_DATA, CONVERSATION_OBJECT, ROW_WRAPPER; - + public static Type[] get_types() { return { typeof (FormattedConversationData), // CONVERSATION_DATA @@ -34,37 +40,37 @@ typeof (RowWrapper) // ROW_WRAPPER }; } - + public string to_string() { switch (this) { case CONVERSATION_DATA: return "data"; - + case CONVERSATION_OBJECT: return "envelope"; - + case ROW_WRAPPER: return "wrapper"; - + default: assert_not_reached(); } } } - + private class RowWrapper : Geary.BaseObject { public Geary.App.Conversation conversation; public Gtk.TreeRowReference row; - + public RowWrapper(Gtk.TreeModel model, Geary.App.Conversation conversation, Gtk.TreePath path) { this.conversation = conversation; this.row = new Gtk.TreeRowReference(model, path); } - + public Gtk.TreePath get_path() { return row.get_path(); } - + public bool get_iter(out Gtk.TreeIter iter) { return row.get_model().get_iter(out iter, get_path()); } @@ -77,7 +83,7 @@ Geary.App.Conversation a, b; model.get(aiter, Column.CONVERSATION_OBJECT, out a); model.get(biter, Column.CONVERSATION_OBJECT, out b); - return compare_conversation_ascending(a, b); + return Util.Email.compare_conversation_ascending(a, b); } @@ -106,7 +112,7 @@ Priority.LOW, 60, update_date_strings ); this.email_store = new Geary.App.EmailStore( - conversations.folder.account + conversations.base_folder.account ); GearyApplication.instance.config.settings.changed[Configuration.DISPLAY_PREVIEW_KEY].connect( on_display_preview_changed); @@ -119,11 +125,12 @@ conversations.email_flags_changed.connect(on_email_flags_changed); // add all existing conversations - on_conversations_added(conversations.get_conversations()); + on_conversations_added(conversations.read_only_view); } public void destroy() { this.cancellable.cancel(); + this.email_store = null; clear(); // Release circular refs. @@ -138,10 +145,10 @@ Gtk.TreeIter iter; if (!get_iter(out iter, path)) return null; - + return get_conversation_at_iter(iter); } - + private async void refresh_previews_async(Geary.App.ConversationMonitor conversation_monitor) { // Use a mutex because it's possible for the conversation monitor to fire multiple // "scan-started" signals as messages come in fast and furious, but only want to process @@ -149,88 +156,91 @@ // same set int token; try { - token = yield refresh_mutex.claim_async(); + token = yield refresh_mutex.claim_async(this.cancellable); } catch (Error err) { debug("Unable to claim refresh mutex: %s", err.message); - + return; } - + preview_monitor.notify_start(); - + yield do_refresh_previews_async(conversation_monitor); - + preview_monitor.notify_finish(); - + try { refresh_mutex.release(ref token); } catch (Error err) { debug("Unable to release refresh mutex: %s", err.message); } } - + // should only be called by refresh_previews_async() private async void do_refresh_previews_async(Geary.App.ConversationMonitor conversation_monitor) { if (conversation_monitor == null || !GearyApplication.instance.config.display_preview) return; - + Gee.Set needing_previews = get_emails_needing_previews(); - + Gee.ArrayList emails = new Gee.ArrayList(); if (needing_previews.size > 0) emails.add_all(yield do_get_previews_async(needing_previews)); if (emails.size < 1) return; - - debug("Displaying %d previews for %s...", emails.size, conversation_monitor.folder.to_string()); + foreach (Geary.Email email in emails) { - Geary.App.Conversation? conversation = conversation_monitor.get_conversation_for_email(email.id); - if (conversation != null) + Geary.App.Conversation? conversation = conversation_monitor.get_by_email_identifier(email.id); + // The conversation can be null if e.g. a search is + // changing quickly and the original has evaporated + // already. + if (conversation != null) { set_preview_for_conversation(conversation, email); - else - debug("Couldn't find conversation for %s", email.id.to_string()); + } } - debug("Displayed %d previews for %s", emails.size, conversation_monitor.folder.to_string()); } - + private async Gee.Collection do_get_previews_async( Gee.Collection emails_needing_previews) { Geary.Folder.ListFlags flags = (loading_local_only) ? Geary.Folder.ListFlags.LOCAL_ONLY : Geary.Folder.ListFlags.NONE; Gee.Collection? emails = null; try { - debug("Loading %d previews...", emails_needing_previews.size); emails = yield email_store.list_email_by_sparse_id_async(emails_needing_previews, ConversationListStore.WITH_PREVIEW_FIELDS, flags, cancellable); - debug("Loaded %d previews...", emails_needing_previews.size); - } catch (Error err) { - // Ignore NOT_FOUND, as that's entirely possible when waiting for the remote to open - if (!(err is Geary.EngineError.NOT_FOUND)) - debug("Unable to fetch preview: %s", err.message); + } catch (GLib.IOError.CANCELLED err) { + // All good + } catch (Geary.EngineError.NOT_FOUND err) { + // All good also, as that's entirely possible when waiting + // for the remote to open + } catch (GLib.Error err) { + warning("Unable to fetch preview: %s", err.message); } - + return emails ?? new Gee.ArrayList(); } - + private Gee.Set get_emails_needing_previews() { Gee.Set needing = new Gee.HashSet(); - + // sort the conversations so the previews are fetched from the newest to the oldest, matching // the user experience - Gee.TreeSet sorted_conversations = new Gee.TreeSet( - compare_conversation_descending); - sorted_conversations.add_all(this.conversations.get_conversations()); + Gee.TreeSet sorted_conversations = + new Gee.TreeSet( + Util.Email.compare_conversation_descending + ); + sorted_conversations.add_all(this.conversations.read_only_view); foreach (Geary.App.Conversation conversation in sorted_conversations) { // find oldest unread message for the preview Geary.Email? need_preview = null; foreach (Geary.Email email in conversation.get_emails(Geary.App.Conversation.Ordering.RECV_DATE_ASCENDING)) { if (email.email_flags.is_unread()) { need_preview = email; - + break; } } - + // if all are read, use newest in-folder message, then newest out-of-folder if not // present if (need_preview == null) { @@ -238,34 +248,34 @@ if (need_preview == null) continue; } - + Geary.Email? current_preview = get_preview_for_conversation(conversation); - + // if all preview fields present and it's the same email, don't need to refresh if (current_preview != null && need_preview.id.equal_to(current_preview.id) && current_preview.fields.is_all_set(ConversationListStore.WITH_PREVIEW_FIELDS)) { continue; } - + needing.add(need_preview.id); } - + return needing; } - + private Geary.Email? get_preview_for_conversation(Geary.App.Conversation conversation) { Gtk.TreeIter iter; if (!get_iter_for_conversation(conversation, out iter)) { debug("Unable to find preview for conversation"); - + return null; } - + FormattedConversationData? message_data = get_message_data_at_iter(iter); return message_data == null ? null : message_data.preview; } - + private void set_preview_for_conversation(Geary.App.Conversation conversation, Geary.Email preview) { Gtk.TreeIter iter; if (get_iter_for_conversation(conversation, out iter)) @@ -278,23 +288,23 @@ FormattedConversationData conversation_data = new FormattedConversationData( conversation, preview, - this.conversations.folder, - this.conversations.folder.account.information.get_all_mailboxes() + this.conversations.base_folder, + this.conversations.base_folder.account.information.sender_mailboxes ); Gtk.TreePath? path = get_path(iter); assert(path != null); RowWrapper wrapper = new RowWrapper(this, conversation, path); - + set(iter, Column.CONVERSATION_DATA, conversation_data, Column.CONVERSATION_OBJECT, conversation, Column.ROW_WRAPPER, wrapper ); - + row_map.set(conversation, wrapper); } - + private void refresh_conversation(Geary.App.Conversation conversation) { Gtk.TreeIter iter; if (!get_iter_for_conversation(conversation, out iter)) { @@ -302,11 +312,11 @@ add_conversation(conversation); return; } - + Geary.Email? last_email = conversation.get_latest_recv_email(Geary.App.Conversation.Location.ANYWHERE); if (last_email == null) { debug("Cannot refresh conversation: last email is null"); - + #if VALA_0_36 remove(ref iter); #else @@ -314,16 +324,16 @@ #endif return; } - + set_row(iter, conversation, last_email); - + Gtk.TreePath? path = get_path(iter); if (path != null) row_changed(path, iter); else debug("Cannot refresh conversation: no path for iterator"); } - + private void refresh_flags(Geary.App.Conversation conversation) { Gtk.TreeIter iter; if (!get_iter_for_conversation(conversation, out iter)) { @@ -331,55 +341,55 @@ add_conversation(conversation); return; } - + FormattedConversationData? existing_message_data = get_message_data_at_iter(iter); if (existing_message_data == null) return; - + existing_message_data.is_unread = conversation.is_unread(); existing_message_data.is_flagged = conversation.is_flagged(); - + Gtk.TreePath? path = get_path(iter); if (path != null) row_changed(path, iter); } - + public Gtk.TreePath? get_path_for_conversation(Geary.App.Conversation conversation) { RowWrapper? wrapper = row_map.get(conversation); - + return (wrapper != null) ? wrapper.get_path() : null; } - + private bool get_iter_for_conversation(Geary.App.Conversation conversation, out Gtk.TreeIter iter) { RowWrapper? wrapper = row_map.get(conversation); if (wrapper != null) return wrapper.get_iter(out iter); - + // use get_iter_first() because boxing Gtk.TreeIter with a nullable is problematic with // current bindings get_iter_first(out iter); - + return false; } - + private bool has_conversation(Geary.App.Conversation conversation) { return row_map.has_key(conversation); } - + private Geary.App.Conversation? get_conversation_at_iter(Gtk.TreeIter iter) { Geary.App.Conversation? conversation; get(iter, Column.CONVERSATION_OBJECT, out conversation); - + return conversation; } - + private FormattedConversationData? get_message_data_at_iter(Gtk.TreeIter iter) { FormattedConversationData? message_data; get(iter, Column.CONVERSATION_DATA, out message_data); - + return message_data; } - + private void remove_conversation(Geary.App.Conversation conversation) { Gtk.TreeIter iter; if (get_iter_for_conversation(conversation, out iter)) @@ -388,44 +398,44 @@ #else remove(iter); #endif - + row_map.unset(conversation); } - + private bool add_conversation(Geary.App.Conversation conversation) { Geary.Email? last_email = conversation.get_latest_recv_email(Geary.App.Conversation.Location.ANYWHERE); if (last_email == null) { debug("Cannot add conversation: last email is null"); - + return false; } - + if (has_conversation(conversation)) { debug("Conversation already present; not adding"); - + return false; } - + Gtk.TreeIter iter; append(out iter); set_row(iter, conversation, last_email); - + return true; } - + private void on_scan_completed(Geary.App.ConversationMonitor sender) { refresh_previews_async.begin(sender); loading_local_only = false; } - + private void on_conversations_added(Gee.Collection conversations) { // this handler is used to initialize the display, so it's possible for an empty list to // be passed in (the ConversationMonitor signal should never do this) if (conversations.size == 0) return; - + conversations_added(true); - + debug("Adding %d conversations.", conversations.size); int added = 0; foreach (Geary.App.Conversation conversation in conversations) { @@ -433,10 +443,10 @@ added++; } debug("Added %d/%d conversations.", added, conversations.size); - + conversations_added(false); } - + private void on_conversations_removed(Gee.Collection conversations) { conversations_removed(true); foreach (Geary.App.Conversation removed in conversations) @@ -451,18 +461,18 @@ debug("Unable to append conversation; conversation not present in list store"); } } - + private void on_conversation_trimmed(Geary.App.Conversation conversation) { refresh_conversation(conversation); } - + private void on_display_preview_changed() { refresh_previews_async.begin(this.conversations); } - + private void on_email_flags_changed(Geary.App.Conversation conversation) { refresh_flags(conversation); - + // refresh previews because the oldest unread message is displayed as the preview, and if // that's changed, need to change the preview // TODO: need support code to load preview for single conversation, not scan all @@ -477,10 +487,10 @@ private bool update_date_string(Gtk.TreeModel model, Gtk.TreePath path, Gtk.TreeIter iter) { FormattedConversationData? message_data; model.get(iter, Column.CONVERSATION_DATA, out message_data); - + if (message_data != null && message_data.update_date_string()) row_changed(path, iter); - + // Continue iterating, don't stop return false; } diff -Nru geary-0.12.4/src/client/conversation-list/conversation-list-view.vala geary-3.32.0/src/client/conversation-list/conversation-list-view.vala --- geary-0.12.4/src/client/conversation-list/conversation-list-view.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/conversation-list/conversation-list-view.vala 2019-03-17 13:39:29.000000000 +0000 @@ -6,9 +6,12 @@ public class ConversationListView : Gtk.TreeView, Geary.BaseInterface { const int LOAD_MORE_HEIGHT = 100; - + + // Used to be able to refer to the action names of the MainWindow + private weak MainWindow main_window; + private bool enable_load_more = true; - + // Used to avoid repeated calls to load_more(). Contains the last "upper" bound of the // scroll adjustment seen at the call to load_more(). private double last_upper = -1.0; @@ -16,30 +19,30 @@ private Geary.App.ConversationMonitor? conversation_monitor; private Gee.Set? current_visible_conversations = null; private Geary.Scheduler.Scheduled? scheduled_update_visible_conversations = null; - private Gtk.Menu? context_menu = null; private Gee.Set selected = new Gee.HashSet(); private Geary.IdleManager selection_update; private bool suppress_selection = false; public signal void conversations_selected(Gee.Set selected); - + // Signal for when a conversation has been double-clicked, or selected and enter is pressed. public signal void conversation_activated(Geary.App.Conversation activated); - + public virtual signal void load_more() { enable_load_more = false; } - + public signal void mark_conversations(Gee.Collection conversations, Geary.EmailFlags? flags_to_add, Geary.EmailFlags? flags_to_remove, bool only_mark_preview); - + public signal void visible_conversations_changed(Gee.Set visible); - public ConversationListView() { + public ConversationListView(MainWindow parent) { base_ref(); set_show_expanders(false); set_headers_visible(false); + this.main_window = parent; append_column(create_column(ConversationListStore.Column.CONVERSATION_DATA, new ConversationListCellRenderer(), ConversationListStore.Column.CONVERSATION_DATA.to_string(), @@ -47,7 +50,7 @@ Gtk.TreeSelection selection = get_selection(); selection.set_mode(Gtk.SelectionMode.MULTIPLE); - style_set.connect(on_style_changed); + style_updated.connect(on_style_changed); show.connect(on_show); row_activated.connect(on_row_activated); @@ -56,16 +59,16 @@ // Set up drag and drop. Gtk.drag_source_set(this, Gdk.ModifierType.BUTTON1_MASK, FolderList.Tree.TARGET_ENTRY_LIST, Gdk.DragAction.COPY | Gdk.DragAction.MOVE); - + GearyApplication.instance.config.settings.changed[Configuration.DISPLAY_PREVIEW_KEY].connect( on_display_preview_changed); GearyApplication.instance.controller.notify[GearyController.PROP_CURRENT_CONVERSATION]. connect(on_conversation_monitor_changed); - + // Watch for mouse events. motion_notify_event.connect(on_motion_notify_event); leave_notify_event.connect(on_leave_notify_event); - + // GtkTreeView binds Ctrl+N to "move cursor to next". Not so interested in that, so we'll // remove it. unowned Gtk.BindingSet? binding_set = Gtk.BindingSet.find("GtkTreeView"); @@ -99,6 +102,7 @@ old_store.row_changed.disconnect(on_rows_changed); old_store.row_deleted.disconnect(on_rows_changed); old_store.row_deleted.disconnect(on_row_deleted); + old_store.destroy(); } if (new_store != null) { @@ -140,30 +144,25 @@ if (conversation_monitor != null) { conversation_monitor.scan_started.disconnect(on_scan_started); conversation_monitor.scan_completed.disconnect(on_scan_completed); - conversation_monitor.seed_completed.disconnect(on_seed_completed); } - + conversation_monitor = GearyApplication.instance.controller.current_conversations; - + if (conversation_monitor != null) { conversation_monitor.scan_started.connect(on_scan_started); conversation_monitor.scan_completed.connect(on_scan_completed); - conversation_monitor.seed_completed.connect(on_seed_completed); } } - + private void on_scan_started() { enable_load_more = false; } - + private void on_scan_completed() { enable_load_more = true; // Select the first conversation, if autoselect is enabled, - // nothing has been selected yet and we're not composing. Do - // this here instead of in on_seed_completed since we want to - // to select the first row on folder change as soon as - // possible. + // nothing has been selected yet and we're not composing. if (GearyApplication.instance.config.autoselect && get_selection().count_selected_rows() == 0 && !GearyApplication.instance.controller.any_inline_composers()) { @@ -171,13 +170,6 @@ } } - private void on_seed_completed() { - if (!GearyApplication.instance.config.autoselect) { - // Notify that no conversations will be selected - conversations_selected(this.selected.read_only_view); - } - } - private void on_conversations_added(bool start) { Gtk.Adjustment? adjustment = get_adjustment(); if (start) { @@ -215,7 +207,7 @@ debug("Parent was not scrolled window"); return null; } - + return parent.get_vadjustment(); } @@ -225,16 +217,16 @@ int cell_y; Gtk.TreePath? path; get_path_at_pos((int) event.x, (int) event.y, out path, null, out cell_x, out cell_y); - + // If the user clicked in an empty area, do nothing. if (path == null) return false; - + // Handle clicks to toggle read and starred status. if ((event.state & Gdk.ModifierType.SHIFT_MASK) == 0 && (event.state & Gdk.ModifierType.CONTROL_MASK) == 0 && event.type == Gdk.EventType.BUTTON_PRESS) { - + // Click positions depend on whether the preview is enabled. bool read_clicked = false; bool star_clicked = false; @@ -245,7 +237,7 @@ read_clicked = cell_x < 25 && cell_y >= 8 && cell_y <= 22; star_clicked = cell_x < 25 && cell_y >= 28 && cell_y <= 43; } - + // Get the current conversation. If it's selected, we'll apply the mark operation to // all selected conversations; otherwise, it just applies to this one. Geary.App.Conversation conversation = get_model().get_conversation_at_path(path); @@ -254,98 +246,86 @@ to_mark = GearyApplication.instance.controller.get_selected_conversations(); else to_mark = Geary.iterate(conversation).to_array_list(); - + if (read_clicked) { // Read/unread. Geary.EmailFlags flags = new Geary.EmailFlags(); flags.add(Geary.EmailFlags.UNREAD); - + if (conversation.is_unread()) mark_conversations(to_mark, null, flags, false); else mark_conversations(to_mark, flags, null, true); - + return true; } else if (star_clicked) { // Starred/unstarred. Geary.EmailFlags flags = new Geary.EmailFlags(); flags.add(Geary.EmailFlags.FLAGGED); - + if (conversation.is_flagged()) mark_conversations(to_mark, null, flags, false); else mark_conversations(to_mark, flags, null, true); - + return true; } } - + if (!get_selection().path_is_selected(path) && !GearyApplication.instance.controller.can_switch_conversation_view()) return true; - + if (event.button == 3 && event.type == Gdk.EventType.BUTTON_PRESS) { Geary.App.Conversation conversation = get_model().get_conversation_at_path(path); - - string?[] action_names = {}; - action_names += GearyController.ACTION_DELETE_CONVERSATION; - + + Menu context_menu_model = new Menu(); + context_menu_model.append(_("Delete conversation"), "win."+GearyController.ACTION_DELETE_CONVERSATION); + if (conversation.is_unread()) - action_names += GearyController.ACTION_MARK_AS_READ; - + context_menu_model.append(_("Mark as _Read"), "win."+GearyController.ACTION_MARK_AS_READ); + if (conversation.has_any_read_message()) - action_names += GearyController.ACTION_MARK_AS_UNREAD; - + context_menu_model.append(_("Mark as _Unread"), "win."+GearyController.ACTION_MARK_AS_UNREAD); + if (conversation.is_flagged()) - action_names += GearyController.ACTION_MARK_AS_UNSTARRED; + context_menu_model.append(_("U_nstar"), "win."+GearyController.ACTION_MARK_AS_UNSTARRED); else - action_names += GearyController.ACTION_MARK_AS_STARRED; - - // treat null as separator - action_names += null; - action_names += GearyController.ACTION_REPLY_TO_MESSAGE; - action_names += GearyController.ACTION_REPLY_ALL_MESSAGE; - action_names += GearyController.ACTION_FORWARD_MESSAGE; - - context_menu = new Gtk.Menu(); - foreach (string? action_name in action_names) { - if (action_name == null) { - context_menu.add(new Gtk.SeparatorMenuItem()); - - continue; - } - - Gtk.Action? menu_action = GearyApplication.instance.actions.get_action(action_name); - if (menu_action != null) - context_menu.add(menu_action.create_menu_item()); - } - - context_menu.show_all(); - context_menu.popup(null, null, null, event.button, event.time); - + context_menu_model.append(_("_Star"), "win."+GearyController.ACTION_MARK_AS_STARRED); + + Menu actions_section = new Menu(); + actions_section.append(_("_Reply"), "win."+GearyController.ACTION_REPLY_TO_MESSAGE); + actions_section.append(_("R_eply All"), "win."+GearyController.ACTION_REPLY_ALL_MESSAGE); + actions_section.append(_("_Forward"), "win."+GearyController.ACTION_FORWARD_MESSAGE); + context_menu_model.append_section(null, actions_section); + + Gtk.Menu context_menu = new Gtk.Menu.from_model(context_menu_model); + context_menu.insert_action_group("win", this.main_window); + context_menu.popup_at_pointer(event); + // When the conversation under the mouse is selected, stop event propagation return get_selection().path_is_selected(path); } - + return false; } private void on_style_changed() { // Recalculate dimensions of child cells. ConversationListCellRenderer.style_changed(this); - + schedule_visible_conversations_changed(); } - + private void on_show() { // Wait until we're visible to set this signal up. ((Gtk.Scrollable) this).get_vadjustment().value_changed.connect(on_value_changed); } - + private void on_value_changed() { if (!enable_load_more) return; - + // Check if we're at the very bottom of the list. If we are, it's time to // issue a load_more signal. Gtk.Adjustment adjustment = ((Gtk.Scrollable) this).get_vadjustment(); @@ -355,24 +335,24 @@ load_more(); last_upper = upper; } - + schedule_visible_conversations_changed(); } - + private static Gtk.TreeViewColumn create_column(ConversationListStore.Column column, Gtk.CellRenderer renderer, string attr, int width = 0) { Gtk.TreeViewColumn view_column = new Gtk.TreeViewColumn.with_attributes(column.to_string(), renderer, attr, column); view_column.set_resizable(true); - + if (width != 0) { view_column.set_sizing(Gtk.TreeViewColumnSizing.FIXED); view_column.set_fixed_width(width); } - + return view_column; } - + private List get_all_selected_paths() { Gtk.TreeModel model; return get_selection().get_selected_rows(out model); @@ -419,35 +399,35 @@ public Gee.Set get_visible_conversations() { Gee.HashSet visible_conversations = new Gee.HashSet(); - + Gtk.TreePath start_path; Gtk.TreePath end_path; if (!get_visible_range(out start_path, out end_path)) return visible_conversations; - + while (start_path.compare(end_path) <= 0) { Geary.App.Conversation? conversation = get_model().get_conversation_at_path(start_path); if (conversation != null) visible_conversations.add(conversation); - + start_path.next(); } - + return visible_conversations; } - + public Gee.Set get_selected_conversations() { Gee.HashSet selected_conversations = new Gee.HashSet(); - + foreach (Gtk.TreePath path in get_all_selected_paths()) { Geary.App.Conversation? conversation = get_model().get_conversation_at_path(path); if (path != null) selected_conversations.add(conversation); } - + return selected_conversations; } - + // Always returns false, so it can be used as a one-time SourceFunc private bool update_visible_conversations() { Gee.Set visible_conversations = get_visible_conversations(); @@ -456,14 +436,14 @@ current_visible_conversations, visible_conversations)) { return false; } - + current_visible_conversations = visible_conversations; - + visible_conversations_changed(current_visible_conversations.read_only_view); - + return false; } - + private void schedule_visible_conversations_changed() { scheduled_update_visible_conversations = Geary.Scheduler.on_idle(update_visible_conversations); } @@ -473,7 +453,7 @@ if (path != null) set_cursor(path, null, false); } - + public void select_conversations(Gee.Set conversations) { Gtk.TreeSelection selection = get_selection(); foreach (Geary.App.Conversation conversation in conversations) { @@ -482,36 +462,36 @@ selection.select_path(path); } } - + private void on_row_deleted(Gtk.TreePath path) { // if one or more rows are deleted in the model, reset the last upper limit so scrolling to // the bottom will always activate a reload (this is particularly important if the model // is cleared) last_upper = -1.0; } - + private void on_rows_changed() { schedule_visible_conversations_changed(); } - + private void on_display_preview_changed() { - style_set(null); + style_updated(); model.foreach(refresh_path); - + schedule_visible_conversations_changed(); } - + private bool refresh_path(Gtk.TreeModel model, Gtk.TreePath path, Gtk.TreeIter iter) { model.row_changed(path, iter); return false; } - + private void on_row_activated(Gtk.TreePath path) { Geary.App.Conversation? c = get_model().get_conversation_at_path(path); if (c != null) conversation_activated(c); } - + // Enable/disable hover effect on all selected cells. private void set_hover_selected(bool hover) { ConversationListCellRenderer.set_hover_selected(hover); diff -Nru geary-0.12.4/src/client/conversation-list/formatted-conversation-data.vala geary-3.32.0/src/client/conversation-list/formatted-conversation-data.vala --- geary-0.12.4/src/client/conversation-list/formatted-conversation-data.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/conversation-list/formatted-conversation-data.vala 2019-03-17 13:39:29.000000000 +0000 @@ -7,37 +7,41 @@ // Stores formatted data for a message. public class FormattedConversationData : Geary.BaseObject { public const int LINE_SPACING = 6; - + private const string ME = _("Me"); private const string STYLE_EXAMPLE = "Gg"; // Use both upper and lower case to get max height. private const int TEXT_LEFT = LINE_SPACING * 2 + IconFactory.UNREAD_ICON_SIZE; private const double DIM_TEXT_AMOUNT = 0.05; private const double DIM_PREVIEW_TEXT_AMOUNT = 0.25; - + private const int FONT_SIZE_DATE = 10; private const int FONT_SIZE_SUBJECT = 9; private const int FONT_SIZE_FROM = 11; private const int FONT_SIZE_PREVIEW = 8; - + private class ParticipantDisplay : Geary.BaseObject, Gee.Hashable { public Geary.RFC822.MailboxAddress address; public bool is_unread; - + public ParticipantDisplay(Geary.RFC822.MailboxAddress address, bool is_unread) { this.address = address; this.is_unread = is_unread; } - + public string get_full_markup(Gee.List account_mailboxes) { - return get_as_markup((address in account_mailboxes) ? ME : address.get_short_address()); + return get_as_markup((address in account_mailboxes) ? ME : address.to_short_display()); } - + public string get_short_markup(Gee.List account_mailboxes) { if (address in account_mailboxes) return get_as_markup(ME); - - string short_address = address.get_short_address().strip(); - + + if (address.is_spoofed()) { + return get_full_markup(account_mailboxes); + } + + string short_address = Markup.escape_text(address.to_short_display()); + if (", " in short_address) { // assume address is in Last, First format string[] tokens = short_address.split(", ", 2); @@ -45,36 +49,45 @@ if (Geary.String.is_empty(short_address)) return get_full_markup(account_mailboxes); } - + // use first name as delimited by a space string[] tokens = short_address.split(" ", 2); if (tokens.length < 1) return get_full_markup(account_mailboxes); - + string first_name = tokens[0].strip(); if (Geary.String.is_empty_or_whitespace(first_name)) return get_full_markup(account_mailboxes); - + return get_as_markup(first_name); } - + private string get_as_markup(string participant) { - return "%s%s%s".printf( - is_unread ? "" : "", Geary.HTML.escape_markup(participant), is_unread ? "" : ""); + string markup = Geary.HTML.escape_markup(participant); + + if (is_unread) { + markup = "%s".printf(markup); + } + + if (this.address.is_spoofed()) { + markup = "%s".printf(markup); + } + + return markup; } - + public bool equal_to(ParticipantDisplay other) { return address.equal_to(other.address); } - + public uint hash() { return address.hash(); } } - + private static int cell_height = -1; private static int preview_height = -1; - + public bool is_unread { get; set; } public bool is_flagged { get; set; } public string date { get; private set; } @@ -82,51 +95,50 @@ public string? body { get; private set; default = null; } // optional public int num_emails { get; set; } public Geary.Email? preview { get; private set; default = null; } - + private Geary.App.Conversation? conversation = null; private Gee.List? account_owner_emails = null; private bool use_to = true; private CountBadge count_badge = new CountBadge(2); - + // Creates a formatted message data from an e-mail. public FormattedConversationData(Geary.App.Conversation conversation, Geary.Email preview, Geary.Folder folder, Gee.List account_owner_emails) { - assert(preview.fields.fulfills(ConversationListStore.REQUIRED_FIELDS)); - + this.conversation = conversation; this.account_owner_emails = account_owner_emails; use_to = (folder != null) && folder.special_folder_type.is_outgoing(); - + // Load preview-related data. update_date_string(); - this.subject = EmailUtil.strip_subject_prefixes(preview); + this.subject = Util.Email.strip_subject_prefixes(preview); this.body = Geary.String.reduce_whitespace(preview.get_preview_as_string()); this.preview = preview; - + // Load conversation-related data. this.is_unread = conversation.is_unread(); this.is_flagged = conversation.is_flagged(); this.num_emails = conversation.get_count(); } - + public bool update_date_string() { // get latest email *in folder* for the conversation's date, fall back on out-of-folder Geary.Email? latest = conversation.get_latest_recv_email(Geary.App.Conversation.Location.IN_FOLDER_OUT_OF_FOLDER); if (latest == null || latest.properties == null) return false; - + // conversation list store sorts by date-received, so display that instead of sender's // Date: string new_date = Date.pretty_print(latest.properties.date_received, GearyApplication.instance.config.clock_format); if (new_date == date) return false; - + date = new_date; - + return true; } - + // Creates an example message (used interally for styling calculations.) public FormattedConversationData.create_example() { this.is_unread = false; @@ -136,20 +148,20 @@ this.body = STYLE_EXAMPLE + "\n" + STYLE_EXAMPLE; this.num_emails = 1; } - + private uint8 gdk_to_rgb(double gdk) { return (uint8) (gdk.clamp(0.0, 1.0) * 255.0); } - + private Gdk.RGBA dim_rgba(Gdk.RGBA rgba, double amount) { amount = amount.clamp(0.0, 1.0); - + // can't use ternary in struct initializer due to this bug: // https://bugzilla.gnome.org/show_bug.cgi?id=684742 double dim_red = (rgba.red >= 0.5) ? -amount : amount; double dim_green = (rgba.green >= 0.5) ? -amount : amount; double dim_blue = (rgba.blue >= 0.5) ? -amount : amount; - + return Gdk.RGBA() { red = (rgba.red + dim_red).clamp(0.0, 1.0), green = (rgba.green + dim_green).clamp(0.0, 1.0), @@ -157,20 +169,20 @@ alpha = rgba.alpha }; } - + private string rgba_to_markup(Gdk.RGBA rgba) { return "#%02x%02x%02x".printf( gdk_to_rgb(rgba.red), gdk_to_rgb(rgba.green), gdk_to_rgb(rgba.blue)); } - + private Gdk.RGBA get_foreground_rgba(Gtk.Widget widget, bool selected) { return widget.get_style_context().get_color(selected ? Gtk.StateFlags.SELECTED : Gtk.StateFlags.NORMAL); } - + private string get_participants_markup(Gtk.Widget widget, bool selected) { if (conversation == null || account_owner_emails == null || account_owner_emails.size == 0) return ""; - + // Build chronological list of AuthorDisplay records, setting to unread if any message by // that author is unread Gee.ArrayList list = new Gee.ArrayList(); @@ -179,7 +191,7 @@ Geary.RFC822.MailboxAddresses? addresses = use_to ? message.to : message.from; if (addresses == null || addresses.size < 1) continue; - + foreach (Geary.RFC822.MailboxAddress address in addresses) { ParticipantDisplay participant_display = new ParticipantDisplay(address, message.email_flags.is_unread()); @@ -191,14 +203,14 @@ continue; } - + // if present and this message is unread but the prior were read, // this author is now unread if (message.email_flags.is_unread() && !list[existing_index].is_unread) list[existing_index].is_unread = true; } } - + StringBuilder builder = new StringBuilder("".printf( rgba_to_markup(get_foreground_rgba(widget, selected)))); if (list.size == 1) { @@ -209,42 +221,42 @@ foreach (ParticipantDisplay participant in list) { if (!first) builder.append(", "); - + builder.append(participant.get_short_markup(account_owner_emails)); first = false; } } builder.append(""); - + return builder.str; } - - public void render(Cairo.Context ctx, Gtk.Widget widget, Gdk.Rectangle background_area, + + public void render(Cairo.Context ctx, Gtk.Widget widget, Gdk.Rectangle background_area, Gdk.Rectangle cell_area, Gtk.CellRendererState flags, bool hover_select) { render_internal(widget, cell_area, ctx, flags, false, hover_select); } - + // Call this on style changes. public void calculate_sizes(Gtk.Widget widget) { render_internal(widget, null, null, 0, true, false); } - + // Must call calculate_sizes() first. public int get_height() { assert(cell_height != -1); // ensures calculate_sizes() was called. return cell_height; } - + // Can be used for rendering or calculating height. - private void render_internal(Gtk.Widget widget, Gdk.Rectangle? cell_area, + private void render_internal(Gtk.Widget widget, Gdk.Rectangle? cell_area, Cairo.Context? ctx, Gtk.CellRendererState flags, bool recalc_dims, bool hover_select) { bool display_preview = GearyApplication.instance.config.display_preview; int y = LINE_SPACING + (cell_area != null ? cell_area.y : 0); - + bool selected = (flags & Gtk.CellRendererState.SELECTED) != 0; bool hover = (flags & Gtk.CellRendererState.PRELIT) != 0 || (selected && hover_select); - + // Date field. Pango.Rectangle ink_rect = render_date(widget, cell_area, ctx, y, selected); @@ -255,33 +267,33 @@ // If we are displaying a preview then the message counter goes on the same line as the // preview, otherwise it is with the subject. int preview_height = 0; - + // Setup counter badge. count_badge.count = num_emails; int counter_width = count_badge.get_width(widget) + LINE_SPACING; int counter_x = cell_area != null ? cell_area.width - cell_area.x - counter_width + (LINE_SPACING / 2) : 0; - + if (display_preview) { // Subject field. render_subject(widget, cell_area, ctx, y, selected); y += ink_rect.height + ink_rect.y + LINE_SPACING; - + // Number of e-mails field. count_badge.render(widget, ctx, counter_x, y, selected); - + // Body preview. ink_rect = render_preview(widget, cell_area, ctx, y, selected, counter_width); preview_height = ink_rect.height + ink_rect.y + LINE_SPACING; } else { // Number of e-mails field. count_badge.render(widget, ctx, counter_x, y, selected); - + // Subject field. render_subject(widget, cell_area, ctx, y, selected, counter_width); y += ink_rect.height + ink_rect.y + LINE_SPACING; } - + // Draw separator line. if (ctx != null && cell_area != null) { ctx.set_line_width(1.0); @@ -290,14 +302,14 @@ ctx.line_to(cell_area.x + cell_area.width + 1, cell_area.y + cell_area.height); ctx.stroke(); } - + if (recalc_dims) { FormattedConversationData.preview_height = preview_height; FormattedConversationData.cell_height = y + preview_height; } else { int unread_y = display_preview ? cell_area.y + LINE_SPACING * 2 : cell_area.y + LINE_SPACING; - + // Unread indicator. if (is_unread || hover) { Gdk.Pixbuf read_icon = IconFactory.instance.load_symbolic( @@ -306,7 +318,7 @@ Gdk.cairo_set_source_pixbuf(ctx, read_icon, cell_area.x + LINE_SPACING, unread_y); ctx.paint(); } - + // Starred indicator. if (is_flagged || hover) { int star_y = cell_area.y + (cell_area.height / 2) + (display_preview ? LINE_SPACING : 0); @@ -318,13 +330,13 @@ } } } - + private Pango.Rectangle render_date(Gtk.Widget widget, Gdk.Rectangle? cell_area, Cairo.Context? ctx, int y, bool selected) { string date_markup = "%s".printf( rgba_to_markup(dim_rgba(get_foreground_rgba(widget, selected), DIM_TEXT_AMOUNT)), Geary.HTML.escape_markup(date)); - + Pango.Rectangle? ink_rect; Pango.Rectangle? logical_rect; Pango.FontDescription font_date = new Pango.FontDescription(); @@ -340,11 +352,11 @@ } return ink_rect; } - + private Pango.Rectangle render_from(Gtk.Widget widget, Gdk.Rectangle? cell_area, Cairo.Context? ctx, int y, bool selected, Pango.Rectangle ink_rect) { string from_markup = (conversation != null) ? get_participants_markup(widget, selected) : STYLE_EXAMPLE; - + Pango.FontDescription font_from = new Pango.FontDescription(); font_from.set_size(FONT_SIZE_FROM * Pango.SCALE); Pango.Layout layout_from = widget.create_pango_layout(null); @@ -360,13 +372,13 @@ } return ink_rect; } - + private void render_subject(Gtk.Widget widget, Gdk.Rectangle? cell_area, Cairo.Context? ctx, int y, bool selected, int counter_width = 0) { string subject_markup = "%s".printf( rgba_to_markup(dim_rgba(get_foreground_rgba(widget, selected), DIM_TEXT_AMOUNT)), Geary.HTML.escape_markup(subject)); - + Pango.FontDescription font_subject = new Pango.FontDescription(); font_subject.set_size(FONT_SIZE_SUBJECT * Pango.SCALE); if (is_unread) @@ -382,27 +394,27 @@ Pango.cairo_show_layout(ctx, layout_subject); } } - + private Pango.Rectangle render_preview(Gtk.Widget widget, Gdk.Rectangle? cell_area, Cairo.Context? ctx, int y, bool selected, int counter_width = 0) { double dim = selected ? DIM_TEXT_AMOUNT : DIM_PREVIEW_TEXT_AMOUNT; string preview_markup = "%s".printf( rgba_to_markup(dim_rgba(get_foreground_rgba(widget, selected), dim)), Geary.String.is_empty(body) ? "" : Geary.HTML.escape_markup(body)); - + Pango.FontDescription font_preview = new Pango.FontDescription(); font_preview.set_size(FONT_SIZE_PREVIEW * Pango.SCALE); - + Pango.Layout layout_preview = widget.create_pango_layout(null); layout_preview.set_font_description(font_preview); - + layout_preview.set_markup(preview_markup, -1); layout_preview.set_wrap(Pango.WrapMode.WORD); layout_preview.set_ellipsize(Pango.EllipsizeMode.END); if (ctx != null && cell_area != null) { layout_preview.set_width((cell_area.width - TEXT_LEFT - counter_width - LINE_SPACING) * Pango.SCALE); layout_preview.set_height(preview_height * Pango.SCALE); - + ctx.move_to(cell_area.x + TEXT_LEFT, y); Pango.cairo_show_layout(ctx, layout_preview); } else { @@ -415,6 +427,6 @@ layout_preview.get_pixel_extents(out ink_rect, out logical_rect); return ink_rect; } - + } diff -Nru geary-0.12.4/src/client/conversation-viewer/conversation-email.vala geary-3.32.0/src/client/conversation-viewer/conversation-email.vala --- geary-0.12.4/src/client/conversation-viewer/conversation-email.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/conversation-viewer/conversation-email.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,6 +1,6 @@ /* * Copyright 2016 Software Freedom Conservancy Inc. - * Copyright 2016 Michael Gratton + * Copyright 2016,2019 Michael Gratton * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. @@ -21,6 +21,43 @@ // hover style isn't applied to it. + /** Fields that must be available for constructing the view. */ + internal const Geary.Email.Field REQUIRED_FOR_CONSTRUCT = ( + Geary.Email.Field.ENVELOPE | + Geary.Email.Field.PREVIEW | + Geary.Email.Field.FLAGS + ); + + /** Fields that must be available for loading the body. */ + internal const Geary.Email.Field REQUIRED_FOR_LOAD = ( + // Include those needed by the constructor since we'll replace + // the ctor's email arg value once the body has been fully + // loaded + REQUIRED_FOR_CONSTRUCT | + Geary.Email.REQUIRED_FOR_MESSAGE + ); + + // Time to wait loading the body before showing the progress meter + private const int BODY_LOAD_TIMEOUT_MSEC = 250; + + + /** Specifies the loading state for a message part. */ + public enum LoadState { + + /** Loading has not started. */ + NOT_STARTED, + + /** Loading has started, but not completed. */ + STARTED, + + /** Loading has started and completed. */ + COMPLETED, + + /** Loading has started but encountered an error. */ + FAILED; + + } + /** * Iterator that returns all message views in an email view. */ @@ -184,7 +221,7 @@ } } catch (Error error) { debug("Failed to load icon for attachment '%s': %s", - this.attachment.id, + this.attachment.file.get_path(), error.message); } @@ -206,6 +243,8 @@ private const string ACTION_MARK_READ = "mark_read"; private const string ACTION_MARK_UNREAD = "mark_unread"; private const string ACTION_MARK_UNREAD_DOWN = "mark_unread_down"; + private const string ACTION_TRASH_MESSAGE = "trash_msg"; + private const string ACTION_DELETE_MESSAGE = "delete_msg"; private const string ACTION_OPEN_ATTACHMENTS = "open_attachments"; private const string ACTION_PRINT = "print"; private const string ACTION_REPLY_SENDER = "reply_sender"; @@ -222,7 +261,14 @@ private const string STARRED_CLASS = "geary-starred"; private const string UNREAD_CLASS = "geary-unread"; - /** The specific email that is displayed by this view. */ + /** + * The specific email that is displayed by this view. + * + * This object is updated as additional fields are loaded, so it + * should not be relied on to a) contain required fields without + * testing or b) assumed to be the same over the life of this view + * object. + */ public Geary.Email email { get; private set; } /** Determines if the email is showing a preview or the full message. */ @@ -240,6 +286,12 @@ } } + /** Determines if the email is a draft message. */ + public bool is_draft { get; private set; } + + /** The email's primary originator, if any. */ + public Geary.RFC822.MailboxAddress? primary_originator { get; private set; } + /** The view displaying the email's primary message headers and body. */ public ConversationMessage primary_message { get; private set; } @@ -250,23 +302,36 @@ private Gee.List _attached_messages = new Gee.LinkedList(); - /** Determines if all message's web views have finished loading. */ - public bool message_bodies_loaded { get; private set; default = false; } + /** Determines the message body loading state. */ + public LoadState message_body_state { get; private set; default = NOT_STARTED; } - // Contacts for the email's account + // Store from which to load message content, if needed + private Geary.App.EmailStore email_store; + + // Store from which to lookup contacts private Geary.ContactStore contact_store; + // Store from which to load avatars + private Application.AvatarStore avatar_store; + + // Cancellable to use when loading message content + private GLib.Cancellable load_cancellable; + private Configuration config; + private Geary.TimeoutManager body_loading_timeout; + + + /** Determines if all message's web views have finished loading. */ + private Geary.Nonblocking.Spinlock message_bodies_loaded_lock = + new Geary.Nonblocking.Spinlock(); + // Message view with selected text, if any private ConversationMessage? body_selection_message = null; - // Attachment ids that have been displayed inline - private Gee.HashSet inlined_content_ids = new Gee.HashSet(); - // A subset of the message's attachments that are displayed in the // attachments view - Gee.Collection displayed_attachments = + private Gee.List displayed_attachments = new Gee.LinkedList(); // Message-specific actions @@ -307,6 +372,15 @@ private Gtk.Menu attachments_menu; + private Menu email_menu; + private Menu email_menu_model; + private Menu email_menu_trash; + private Menu email_menu_delete; + private bool shift_key_down; + + + /** Fired when an error occurs loading the message body. */ + public signal void load_error(GLib.Error err); /** Fired when the user clicks "reply" in the message menu. */ public signal void reply_to_message(); @@ -327,6 +401,12 @@ Geary.NamedFlag? to_add, Geary.NamedFlag? to_remove ); + /** Fired when the user clicks "trash" in the message menu. */ + public signal void trash_message(); + + /** Fired when the user clicks "delete" in the message menu. */ + public signal void delete_message(); + /** Fired when the user activates an attachment. */ public signal void attachments_activated( Gee.Collection attachments @@ -343,6 +423,9 @@ /** Fired when the view source action is activated. */ public signal void view_source(); + /** Fired when a internal link is activated */ + public signal void internal_link_activated(int y); + /** Fired when the user selects text in a message. */ internal signal void body_selection_changed(bool has_selection); @@ -355,14 +438,21 @@ * long-running loading processes. */ public ConversationEmail(Geary.Email email, - Geary.ContactStore contact_store, + Geary.App.EmailStore email_store, + Application.AvatarStore avatar_store, Configuration config, bool is_sent, - bool is_draft) { + bool is_draft, + GLib.Cancellable load_cancellable) { base_ref(); this.email = email; - this.contact_store = contact_store; + this.is_draft = is_draft; + this.primary_originator = Util.Email.get_primary_originator(email); + this.email_store = email_store; + this.contact_store = email_store.account.get_contact_store(); + this.avatar_store = avatar_store; this.config = config; + this.load_cancellable = load_cancellable; if (is_sent) { get_style_context().add_class(SENT_CLASS); @@ -372,7 +462,7 @@ forward_message(); }); add_action(ACTION_PRINT).activate.connect(() => { - print(); + print.begin(); }); add_action(ACTION_MARK_READ).activate.connect(() => { mark_email(null, Geary.EmailFlags.UNREAD); @@ -383,6 +473,12 @@ add_action(ACTION_MARK_UNREAD_DOWN).activate.connect(() => { mark_email_from_here(Geary.EmailFlags.UNREAD, null); }); + add_action(ACTION_TRASH_MESSAGE).activate.connect(() => { + trash_message(); + }); + add_action(ACTION_DELETE_MESSAGE).activate.connect(() => { + delete_message(); + }); add_action(ACTION_OPEN_ATTACHMENTS, false).activate.connect(() => { attachments_activated(get_selected_attachments()); }); @@ -412,52 +508,37 @@ }); insert_action_group("eml", message_actions); - // Construct CID resources from attachments - - Gee.Map cid_resources = - new Gee.HashMap(); - foreach (Geary.Attachment att in email.attachments) { - if (att.content_id != null) { - try { - cid_resources[att.content_id] = - new Geary.Memory.FileBuffer(att.file, true); - } catch (Error err) { - debug("Could not open attachment: %s", err.message); - } - } - } - // Construct the view for the primary message, hook into it - Geary.RFC822.Message message; - try { - message = email.get_message(); - } catch (Error error) { - debug("Error loading primary message: %s", error.message); - return; - } - bool load_images = email.load_remote_images().is_certain(); - Geary.Contact contact = this.contact_store.get_by_rfc822( - message.get_primary_originator() - ); + + Geary.Contact? contact = null; + if (this.primary_originator != null) { + contact = this.contact_store.get_by_rfc822(this.primary_originator); + } if (contact != null) { load_images |= contact.always_load_remote_images(); } - this.primary_message = new ConversationMessage(message, config, load_images); - this.primary_message.web_view.add_internal_resources(cid_resources); + this.primary_message = new ConversationMessage.from_email( + email, load_images, config + ); connect_message_view_signals(this.primary_message); this.primary_message.summary.add(this.actions); + // Wire up the rest of the UI + Gtk.Builder builder = new Gtk.Builder.from_resource( "/org/gnome/Geary/conversation-email-menus.ui" ); - this.email_menubutton.set_menu_model( - (MenuModel) builder.get_object("email_menu") - ); + this.email_menu = new Menu(); + this.email_menu_model = (Menu) builder.get_object("email_menu"); + this.email_menu_trash = (Menu) builder.get_object("email_menu_trash"); + this.email_menu_delete = (Menu) builder.get_object("email_menu_delete"); + this.email_menubutton.set_menu_model(this.email_menu); this.email_menubutton.set_sensitive(false); + this.email_menubutton.toggled.connect(this.on_email_menu); this.attachments_menu = new Gtk.Menu.from_model( (MenuModel) builder.get_object("attachments_menu") @@ -474,23 +555,18 @@ this.primary_message.infobars.add(this.not_saved_infobar); - pack_start(this.primary_message, true, true, 0); - update_email_state(); + email_store.account.incoming.notify["current-status"].connect( + on_service_status_change + ); - // Add sub_messages container and message viewers if any + this.load_cancellable.cancelled.connect(on_load_cancelled); - Gee.List sub_messages = message.get_sub_messages(); - if (sub_messages.size > 0) { - this.primary_message.body_container.add(this.sub_messages); - } - foreach (Geary.RFC822.Message sub_message in sub_messages) { - ConversationMessage attached_message = - new ConversationMessage(sub_message, config, false); - connect_message_view_signals(attached_message); - attached_message.web_view.add_internal_resources(cid_resources); - this.sub_messages.add(attached_message); - this._attached_messages.add(attached_message); - } + this.body_loading_timeout = new Geary.TimeoutManager.milliseconds( + BODY_LOAD_TIMEOUT_MSEC, this.on_body_loading_timeout + ); + + pack_start(this.primary_message, true, true, 0); + update_email_state(); } ~ConversationEmail() { @@ -498,48 +574,105 @@ } /** - * Starts loading the complete email. + * Loads the avatar for the primary message. + */ + public async void load_avatar(Application.AvatarStore store) + throws GLib.Error { + try { + yield this.primary_message.load_avatar(store, this.load_cancellable); + } catch (IOError.CANCELLED err) { + // okay + } catch (Error err) { + Geary.RFC822.MailboxAddress? from = this.primary_originator; + debug("Avatar load failed for \"%s\": %s", + from != null ? from.to_string() : "", err.message); + } + } + + /** + * Loads the message body and attachments. * - * This method will load the avatar and message body for the - * primary message and any attached messages, as well as - * attachment names, types and icons. - */ - public async void start_loading(ConversationListBox.AvatarStore avatars, - Cancellable load_cancelled) { - foreach (ConversationMessage view in this) { - if (load_cancelled.is_cancelled()) { - break; + * This potentially hits the database if the email that the view + * was constructed from doesn't satisfy requirements, loads + * attachments, including views and avatars for any attached + * messages, and waits for the primary message body content to + * have been loaded by its web view before returning. + */ + public async void load_body() + throws GLib.Error { + this.message_body_state = STARTED; + + // Ensure we have required data to load the message + + bool loaded = this.email.fields.fulfills(REQUIRED_FOR_LOAD); + if (!loaded) { + this.body_loading_timeout.start(); + try { + this.email = yield this.email_store.fetch_email_async( + this.email.id, + REQUIRED_FOR_LOAD, + LOCAL_ONLY, // Throws an error if not downloaded + this.load_cancellable + ); + loaded = true; + this.body_loading_timeout.reset(); + } catch (Geary.EngineError.INCOMPLETE_MESSAGE err) { + // Don't have the complete message at the moment, so + // download it in the background. Don't reset the body + // load timeout here since this will attempt to fetch + // from the remote + this.fetch_remote_body.begin(); + } catch (GLib.IOError.CANCELLED err) { + this.body_loading_timeout.reset(); + throw err; + } catch (GLib.Error err) { + this.body_loading_timeout.reset(); + handle_load_failure(err); + throw err; } - yield view.load_message_body(load_cancelled); - view.load_avatar.begin(avatars, load_cancelled); } - // Only load attachments once the web views have finished - // loading, since we want to know if any attachments marked as - // being inline were actually not displayed inline, and hence - // need to be displayed as if they were attachments. - if (!load_cancelled.is_cancelled()) { - if (this.message_bodies_loaded) { - yield load_attachments(load_cancelled); - } else { - this.notify["message-bodies-loaded"].connect(() => { - load_attachments.begin(load_cancelled); - }); + if (loaded) { + try { + yield update_body(); + } catch (GLib.Error err) { + this.body_loading_timeout.reset(); + handle_load_failure(err); + throw err; } + yield this.message_bodies_loaded_lock.wait_async( + this.load_cancellable + ); } } /** + * Enables or disables actions that require folder support. + */ + public void set_folder_actions_enabled(bool supports_trash, bool supports_delete) { + set_action_enabled(ACTION_TRASH_MESSAGE, supports_trash); + set_action_enabled(ACTION_DELETE_MESSAGE, supports_delete); + } + + /** + * Substitutes the "Delete Message" button for the "Move Message to Trash" + * button if the Shift key is pressed. + */ + public void shift_key_changed(bool pressed) { + this.shift_key_down = pressed; + this.on_email_menu(); + } + + /** * Shows the complete message: headers, body and attachments. */ public void expand_email(bool include_transitions=true) { - is_collapsed = false; + this.is_collapsed = false; update_email_state(); - attachments_button.set_sensitive(true); - email_menubutton.set_sensitive(true); - primary_message.show_message_body(include_transitions); - foreach (ConversationMessage attached in this._attached_messages) { - attached.show_message_body(include_transitions); + this.attachments_button.set_sensitive(true); + this.email_menubutton.set_sensitive(true); + foreach (ConversationMessage message in this) { + message.show_message_body(include_transitions); } } @@ -611,8 +744,19 @@ return action; } + private bool get_action_enabled(string name) { + SimpleAction? action = + this.message_actions.lookup_action(name) as SimpleAction; + if (action != null) { + return action.get_enabled(); + } else { + return false; + } + } + private void set_action_enabled(string name, bool enabled) { - SimpleAction? action = this.message_actions.lookup(name) as SimpleAction; + SimpleAction? action = + this.message_actions.lookup_action(name) as SimpleAction; if (action != null) { action.set_enabled(enabled); } @@ -621,29 +765,115 @@ private void connect_message_view_signals(ConversationMessage view) { view.flag_remote_images.connect(on_flag_remote_images); view.remember_remote_images.connect(on_remember_remote_images); - view.web_view.internal_resource_loaded.connect((id) => { - this.inlined_content_ids.add(id); - }); - view.web_view.notify["has-valid-height"].connect(() => { - bool all_loaded = true; - foreach (ConversationMessage message in this) { - if (!message.web_view.has_valid_height) { - all_loaded = false; - break; - } - } - if (all_loaded == true && !this.message_bodies_loaded) { - // Only update the property value if not already - // true - this.message_bodies_loaded = true; - } + view.internal_link_activated.connect((y) => { + internal_link_activated(y); }); + view.web_view.internal_resource_loaded.connect(on_resource_loaded); + view.web_view.content_loaded.connect(on_content_loaded); view.web_view.selection_changed.connect((has_selection) => { this.body_selection_message = has_selection ? view : null; body_selection_changed(has_selection); }); } + private async void fetch_remote_body() { + if (is_online()) { + // XXX Need proper progress reporting here, rather than just + // doing a pulse + if (!this.body_loading_timeout.is_running) { + this.body_loading_timeout.start(); + } + + Geary.Email? loaded = null; + try { + debug("Downloading remote message: %s", this.email.to_string()); + loaded = yield this.email_store.fetch_email_async( + this.email.id, + REQUIRED_FOR_LOAD, + FORCE_UPDATE, + this.load_cancellable + ); + } catch (GLib.IOError.CANCELLED err) { + // All good + } catch (GLib.Error err) { + debug("Remote message download failed: %s", err.message); + handle_load_failure(err); + } + + this.body_loading_timeout.reset(); + + if (loaded != null && !this.load_cancellable.is_cancelled()) { + try { + this.email = loaded; + yield update_body(); + } catch (GLib.Error err) { + debug("Remote message update failed: %s", err.message); + handle_load_failure(err); + } + } + } else { + this.body_loading_timeout.reset(); + handle_load_offline(); + } + } + + private async void update_body() + throws GLib.Error { + Geary.RFC822.Message message = this.email.get_message(); + + // Load all mime parts and construct CID resources from them + + Gee.Map cid_resources = + new Gee.HashMap(); + foreach (Geary.Attachment attachment in email.attachments) { + // Assume all parts are attachments. As the primary and + // secondary message bodies are loaded, any displayed + // inline will be removed from the list. + this.displayed_attachments.add(attachment); + + if (attachment.content_id != null) { + try { + cid_resources[attachment.content_id] = + new Geary.Memory.FileBuffer(attachment.file, true); + } catch (Error err) { + debug("Could not open attachment: %s", err.message); + } + } + } + this.attachments_button.set_visible(!this.displayed_attachments.is_empty); + + // Load all messages + + this.primary_message.web_view.add_internal_resources(cid_resources); + yield this.primary_message.load_message_body( + message, this.load_cancellable + ); + + Gee.List sub_messages = message.get_sub_messages(); + if (sub_messages.size > 0) { + this.primary_message.body_container.add(this.sub_messages); + } + foreach (Geary.RFC822.Message sub_message in sub_messages) { + ConversationMessage attached_message = + new ConversationMessage.from_message( + sub_message, false, this.config + ); + connect_message_view_signals(attached_message); + attached_message.web_view.add_internal_resources(cid_resources); + this.sub_messages.add(attached_message); + this._attached_messages.add(attached_message); + attached_message.load_avatar.begin( + this.avatar_store, this.load_cancellable + ); + yield attached_message.load_message_body( + sub_message, this.load_cancellable + ); + if (!this.is_collapsed) { + attached_message.show_message_body(false); + } + } + } + private void update_email_state() { Geary.EmailFlags? flags = this.email.email_flags; Gtk.StyleContext style = get_style_context(); @@ -676,37 +906,10 @@ } } - private async void load_attachments(Cancellable load_cancelled) { - // Determine if we have any attachments to be displayed. This - // relies on the primary and any attached message bodies - // having being already loaded, so that we know which - // attachments have been shown inline and hence do not need to - // be included here. - foreach (Geary.Attachment attachment in email.attachments) { - if (!(attachment.content_id in this.inlined_content_ids)) { - Geary.Mime.DispositionType? disposition = null; - if (attachment.content_disposition != null) { - disposition = attachment.content_disposition.disposition_type; - } - // Display both any attachment and inline parts that - // have already not been inlined. Although any inline - // parts should be referred to by other content in a - // multipart/related or multipart/alternative - // container, or inlined if in a multipart/mixed - // container, this cannot be not guaranteed. C.f. Bug - // 769868. - if (disposition != null && - disposition == Geary.Mime.DispositionType.ATTACHMENT || - disposition == Geary.Mime.DispositionType.INLINE) { - this.displayed_attachments.add(attachment); - } - } - } - - // Now we can actually show the attachments, if any - if (!this.displayed_attachments.is_empty) { - this.attachments_button.show(); - this.attachments_button.set_sensitive(!this.is_collapsed); + private void update_displayed_attachments() { + bool has_attachments = !this.displayed_attachments.is_empty; + this.attachments_button.set_visible(has_attachments); + if (has_attachments) { this.primary_message.body_container.add(this.attachments); if (this.displayed_attachments.size > 1) { @@ -715,12 +918,9 @@ } foreach (Geary.Attachment attachment in this.displayed_attachments) { - if (load_cancelled.is_cancelled()) { - return; - } AttachmentView view = new AttachmentView(attachment); this.attachments_view.add(view); - yield view.load_icon(load_cancelled); + view.load_icon.begin(this.load_cancellable); } } } @@ -735,9 +935,77 @@ return selected; } - private void print() { - // XXX This isn't anywhere near good enough - headers aren't - // being printed. + private void handle_load_failure(GLib.Error err) { + load_error(err); + this.message_body_state = FAILED; + this.primary_message.show_load_error_pane(); + } + + private void handle_load_offline() { + this.message_body_state = FAILED; + this.primary_message.show_offline_pane(); + } + + private inline bool is_online() { + return (this.email_store.account.incoming.current_status == CONNECTED); + } + + /** + * Updates the email menu if it is open. + */ + private void on_email_menu() { + if (this.email_menubutton.active) { + this.email_menu.remove_all(); + + bool supports_trash = get_action_enabled(ACTION_TRASH_MESSAGE); + bool supports_delete = get_action_enabled(ACTION_DELETE_MESSAGE); + bool show_trash_button = !this.shift_key_down && (supports_trash || !supports_delete); + GtkUtil.menu_foreach(this.email_menu_model, (label, name, target, section) => { + if ((section != this.email_menu_trash || show_trash_button) && + (section != this.email_menu_delete || !show_trash_button)) { + this.email_menu.append_item(new MenuItem.section(label, section)); + } + }); + } + } + + private async void print() throws Error { + Json.Builder builder = new Json.Builder(); + builder.begin_object(); + if (this.email.from != null) { + builder.set_member_name(_("From:")); + builder.add_string_value(this.email.from.to_string()); + } + if (this.email.to != null) { + // Translators: Human-readable version of the RFC 822 To header + builder.set_member_name(_("To:")); + builder.add_string_value(this.email.to.to_string()); + } + if (this.email.cc != null) { + // Translators: Human-readable version of the RFC 822 CC header + builder.set_member_name(_("Cc:")); + builder.add_string_value(this.email.cc.to_string()); + } + if (this.email.bcc != null) { + // Translators: Human-readable version of the RFC 822 BCC header + builder.set_member_name(_("Bcc:")); + builder.add_string_value(this.email.bcc.to_string()); + } + if (this.email.date != null) { + // Translators: Human-readable version of the RFC 822 Date header + builder.set_member_name(_("Date:")); + builder.add_string_value(this.email.date.to_string()); + } + if (this.email.subject != null) { + // Translators: Human-readable version of the RFC 822 Subject header + builder.set_member_name(_("Subject:")); + builder.add_string_value(this.email.subject.to_string()); + } + builder.end_object(); + Json.Generator generator = new Json.Generator(); + generator.set_root(builder.get_root()); + string js = "geary.addPrintHeaders(" + generator.to_data(null) + ");"; + yield this.primary_message.web_view.run_javascript(js, null); Gtk.Window? window = get_toplevel() as Gtk.Window; WebKit.PrintOperation op = new WebKit.PrintOperation( @@ -745,10 +1013,6 @@ ); Gtk.PrintSettings settings = new Gtk.PrintSettings(); - if (!Geary.String.is_empty(this.config.print_dir)) { - settings.set(Gtk.PRINT_SETTINGS_OUTPUT_DIR, this.config.print_dir); - } - if (this.email.subject != null) { string file_name = Geary.String.reduce_whitespace(this.email.subject.value); file_name = file_name.replace("/", "_"); @@ -763,12 +1027,14 @@ op.set_print_settings(settings); op.run_dialog(window); + } - string? file_uri = op.get_print_settings().get(Gtk.PRINT_SETTINGS_OUTPUT_URI); - if (!Geary.String.is_empty(file_uri)) { - File print_file = File.new_for_uri(file_uri); - this.config.print_dir = print_file.get_parent().get_path(); - } + private void on_body_loading_timeout() { + this.primary_message.show_loading_pane(); + } + + private void on_load_cancelled() { + this.body_loading_timeout.reset(); } private void on_flag_remote_images(ConversationMessage view) { @@ -778,25 +1044,50 @@ private void on_remember_remote_images(ConversationMessage view) { - Geary.RFC822.MailboxAddress? sender = view.message.get_primary_originator(); - if (sender == null) { - debug("Couldn't find sender for message: %s", email.id.to_string()); - return; + Geary.RFC822.MailboxAddress? sender = this.primary_originator; + if (sender != null) { + Geary.Contact? contact = this.contact_store.get_by_rfc822(sender); + if (contact != null) { + Geary.ContactFlags flags = new Geary.ContactFlags(); + flags.add(Geary.ContactFlags.ALWAYS_LOAD_REMOTE_IMAGES); + this.contact_store.mark_contacts_async.begin( + Geary.Collection.single(contact), flags, null + ); + } } + } - Geary.Contact? contact = contact_store.get_by_rfc822( - view.message.get_primary_originator() - ); - if (contact == null) { - debug("Couldn't find contact for %s", sender.to_string()); - return; + private void on_resource_loaded(string id) { + Gee.Iterator displayed = + this.displayed_attachments.iterator(); + while (displayed.has_next()) { + displayed.next(); + Geary.Attachment? attachment = displayed.get(); + if (attachment.content_id == id) { + displayed.remove(); + } } + } - Geary.ContactFlags flags = new Geary.ContactFlags(); - flags.add(Geary.ContactFlags.ALWAYS_LOAD_REMOTE_IMAGES); - Gee.ArrayList contact_list = new Gee.ArrayList(); - contact_list.add(contact); - contact_store.mark_contacts_async.begin(contact_list, flags, null); + private void on_content_loaded() { + bool all_loaded = true; + foreach (ConversationMessage message in this) { + if (!message.web_view.is_content_loaded) { + all_loaded = false; + break; + } + } + if (all_loaded && this.message_body_state != COMPLETED) { + this.message_body_state = COMPLETED; + this.message_bodies_loaded_lock.blind_notify(); + + // Update attachments once the web views have finished + // loading, since we want to know if any attachments + // marked as being inline were actually not displayed + // inline, and hence need to be displayed as if they were + // attachments. + this.update_displayed_attachments(); + } } [GtkCallback] @@ -819,4 +1110,12 @@ len < this.displayed_attachments.size); } + private void on_service_status_change() { + if (this.message_body_state == FAILED && + !this.load_cancellable.is_cancelled() && + is_online()) { + this.fetch_remote_body.begin(); + } + } + } diff -Nru geary-0.12.4/src/client/conversation-viewer/conversation-list-box.vala geary-3.32.0/src/client/conversation-viewer/conversation-list-box.vala --- geary-0.12.4/src/client/conversation-viewer/conversation-list-box.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/conversation-viewer/conversation-list-box.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,6 +1,6 @@ /* * Copyright 2016 Software Freedom Conservancy Inc. - * Copyright 2016 Michael Gratton + * Copyright 2016,2019 Michael Gratton * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. @@ -20,16 +20,15 @@ */ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface { - /** Fields that must be available for display as a conversation. */ - private const Geary.Email.Field REQUIRED_FIELDS = - Geary.Email.Field.HEADER - | Geary.Email.Field.BODY - | Geary.Email.Field.ORIGINATORS - | Geary.Email.Field.RECEIVERS - | Geary.Email.Field.SUBJECT - | Geary.Email.Field.DATE - | Geary.Email.Field.FLAGS - | Geary.ComposedEmail.REQUIRED_REPLY_FIELDS; + /** Fields that must be available for listing conversation email. */ + public const Geary.Email.Field REQUIRED_FIELDS = ( + // Sorting the conversation + Geary.Email.Field.DATE | + // Determine unread/starred, etc + Geary.Email.Field.FLAGS | + // Determine if the message is from the sender or not + Geary.Email.Field.ORIGINATORS + ); // Offset from the top of the list box which emails views will // scrolled to, so the user can see there are additional messages @@ -39,22 +38,203 @@ // account. private const int EMAIL_TOP_OFFSET = 32; - // Loading spinner timeout - private const int LOADING_TIMEOUT_MSEC = 150; + // Amount of time to wait after the user took some action that may + // be interpreted as marking the email as read before actually + // checking + private const int MARK_READ_TIMEOUT_MSEC = 250; + // Amount of pixels that need to be shown of an email's body to + // mark it as read + private const int MARK_READ_PADDING = 50; - // Base class for list rows it the list box - private abstract class ConversationRow : Gtk.ListBoxRow, Geary.BaseInterface { + + /** Manages find/search term matching in a conversation. */ + public class SearchManager : Geary.BaseObject { + + + // The list that owns this manager + private weak ConversationListBox list; + + // Conversation being managed + private Geary.App.Conversation conversation; + + // Cached search terms to apply to new messages + private Gee.Set? terms = null; + + // Total number of search matches found + private uint matches_found = 0; + + // Cancellable used when highlighting search matches + private GLib.Cancellable highlight_cancellable = new GLib.Cancellable(); + + + /** Fired when the number of matching emails has changed. */ + public signal void matches_updated(uint matches); + + + internal SearchManager(ConversationListBox list, + Geary.App.Conversation conversation) { + this.list = list; + this.conversation = conversation; + } + + + /** + * Loads search term matches for this list's emails. + */ + public async void highlight_matching_email(Geary.SearchQuery query) + throws GLib.Error { + cancel(); + + // Keep a copy of the current cancellable so it can't get + // changed out from underneath the execution of this method + GLib.Cancellable cancellable = this.highlight_cancellable; + + Geary.Account account = this.conversation.base_folder.account; + Gee.Collection? matching = + yield account.local_search_async( + query, + this.conversation.get_count(), + 0, + null, + this.conversation.get_email_ids(), + cancellable + ); + + if (matching != null) { + Gee.Set? terms = + yield account.get_search_matches_async( + query, matching, cancellable + ); + + if (cancellable.is_cancelled()) { + throw new GLib.IOError.CANCELLED( + "Search term highlighting cancelled" + ); + } + + if (terms != null && !terms.is_empty) { + this.terms = terms; + + // Scroll to the first matching row first + EmailRow? first = null; + foreach (Geary.EmailIdentifier id in matching) { + EmailRow? row = this.list.get_email_row_by_id(id); + if (row != null && + (first == null || row.get_index() < first.get_index())) { + first = row; + } + } + if (first != null) { + this.list.scroll_to(first); + } + + // Now expand them all + foreach (Geary.EmailIdentifier id in matching) { + EmailRow? row = this.list.get_email_row_by_id(id); + if (row != null) { + apply_terms(row, terms, cancellable); + row.expand.begin(); + } + } + } + } + } + + /** + * Highlights matching terms in the given email row, if any. + */ + internal void highlight_row_if_matching(EmailRow row) { + if (this.terms != null) { + apply_terms(row, this.terms, this.highlight_cancellable); + } + } + + /** + * Removes search term highlighting from all messages. + */ + public void unmark_terms() { + cancel(); + + this.list.foreach((child) => { + EmailRow? row = child as EmailRow; + if (row != null) { + if (row.is_search_match) { + row.is_search_match = false; + foreach (ConversationMessage msg_view in row.view) { + msg_view.unmark_search_terms(); + } + } + } + }); + } + + public void cancel() { + this.highlight_cancellable.cancel(); + this.highlight_cancellable = new Cancellable(); + this.terms = null; + this.matches_found = 0; + notify_matches_updated(); + } + + private void apply_terms(EmailRow row, + Gee.Set? terms, + GLib.Cancellable cancellable) { + if (row.view.message_body_state == COMPLETED) { + this.apply_terms_impl.begin( + row, terms, cancellable, apply_terms_impl_finished + ); + } else { + row.view.notify["message-body-state"].connect(() => { + this.apply_terms_impl.begin( + row, terms, cancellable, apply_terms_impl_finished + ); + }); + } + } + + // This should only be called from apply_terms above + private async uint apply_terms_impl(EmailRow row, + Gee.Set? terms, + GLib.Cancellable cancellable) + throws GLib.IOError.CANCELLED { + uint count = 0; + foreach (ConversationMessage view in row.view) { + if (cancellable.is_cancelled()) { + throw new GLib.IOError.CANCELLED( + "Applying search terms cancelled" + ); + } + count += yield view.highlight_search_terms(terms, cancellable); + } + + row.is_search_match = (count > 0); + return count; + } + + private void apply_terms_impl_finished(GLib.Object? obj, + GLib.AsyncResult res) { + try { + this.matches_found += this.apply_terms_impl.end(res); + notify_matches_updated(); + } catch (GLib.IOError.CANCELLED err) { + // All good + } + } + + private inline void notify_matches_updated() { + matches_updated(this.matches_found); + } + + } + + + // Base class for list rows in the list box + internal abstract class ConversationRow : Gtk.ListBoxRow, Geary.BaseInterface { protected const string EXPANDED_CLASS = "geary-expanded"; - private const string FIRST_CLASS = "geary-first"; - private const string LAST_CLASS = "geary-last"; -#if !GTK_3_20 - // GTK < 3.20+ style workarounds. Keep this in sync - // with geary.css. - private const int CANT_USE_PADDING_WORKAROUND = 18; -#endif + // The email being displayed by this row, if any public Geary.Email? email { get; private set; default = null; } @@ -65,44 +245,11 @@ return this._is_expanded; } protected set { -#if !GTK_3_20 - // GTK+ < 3.20 style workaround. Keep this in sync - // with geary.css - this.margin_bottom = value ? 6 : 0; -#endif this._is_expanded = value; } } private bool _is_expanded = false; - // Designate this row as the first visible row in the - // conversation listbox, or not. See Bug 764710 and - // ::update_first_last_row() below. - internal bool is_first { - set { - set_style_context_class(FIRST_CLASS, value); -#if !GTK_3_20 - // GTK < 3.20+ style workarounds. Keep this in sync - // with geary.css. - this.margin_top = CANT_USE_PADDING_WORKAROUND; -#endif - } - } - - // Designate this row as the last visible row in the - // conversation listbox, or not. See Bug 764710 and - // ::update_first_last_row() below. - internal bool is_last { - set { - set_style_context_class(LAST_CLASS, value); -#if !GTK_3_20 - // GTK < 3.20+ style workarounds. Keep this in sync - // with geary.css. - this.margin_bottom = CANT_USE_PADDING_WORKAROUND; -#endif - } - } - // We can only scroll to a specific row once it has been // allocated space. This signal allows the viewer to hook up @@ -110,17 +257,10 @@ public signal void should_scroll(); - public ConversationRow(Geary.Email? email) { + protected ConversationRow(Geary.Email? email) { base_ref(); this.email = email; show(); - -#if !GTK_3_20 - // GTK < 3.20+ style workarounds. Keep this in sync with - // geary.css. - this.margin_start = CANT_USE_PADDING_WORKAROUND; - this.margin_end = CANT_USE_PADDING_WORKAROUND; -#endif } ~ConversationRow() { @@ -128,7 +268,8 @@ } // Request the row be expanded, if supported. - public virtual new void expand() { + public virtual new async void expand() + throws GLib.Error { // Not supported by default } @@ -151,7 +292,7 @@ } } - protected virtual void on_size_allocate() { + protected void on_size_allocate() { // Disable should_scroll so we don't keep on scrolling // later, like when the window has been resized. this.size_allocate.disconnect(on_size_allocate); @@ -162,7 +303,7 @@ // Displays a single ConversationEmail in the list box - private class EmailRow : ConversationRow { + internal class EmailRow : ConversationRow { private const string MATCH_CLASS = "geary-matched"; @@ -192,14 +333,13 @@ add(view); } - public override void expand() { + public override async void expand() + throws GLib.Error { this.is_expanded = true; - foreach (ConversationMessage message in this.view) { - if (!message.web_view.has_valid_height) { - message.web_view.queue_resize(); - } - }; update_row_expansion(); + if (this.view.message_body_state == NOT_STARTED) { + yield this.view.load_body(); + } } public override void collapse() { @@ -208,20 +348,6 @@ update_row_expansion(); } - protected override void on_size_allocate() { - // We need to wait the web view to load first, so that the - // message has a non-trivial height, and then wait for it - // to be reallocated, so that it picks up the web_view's - // height. - if (view.primary_message.web_view.has_valid_height) { - // Disable should_scroll after the message body has - // been loaded so we don't keep on scrolling later, - // like when the window has been resized. - this.size_allocate.disconnect(on_size_allocate); - } - should_scroll(); - } - private inline void update_row_expansion() { if (this.is_expanded || this.is_pinned) { get_style_context().add_class(EXPANDED_CLASS); @@ -235,118 +361,41 @@ } - // Displays a single embedded composer in the list box - private class ComposerRow : ConversationRow { + // Displays a loading widget in the list box + internal class LoadingRow : ConversationRow { - // The embedded composer for this row - public ComposerEmbed view { get; private set; } + protected const string LOADING_CLASS = "geary-loading"; - public ComposerRow(ComposerEmbed view) { - base(view.referred); - this.view = view; - this.is_expanded = true; - get_style_context().add_class(EXPANDED_CLASS); - add(this.view); - } - - } + public LoadingRow() { + base(null); + get_style_context().add_class(LOADING_CLASS); - /** - * Email address avatar loader and cache. - */ - public class AvatarStore { - - - private Soup.Session session; - private Gee.Map loaders = - new Gee.HashMap(); - - - internal AvatarStore(Soup.Session session) { - this.session = session; - } - - internal async Gdk.Pixbuf? load(Geary.RFC822.MailboxAddress address, - int pixel_size, - Cancellable load_cancelled) - throws Error { - string key = address.to_string(); - AvatarLoader loader = this.loaders.get(key); - if (loader == null) { - // Haven't started loading the avatar, so do it now - loader = new AvatarLoader(address, pixel_size); - this.loaders.set(key, loader); - yield loader.load(this.session, load_cancelled); - } else { - // Load has already started, so wait for it to finish - yield loader.lock.wait_async(); - } - return loader.avatar; + Gtk.Spinner spinner = new Gtk.Spinner(); + spinner.height_request = 16; + spinner.width_request = 16; + spinner.show(); + spinner.start(); + add(spinner); } } - // Initiates and manages an avatar load - private class AvatarLoader : Geary.BaseObject { - - - internal Gdk.Pixbuf? avatar = null; - internal Geary.Nonblocking.Semaphore lock = - new Geary.Nonblocking.Semaphore(); - - private Geary.RFC822.MailboxAddress address; - private int pixel_size; - - - internal AvatarLoader(Geary.RFC822.MailboxAddress address, - int pixel_size) { - this.address = address; - this.pixel_size = pixel_size; - } - - internal async void load(Soup.Session session, - Cancellable load_cancelled) - throws Error { - Soup.Message message = new Soup.Message( - "GET", - Gravatar.get_image_uri( - this.address, - Gravatar.Default.NOT_FOUND, - this.pixel_size - ) - ); + // Displays a single embedded composer in the list box + internal class ComposerRow : ConversationRow { - Error? workaround_err = null; - try { - // We want to just pass load_cancelled to send_async - // here, but per Bug 778720 this is causing some - // crashy race in libsoup's cache implementation, so - // for now just let the load go through and manually - // check to see if the load has been cancelled before - // setting the avatar - InputStream data = yield session.send_async( - message, - null // should be 'load_cancelled' - ); - if (message.status_code == 200 && - data != null && - !load_cancelled.is_cancelled()) { - this.avatar = yield new Gdk.Pixbuf.from_stream_at_scale_async( - data, pixel_size, pixel_size, true, load_cancelled - ); - } - } catch (Error err) { - workaround_err = err; - } + // The embedded composer for this row + public ComposerEmbed view { get; private set; } - this.lock.blind_notify(); - if (workaround_err != null) { - throw workaround_err; - } + public ComposerRow(ComposerEmbed view) { + base(view.referred); + this.view = view; + this.is_expanded = true; + get_style_context().add_class(EXPANDED_CLASS); + add(this.view); } } @@ -413,30 +462,21 @@ /** Conversation being displayed. */ public Geary.App.Conversation conversation { get; private set; } - // Folder from which the conversation was loaded - internal Geary.Folder location { get; private set; } + /** Search manager for highlighting search terms in this list. */ + public SearchManager search { get; private set; } // Used to load messages in conversation. private Geary.App.EmailStore email_store; - // Contacts for the account this conversation exists in - private Geary.ContactStore contact_store; - // Avatars for this conversation - private AvatarStore avatar_store; - - // Account this conversation belongs to - private Geary.AccountInformation account_info; + private Application.AvatarStore avatar_store; - // Was this conversation loaded from the drafts folder? - private bool is_draft_folder; + // App config + private Configuration config; // Cancellable for this conversation's data loading. private Cancellable cancellable = new Cancellable(); - // App config - private Configuration config; - // Email view with selected text, if any private ConversationEmail? body_selected_view = null; @@ -447,17 +487,7 @@ // The id of the draft referred to by the current composer. private Geary.EmailIdentifier? draft_id = null; - // First and last visible row in the list, if any - private ConversationRow? first_row = null; - private ConversationRow? last_row = null; - - // Cached search terms to apply to new messages - private Gee.Set? search_terms = null; - - // Total number of search matches found - private uint search_matches_found = 0; - - private uint loading_timeout_id = 0; + private Geary.TimeoutManager mark_read_timer; /** Keyboard action to scroll the conversation. */ @@ -486,18 +516,21 @@ break; } adj.set_value(value); + this.mark_read_timer.start(); } /** Keyboard action to shift focus to the next message, if any. */ [Signal (action=true)] public virtual signal void focus_next() { this.move_cursor(Gtk.MovementStep.DISPLAY_LINES, 1); + this.mark_read_timer.start(); } /** Keyboard action to shift focus to the prev message, if any. */ [Signal (action=true)] public virtual signal void focus_prev() { this.move_cursor(Gtk.MovementStep.DISPLAY_LINES, -1); + this.mark_read_timer.start(); } /** Fired when an email view is added to the conversation list. */ @@ -510,63 +543,40 @@ public signal void mark_emails(Gee.Collection emails, Geary.EmailFlags? flags_to_add, Geary.EmailFlags? flags_to_remove); - /** Fired when an email that matches the current search terms is found. */ - public signal void search_matches_updated(uint matches); - /** * Constructs a new conversation list box instance. */ public ConversationListBox(Geary.App.Conversation conversation, - Geary.Folder location, - Geary.App.EmailStore? email_store, - Geary.ContactStore contact_store, - Geary.AccountInformation account_info, - bool is_draft_folder, + Geary.App.EmailStore email_store, + Application.AvatarStore avatar_store, Configuration config, - Soup.Session avatar_session, Gtk.Adjustment adjustment) { base_ref(); this.conversation = conversation; - this.location = location; this.email_store = email_store; - this.contact_store = contact_store; - this.avatar_store = new AvatarStore(avatar_session); - this.account_info = account_info; - this.is_draft_folder = is_draft_folder; + this.avatar_store = avatar_store; this.config = config; + this.search = new SearchManager(this, conversation); + + this.mark_read_timer = new Geary.TimeoutManager.milliseconds( + MARK_READ_TIMEOUT_MSEC, this.check_mark_read + ); + + this.selection_mode = NONE; + get_style_context().add_class("background"); get_style_context().add_class("conversation-listbox"); -#if !GTK_3_20 - // GTK < 3.20+ style workaround - get_style_context().remove_class("list"); -#endif set_adjustment(adjustment); - set_selection_mode(Gtk.SelectionMode.NONE); set_sort_func(ConversationListBox.on_sort); - this.realize.connect(() => { - adjustment.value_changed.connect(() => { check_mark_read(); }); - }); this.row_activated.connect(on_row_activated); - this.size_allocate.connect(() => { check_mark_read(); }); this.conversation.appended.connect(on_conversation_appended); this.conversation.trimmed.connect(on_conversation_trimmed); this.conversation.email_flags_changed.connect(on_update_flags); - - // If the load is taking too long, display a spinner - this.loading_timeout_id = - Timeout.add(LOADING_TIMEOUT_MSEC, () => { - if (this.loading_timeout_id != 0) { - debug("Loading timed out"); - show_loading(); - } - this.loading_timeout_id = 0; - return Source.REMOVE; - }); } ~ConversationListBox() { @@ -574,77 +584,71 @@ } public override void destroy() { - if (this.loading_timeout_id != 0) { - Source.remove(this.loading_timeout_id); - // Clear in case this is called twice - this.loading_timeout_id = 0; - } + this.search.cancel(); this.cancellable.cancel(); this.email_rows.clear(); + this.mark_read_timer.reset(); base.destroy(); } - public async void load_conversation() - throws Error { - // Fetch full emails from the conversation - Gee.Collection full_emails = - yield load_full_emails( - this.conversation.get_emails( - Geary.App.Conversation.Ordering.SENT_DATE_ASCENDING - ) - ); + public async void load_conversation(Geary.SearchQuery? query) + throws GLib.Error { + set_sort_func(null); - // Add them all - EmailRow? first_expanded_row = null; - foreach (Geary.Email full_email in full_emails) { - if (this.cancellable.is_cancelled()) { - break; - } - if (!(full_email.id in this.email_rows)) { - EmailRow row = add_email(full_email); - if (row.is_expanded && - (first_expanded_row == null || - on_sort(row, first_expanded_row) < 0)) { - first_expanded_row = row; + Gee.Collection? all_email = this.conversation.get_emails( + Geary.App.Conversation.Ordering.SENT_DATE_ASCENDING + ); + + // Work out what the first interesting email is, and load it + // before all of the email before and after that so we can + // load them in an optimal order. + Gee.LinkedList uninteresting = + new Gee.LinkedList(); + Geary.Email? first_interesting = null; + Gee.LinkedList post_interesting = + new Gee.LinkedList(); + foreach (Geary.Email email in all_email) { + if (first_interesting == null) { + if (email.is_unread().is_certain() || + email.is_flagged().is_certain()) { + first_interesting = email; + } else { + // Inserted reversed so most recent uninteresting + // rows are added first. + uninteresting.insert(0, email); } + } else { + post_interesting.add(email); } } - update_first_last_row(); - EmailRow? last_email = this.last_row as EmailRow; - - if (last_email != null && !this.cancellable.is_cancelled()) { - // If no other row was expanded by default, use the last - if (first_expanded_row == null) { - last_email.expand(); - first_expanded_row = last_email; - } + if (first_interesting == null) { + // No interesting messages found so use the last one. + first_interesting = uninteresting.remove_at(0); + } + EmailRow interesting_row = add_email(first_interesting); - // Start the first expanded row loading before any others, - // scroll the view to it when its done - yield first_expanded_row.view.start_loading( - this.avatar_store, this.cancellable - ); - first_expanded_row.should_scroll.connect(scroll_to); - first_expanded_row.enable_should_scroll(); + // If we have at least one uninteresting and one + // post-interesting to load afterwards, show a spinner above + // the interesting row to act as a placeholder. + if (!uninteresting.is_empty && !post_interesting.is_empty) { + insert(new LoadingRow(), 0); + } - // Start everything else loading - this.foreach((child) => { - if (!this.cancellable.is_cancelled()) { - EmailRow? row = child as EmailRow; - if (row != null && row != first_expanded_row) { - row.view.start_loading.begin( - this.avatar_store, this.cancellable - ); - } - } - }); + // Load the interesting row completely up front, and load the + // remaining in the background so we can return fast. + interesting_row.view.load_avatar.begin(this.avatar_store); + yield interesting_row.expand(); + this.finish_loading.begin( + query, uninteresting, post_interesting + ); - debug("Conversation loading complete"); - } + this.mark_read_timer.start(); + } - this.loading_timeout_id = 0; - set_placeholder(null); + /** Cancels loading the current conversation, if still in progress */ + public void cancel_conversation_load() { + this.cancellable.cancel(); } /** @@ -704,9 +708,10 @@ ComposerRow row = new ComposerRow(embed); row.enable_should_scroll(); - row.should_scroll.connect(() => { scroll_to(row); }); + // Use row param rather than row var from closure to avoid a + // circular ref. + row.should_scroll.connect((row) => { scroll_to(row); }); add(row); - update_first_last_row(); embed.composer.draft_id_changed.connect((id) => { this.draft_id = id; }); embed.vanished.connect(() => { @@ -721,6 +726,13 @@ } /** + * Marks all email with a visible body read. + */ + public void mark_visible_read() { + this.mark_read_timer.start(); + } + + /** * Displays an email as being read, regardless of its actual flags. */ public void mark_manual_read(Geary.EmailIdentifier id) { @@ -741,96 +753,6 @@ } /** - * Loads search term matches for this list's emails. - */ - public async void load_search_terms() { - Geary.SearchFolder search = (Geary.SearchFolder) this.location; - Geary.SearchQuery? query = search.search_query; - if (query != null) { - - // List all IDs of emails we're viewing. - Gee.Collection ids = - new Gee.ArrayList(); - foreach (Gee.Map.Entry entry - in this.email_rows.entries) { - if (entry.value.get_visible()) { - ids.add(entry.key); - } - } - - Gee.Set? search_matches = null; - try { - search_matches = yield search.get_search_matches_async( - ids, cancellable - ); - } catch (Error e) { - debug("Error highlighting search results: %s", e.message); - // Continue on here since if nothing else we have the - // fudging to fall back on immediately below. - } - - if (search_matches == null) - search_matches = new Gee.HashSet(); - - // This applies a fudge-factor set of matches when the database results - // aren't entirely satisfactory, such as when you search for an email - // address and the database tokenizes out the @ and ., etc. It's not meant - // to be comprehensive, just a little extra highlighting applied to make - // the results look a little closer to what you typed. - foreach (string word in query.raw.split(" ")) { - if (word.has_suffix("\"")) - word = word.substring(0, word.length - 1); - if (word.has_prefix("\"")) - word = word.substring(1); - - if (!Geary.String.is_empty_or_whitespace(word)) - search_matches.add(word); - } - - if (!this.cancellable.is_cancelled()) { - highlight_search_terms(search_matches); - } - } - } - - /** - * Applies search term highlighting to all email views. - * - * Returns true if any were found, else returns false. - */ - public void highlight_search_terms(Gee.Set terms) { - this.search_terms = terms; - this.search_matches_found = 0; - foreach (Gtk.Widget child in get_children()) { - EmailRow? row = child as EmailRow; - if (row != null) { - apply_search_terms(row); - } - } - } - - /** - * Removes search term highlighting from all messages. - */ - public void unmark_search_terms() { - this.search_terms = null; - this.search_matches_found = 0; - - this.foreach((child) => { - EmailRow? row = child as EmailRow; - if (row != null) { - if (row.is_search_match) { - row.is_search_match = false; - foreach (ConversationMessage msg_view in row.view) { - msg_view.unmark_search_terms(); - } - } - } - }); - search_matches_updated(this.search_matches_found); - } - - /** * Increases the magnification level used for displaying messages. */ public void zoom_in() { @@ -855,59 +777,122 @@ */ public void zoom_reset() { message_view_iterator().foreach((msg_view) => { - msg_view.web_view.zoom_level = 1.0f; + msg_view.web_view.zoom_reset(); return true; }); } - // Given some emails, fetch the full versions with all required fields. - private async Gee.Collection load_full_emails( - Gee.Collection emails) throws Error { - Gee.ArrayList ids = new Gee.ArrayList(); - foreach (Geary.Email email in emails) - ids.add(email.id); - - Gee.Collection? full_emails = - yield this.email_store.list_email_by_sparse_id_async( - ids, - REQUIRED_FIELDS, - Geary.Folder.ListFlags.NONE, - this.cancellable - ); + /** Returns the email row for the given id, if any. */ + internal EmailRow? get_email_row_by_id(Geary.EmailIdentifier id) { + return this.email_rows.get(id); + } + + private async void finish_loading(Geary.SearchQuery? query, + Gee.LinkedList to_insert, + Gee.LinkedList to_append) + throws GLib.Error { + // Add emails to append first because if the first interesting + // message was short, these will show up in the UI under it, + // filling the empty space. + foreach (Geary.Email email in to_append) { + EmailRow row = add_email(email); + yield row.view.load_avatar(this.avatar_store); + if (is_interesting(email)) { + yield row.expand(); + } + yield throttle_loading(); + } + + // Since first rows may have extra margin, remove that from + // the height of rows when adjusting scrolling. + Gtk.ListBoxRow initial_row = get_row_at_index(0); + int loading_height = 0; + if (initial_row is LoadingRow) { + loading_height = GtkUtil.get_border_box_height(initial_row); + remove(initial_row); + } + + // None of these will be interesting, so just add them all, + // but keep the scrollbar adjusted so that the first + // interesting message remains visible. + Gtk.Adjustment listbox_adj = get_adjustment(); + foreach (Geary.Email email in to_insert) { + EmailRow row = add_email(email, false); + // Since uninteresting rows are inserted above the + // first expanded, adjust the scrollbar as they are + // inserted so as to keep the list scrolled to the + // same place. + row.enable_should_scroll(); + row.should_scroll.connect(() => { + listbox_adj.value += GtkUtil.get_border_box_height(row); + }); - if (full_emails == null) { - full_emails = Gee.Collection.empty(); + // Only adjust for the loading row going away once + loading_height = 0; + + yield row.view.load_avatar(this.avatar_store); + yield throttle_loading(); } - return full_emails; + set_sort_func(on_sort); + + if (query != null) { + // XXX this sucks for large conversations because it can take + // a long time for the load to complete and hence for + // matches to show up. + yield this.search.highlight_matching_email(query); + } + } + + private inline async void throttle_loading() throws GLib.IOError { + // Give GTK a moment to process newly added rows, so when + // updating the adjustment below the values are + // valid. Priority must be low otherwise other async tasks + // (like cancelling loading if another conversation is + // selected) won't get a look in until this is done. + GLib.Idle.add( + this.throttle_loading.callback, GLib.Priority.LOW + ); + yield; + + // Check for cancellation after resuming in case the load was + // cancelled in the mean time. + if (this.cancellable.is_cancelled()) { + throw new GLib.IOError.CANCELLED( + "Conversation load cancelled" + ); + } } // Loads full version of an email, adds it to the listbox private async void load_full_email(Geary.EmailIdentifier id) - throws Error { + throws GLib.Error { + // Even though it would save a around-trip, don't load the + // full email here so that ConverationEmail can handle it if + // the full email isn't actually available in the same way as + // any other. Geary.Email full_email = yield this.email_store.fetch_email_async( - id, REQUIRED_FIELDS, Geary.Folder.ListFlags.NONE, this.cancellable + id, + REQUIRED_FIELDS | ConversationEmail.REQUIRED_FOR_CONSTRUCT, + Geary.Folder.ListFlags.NONE, + this.cancellable ); if (!this.cancellable.is_cancelled()) { EmailRow row = add_email(full_email); - update_first_last_row(); - yield row.view.start_loading(this.avatar_store, this.cancellable); + yield row.view.load_avatar(this.avatar_store); + this.search.highlight_row_if_matching(row); + yield row.expand(); } } // Constructs a row and view for an email, adds it to the listbox - private EmailRow add_email(Geary.Email email) { - // Should be able to edit draft emails from any - // conversation. This test should be more like "is in drafts - // folder" - bool is_in_folder = this.conversation.is_in_current_folder(email.id); - bool is_draft = (this.is_draft_folder && is_in_folder); - + private EmailRow add_email(Geary.Email email, bool append_row = true) { bool is_sent = false; + Geary.Account account = this.conversation.base_folder.account; if (email.from != null) { foreach (Geary.RFC822.MailboxAddress from in email.from) { - if (this.account_info.has_email_address(from)) { + if (account.information.has_sender_mailbox(from)) { is_sent = true; break; } @@ -916,16 +901,22 @@ ConversationEmail view = new ConversationEmail( email, - this.contact_store, + this.email_store, + this.avatar_store, this.config, is_sent, - is_draft + is_draft(email), + this.cancellable ); view.mark_email.connect(on_mark_email); view.mark_email_from_here.connect(on_mark_email_from_here); + view.internal_link_activated.connect(on_internal_link_activated); view.body_selection_changed.connect((email, has_selection) => { this.body_selected_view = has_selection ? email : null; }); + view.notify["message-body-state"].connect( + on_message_body_state_notify + ); ConversationMessage conversation_message = view.primary_message; conversation_message.body_container.button_release_event.connect_after((event) => { @@ -938,20 +929,12 @@ EmailRow row = new EmailRow(view); this.email_rows.set(email.id, row); - add(row); - email_added(view); - - // Expand interesting messages by default - if (email.is_unread().is_certain() || - email.is_flagged().is_certain() || - is_draft) { - row.expand(); - } - - // Apply any existing search terms to the new row - if (this.search_terms != null) { - apply_search_terms(row); + if (append_row) { + add(row); + } else { + insert(row, 0); } + email_added(view); return row; } @@ -965,15 +948,6 @@ } } - private void show_loading() { - Gtk.Spinner spinner = new Gtk.Spinner(); - spinner.set_size_request(32, 32); - spinner.halign = spinner.valign = Gtk.Align.CENTER; - spinner.start(); - spinner.show(); - set_placeholder(spinner); - } - private void scroll_to(ConversationRow row) { Gtk.Allocation? alloc = null; row.get_allocation(out alloc); @@ -987,26 +961,43 @@ get_adjustment().set_value(y); } + private void scroll_to_anchor(EmailRow row, int anchor_y) { + Gtk.Allocation? alloc = null; + row.get_allocation(out alloc); + + int x = 0, y = 0; + ConversationWebView web_view = row.view.primary_message.web_view; + web_view.translate_coordinates(row, x, anchor_y, out x, out y); + + Gtk.Adjustment adj = get_adjustment(); + y = alloc.y + y; + adj.set_value(y); + + } + /** * Finds any currently visible messages, marks them as being read. */ private void check_mark_read() { - Gee.ArrayList email_ids = - new Gee.ArrayList(); - + Gee.List email_ids = + new Gee.LinkedList(); Gtk.Adjustment adj = get_adjustment(); int top_bound = (int) adj.value; int bottom_bound = top_bound + (int) adj.page_size; - email_view_iterator().foreach((email_view) => { - const int TEXT_PADDING = 50; - ConversationMessage conversation_message = email_view.primary_message; + this.foreach((child) => { // Don't bother with not-yet-loaded emails since the // size of the body will be off, affecting the visibility // of emails further down the conversation. - if (email_view.email.is_unread().is_certain() && - conversation_message.web_view.has_valid_height && - !email_view.is_manually_read) { + EmailRow? row = child as EmailRow; + ConversationEmail? view = (row != null) ? row.view : null; + Geary.Email? email = (view != null) ? view.email : null; + if (row != null && + row.is_expanded && + view.message_body_state == COMPLETED && + !view.is_manually_read && + email.is_unread().is_certain()) { + ConversationMessage conversation_message = view.primary_message; int body_top = 0; int body_left = 0; ConversationWebView web_view = conversation_message.web_view; @@ -1022,16 +1013,15 @@ // Only mark the email as read if it's actually visible if (body_height > 0 && body_bottom > top_bound && - body_top + TEXT_PADDING < bottom_bound) { - email_ids.add(email_view.email.id); + body_top + MARK_READ_PADDING < bottom_bound) { + email_ids.add(view.email.id); // Since it can take some time for the new flags // to round-trip back to our signal handlers, // mark as manually read here - email_view.is_manually_read = true; + view.is_manually_read = true; } } - return true; }); if (email_ids.size > 0) { @@ -1041,64 +1031,6 @@ } } - // Due to Bug 764710, we can only use the CSS :last-child selector - // for GTK themes after 3.20.3, so for now manually maintain a - // class on the last box so we can emulate it - private void update_first_last_row() { - ConversationRow? first = null; - ConversationRow? last = null; - this.foreach((child) => { - if (first == null) { - first = (ConversationRow) child; - } - last = (ConversationRow) child; - }); - - if (this.first_row != first) { - if (this.first_row != null) { - this.first_row.is_first = false; - } - - this.first_row = first; - this.first_row.is_first = true; - } - - if (this.last_row != last) { - if (this.last_row != null) { - this.last_row.is_last = false; - } - - this.last_row = last; - this.last_row.is_last = true; - } - } - - private void apply_search_terms(EmailRow row) { - // Message bodies need to be loaded to be able to search for - // them? - if (row.view.message_bodies_loaded) { - this.apply_search_terms_impl.begin(row); - } else { - row.view.notify["message-bodies-loaded"].connect(() => { - this.apply_search_terms_impl.begin(row); - }); - } - } - - // This should only be called from apply_search_terms above - private async void apply_search_terms_impl(EmailRow row) { - bool found = false; - foreach (ConversationMessage view in row.view) { - uint count = yield view.highlight_search_terms(this.search_terms); - if (count > 0) { - found = true; - } - this.search_matches_found += count; - } - row.is_search_match = found; - search_matches_updated(this.search_matches_found); - } - /** * Returns an new Iterable over all email views in the viewer */ @@ -1119,6 +1051,30 @@ ); } + /** Determines if an email should be expanded by default. */ + private inline bool is_interesting(Geary.Email email) { + return ( + email.is_unread().is_certain() || + email.is_flagged().is_certain() || + is_draft(email) + ); + } + + /** Determines if an email should be considered to be a draft. */ + private inline bool is_draft(Geary.Email email) { + // XXX should be able to edit draft emails from any + // conversation. This test should be more like "is in drafts + // folder" + Geary.SpecialFolderType type = + this.conversation.base_folder.special_folder_type; + bool is_in_folder = this.conversation.is_in_base_folder(email.id); + + return ( + is_in_folder && type == Geary.SpecialFolderType.DRAFTS // || + //email.flags.is_draft() + ); + } + private void on_conversation_appended(Geary.App.Conversation conversation, Geary.Email email) { on_conversation_appended_async.begin(conversation, email); @@ -1128,7 +1084,7 @@ Geary.App.Conversation conversation, Geary.Email part_email) { // Don't add rows that are already present, or that are // currently being edited. - if (!(part_email.id in this.email_rows) && + if (!this.email_rows.has_key(part_email.id) && part_email.id != this.draft_id) { load_full_email.begin(part_email.id, (obj, ret) => { try { @@ -1184,6 +1140,14 @@ mark_emails(ids, flag_to_flags(to_add), flag_to_flags(to_remove)); } + private void on_message_body_state_notify(GLib.Object obj, + GLib.ParamSpec param) { + ConversationEmail? view = obj as ConversationEmail; + if (view != null && view.message_body_state == COMPLETED) { + this.mark_read_timer.start(); + } + } + private Geary.EmailFlags? flag_to_flags(Geary.NamedFlag? flag) { Geary.EmailFlags flags = null; if (flag != null) { @@ -1201,13 +1165,18 @@ // be appended last. Finally, don't let rows with active // composers be collapsed. if (row.is_expanded) { - if (row != this.last_row) { + if (get_row_at_index(row.get_index() + 1) != null) { row.collapse(); } } else { - row.expand(); + row.expand.begin(); } } } + private void on_internal_link_activated(ConversationEmail email, int y) { + EmailRow row = get_email_row_by_id(email.email.id); + scroll_to_anchor(row, y); + } + } diff -Nru geary-0.12.4/src/client/conversation-viewer/conversation-message.vala geary-3.32.0/src/client/conversation-viewer/conversation-message.vala --- geary-0.12.4/src/client/conversation-viewer/conversation-message.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/conversation-viewer/conversation-message.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,6 +1,6 @@ /* * Copyright 2016 Software Freedom Conservancy Inc. - * Copyright 2016 Michael Gratton + * Copyright 2016-2019 Michael Gratton * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. @@ -20,19 +20,15 @@ private const string FROM_CLASS = "geary-from"; private const string MATCH_CLASS = "geary-match"; + private const string INTERNAL_ANCHOR_PREFIX = "geary:body#"; private const string REPLACED_CID_TEMPLATE = "replaced_%02u@geary"; private const string REPLACED_IMAGE_CLASS = "geary_replaced_inline_image"; private const int MAX_PREVIEW_BYTES = Geary.Email.MAX_PREVIEW_BYTES; - - internal static inline bool has_distinct_name( - Geary.RFC822.MailboxAddress address) { - return ( - !Geary.String.is_empty(address.name) && - address.name != address.address - ); - } + private const int SHOW_PROGRESS_TIMEOUT_MSEC = 1000; + private const int HIDE_PROGRESS_TIMEOUT_MSEC = 1000; + private const int PULSE_TIMEOUT_MSEC = 250; // Widget used to display sender/recipient email addresses in @@ -50,7 +46,7 @@ public AddressFlowBoxChild(Geary.RFC822.MailboxAddress address, Type type = Type.OTHER) { this.address = address; - this.search_value = address.address.casefold(); + this.search_value = address.to_searchable_string().casefold(); // We use two label instances here when address has // distinct parts so we can dim the secondary part, if @@ -60,28 +56,41 @@ Gtk.Grid address_parts = new Gtk.Grid(); + bool is_spoofed = address.is_spoofed(); + if (is_spoofed) { + Gtk.Image spoof_img = new Gtk.Image.from_icon_name( + "dialog-warning-symbolic", Gtk.IconSize.SMALL_TOOLBAR + ); + this.set_tooltip_text( + _("This email address may have been forged") + ); + address_parts.add(spoof_img); + } + Gtk.Label primary = new Gtk.Label(null); primary.ellipsize = Pango.EllipsizeMode.END; - GtkUtil.set_label_xalign(primary, 0.0f); + primary.set_halign(Gtk.Align.START); primary.get_style_context().add_class(PRIMARY_CLASS); if (type == Type.FROM) { primary.get_style_context().add_class(FROM_CLASS); } address_parts.add(primary); - if (has_distinct_name(address)) { - primary.set_text(address.name); + string display_address = address.to_address_display("", ""); + + // Don't display the name if it looks spoofed, to reduce + // chance of the user of being tricked by malware. + if (address.has_distinct_name() && !is_spoofed) { + primary.set_text(address.to_short_display()); Gtk.Label secondary = new Gtk.Label(null); secondary.ellipsize = Pango.EllipsizeMode.END; - GtkUtil.set_label_xalign(secondary, 0.0f); + secondary.set_halign(Gtk.Align.START); secondary.get_style_context().add_class(Gtk.STYLE_CLASS_DIM_LABEL); - secondary.set_text(address.address); + secondary.set_text(display_address); address_parts.add(secondary); - - this.search_value = address.name.casefold() + this.search_value; } else { - primary.set_text(address.address); + primary.set_text(display_address); } // Update prelight state when mouse-overed. @@ -138,9 +147,6 @@ private const string ACTION_SELECT_ALL = "select_all"; - /** The specific RFC822 message displayed by this view. */ - public Geary.RFC822.Message message { get; private set; } - /** Box containing the preview and full header widgets. */ [GtkChild] internal Gtk.Grid summary; @@ -152,17 +158,19 @@ /** HTML view that displays the message body. */ internal ConversationWebView web_view { get; private set; } + private Geary.RFC822.MailboxAddress? primary_originator; + [GtkChild] private Gtk.Image avatar; [GtkChild] - private Gtk.Revealer preview_revealer; + private Gtk.Revealer compact_revealer; [GtkChild] - private Gtk.Label preview_from; + private Gtk.Label compact_from; [GtkChild] - private Gtk.Label preview_date; + private Gtk.Label compact_date; [GtkChild] - private Gtk.Label preview_body; + private Gtk.Label compact_body; [GtkChild] private Gtk.Revealer header_revealer; @@ -196,7 +204,7 @@ [GtkChild] public Gtk.Grid body_container; [GtkChild] - public Gtk.ProgressBar body_progress; + private Gtk.ProgressBar body_progress; [GtkChild] private Gtk.Popover link_popover; @@ -208,6 +216,8 @@ [GtkChild] private Gtk.InfoBar remote_images_infobar; + private Gtk.Widget? body_placeholder = null; + // The web_view's context menu private Gtk.Menu? context_menu = null; @@ -245,10 +255,16 @@ private Geary.TimeoutManager show_progress_timeout = null; private Geary.TimeoutManager hide_progress_timeout = null; + // Timer for pulsing progress bar + private Geary.TimeoutManager progress_pulse; + /** Fired when the user clicks a link in the email. */ public signal void link_activated(string link); + /** Fired when the user clicks a internal link in the email. */ + public signal void internal_link_activated(int y); + /** Fired when the user requests remote images be loaded. */ public signal void flag_remote_images(); @@ -263,24 +279,72 @@ /** - * Constructs a new view to display an RFC 822 message headers and body. + * Constructs a new view from an email's headers and body. * * This method sets up most of the user interface for displaying * the message, but does not attempt any possibly long-running * loading processes. */ - public ConversationMessage(Geary.RFC822.Message message, - Configuration config, - bool load_remote_images) { + public ConversationMessage.from_email(Geary.Email email, + bool load_remote_images, + Configuration config) { + this( + Util.Email.get_primary_originator(email), + email.from, + email.reply_to, + email.sender, + email.to, + email.cc, + email.bcc, + email.date, + email.subject, + email.preview != null ? email.preview.buffer.get_valid_utf8() : null, + load_remote_images, + config + ); + } + + /** + * Constructs a new view from an RFC 822 message's headers and body. + * + * This method sets up most of the user interface for displaying + * the message, but does not attempt any possibly long-running + * loading processes. + */ + public ConversationMessage.from_message(Geary.RFC822.Message message, + bool load_remote_images, + Configuration config) { + this( + Util.Email.get_primary_originator(message), + message.from, + message.reply_to, + message.sender, + message.to, + message.cc, + message.bcc, + message.date, + message.subject, + message.get_preview(), + load_remote_images, + config + ); + } + + private ConversationMessage(Geary.RFC822.MailboxAddress? primary_originator, + Geary.RFC822.MailboxAddresses? from, + Geary.RFC822.MailboxAddresses? reply_to, + Geary.RFC822.MailboxAddress? sender, + Geary.RFC822.MailboxAddresses? to, + Geary.RFC822.MailboxAddresses? cc, + Geary.RFC822.MailboxAddresses? bcc, + Geary.RFC822.Date? date, + Geary.RFC822.Subject? subject, + string? preview, + bool load_remote_images, + Configuration config) { base_ref(); - this.message = message; this.is_loading_images = load_remote_images; - -#if !GTK_3_20 - // GTK < 3.20+ style workarounds. Keep this in sync with - // geary.css. - this.summary.border_width = 12; -#endif + this.primary_originator = primary_originator; // Actions @@ -295,9 +359,7 @@ this.web_view.get_inspector().show(); }); add_action(ACTION_OPEN_LINK, true, VariantType.STRING) - .activate.connect((param) => { - link_activated(param.get_string()); - }); + .activate.connect(on_link_activated); add_action(ACTION_SAVE_IMAGE, true, new VariantType("(sms)")) .activate.connect(on_save_image); add_action(ACTION_SEARCH_FROM, true, VariantType.STRING) @@ -325,50 +387,56 @@ (MenuModel) builder.get_object("context_menu_inspector"); } - // Preview headers + // Compact headers // Translators: This is displayed in place of the from address // when the message has no from address. string empty_from = _("No sender"); - this.preview_from.set_text(format_originator_preview(empty_from)); - this.preview_from.get_style_context().add_class(FROM_CLASS); + this.compact_from.set_text(format_originator_compact(from, empty_from)); + this.compact_from.get_style_context().add_class(FROM_CLASS); string date_text = ""; string date_tooltip = ""; - if (this.message.date != null) { + if (date != null) { date_text = Date.pretty_print( - this.message.date.value, config.clock_format + date.value, config.clock_format ); date_tooltip = Date.pretty_print_verbose( - this.message.date.value, config.clock_format + date.value, config.clock_format ); } - this.preview_date.set_text(date_text); - this.preview_date.set_tooltip_text(date_tooltip); + this.compact_date.set_text(date_text); + this.compact_date.set_tooltip_text(date_tooltip); - string preview = this.message.get_preview(); - if (preview.length > MAX_PREVIEW_BYTES) { - preview = Geary.String.safe_byte_substring(preview, MAX_PREVIEW_BYTES); - // Add an ellipsis in case the wider is wider than the text - preview += "…"; + if (preview != null) { + string clean_preview = preview; + if (preview.length > MAX_PREVIEW_BYTES) { + clean_preview = Geary.String.safe_byte_substring( + preview, MAX_PREVIEW_BYTES + ); + // Add an ellipsis in case the view is wider is wider than + // the text + clean_preview += "…"; + } + this.compact_body.set_text(clean_preview); } - this.preview_body.set_text(preview); // Full headers - fill_originator_addresses(empty_from); + fill_originator_addresses(from, reply_to, sender, empty_from); this.date.set_text(date_text); this.date.set_tooltip_text(date_tooltip); - if (this.message.subject != null) { - this.subject.set_text(this.message.subject.value); + if (subject != null) { + this.subject.set_text(subject.value); this.subject.set_visible(true); - this.subject_searchable = this.message.subject.value.casefold(); + this.subject_searchable = subject.value.casefold(); } - fill_header_addresses(this.to_header, this.message.to); - fill_header_addresses(this.cc_header, this.message.cc); - fill_header_addresses(this.bcc_header, this.message.bcc); + fill_header_addresses(this.to_header, to); + fill_header_addresses(this.cc_header, cc); + fill_header_addresses(this.bcc_header, bcc); + // Web view @@ -379,13 +447,10 @@ this.web_view.context_menu.connect(on_context_menu); this.web_view.deceptive_link_clicked.connect(on_deceptive_link_clicked); this.web_view.link_activated.connect((link) => { - link_activated(link); + on_link_activated(new GLib.Variant("s", link)); }); - this.web_view.load_changed.connect(on_load_changed); this.web_view.mouse_target_changed.connect(on_mouse_target_changed); - this.web_view.notify["estimated-load-progress"].connect(() => { - this.body_progress.set_fraction(this.web_view.estimated_load_progress); - }); + this.web_view.notify["is-loading"].connect(on_is_loading_notify); this.web_view.resource_load_started.connect(on_resource_load_started); this.web_view.remote_image_load_blocked.connect(() => { this.remote_images_infobar.show(); @@ -397,12 +462,17 @@ this.body_container.set_has_tooltip(true); // Used to show link URLs this.body_container.add(this.web_view); - this.show_progress_timeout = new Geary.TimeoutManager.seconds( - 1, () => { this.body_progress.show(); } + this.show_progress_timeout = new Geary.TimeoutManager.milliseconds( + SHOW_PROGRESS_TIMEOUT_MSEC, this.on_show_progress_timeout ); - this.hide_progress_timeout = new Geary.TimeoutManager.seconds( - 1, () => { this.body_progress.hide(); } + this.hide_progress_timeout = new Geary.TimeoutManager.milliseconds( + HIDE_PROGRESS_TIMEOUT_MSEC, this.on_hide_progress_timeout ); + + this.progress_pulse = new Geary.TimeoutManager.milliseconds( + PULSE_TIMEOUT_MSEC, this.body_progress.pulse + ); + this.progress_pulse.repetition = FOREVER; } ~ConversationMessage() { @@ -412,60 +482,156 @@ public override void destroy() { this.show_progress_timeout.reset(); this.hide_progress_timeout.reset(); + this.progress_pulse.reset(); this.resources.clear(); this.searchable_addresses.clear(); base.destroy(); } /** - * Shows the complete message and hides the preview headers. + * Shows the complete message and hides the compact headers. */ public void show_message_body(bool include_transitions=true) { - set_revealer(this.preview_revealer, false, include_transitions); + set_revealer(this.compact_revealer, false, include_transitions); set_revealer(this.header_revealer, true, include_transitions); set_revealer(this.body_revealer, true, include_transitions); } /** - * Hides the complete message and shows the preview headers. + * Hides the complete message and shows the compact headers. */ public void hide_message_body() { - preview_revealer.set_reveal_child(true); + compact_revealer.set_reveal_child(true); header_revealer.set_reveal_child(false); body_revealer.set_reveal_child(false); } + /** Shows a panel when an email is being loaded. */ + public void show_loading_pane() { + Components.PlaceholderPane pane = new Components.PlaceholderPane(); + pane.icon_name = "content-loading-symbolic"; + pane.title = ""; + pane.subtitle = ""; + + // Don't want to break the announced freeze for 0.13, so just + // hope the icon gets the message across for now and replace + // them with the ones below for 0.14. + + // Translators: Title label for placeholder when multiple + // an error occurs loading a message for display. + //pane.title = _("A problem occurred"); + // Translators: Sub-title label for placeholder when multiple + // an error occurs loading a message for display. + //pane.subtitle = _( + // "This email cannot currently be displayed" + //); + show_placeholder_pane(pane); + start_progress_pulse(); + } + + /** Shows an error panel when email loading failed. */ + public void show_load_error_pane() { + Components.PlaceholderPane pane = new Components.PlaceholderPane(); + pane.icon_name = "network-error-symbolic"; + pane.title = ""; + pane.subtitle = ""; + + // Don't want to break the announced freeze for 0.13, so just + // hope the icon gets the message across for now and replace + // them with the ones below for 0.14. + + // Translators: Title label for placeholder when multiple + // an error occurs loading a message for display. + //pane.title = _("A problem occurred"); + // Translators: Sub-title label for placeholder when multiple + // an error occurs loading a message for display. + //pane.subtitle = _( + // "This email cannot currently be displayed" + //); + show_placeholder_pane(pane); + stop_progress_pulse(); + } + + /** Shows an error panel when offline. */ + public void show_offline_pane() { + show_message_body(true); + Components.PlaceholderPane pane = new Components.PlaceholderPane(); + pane.icon_name = "network-offline-symbolic"; + pane.title = ""; + pane.subtitle = ""; + + // Don't want to break the announced freeze for 0.13, so just + // hope the icon gets the message across for now and replace + // them with the ones below for 0.14. + + // // Translators: Title label for placeholder when loading a + // // message for display but the account is offline. + // pane.title = _("Offline"); + // // Translators: Sub-title label for placeholder when loading a + // // message for display but the account is offline. + // pane.subtitle = _( + // "This email will be downloaded when reconnected to the Internet" + // ); + show_placeholder_pane(pane); + stop_progress_pulse(); + } + + /** Shows and initialises the progress meter. */ + public void start_progress_loading( ) { + this.progress_pulse.reset(); + this.body_progress.fraction = 0.1; + this.show_progress_timeout.start(); + this.hide_progress_timeout.reset(); + } + + /** Hides the progress meter. */ + public void stop_progress_loading( ) { + this.body_progress.fraction = 1.0; + this.show_progress_timeout.reset(); + this.hide_progress_timeout.start(); + } + + /** Shows and starts pulsing the progress meter. */ + public void start_progress_pulse() { + this.body_progress.show(); + this.progress_pulse.start(); + } + + /** Hides and stops pulsing the progress meter. */ + public void stop_progress_pulse() { + this.body_progress.hide(); + this.progress_pulse.reset(); + } + /** * Starts loading the avatar for the message's sender. */ - public async void load_avatar(ConversationListBox.AvatarStore loader, - Cancellable load_cancelled) { - const int PIXEL_SIZE = 32; - Geary.RFC822.MailboxAddress? primary = message.get_primary_originator(); - if (primary != null) { + public async void load_avatar(Application.AvatarStore loader, + GLib.Cancellable load_cancelled) + throws GLib.Error { + if (load_cancelled.is_cancelled()) { + throw new GLib.IOError.CANCELLED("Conversation load cancelled"); + } + + // We occasionally get crashes calling as below + // Gtk.Image.get_pixel_size() when the image is null. There's + // perhaps some race going on there. So we need to hard-code + // the size here and keep it in sync with + // ui/conversation-message.ui. :( + const int PIXEL_SIZE = 48; + if (this.primary_originator != null) { int window_scale = get_scale_factor(); - // We occasionally get crashes calling as below - // Gtk.Image.get_pixel_size() when the image is - // null. There's perhaps some race going on there. So we - // need to hard-code the size and keep it in sync with - // ui/conversation-message.ui. :( - // //int pixel_size = this.avatar.get_pixel_size() * window_scale; int pixel_size = PIXEL_SIZE * window_scale; - try { - Gdk.Pixbuf? avatar_buf = yield loader.load( - primary, pixel_size, load_cancelled + Gdk.Pixbuf? avatar_buf = yield loader.load( + this.primary_originator, pixel_size, load_cancelled + ); + if (avatar_buf != null) { + this.avatar.set_from_surface( + Gdk.cairo_surface_create_from_pixbuf( + avatar_buf, window_scale, get_window() + ) ); - if (avatar_buf != null) { - this.avatar.set_from_surface( - Gdk.cairo_surface_create_from_pixbuf( - avatar_buf, window_scale, get_window() - ) - ); - } - } catch (Error err) { - debug("Avatar load failed for %s: %s", - primary.to_string(), err.message); } } } @@ -473,12 +639,20 @@ /** * Starts loading the message body in the HTML view. */ - public async void load_message_body(Cancellable load_cancelled) { + public async void load_message_body(Geary.RFC822.Message message, + GLib.Cancellable load_cancelled) + throws GLib.Error { + if (load_cancelled.is_cancelled()) { + throw new GLib.IOError.CANCELLED("Conversation load cancelled"); + } + + show_placeholder_pane(null); + string? body_text = null; try { - body_text = (this.message.has_html_body()) - ? this.message.get_html_body(inline_image_replacer) - : this.message.get_plain_body(true, inline_image_replacer); + body_text = (message.has_html_body()) + ? message.get_html_body(inline_image_replacer) + : message.get_plain_body(true, inline_image_replacer); } catch (Error err) { debug("Could not get message text. %s", err.message); } @@ -493,10 +667,9 @@ * Highlighting includes both in the message headers, and the * mesage body. returns the number of matching search terms. */ - public async uint highlight_search_terms(Gee.Set search_matches) { - // Remove existing highlights - this.web_view.get_find_controller().search_finish(); - + public async uint highlight_search_terms(Gee.Set search_matches, + GLib.Cancellable cancellable) + throws GLib.IOError.CANCELLED { uint headers_found = 0; uint webkit_found = 0; foreach(string raw_match in search_matches) { @@ -516,7 +689,9 @@ } } - webkit_found += yield this.web_view.highlight_search_terms(search_matches); + webkit_found += yield this.web_view.highlight_search_terms( + search_matches, cancellable + ); return headers_found + webkit_found; } @@ -538,7 +713,8 @@ } private void set_action_enabled(string name, bool enabled) { - SimpleAction? action = this.message_actions.lookup(name) as SimpleAction; + SimpleAction? action = + this.message_actions.lookup_action(name) as SimpleAction; if (action != null) { action.set_enabled(enabled); } @@ -575,18 +751,18 @@ return menu; } - private string format_originator_preview(string empty_from_text) { + private string format_originator_compact(Geary.RFC822.MailboxAddresses? from, + string empty_from_text) { string text = ""; - if (this.message.from != null && this.message.from.size > 0) { + if (from != null && from.size > 0) { int i = 0; - Gee.List list = - this.message.from.get_all(); + Gee.List list = from.get_all(); foreach (Geary.RFC822.MailboxAddress addr in list) { - text += has_distinct_name(addr) ? addr.name : addr.address; + text += addr.to_short_display(); if (++i < list.size) // Translators: This separates multiple 'from' - // addresses in the header preview for a message. + // addresses in the compact header for a message. text += _(", "); } } else { @@ -596,10 +772,13 @@ return text; } - private void fill_originator_addresses(string empty_from_text) { + private void fill_originator_addresses(Geary.RFC822.MailboxAddresses? from, + Geary.RFC822.MailboxAddresses? reply_to, + Geary.RFC822.MailboxAddress? sender, + string empty_from_text) { // Show any From header addresses - if (this.message.from != null && this.message.from.size > 0) { - foreach (Geary.RFC822.MailboxAddress address in this.message.from) { + if (from != null && from.size > 0) { + foreach (Geary.RFC822.MailboxAddress address in from) { AddressFlowBoxChild child = new AddressFlowBoxChild( address, AddressFlowBoxChild.Type.FROM ); @@ -619,10 +798,9 @@ // Show the Sender header addresses if present, but only if // not already in the From header. - if (this.message.sender != null && - (this.message.from == null || - !this.message.from.contains_normalized(this.message.sender.address))) { - AddressFlowBoxChild child = new AddressFlowBoxChild(this.message.sender); + if (sender != null && + (from == null || !from.contains_normalized(sender.address))) { + AddressFlowBoxChild child = new AddressFlowBoxChild(sender); this.searchable_addresses.add(child); this.sender_header.show(); this.sender_address.add(child); @@ -630,10 +808,9 @@ // Show any Reply-To header addresses if present, but only if // each is not already in the From header. - if (this.message.reply_to != null) { - foreach (Geary.RFC822.MailboxAddress address in this.message.reply_to) { - if (this.message.from == null || - !this.message.from.contains_normalized(address.address)) { + if (reply_to != null) { + foreach (Geary.RFC822.MailboxAddress address in reply_to) { + if (from == null || !from.contains_normalized(address.address)) { AddressFlowBoxChild child = new AddressFlowBoxChild(address); this.searchable_addresses.add(child); this.reply_to_addresses.add(child); @@ -658,37 +835,40 @@ } } - // This delegate is called from within Geary.RFC822.Message.get_body while assembling the plain - // or HTML document when a non-text MIME part is encountered within a multipart/mixed container. - // If this returns null, the MIME part is dropped from the final returned document; otherwise, - // this returns HTML that is placed into the document in the position where the MIME part was - // found - private string? inline_image_replacer(string? filename, Geary.Mime.ContentType? content_type, - Geary.Mime.ContentDisposition? disposition, string? content_id, Geary.Memory.Buffer buffer) { - if (content_type == null) { - debug("Not displaying inline: no Content-Type"); - return null; - } - + // This delegate is called from within + // Geary.RFC822.Message.get_body while assembling the plain or + // HTML document when a non-text MIME part is encountered within a + // multipart/mixed container. If this returns null, the MIME part + // is dropped from the final returned document; otherwise, this + // returns HTML that is placed into the document in the position + // where the MIME part was found + private string? inline_image_replacer(Geary.RFC822.Part part) { + Geary.Mime.ContentType content_type = part.get_effective_content_type(); if (content_type.media_type != "image" || !this.web_view.can_show_mime_type(content_type.to_string())) { - debug("Not displaying %s inline: unsupported Content-Type", content_type.to_string()); + debug("Not displaying %s inline: unsupported Content-Type", + content_type.to_string()); return null; } - string id = content_id; + string? id = part.content_id; if (id == null) { id = REPLACED_CID_TEMPLATE.printf(this.next_replaced_buffer_number++); } - this.web_view.add_internal_resource(id, buffer); + try { + this.web_view.add_internal_resource(id, part.write_to_buffer()); + } catch (Geary.RFC822Error err) { + debug("Failed to get inline buffer: %s", err.message); + return null; + } // Translators: This string is used as the HTML IMG ALT // attribute value when displaying an inline image in an email // that did not specify a file name. E.g. Image".printf( @@ -700,13 +880,33 @@ } private void show_images(bool remember) { + start_progress_loading(); this.is_loading_images = true; + this.remote_resources_requested = 0; + this.remote_resources_loaded = 0; this.web_view.load_remote_images(); if (remember) { flag_remote_images(); } } + private void show_placeholder_pane(Gtk.Widget? placeholder) { + if (this.body_placeholder != null) { + this.body_placeholder.hide(); + this.body_container.remove(this.body_placeholder); + this.body_placeholder = null; + } + + if (placeholder != null) { + this.body_placeholder = placeholder; + this.web_view.hide(); + this.body_container.add(placeholder); + show_message_body(true); + } else { + this.web_view.show(); + } + } + private inline void set_revealer(Gtk.Revealer revealer, bool expand, bool use_transition) { @@ -718,13 +918,23 @@ revealer.set_transition_type(transition); } - private void on_load_changed(WebKit.LoadEvent load_event) { - if (load_event != WebKit.LoadEvent.FINISHED) { - this.hide_progress_timeout.reset(); - this.body_progress.pulse(); + private void on_show_progress_timeout() { + if (this.body_progress.fraction < 0.99) { + this.progress_pulse.reset(); + this.body_progress.show(); + } + } + + private void on_hide_progress_timeout() { + this.progress_pulse.reset(); + this.body_progress.hide(); + } + + private void on_is_loading_notify() { + if (this.web_view.is_loading) { + start_progress_loading(); } else { - this.show_progress_timeout.reset(); - this.hide_progress_timeout.start(); + stop_progress_loading(); } } @@ -736,33 +946,20 @@ // We only want to show the body loading progress meter if we // are actually loading some images, so do it here rather than - // in on_load_changed. - if (this.is_loading_images && - !res.get_uri().has_prefix(ClientWebView.INTERNAL_URL_PREFIX)) { - this.show_progress_timeout.start(); - this.body_progress.pulse(); - if (!this.web_view.is_loading) { - // The initial page load has finished, so we must be - // loading a remote image afterwards at the user's - // request. We can't rely on the load_changed signal - // to stop the timer or estimated-load-progress - // changing, so manually manage it here. - this.remote_resources_requested++; - res.finished.connect(() => { - this.remote_resources_loaded++; - this.body_progress.set_fraction( - (this.remote_resources_loaded / - this.remote_resources_requested) + - this.body_progress.get_pulse_step() - ); - if (this.remote_resources_loaded >= - this.remote_resources_requested) { - this.show_progress_timeout.start(); - this.hide_progress_timeout.start(); - } - }); - } - } + // in on_is_loading_notify. + this.remote_resources_requested++; + res.finished.connect(() => { + this.remote_resources_loaded++; + this.body_progress.fraction = ( + (float) this.remote_resources_loaded / + (float) this.remote_resources_requested + ); + + if (this.remote_resources_loaded == + this.remote_resources_requested) { + stop_progress_loading(); + } + }); } [GtkCallback] @@ -776,7 +973,7 @@ Gee.Map values = new Gee.HashMap(); values[ACTION_OPEN_LINK] = Geary.ComposedEmail.MAILTO_SCHEME + address.address; - values[ACTION_COPY_EMAIL] = address.get_full_address(); + values[ACTION_COPY_EMAIL] = address.to_full_display(); values[ACTION_SEARCH_FROM] = address.address; Menu model = new Menu(); @@ -826,7 +1023,7 @@ if (hit_test.context_is_image()) { string uri = hit_test.get_image_uri(); - set_action_enabled(ACTION_SAVE_IMAGE, uri in this.resources); + set_action_enabled(ACTION_SAVE_IMAGE, this.resources.has_key(uri)); model.append_section( null, set_action_param_value( @@ -847,7 +1044,7 @@ this.context_menu = new Gtk.Menu.from_model(model); this.context_menu.attach_to_widget(this, null); - this.context_menu.popup(null, null, null, 0, event.get_time()); + this.context_menu.popup_at_pointer(event); return true; } @@ -962,4 +1159,28 @@ }); } + private void on_link_activated(GLib.Variant? param) { + string link = param.get_string(); + + if (link.has_prefix(INTERNAL_ANCHOR_PREFIX)) { + long start = INTERNAL_ANCHOR_PREFIX.length; + long end = link.length; + string anchor_body = link.substring(start, end - start); + this.web_view.get_anchor_target_y.begin(anchor_body, (obj, res) => { + try { + int y = this.web_view.get_anchor_target_y.end(res); + if (y > 0) { + internal_link_activated(y); + } else { + debug("Failed to get anchor destination"); + } + } catch (GLib.Error err) { + debug("Failed to get anchor destination"); + } + }); + } else { + link_activated(link); + } + } + } diff -Nru geary-0.12.4/src/client/conversation-viewer/conversation-viewer.vala geary-3.32.0/src/client/conversation-viewer/conversation-viewer.vala --- geary-0.12.4/src/client/conversation-viewer/conversation-viewer.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/conversation-viewer/conversation-viewer.vala 2019-03-17 13:39:29.000000000 +0000 @@ -26,6 +26,8 @@ get { return (get_visible_child() == this.composer_page); } } + private Configuration config; + // Stack pages [GtkChild] private Gtk.Spinner loading_page; @@ -67,32 +69,57 @@ /** * Constructs a new conversation view instance. */ - public ConversationViewer() { + public ConversationViewer(Configuration config) { base_ref(); + this.config = config; - EmptyPlaceholder no_conversations = new EmptyPlaceholder(); + Components.PlaceholderPane no_conversations = + new Components.PlaceholderPane(); + no_conversations.icon_name = "folder-symbolic"; + // Translators: Title label for placeholder when no + // conversations have been selected. no_conversations.title = _("No conversations selected"); + // Translators: Sub-title label for placeholder when no + // conversations have been selected. no_conversations.subtitle = _( "Selecting a conversation from the list will display it here" ); this.no_conversations_page.add(no_conversations); - EmptyPlaceholder multi_conversations = new EmptyPlaceholder(); + Components.PlaceholderPane multi_conversations = + new Components.PlaceholderPane(); + multi_conversations.icon_name = "folder-symbolic"; + // Translators: Title label for placeholder when multiple + // conversations have been selected. multi_conversations.title = _("Multiple conversations selected"); + // Translators: Sub-title label for placeholder when multiple + // conversations have been selected. multi_conversations.subtitle = _( "Choosing an action will apply to all selected conversations" ); this.multiple_conversations_page.add(multi_conversations); - EmptyPlaceholder empty_folder = new EmptyPlaceholder(); + Components.PlaceholderPane empty_folder = + new Components.PlaceholderPane(); + empty_folder.icon_name = "folder-symbolic"; + // Translators: Title label for placeholder when no + // conversations have exist in a folder. empty_folder.title = _("No conversations found"); + // Translators: Sub-title label for placeholder when no + // conversations have exist in a folder. empty_folder.subtitle = _( "This folder does not contain any conversations" ); this.empty_folder_page.add(empty_folder); - EmptyPlaceholder empty_search = new EmptyPlaceholder(); + Components.PlaceholderPane empty_search = + new Components.PlaceholderPane(); + empty_search.icon_name = "folder-symbolic"; + // Translators: Title label for placeholder when no + // conversations have been found in a search. empty_search.title = _("No conversations found"); + // Translators: Sub-title label for placeholder when no + // conversations have been found in a search. empty_search.subtitle = _( "Your search returned no results, try refining your search terms" ); @@ -193,26 +220,26 @@ set_visible_child(this.empty_search_page); } + /** Shows and focuses the find entry. */ + public void enable_find() { + this.conversation_find_bar.set_search_mode(true); + this.conversation_find_entry.grab_focus(); + } + /** * Shows a conversation in the viewer. */ public async void load_conversation(Geary.App.Conversation conversation, - Geary.Folder location, - Configuration config, - Soup.Session avatar_session) - throws Error { + Geary.App.EmailStore email_store, + Application.AvatarStore avatar_store) + throws GLib.Error { remove_current_list(); - Geary.Account account = location.account; ConversationListBox new_list = new ConversationListBox( conversation, - location, - new Geary.App.EmailStore(account), - account.get_contact_store(), - account.information, - location.special_folder_type == Geary.SpecialFolderType.DRAFTS, - config, - avatar_session, + email_store, + avatar_store, + this.config, this.conversation_scroller.get_vadjustment() ); @@ -225,7 +252,7 @@ // are expanded and highlighted as they are added. this.conversation_find_next.set_sensitive(false); this.conversation_find_prev.set_sensitive(false); - new_list.search_matches_updated.connect((count) => { + new_list.search.matches_updated.connect((count) => { bool found = count > 0; this.conversation_find_entry.set_icon_from_icon_name( Gtk.EntryIconPosition.PRIMARY, @@ -235,21 +262,23 @@ this.conversation_find_next.set_sensitive(found); this.conversation_find_prev.set_sensitive(found); }); - Gee.Set? find_terms = get_find_search_terms(); - if (find_terms != null) { - new_list.highlight_search_terms(find_terms); - } - add_new_list(new_list); set_visible_child(this.conversation_page); - yield new_list.load_conversation(); - - // Highlight matching terms from the search if it exists, but - // don't clobber any find terms. - if (find_terms == null && location is Geary.SearchFolder) { - yield new_list.load_search_terms(); + // Highlight matching terms from find if active, otherwise + // from the search folder if that's where we are at + Geary.SearchQuery? query = get_find_search_query( + conversation.base_folder.account + ); + if (query == null) { + Geary.SearchFolder? search_folder = + conversation.base_folder as Geary.SearchFolder; + if (search_folder != null) { + query = search_folder.search_query; + } } + + yield new_list.load_conversation(query); } // Add a new conversation list to the UI @@ -270,15 +299,15 @@ // Remove any existing conversation list, cancelling its loading private void remove_current_list() { - // XXX GTK+ Bug 778190 workaround - this.conversation_scroller.destroy(); - new_conversation_scroller(); - - // Notify that the current list was removed if (this.current_list != null) { + this.current_list.cancel_conversation_load(); this.conversation_removed(this.current_list); this.current_list = null; } + + // XXX GTK+ Bug 778190 workaround + this.conversation_scroller.destroy(); // removes the list + new_conversation_scroller(); } private void new_conversation_scroller() { @@ -293,8 +322,15 @@ scroller.set_hexpand(true); scroller.set_vexpand(true); scroller.show(); + scroller.scroll_event.connect( + on_conversation_scroll + ); + scroller.get_vscrollbar().button_release_event.connect( + on_conversation_scroll + ); this.conversation_scroller = scroller; this.conversation_page.add(scroller); + } /** @@ -319,21 +355,27 @@ base.set_visible_child(widget); } - private Gee.Set? get_find_search_terms() { - Gee.Set? terms = null; - string search = this.conversation_find_entry.get_text().strip(); - if (search.length > 0) { - terms = new Gee.HashSet(); - terms.add(search); + private Geary.SearchQuery? get_find_search_query(Geary.Account account) { + Geary.SearchQuery? query = null; + if (this.conversation_find_bar.get_search_mode()) { + string text = this.conversation_find_entry.get_text().strip(); + // Require find string of at least two chars to avoid + // opening every message in the conversation as soon as + // the user presses a key + if (text.length >= 2) { + query = account.open_search( + text, this.config.get_search_strategy() + ); + } } - return terms; + return query; } [GtkCallback] private void on_find_mode_changed(Object obj, ParamSpec param) { if (this.current_list != null) { if (this.conversation_find_bar.get_search_mode()) { - // Find was enabled + // Find became enabled ConversationEmail? email_view = this.current_list.get_selection_view(); if (email_view != null) { @@ -346,12 +388,19 @@ }); } } else { - // Find was disabled - this.current_list.unmark_search_terms(); - if (!(this.current_list.location is Geary.SearchFolder)) { - //this.current_list.update_collapsed_state(); - } else { - this.current_list.load_search_terms.begin(); + // Find became disabled, re-show search terms if any + this.current_list.search.unmark_terms(); + Geary.SearchFolder? search_folder = ( + this.current_list.conversation.base_folder + as Geary.SearchFolder + ); + if (search_folder != null) { + Geary.SearchQuery? search_query = search_folder.search_query; + if (search_query != null) { + this.current_list.search.highlight_matching_email.begin( + search_query + ); + } } } } @@ -362,11 +411,11 @@ this.conversation_find_next.set_sensitive(false); this.conversation_find_prev.set_sensitive(false); if (this.current_list != null) { - Gee.Set? terms = get_find_search_terms(); - if (terms != null) { - // Have a search string - this.current_list.highlight_search_terms(terms); - // XXX scroll to first match + Geary.SearchQuery? query = get_find_search_query( + this.current_list.conversation.base_folder.account + ); + if (query != null) { + this.current_list.search.highlight_matching_email.begin(query); } } } @@ -385,5 +434,11 @@ } } -} + private bool on_conversation_scroll() { + if (this.current_list != null) { + this.current_list.mark_visible_read(); + } + return Gdk.EVENT_PROPAGATE; + } +} diff -Nru geary-0.12.4/src/client/conversation-viewer/conversation-web-view.vala geary-3.32.0/src/client/conversation-viewer/conversation-web-view.vala --- geary-0.12.4/src/client/conversation-viewer/conversation-web-view.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/conversation-viewer/conversation-web-view.vala 2019-03-17 13:39:29.000000000 +0000 @@ -8,7 +8,6 @@ public class ConversationWebView : ClientWebView { - private const string USER_CSS = "user-message.css"; private const string DECEPTIVE_LINK_CLICKED = "deceptiveLinkClicked"; @@ -37,11 +36,10 @@ DECEPTIVE_DOMAIN = 2; } - private static WebKit.UserStyleSheet? user_stylesheet = null; private static WebKit.UserStyleSheet? app_stylesheet = null; private static WebKit.UserScript? app_script = null; - public static void load_resources(File user_dir) + public static new void load_resources() throws Error { ConversationWebView.app_script = ClientWebView.load_app_script( "conversation-web-view.js" @@ -49,9 +47,6 @@ ConversationWebView.app_stylesheet = ClientWebView.load_app_stylesheet( "conversation-web-view.css" ); - ConversationWebView.user_stylesheet = ClientWebView.load_user_stylesheet( - user_dir.get_child("user-message.css") - ); } @@ -65,13 +60,12 @@ base(config); this.user_content_manager.add_script(ConversationWebView.app_script); this.user_content_manager.add_style_sheet(ConversationWebView.app_stylesheet); - if (ConversationWebView.user_stylesheet != null) { - this.user_content_manager.add_style_sheet(ConversationWebView.user_stylesheet); - } register_message_handler( DECEPTIVE_LINK_CLICKED, on_deceptive_link_clicked ); + + this.notify["preferred-height"].connect(() => queue_resize()); } /** @@ -95,11 +89,30 @@ } /** + * Returns the y value for a element, by its id + */ + public async int? get_anchor_target_y(string anchor_body) + throws GLib.Error { + WebKit.JavascriptResult result = yield call( + Geary.JS.callable("geary.getAnchorTargetY") + .string(anchor_body), null + ); + return (int) WebKitUtil.to_number(result); + } + + /** * Highlights user search terms in the message view. * * Returns the number of matching search terms. */ - public async uint highlight_search_terms(Gee.Collection search_matches) { + public async uint highlight_search_terms(Gee.Collection terms, + GLib.Cancellable cancellable) + throws GLib.IOError.CANCELLED { + WebKit.FindController controller = get_find_controller(); + + // Remove existing highlights + controller.search_finish(); + // XXX WK2 doesn't deal with the multiple highlighting // required by search folder matches, only single highlighting // for a fine-like interface. For now, just highlight the @@ -107,25 +120,20 @@ uint found = 0; - WebKit.FindController controller = get_find_controller(); SourceFunc callback = this.highlight_search_terms.callback; - ulong found_handler = 0; - ulong not_found_handler = 0; - - found_handler = controller.found_text.connect((count) => { + ulong found_handler = controller.found_text.connect((count) => { found = count; - controller.disconnect(found_handler); - controller.disconnect(not_found_handler); callback(); }); - not_found_handler = controller.failed_to_find_text.connect(() => { - controller.disconnect(found_handler); - controller.disconnect(not_found_handler); + ulong not_found_handler = controller.failed_to_find_text.connect(() => { + callback(); + }); + ulong cancelled_handler = cancellable.cancelled.connect(() => { callback(); }); controller.search( - Geary.Collection.get_first(search_matches), + Geary.Collection.get_first(terms), WebKit.FindOptions.CASE_INSENSITIVE | WebKit.FindOptions.WRAP_AROUND, 128 @@ -133,6 +141,16 @@ yield; + controller.disconnect(found_handler); + controller.disconnect(not_found_handler); + cancellable.disconnect(cancelled_handler); + + if (cancellable.is_cancelled()) { + throw new IOError.CANCELLED( + "ConversationWebView highlight search terms cancelled" + ); + } + return found; } @@ -155,9 +173,6 @@ } - // XXX Surely since we are doing height-for-width, we should be - // overriding get_preferred_height_for_width here, but that - // doesn't seem to work. public override void get_preferred_height(out int minimum_height, out int natural_height) { // XXX clamp height to something not too outrageous so we @@ -183,7 +198,7 @@ private void on_deceptive_link_clicked(WebKit.JavascriptResult result) { try { - JS.GlobalContext context = result.get_global_context(); + unowned JS.GlobalContext context = result.get_global_context(); JS.Object details = WebKitUtil.to_object(result); uint reason = (uint) Geary.JS.to_number( @@ -202,7 +217,7 @@ context, Geary.JS.get_property(context, details, "location")); - Gdk.Rectangle location = new Gdk.Rectangle(); + Gdk.Rectangle location = Gdk.Rectangle(); location.x = (int) Geary.JS.to_number( context, Geary.JS.get_property(context, js_location, "x")); diff -Nru geary-0.12.4/src/client/dialogs/alert-dialog.vala geary-3.32.0/src/client/dialogs/alert-dialog.vala --- geary-0.12.4/src/client/dialogs/alert-dialog.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/dialogs/alert-dialog.vala 2019-03-17 13:39:29.000000000 +0000 @@ -6,22 +6,22 @@ class AlertDialog : Object { private Gtk.MessageDialog dialog; - + public AlertDialog(Gtk.Window? parent, Gtk.MessageType message_type, string title, string? description, string? ok_button, string? cancel_button, string? tertiary_button, Gtk.ResponseType tertiary_response_type, string? ok_action_type) { dialog = new Gtk.MessageDialog(parent, Gtk.DialogFlags.DESTROY_WITH_PARENT, message_type, Gtk.ButtonsType.NONE, ""); - + dialog.text = title; dialog.secondary_text = description; - + if (!Geary.String.is_empty_or_whitespace(tertiary_button)) dialog.add_button(tertiary_button, tertiary_response_type); - + if (!Geary.String.is_empty_or_whitespace(cancel_button)) dialog.add_button(cancel_button, Gtk.ResponseType.CANCEL); - + if (!Geary.String.is_empty_or_whitespace(ok_button)) { Gtk.Widget? button = dialog.add_button(ok_button, Gtk.ResponseType.OK); if (!Geary.String.is_empty_or_whitespace(ok_action_type)) { @@ -29,11 +29,11 @@ } } } - + public void use_secondary_markup(bool markup) { dialog.secondary_use_markup = markup; } - + public Gtk.Box get_message_area() { return (Gtk.Box) dialog.get_message_area(); } @@ -43,19 +43,19 @@ if (to_focus != null) to_focus.grab_focus(); } - + // Runs dialog, destroys it, and returns selected response public Gtk.ResponseType run() { Gtk.ResponseType response = (Gtk.ResponseType) dialog.run(); - + dialog.destroy(); - + return response; } } class ConfirmationDialog : AlertDialog { - public ConfirmationDialog(Gtk.Window? parent, string title, string? description, + public ConfirmationDialog(Gtk.Window? parent, string title, string? description, string? ok_button, string? ok_action_type = "") { base (parent, Gtk.MessageType.WARNING, title, description, ok_button, Stock._CANCEL, null, Gtk.ResponseType.NONE, ok_action_type); @@ -79,35 +79,35 @@ class QuestionDialog : AlertDialog { public bool is_checked { get; private set; default = false; } - + private Gtk.CheckButton? checkbutton = null; - - public QuestionDialog(Gtk.Window? parent, string title, string? description, + + public QuestionDialog(Gtk.Window? parent, string title, string? description, string yes_button, string no_button) { base (parent, Gtk.MessageType.QUESTION, title, description, yes_button, no_button, null, Gtk.ResponseType.NONE, "suggested-action"); } - + public QuestionDialog.with_checkbox(Gtk.Window? parent, string title, string? description, string yes_button, string no_button, string checkbox_label, bool checkbox_default) { this (parent, title, description, yes_button, no_button); - + checkbutton = new Gtk.CheckButton.with_mnemonic(checkbox_label); checkbutton.active = checkbox_default; checkbutton.halign = Gtk.Align.END; checkbutton.toggled.connect(on_checkbox_toggled); - + get_message_area().pack_start(checkbutton); - + // this must be done once all the packing is completed get_message_area().show_all(); // the check box may have grabbed keyboard focus, so we put it back to the button set_focus_response(Gtk.ResponseType.OK); - + is_checked = checkbox_default; } - + private void on_checkbox_toggled() { is_checked = checkbutton.active; } diff -Nru geary-0.12.4/src/client/dialogs/attachment-dialog.vala geary-3.32.0/src/client/dialogs/attachment-dialog.vala --- geary-0.12.4/src/client/dialogs/attachment-dialog.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/dialogs/attachment-dialog.vala 2019-03-17 13:39:29.000000000 +0000 @@ -4,6 +4,9 @@ * (version 2.1 or later). See the COPYING file in this distribution. */ +/** + * A FileChooser-like object for choosing attachments for a message. + */ public class AttachmentDialog : Object { private const int PREVIEW_SIZE = 180; @@ -11,11 +14,7 @@ private Configuration config; -#if GTK_3_20 private Gtk.FileChooserNative? chooser = null; -#else - private Gtk.FileChooserDialog? chooser = null; -#endif private Gtk.Image preview_image = new Gtk.Image(); @@ -23,17 +22,8 @@ public AttachmentDialog(Gtk.Window? parent, Configuration config) { this.config = config; - -#if GTK_3_20 this.chooser = new Gtk.FileChooserNative(_("Choose a file"), parent, Gtk.FileChooserAction.OPEN, _("_Attach"), Stock._CANCEL); -#else - this.chooser = new Gtk.FileChooserDialog(_("Choose a file"), parent, Gtk.FileChooserAction.OPEN, Stock._CANCEL, Gtk.ResponseType.CANCEL, _("_Attach"), Gtk.ResponseType.ACCEPT); -#endif - - string? dir = config.attachments_dir; - if (!Geary.String.is_empty(dir)) { - this.chooser.set_current_folder(dir); - } + this.chooser.set_local_only(false); this.chooser.set_select_multiple(true); @@ -45,10 +35,6 @@ this.chooser.update_preview.connect(on_update_preview); } - // XXX Once we depend on GTK+ 3.20 as a minimum, convert this - // class to a subclass of FileChooserNative and remove these API - // compat classes. - public void add_filter(owned Gtk.FileFilter filter) { this.chooser.add_filter(filter); } @@ -58,16 +44,7 @@ } public int run() { - int response = this.chooser.run(); - if (response == Gtk.ResponseType.ACCEPT) { - // Current folder can be null, e.g. if selecting an - // attachment from Recent Files - string? current_folder = this.chooser.get_current_folder(); - if (!Geary.String.is_empty(current_folder)) { - this.config.attachments_dir = current_folder; - } - } - return response; + return this.chooser.run(); } public void hide() { @@ -84,17 +61,17 @@ chooser.set_preview_widget_active(false); return; } - + // read the image format data first int width = 0; int height = 0; Gdk.PixbufFormat? format = Gdk.Pixbuf.get_file_info(filename, out width, out height); - + if (format == null) { chooser.set_preview_widget_active(false); return; } - + // if the image is too big, resize it Gdk.Pixbuf pixbuf; try { @@ -103,23 +80,23 @@ chooser.set_preview_widget_active(false); return; } - + if (pixbuf == null) { chooser.set_preview_widget_active(false); return; } - + pixbuf = pixbuf.apply_embedded_orientation(); - + // distribute the extra space around the image int extra_space = PREVIEW_SIZE - pixbuf.width; int smaller_half = extra_space/2; int larger_half = extra_space - smaller_half; - + // pad the image manually (avoids rounding errors) preview_image.set_margin_start(PREVIEW_PADDING + smaller_half); preview_image.set_margin_end(PREVIEW_PADDING + larger_half); - + // show the preview preview_image.set_from_pixbuf(pixbuf); chooser.set_preview_widget_active(true); diff -Nru geary-0.12.4/src/client/dialogs/certificate-warning-dialog.vala geary-3.32.0/src/client/dialogs/certificate-warning-dialog.vala --- geary-0.12.4/src/client/dialogs/certificate-warning-dialog.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/dialogs/certificate-warning-dialog.vala 2019-03-17 13:39:29.000000000 +0000 @@ -10,41 +10,45 @@ TRUST, ALWAYS_TRUST } - + private const string BULLET = "• "; - + private Gtk.Dialog dialog; - - public CertificateWarningDialog(Gtk.Window? parent, Geary.AccountInformation account_information, - Geary.Service service, TlsCertificateFlags warnings, bool is_validation) { - Gtk.Builder builder = GearyApplication.instance.create_builder("certificate_warning_dialog.glade"); - + + public CertificateWarningDialog(Gtk.Window? parent, + Geary.AccountInformation account, + Geary.ServiceInformation service, + Geary.Endpoint endpoint, + bool is_validation) { + Gtk.Builder builder = GioUtil.create_builder("certificate_warning_dialog.glade"); + dialog = (Gtk.Dialog) builder.get_object("CertificateWarningDialog"); dialog.transient_for = parent; dialog.modal = true; - + Gtk.Label title_label = (Gtk.Label) builder.get_object("untrusted_connection_label"); Gtk.Label top_label = (Gtk.Label) builder.get_object("top_label"); Gtk.Label warnings_label = (Gtk.Label) builder.get_object("warnings_label"); Gtk.Label trust_label = (Gtk.Label) builder.get_object("trust_label"); Gtk.Label dont_trust_label = (Gtk.Label) builder.get_object("dont_trust_label"); Gtk.Label contact_label = (Gtk.Label) builder.get_object("contact_label"); - - title_label.label = _("Untrusted Connection: %s").printf(account_information.display_name); - - Geary.Endpoint endpoint = account_information.get_endpoint_for_service(service); + + title_label.label = _("Untrusted Connection: %s").printf(account.display_name); + top_label.label = _("The identity of the %s mail server at %s:%u could not be verified.").printf( - service.user_label(), endpoint.remote_address.hostname, endpoint.remote_address.port); - - warnings_label.label = generate_warning_list(warnings); + service.protocol.to_value(), service.host, service.port); + + warnings_label.label = generate_warning_list( + endpoint.tls_validation_warnings + ); warnings_label.use_markup = true; - + trust_label.label = "" +_("Selecting “Trust This Server” or “Always Trust This Server” may cause your username and password to be transmitted insecurely.") + ""; trust_label.use_markup = true; - + if (is_validation) { // could be a new or existing account dont_trust_label.label = @@ -57,55 +61,54 @@ dont_trust_label.label = "" + _("Selecting “Don’t Trust This Server” will cause Geary to stop accessing this account.") - + " " - + _("Geary will exit if you have no other open email accounts."); + + " "; } dont_trust_label.use_markup = true; - + contact_label.label = _("Contact your system administrator or email service provider if you have any question about these issues."); } - + private static string generate_warning_list(TlsCertificateFlags warnings) { StringBuilder builder = new StringBuilder(); - + if ((warnings & TlsCertificateFlags.UNKNOWN_CA) != 0) builder.append(BULLET + _("The server’s certificate is not signed by a known authority") + "\n"); - + if ((warnings & TlsCertificateFlags.BAD_IDENTITY) != 0) builder.append(BULLET + _("The server’s identity does not match the identity in the certificate") + "\n"); - + if ((warnings & TlsCertificateFlags.EXPIRED) != 0) builder.append(BULLET + _("The server’s certificate has expired") + "\n"); - + if ((warnings & TlsCertificateFlags.NOT_ACTIVATED) != 0) builder.append(BULLET + _("The server’s certificate has not been activated") + "\n"); - + if ((warnings & TlsCertificateFlags.REVOKED) != 0) builder.append(BULLET + _("The server’s certificate has been revoked and is now invalid") + "\n"); - + if ((warnings & TlsCertificateFlags.INSECURE) != 0) builder.append(BULLET + _("The server’s certificate is considered insecure") + "\n"); - + if ((warnings & TlsCertificateFlags.GENERIC_ERROR) != 0) builder.append(BULLET + _("An error has occurred processing the server’s certificate") + "\n"); - + return builder.str; } - + public Result run() { dialog.show_all(); int response = dialog.run(); dialog.destroy(); - + // these values are defined in the Glade file switch (response) { case 1: return Result.TRUST; - + case 2: return Result.ALWAYS_TRUST; - + default: return Result.DONT_TRUST; } diff -Nru geary-0.12.4/src/client/dialogs/dialogs-problem-details-dialog.vala geary-3.32.0/src/client/dialogs/dialogs-problem-details-dialog.vala --- geary-0.12.4/src/client/dialogs/dialogs-problem-details-dialog.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/src/client/dialogs/dialogs-problem-details-dialog.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,108 @@ +/* + * Copyright 2019 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +/** + * Displays technical details when a problem has been reported. + */ +[GtkTemplate (ui = "/org/gnome/Geary/problem-details-dialog.ui")] +public class Dialogs.ProblemDetailsDialog : Gtk.Dialog { + + + private Geary.ErrorContext error; + private Geary.AccountInformation? account; + private Geary.ServiceInformation? service; + + [GtkChild] + private Gtk.TextView detail_text; + + + public ProblemDetailsDialog(Gtk.Window parent, + Geary.ErrorContext error, + Geary.AccountInformation? account, + Geary.ServiceInformation? service) { + Object(use_header_bar: 1); + set_default_size(600, -1); + + this.error = error; + this.account = account; + this.service = service; + + this.detail_text.buffer.text = format_details(); + } + + public ProblemDetailsDialog.for_problem_report(Gtk.Window parent, + Geary.ProblemReport report) { + Geary.ServiceProblemReport? service_report = + report as Geary.ServiceProblemReport; + Geary.AccountProblemReport? account_report = + report as Geary.AccountProblemReport; + this( + parent, + report.error, + account_report != null ? account_report.account : null, + service_report != null ? service_report.service : null + ); + } + + private string format_details() { + StringBuilder details = new StringBuilder(); + details.append_printf( + "Geary version: %s\n", + GearyApplication.VERSION + ); + details.append_printf( + "GTK version: %u.%u.%u\n", + Gtk.get_major_version(), Gtk.get_minor_version(), Gtk.get_micro_version() + ); + details.append_printf( + "Desktop: %s\n", + Environment.get_variable("XDG_CURRENT_DESKTOP") ?? "Unknown" + ); + if (this.account != null) { + details.append_printf( + "Account id: %s\n", + this.account.id + ); + details.append_printf( + "Account provider: %s\n", + this.account.service_provider.to_string() + ); + } + if (this.service != null) { + details.append_printf( + "Service type: %s\n", + this.service.protocol.to_string() + ); + details.append_printf( + "Service host: %s\n", + this.service.host + ); + } + if (this.error == null) { + details.append("No error reported"); + } else { + details.append_printf( + "Error type: %s\n", this.error.format_error_type() + ); + details.append_printf( + "Message: %s\n", this.error.thrown.message + ); + details.append("Back trace:\n"); + foreach (Geary.ErrorContext.StackFrame frame in + this.error.backtrace) { + details.append_printf(" - %s\n", frame.to_string()); + } + } + return details.str; + } + + [GtkCallback] + private void on_copy_clicked() { + get_clipboard(Gdk.SELECTION_CLIPBOARD).set_text(format_details(), -1); + } + +} diff -Nru geary-0.12.4/src/client/dialogs/password-dialog.vala geary-3.32.0/src/client/dialogs/password-dialog.vala --- geary-0.12.4/src/client/dialogs/password-dialog.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/dialogs/password-dialog.vala 2019-03-17 13:39:29.000000000 +0000 @@ -3,7 +3,7 @@ * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ - + /** * Displays a dialog for collecting the user's password, without allowing them to change their * other data. @@ -14,68 +14,67 @@ // details: https://bugzilla.gnome.org/show_bug.cgi?id=679006 private const string PRIMARY_TEXT_MARKUP = "%s"; private const string PRIMARY_TEXT_FIRST_TRY = _("Geary requires your email password to continue"); - + private Gtk.Dialog dialog; private Gtk.Entry entry_password; private Gtk.CheckButton check_remember_password; private Gtk.Button ok_button; - + public string password { get; private set; default = ""; } public bool remember_password { get; private set; } - - public PasswordDialog(Gtk.Window? parent, bool smtp, Geary.AccountInformation account_information, - Geary.ServiceFlag password_flags) { - Gtk.Builder builder = GearyApplication.instance.create_builder("password-dialog.glade"); - + + public PasswordDialog(Gtk.Window? parent, + Geary.AccountInformation account, + Geary.ServiceInformation service, + Geary.Credentials? credentials) { + Gtk.Builder builder = GioUtil.create_builder("password-dialog.glade"); + dialog = (Gtk.Dialog) builder.get_object("PasswordDialog"); dialog.transient_for = parent; dialog.set_type_hint(Gdk.WindowTypeHint.DIALOG); dialog.set_default_response(Gtk.ResponseType.OK); - + entry_password = (Gtk.Entry) builder.get_object("entry: password"); check_remember_password = (Gtk.CheckButton) builder.get_object("check: remember_password"); - + Gtk.Label label_username = (Gtk.Label) builder.get_object("label: username"); Gtk.Label label_smtp = (Gtk.Label) builder.get_object("label: smtp"); - + // Load translated text for labels with markup unsupported by glade. Gtk.Label primary_text_label = (Gtk.Label) builder.get_object("primary_text_label"); primary_text_label.set_markup(PRIMARY_TEXT_MARKUP.printf(PRIMARY_TEXT_FIRST_TRY)); - if (smtp) { - label_username.set_text(account_information.smtp_credentials.user ?? ""); - entry_password.set_text(account_information.smtp_credentials.pass ?? ""); - } else { - label_username.set_text(account_information.imap_credentials.user ?? ""); - entry_password.set_text(account_information.imap_credentials.pass ?? ""); + if (credentials != null) { + label_username.set_text(credentials.user); + entry_password.set_text(credentials.token ?? ""); } - check_remember_password.active = (smtp ? account_information.smtp_remember_password - : account_information.imap_remember_password); - if (smtp) + check_remember_password.active = service.remember_password; + + if ((service.protocol == Geary.Protocol.SMTP)) { label_smtp.show(); - + } + ok_button = (Gtk.Button) builder.get_object("authenticate_button"); - + refresh_ok_button_sensitivity(); entry_password.changed.connect(refresh_ok_button_sensitivity); } - + private void refresh_ok_button_sensitivity() { ok_button.sensitive = !Geary.String.is_empty_or_whitespace(entry_password.get_text()); } - + public bool run() { dialog.show(); - dialog.get_action_area().show_all(); - + Gtk.ResponseType response = (Gtk.ResponseType) dialog.run(); if (response == Gtk.ResponseType.OK) { password = entry_password.get_text(); remember_password = check_remember_password.active; } - + dialog.destroy(); - + return (response == Gtk.ResponseType.OK); } } diff -Nru geary-0.12.4/src/client/dialogs/upgrade-dialog.vala geary-3.32.0/src/client/dialogs/upgrade-dialog.vala --- geary-0.12.4/src/client/dialogs/upgrade-dialog.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/dialogs/upgrade-dialog.vala 2019-03-17 13:39:29.000000000 +0000 @@ -6,55 +6,55 @@ public class UpgradeDialog : Object { public const string PROP_VISIBLE_NAME = "visible"; - + // Progress monitor associated with the upgrade. public Geary.AggregateProgressMonitor monitor { public get; private set; default = new Geary.AggregateProgressMonitor(); } - + // Whether or not this dialog is visible. public bool visible { get; set; } - + private Gtk.Dialog dialog; private Gee.HashSet cancellables = new Gee.HashSet(); - + /** * Creates and loads the upgrade progress dialog. */ public UpgradeDialog() { // Load UI. - Gtk.Builder builder = GearyApplication.instance.create_builder("upgrade_dialog.glade"); + Gtk.Builder builder = GioUtil.create_builder("upgrade_dialog.glade"); dialog = (Gtk.Dialog) builder.get_object("dialog"); - + // Hook up signals. monitor.start.connect(on_start); monitor.finish.connect(on_close); dialog.delete_event.connect(on_delete_event); - + // Bind visibility flag. dialog.bind_property(PROP_VISIBLE_NAME, this, PROP_VISIBLE_NAME, BindingFlags.BIDIRECTIONAL | BindingFlags.SYNC_CREATE); } - + private void on_start() { dialog.show(); } - + private bool on_delete_event() { // Don't allow window to close until we're finished. return !monitor.is_in_progress; } - + private void on_close() { // If the user quit the dialog before the upgrade completed, cancel everything. if (monitor.is_in_progress) { foreach(Cancellable c in cancellables) c.cancel(); } - + if (dialog.visible) dialog.hide(); } - + /** * Add accounts before opening them. */ diff -Nru geary-0.12.4/src/client/folder-list/folder-list-abstract-folder-entry.vala geary-3.32.0/src/client/folder-list/folder-list-abstract-folder-entry.vala --- geary-0.12.4/src/client/folder-list/folder-list-abstract-folder-entry.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/folder-list/folder-list-abstract-folder-entry.vala 2019-03-17 13:39:29.000000000 +0000 @@ -11,19 +11,19 @@ */ public abstract class FolderList.AbstractFolderEntry : Geary.BaseObject, Sidebar.Entry, Sidebar.SelectableEntry { public Geary.Folder folder { get; private set; } - - public AbstractFolderEntry(Geary.Folder folder) { + + protected AbstractFolderEntry(Geary.Folder folder) { this.folder = folder; } - + public abstract string get_sidebar_name(); - + public abstract string? get_sidebar_tooltip(); - + public abstract string? get_sidebar_icon(); - + public abstract int get_count(); - + public virtual string to_string() { return "AbstractFolderEntry: " + get_sidebar_name(); } diff -Nru geary-0.12.4/src/client/folder-list/folder-list-account-branch.vala geary-3.32.0/src/client/folder-list/folder-list-account-branch.vala --- geary-0.12.4/src/client/folder-list/folder-list-account-branch.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/folder-list/folder-list-account-branch.vala 2019-03-17 13:39:29.000000000 +0000 @@ -9,71 +9,77 @@ public Geary.Account account { get; private set; } public SpecialGrouping user_folder_group { get; private set; } public Gee.HashMap folder_entries { get; private set; } - + + private string display_name = ""; + public AccountBranch(Geary.Account account) { - base(new Sidebar.Header(account.information.nickname), - Sidebar.Branch.Options.NONE, normal_folder_comparator, special_folder_comparator); - + base(new Sidebar.Header(account.information.display_name), + Sidebar.Branch.Options.AUTO_OPEN_ON_NEW_CHILD, normal_folder_comparator, special_folder_comparator); + this.account = account; user_folder_group = new SpecialGrouping(2, "", "tag-symbolic"); folder_entries = new Gee.HashMap(); - - account.information.notify["nickname"].connect(on_nicknamed_changed); - + + this.display_name = account.information.display_name; + account.information.changed.connect(on_information_changed); + entry_removed.connect(on_entry_removed); entry_moved.connect(check_user_folders); } - + ~AccountBranch() { - account.information.notify["nickname"].disconnect(on_nicknamed_changed); + account.information.changed.disconnect(on_information_changed); entry_removed.disconnect(on_entry_removed); entry_moved.disconnect(check_user_folders); } - - private void on_nicknamed_changed() { - ((Sidebar.Grouping) get_root()).rename(account.information.nickname); + + private void on_information_changed() { + if (this.display_name != this.account.information.display_name) { + this.display_name = account.information.display_name; + ((Sidebar.Grouping) get_root()).rename(this.display_name); + } } - + private static int special_grouping_comparator(Sidebar.Entry a, Sidebar.Entry b) { SpecialGrouping? grouping_a = a as SpecialGrouping; SpecialGrouping? grouping_b = b as SpecialGrouping; - + assert(grouping_a != null || grouping_b != null); - + int position_a = (grouping_a != null ? grouping_a.position : 0); int position_b = (grouping_b != null ? grouping_b.position : 0); - + return position_a - position_b; } - + private static int special_folder_comparator(Sidebar.Entry a, Sidebar.Entry b) { if (a is Sidebar.Grouping || b is Sidebar.Grouping) return special_grouping_comparator(a, b); - + assert(a is FolderEntry); assert(b is FolderEntry); - + FolderEntry entry_a = (FolderEntry) a; FolderEntry entry_b = (FolderEntry) b; Geary.SpecialFolderType type_a = entry_a.folder.special_folder_type; Geary.SpecialFolderType type_b = entry_b.folder.special_folder_type; - + assert(type_a != Geary.SpecialFolderType.NONE); assert(type_b != Geary.SpecialFolderType.NONE); - + // Special folders are ordered by their enum value. return (int) type_a - (int) type_b; } - + private static int normal_folder_comparator(Sidebar.Entry a, Sidebar.Entry b) { // Non-special folders are compared based on name. return a.get_sidebar_name().collate(b.get_sidebar_name()); } - + public FolderEntry? get_entry_for_path(Geary.FolderPath folder_path) { return folder_entries.get(folder_path); } - + public void add_folder(Geary.Folder folder) { Sidebar.Entry? graft_point = null; FolderEntry folder_entry = new FolderEntry(folder); @@ -81,10 +87,10 @@ if (special_folder_type != Geary.SpecialFolderType.NONE) { if (special_folder_type == Geary.SpecialFolderType.SEARCH) return; // Don't show search folder under the account. - + // Special folders go in the root of the account. graft_point = get_root(); - } else if (folder.path.get_parent() == null) { + } else if (folder.path.is_top_level) { // Top-level folders get put in our special user folders group. graft_point = user_folder_group; @@ -92,11 +98,11 @@ graft(get_root(), user_folder_group); } } else { - Sidebar.Entry? entry = folder_entries.get(folder.path.get_parent()); + Sidebar.Entry? entry = folder_entries.get(folder.path.parent); if (entry != null) graft_point = entry; } - + // Due to how we enumerate folders on the server, it's unfortunately // possible now to have two folders that we'd put in the same place in // our tree. In that case, we just ignore the second folder for now. @@ -117,18 +123,18 @@ special_folder_type.to_string()); } } - + public void remove_folder(Geary.Folder folder) { Sidebar.Entry? entry = folder_entries.get(folder.path); if(entry == null) { debug("Could not remove folder %s", folder.to_string()); return; } - + prune(entry); folder_entries.unset(folder.path); } - + private void on_entry_removed(Sidebar.Entry entry) { FolderEntry? folder_entry = entry as FolderEntry; if (folder_entry != null && folder_entries.has_key(folder_entry.folder.path)) diff -Nru geary-0.12.4/src/client/folder-list/folder-list-folder-entry.vala geary-3.32.0/src/client/folder-list/folder-list-folder-entry.vala --- geary-0.12.4/src/client/folder-list/folder-list-folder-entry.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/folder-list/folder-list-folder-entry.vala 2019-03-17 13:39:29.000000000 +0000 @@ -8,7 +8,7 @@ public class FolderList.FolderEntry : FolderList.AbstractFolderEntry, Sidebar.InternalDropTargetEntry, Sidebar.EmphasizableEntry { private bool has_new; - + public FolderEntry(Geary.Folder folder) { base(folder); has_new = false; @@ -16,89 +16,89 @@ folder.properties.notify[Geary.FolderProperties.PROP_NAME_EMAIL_UNREAD].connect(on_counts_changed); folder.display_name_changed.connect(on_display_name_changed); } - + ~FolderEntry() { folder.properties.notify[Geary.FolderProperties.PROP_NAME_EMAIL_TOTAL].disconnect(on_counts_changed); folder.properties.notify[Geary.FolderProperties.PROP_NAME_EMAIL_UNREAD].disconnect(on_counts_changed); folder.display_name_changed.disconnect(on_display_name_changed); } - + public override string get_sidebar_name() { return folder.get_display_name(); } - + public override string? get_sidebar_tooltip() { // Label displaying total number of email messages in a folder string total_msg = ngettext("%d message", "%d messages", folder.properties.email_total). printf(folder.properties.email_total); - + if (folder.properties.email_unread == 0) return total_msg; - + /// Label displaying number of unread email messages in a folder string unread_msg = ngettext("%d unread", "%d unread", folder.properties.email_unread). printf(folder.properties.email_unread); - + /// This string represents the divider between two messages: "n messages" and "n unread", /// shown in the folder list as a tooltip. Please use your languages conventions for /// combining the two, i.e. a comma (",") for English; "6 messages, 3 unread" return _("%s, %s").printf(total_msg, unread_msg); } - + public override string? get_sidebar_icon() { switch (folder.special_folder_type) { case Geary.SpecialFolderType.NONE: return "tag-symbolic"; - + case Geary.SpecialFolderType.INBOX: return "mail-inbox-symbolic"; - + case Geary.SpecialFolderType.DRAFTS: return "mail-drafts-symbolic"; case Geary.SpecialFolderType.SENT: return "mail-sent-symbolic"; - + case Geary.SpecialFolderType.FLAGGED: return "starred-symbolic"; - + case Geary.SpecialFolderType.IMPORTANT: return "task-due-symbolic"; - + case Geary.SpecialFolderType.ALL_MAIL: case Geary.SpecialFolderType.ARCHIVE: return "mail-archive-symbolic"; - + case Geary.SpecialFolderType.SPAM: return "dialog-warning-symbolic"; - + case Geary.SpecialFolderType.TRASH: return "user-trash-symbolic"; - + case Geary.SpecialFolderType.OUTBOX: return "mail-outbox-symbolic"; - + default: assert_not_reached(); } } - + public override string to_string() { return "FolderEntry: " + get_sidebar_name(); } - + public bool is_emphasized() { return has_new; } - + public void set_has_new(bool has_new) { if (this.has_new == has_new) return; - + this.has_new = has_new; is_emphasized_changed(has_new); } - + public bool internal_drop_received(Gdk.DragContext context, Gtk.SelectionData data) { // Copy or move? Gdk.ModifierType mask; @@ -113,29 +113,29 @@ return true; } - + private void on_counts_changed() { sidebar_count_changed(get_count()); sidebar_tooltip_changed(get_sidebar_tooltip()); } - + private void on_display_name_changed() { sidebar_name_changed(folder.get_display_name()); } - + public override int get_count() { switch (folder.special_folder_type) { // for Drafts and Outbox, interested in showing total count, not unread count case Geary.SpecialFolderType.DRAFTS: case Geary.SpecialFolderType.OUTBOX: return folder.properties.email_total; - + // only show counts for Inbox, Spam, and user folders case Geary.SpecialFolderType.INBOX: case Geary.SpecialFolderType.SPAM: case Geary.SpecialFolderType.NONE: return folder.properties.email_unread; - + // otherwise, to avoid clutter, no counts displayed (but are available in tooltip) default: return 0; diff -Nru geary-0.12.4/src/client/folder-list/folder-list-inboxes-branch.vala geary-3.32.0/src/client/folder-list/folder-list-inboxes-branch.vala --- geary-0.12.4/src/client/folder-list/folder-list-inboxes-branch.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/folder-list/folder-list-inboxes-branch.vala 2019-03-17 13:39:29.000000000 +0000 @@ -9,48 +9,48 @@ public class FolderList.InboxesBranch : Sidebar.Branch { public Gee.HashMap folder_entries { get; private set; default = new Gee.HashMap(); } - + public InboxesBranch() { base(new Sidebar.Header(_("Inboxes")), Sidebar.Branch.Options.NONE, inbox_comparator); } - + private static int inbox_comparator(Sidebar.Entry a, Sidebar.Entry b) { assert(a is InboxFolderEntry); assert(b is InboxFolderEntry); - + InboxFolderEntry entry_a = (InboxFolderEntry) a; InboxFolderEntry entry_b = (InboxFolderEntry) b; return Geary.AccountInformation.compare_ascending(entry_a.get_account_information(), entry_b.get_account_information()); } - + public InboxFolderEntry? get_entry_for_account(Geary.Account account) { return folder_entries.get(account); } - + public void add_inbox(Geary.Folder inbox) { assert(inbox.special_folder_type == Geary.SpecialFolderType.INBOX); - + InboxFolderEntry folder_entry = new InboxFolderEntry(inbox); graft(get_root(), folder_entry); - + folder_entries.set(inbox.account, folder_entry); inbox.account.information.notify["ordinal"].connect(on_ordinal_changed); } - + public void remove_inbox(Geary.Account account) { Sidebar.Entry? entry = folder_entries.get(account); if(entry == null) { debug("Could not remove inbox for %s", account.to_string()); return; } - + account.information.notify["ordinal"].disconnect(on_ordinal_changed); prune(entry); folder_entries.unset(account); } - + private void on_ordinal_changed() { reorder_all(); } diff -Nru geary-0.12.4/src/client/folder-list/folder-list-inbox-folder-entry.vala geary-3.32.0/src/client/folder-list/folder-list-inbox-folder-entry.vala --- geary-0.12.4/src/client/folder-list/folder-list-inbox-folder-entry.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/folder-list/folder-list-inbox-folder-entry.vala 2019-03-17 13:39:29.000000000 +0000 @@ -6,25 +6,33 @@ // A FolderEntry for inboxes in the Inboxes branch. public class FolderList.InboxFolderEntry : FolderList.FolderEntry { + + + private string display_name = ""; + + public InboxFolderEntry(Geary.Folder folder) { base(folder); - folder.account.information.notify["nickname"].connect(on_nicknamed_changed); + this.display_name = folder.account.information.display_name; + folder.account.information.changed.connect(on_information_changed); } - + ~InboxFolderEntry() { - folder.account.information.notify["nickname"].disconnect(on_nicknamed_changed); + folder.account.information.changed.disconnect(on_information_changed); } - + public override string get_sidebar_name() { - return folder.account.information.nickname; + return this.display_name; } - + public Geary.AccountInformation get_account_information() { return folder.account.information; } - - private void on_nicknamed_changed() { - sidebar_name_changed(folder.account.information.nickname); + + private void on_information_changed(Geary.AccountInformation config) { + if (this.display_name != config.display_name) { + this.display_name = config.display_name; + sidebar_name_changed(this.display_name); + } } } - diff -Nru geary-0.12.4/src/client/folder-list/folder-list-search-branch.vala geary-3.32.0/src/client/folder-list/folder-list-search-branch.vala --- geary-0.12.4/src/client/folder-list/folder-list-search-branch.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/folder-list/folder-list-search-branch.vala 2019-03-17 13:39:29.000000000 +0000 @@ -11,7 +11,7 @@ public SearchBranch(Geary.SearchFolder folder) { base(new SearchEntry(folder)); } - + public Geary.SearchFolder get_search_folder() { return (Geary.SearchFolder) ((SearchEntry) get_root()).folder; } @@ -20,47 +20,47 @@ public class FolderList.SearchEntry : FolderList.AbstractFolderEntry { public SearchEntry(Geary.SearchFolder folder) { base(folder); - + Geary.Engine.instance.account_available.connect(on_accounts_changed); Geary.Engine.instance.account_unavailable.connect(on_accounts_changed); folder.properties.notify[Geary.FolderProperties.PROP_NAME_EMAIL_TOTAL].connect( on_email_total_changed); } - + ~SearchEntry() { Geary.Engine.instance.account_available.disconnect(on_accounts_changed); Geary.Engine.instance.account_unavailable.disconnect(on_accounts_changed); folder.properties.notify[Geary.FolderProperties.PROP_NAME_EMAIL_TOTAL].disconnect( on_email_total_changed); } - + public override string get_sidebar_name() { return GearyApplication.instance.controller.get_num_accounts() == 1 ? _("Search") : - _("Search %s account").printf(folder.account.information.nickname); + _("Search %s account").printf(folder.account.information.display_name); } - + public override string? get_sidebar_tooltip() { int total = folder.properties.email_total; return ngettext("%d result", "%d results", total).printf(total); } - + public override string? get_sidebar_icon() { return "edit-find-symbolic"; } - + public override string to_string() { return "SearchEntry: " + folder.to_string(); } - + private void on_accounts_changed() { sidebar_name_changed(get_sidebar_name()); sidebar_tooltip_changed(get_sidebar_tooltip()); } - + private void on_email_total_changed() { sidebar_tooltip_changed(get_sidebar_tooltip()); } - + public override int get_count() { return 0; } diff -Nru geary-0.12.4/src/client/folder-list/folder-list-special-grouping.vala geary-3.32.0/src/client/folder-list/folder-list-special-grouping.vala --- geary-0.12.4/src/client/folder-list/folder-list-special-grouping.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/folder-list/folder-list-special-grouping.vala 2019-03-17 13:39:29.000000000 +0000 @@ -10,11 +10,11 @@ // Must be != 0 and unique among SpecialGroupings. Bigger comes later // in the list. If < 0, it comes before non-SpecialGroupings. public int position { get; private set; } - + public SpecialGrouping(int position, string name, string? icon, string? tooltip = null) { base(name, icon, tooltip); - + this.position = position; } } diff -Nru geary-0.12.4/src/client/folder-list/folder-list-tree.vala geary-3.32.0/src/client/folder-list/folder-list-tree.vala --- geary-0.12.4/src/client/folder-list/folder-list-tree.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/folder-list/folder-list-tree.vala 2019-03-17 13:39:29.000000000 +0000 @@ -8,14 +8,14 @@ public const Gtk.TargetEntry[] TARGET_ENTRY_LIST = { { "application/x-geary-mail", Gtk.TargetFlags.SAME_APP, 0 } }; - + private const int INBOX_ORDINAL = -2; // First account branch is zero private const int SEARCH_ORDINAL = -1; - + public signal void folder_selected(Geary.Folder? folder); public signal void copy_conversation(Geary.Folder folder); public signal void move_conversation(Geary.Folder folder); - + private Gee.HashMap account_branches = new Gee.HashMap(); private InboxesBranch inboxes_branch = new InboxesBranch(); @@ -30,7 +30,7 @@ // Set self as a drag destination. Gtk.drag_dest_set(this, Gtk.DestDefaults.MOTION | Gtk.DestDefaults.HIGHLIGHT, TARGET_ENTRY_LIST, Gdk.DragAction.COPY | Gdk.DragAction.MOVE); - + // GtkTreeView binds Ctrl+N to "move cursor to next". Not so interested in that, so we'll // remove it. unowned Gtk.BindingSet? binding_set = Gtk.BindingSet.find("GtkTreeView"); @@ -62,17 +62,17 @@ private void drop_handler(Gdk.DragContext context, Sidebar.Entry? entry, Gtk.SelectionData data, uint info, uint time) { } - + private FolderEntry? get_folder_entry(Geary.Folder folder) { AccountBranch? account_branch = account_branches.get(folder.account); return (account_branch == null ? null : account_branch.get_entry_for_path(folder.path)); } - + public override bool accept_cursor_changed() { return GearyApplication.instance.controller.can_switch_conversation_view(); } - + private void on_entry_selected(Sidebar.SelectableEntry selectable) { AbstractFolderEntry? abstract_folder_entry = selectable as AbstractFolderEntry; if (abstract_folder_entry != null) @@ -83,45 +83,45 @@ FolderEntry? entry = get_folder_entry(folder); if (entry != null) entry.set_has_new(count > 0); - + if (has_branch(inboxes_branch)) { InboxFolderEntry? inbox_entry = inboxes_branch.get_entry_for_account(folder.account); if (inbox_entry != null) inbox_entry.set_has_new(count > 0); } } - + public void set_new_messages_monitor(NewMessagesMonitor? monitor) { if (this.monitor != null) { this.monitor.new_messages_arrived.disconnect(on_new_messages_changed); this.monitor.new_messages_retired.disconnect(on_new_messages_changed); } - + this.monitor = monitor; if (this.monitor != null) { this.monitor.new_messages_arrived.connect(on_new_messages_changed); this.monitor.new_messages_retired.connect(on_new_messages_changed); } } - + public void set_user_folders_root_name(Geary.Account account, string name) { if (account_branches.has_key(account)) account_branches.get(account).user_folder_group.rename(name); } - + public void add_folder(Geary.Folder folder) { if (!account_branches.has_key(folder.account)) account_branches.set(folder.account, new AccountBranch(folder.account)); - + AccountBranch account_branch = account_branches.get(folder.account); if (!has_branch(account_branch)) graft(account_branch, folder.account.information.ordinal); - + if (account_branches.size > 1 && !has_branch(inboxes_branch)) graft(inboxes_branch, INBOX_ORDINAL); // The Inboxes branch comes first. if (folder.special_folder_type == Geary.SpecialFolderType.INBOX) inboxes_branch.add_inbox(folder); - + folder.account.information.notify["ordinal"].connect(on_ordinal_changed); account_branch.add_folder(folder); } @@ -130,28 +130,28 @@ AccountBranch? account_branch = account_branches.get(folder.account); assert(account_branch != null); assert(has_branch(account_branch)); - + // If this is the current folder, unselect it. Sidebar.Entry? entry = account_branch.get_entry_for_path(folder.path); - + // if not found or found but not selected, see if the folder is in the Inboxes branch if (has_branch(inboxes_branch) && (entry == null || !is_selected(entry))) { InboxFolderEntry? inbox_entry = inboxes_branch.get_entry_for_account(folder.account); if (inbox_entry != null && inbox_entry.folder == folder) entry = inbox_entry; } - + // if found and selected, report nothing is selected in preparation for its removal if (entry != null && is_selected(entry)) folder_selected(null); - + // if Inbox, remove from inboxes branch, selected or not if (folder.special_folder_type == Geary.SpecialFolderType.INBOX) inboxes_branch.remove_inbox(folder.account); - + account_branch.remove_folder(folder); } - + public void remove_account(Geary.Account account) { account.information.notify["ordinal"].disconnect(on_ordinal_changed); AccountBranch? account_branch = account_branches.get(account); @@ -163,40 +163,40 @@ break; } } - + if (has_branch(account_branch)) prune(account_branch); account_branches.unset(account); } - + Sidebar.Entry? entry = inboxes_branch.get_entry_for_account(account); if (entry != null && is_selected(entry)) folder_selected(null); - + inboxes_branch.remove_inbox(account); - + if (account_branches.size <= 1 && has_branch(inboxes_branch)) prune(inboxes_branch); } - + public void select_folder(Geary.Folder folder) { FolderEntry? entry = get_folder_entry(folder); if (entry != null) place_cursor(entry, false); } - + public bool select_inbox(Geary.Account account) { if (!has_branch(inboxes_branch)) return false; - + InboxFolderEntry? entry = inboxes_branch.get_entry_for_account(account); if (entry == null) return false; - + place_cursor(entry, false); return true; } - + public override bool drag_motion(Gdk.DragContext context, int x, int y, uint time) { // Run the base version first. bool ret = base.drag_motion(context, x, y, time); @@ -212,11 +212,11 @@ } return ret; } - + private void on_ordinal_changed() { if (account_branches.size <= 1) return; - + // Remove branches where the ordinal doesn't match the graft position. Gee.ArrayList branches_to_reorder = new Gee.ArrayList(); foreach (AccountBranch branch in account_branches.values) { @@ -225,12 +225,12 @@ branches_to_reorder.add(branch); } } - + // Re-add branches with new positions. foreach (AccountBranch branch in branches_to_reorder) graft(branch, branch.account.information.ordinal); } - + public void set_search(Geary.SearchFolder search_folder) { if (search_branch != null && has_branch(search_branch)) { // We already have a search folder. If it's the same one, just @@ -243,12 +243,12 @@ remove_search(); } } - + search_branch = new SearchBranch(search_folder); graft(search_branch, SEARCH_ORDINAL); place_cursor(search_branch.get_root(), false); } - + public void remove_search() { if (search_branch != null) { prune(search_branch); diff -Nru geary-0.12.4/src/client/meson.build geary-3.32.0/src/client/meson.build --- geary-0.12.4/src/client/meson.build 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/src/client/meson.build 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,155 @@ +# Geary client +geary_client_vala_sources = files( + 'application/application-avatar-store.vala', + 'application/application-certificate-manager.vala', + 'application/application-command.vala', + 'application/autostart-manager.vala', + 'application/geary-application.vala', + 'application/geary-args.vala', + 'application/geary-config.vala', + 'application/geary-controller.vala', + 'application/goa-mediator.vala', + 'application/secret-mediator.vala', + + 'accounts/accounts-editor.vala', + 'accounts/accounts-editor-add-pane.vala', + 'accounts/accounts-editor-edit-pane.vala', + 'accounts/accounts-editor-list-pane.vala', + 'accounts/accounts-editor-remove-pane.vala', + 'accounts/accounts-editor-row.vala', + 'accounts/accounts-editor-servers-pane.vala', + 'accounts/accounts-signature-web-view.vala', + 'accounts/accounts-manager.vala', + + 'components/client-web-view.vala', + 'components/components-placeholder-pane.vala', + 'components/components-validator.vala', + 'components/count-badge.vala', + 'components/folder-popover.vala', + 'components/icon-factory.vala', + 'components/main-toolbar.vala', + 'components/main-window.vala', + 'components/main-window-info-bar.vala', + 'components/monitored-progress-bar.vala', + 'components/monitored-spinner.vala', + 'components/search-bar.vala', + 'components/status-bar.vala', + 'components/stock.vala', + + 'composer/composer-box.vala', + 'composer/composer-container.vala', + 'composer/composer-embed.vala', + 'composer/composer-headerbar.vala', + 'composer/composer-link-popover.vala', + 'composer/composer-web-view.vala', + 'composer/composer-widget.vala', + 'composer/composer-window.vala', + 'composer/contact-entry-completion.vala', + 'composer/contact-list-store.vala', + 'composer/contact-list-store-cache.vala', + 'composer/email-entry.vala', + 'composer/spell-check-popover.vala', + + 'conversation-list/conversation-list-cell-renderer.vala', + 'conversation-list/conversation-list-store.vala', + 'conversation-list/conversation-list-view.vala', + 'conversation-list/formatted-conversation-data.vala', + + 'conversation-viewer/conversation-email.vala', + 'conversation-viewer/conversation-list-box.vala', + 'conversation-viewer/conversation-message.vala', + 'conversation-viewer/conversation-viewer.vala', + 'conversation-viewer/conversation-web-view.vala', + + 'dialogs/alert-dialog.vala', + 'dialogs/attachment-dialog.vala', + 'dialogs/certificate-warning-dialog.vala', + 'dialogs/dialogs-problem-details-dialog.vala', + 'dialogs/password-dialog.vala', + 'dialogs/preferences-dialog.vala', + 'dialogs/upgrade-dialog.vala', + + 'folder-list/folder-list-abstract-folder-entry.vala', + 'folder-list/folder-list-account-branch.vala', + 'folder-list/folder-list-folder-entry.vala', + 'folder-list/folder-list-tree.vala', + 'folder-list/folder-list-inboxes-branch.vala', + 'folder-list/folder-list-inbox-folder-entry.vala', + 'folder-list/folder-list-search-branch.vala', + 'folder-list/folder-list-special-grouping.vala', + + 'notification/in-app-notification.vala', + 'notification/libmessagingmenu.vala', + 'notification/libnotify.vala', + 'notification/new-messages-indicator.vala', + 'notification/new-messages-monitor.vala', + 'notification/null-indicator.vala', + 'notification/unity-launcher.vala', + + 'sidebar/sidebar-branch.vala', + 'sidebar/sidebar-common.vala', + 'sidebar/sidebar-count-cell-renderer.vala', + 'sidebar/sidebar-entry.vala', + 'sidebar/sidebar-tree.vala', + + 'util/util-avatar.vala', + 'util/util-date.vala', + 'util/util-email.vala', + 'util/util-files.vala', + 'util/util-gio.vala', + 'util/util-gtk.vala', + 'util/util-international.vala', + 'util/util-migrate.vala', + 'util/util-webkit.vala', +) + +geary_client_sources = [ + geary_client_vala_sources, + geary_resources # Included here so valac can check them +] + +geary_client_dependencies = [ + libmath, + enchant, + folks, + gck, + gcr, + gee, + gio, + goa, + gtk, + json_glib, + libcanberra, + libnotify, + libsecret, + libsoup, + gmime, + libxml, + posix, + webkit2gtk, + geary_engine_dep, +] + +geary_client_vala_options = geary_vala_options + +if libmessagingmenu.found() + geary_client_dependencies += libmessagingmenu + geary_client_vala_options += ['-D', 'HAVE_LIBMESSAGINGMENU'] +endif +if libunity.found() + geary_client_dependencies += libunity + geary_client_vala_options += ['-D', 'HAVE_LIBUNITY'] +endif + +geary_client_lib = static_library('geary-client', + geary_client_sources, + dependencies: geary_client_dependencies, + include_directories: config_h_dir, + vala_args: geary_client_vala_options, + c_args: geary_c_options, +) + +geary_client_dep = declare_dependency( + link_with: geary_client_lib, + include_directories: include_directories('.'), +) diff -Nru geary-0.12.4/src/client/notification/in-app-notification.vala geary-3.32.0/src/client/notification/in-app-notification.vala --- geary-0.12.4/src/client/notification/in-app-notification.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/src/client/notification/in-app-notification.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,66 @@ +/* Copyright 2017 Software Freedom Conservancy Inc. + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +/** + * Represents an in-app notification. + * + * Following the GNOME HIG, it should only contain a label and maybe a button. + */ +[GtkTemplate (ui = "/org/gnome/Geary/in-app-notification.ui")] +public class InAppNotification : Gtk.Revealer { + + /** Length of the default timeout to close the notification. */ + public const uint DEFAULT_KEEPALIVE = 5; + + [GtkChild] + private Gtk.Label message_label; + [GtkChild] + private Gtk.Button action_button; + + /** + * Creates an in-app notification. + * + * @param message The messag that should be displayed. + * @param keepalive The amount of seconds that the notification should stay visible. + */ + public InAppNotification(string message, uint keepalive = DEFAULT_KEEPALIVE) { + this.transition_type = Gtk.RevealerTransitionType.SLIDE_DOWN; + this.message_label.label = message; + + // Close after the given amount of time. + Timeout.add_seconds(keepalive, () => { close(); return false; }); + } + + /** + * Sets a button for the notification. + */ + public void set_button(string label, string action_name) { + this.action_button.visible = true; + this.action_button.label = label; + this.action_button.action_name = action_name; + } + + public override void show() { + base.show(); + this.reveal_child = true; + } + + /** + * Closes the in-app notification. + */ + [GtkCallback] + public void close() { + // Allows for the disappearing transition + this.reveal_child = false; + } + + // Make sure the notification gets destroyed after closing. + [GtkCallback] + private void on_child_revealed(Object src, ParamSpec p) { + if (!this.child_revealed) + destroy(); + } +} diff -Nru geary-0.12.4/src/client/notification/libmessagingmenu.vala geary-3.32.0/src/client/notification/libmessagingmenu.vala --- geary-0.12.4/src/client/notification/libmessagingmenu.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/notification/libmessagingmenu.vala 2019-03-17 13:39:29.000000000 +0000 @@ -7,29 +7,29 @@ public class Libmessagingmenu : NewMessagesIndicator { #if HAVE_LIBMESSAGINGMENU private MessagingMenu.App? app = null; - + public Libmessagingmenu(NewMessagesMonitor monitor) { base (monitor); - + File? desktop_file = GearyApplication.instance.get_desktop_file(); if (desktop_file == null || !desktop_file.has_prefix(GearyApplication.instance.get_install_prefix_dir())) { debug("Only an installed version of Geary with its .desktop file installed can use Messaging Menu"); - + return; } - + app = new MessagingMenu.App("org.gnome.Geary.desktop"); app.register(); app.activate_source.connect(on_activate_source); - + monitor.folder_removed.connect(on_folder_removed); monitor.new_messages_arrived.connect(on_new_messages_changed); monitor.new_messages_retired.connect(on_new_messages_changed); - + debug("Registered messaging-menu indicator"); } - + ~Libmessagingmenu() { if (app != null) { monitor.folder_removed.disconnect(on_folder_removed); @@ -50,36 +50,36 @@ } } } - + private void on_new_messages_changed(Geary.Folder folder, int count) { if (count > 0) show_new_messages_count(folder, count); else remove_new_messages_count(folder); } - + private void on_folder_removed(Geary.Folder folder) { remove_new_messages_count(folder); } - + private void show_new_messages_count(Geary.Folder folder, int count) { if (!GearyApplication.instance.config.show_notifications || !monitor.should_notify_new_messages(folder)) return; - + string source_id = get_source_id(folder); - + if (app.has_source(source_id)) app.set_source_count(source_id, count); else app.append_source_with_count(source_id, null, - _("%s — New Messages").printf(folder.account.information.nickname), count); - + _("%s — New Messages").printf(folder.account.information.display_name), count); + app.draw_attention(source_id); } - + private void remove_new_messages_count(Geary.Folder folder) { string source_id = get_source_id(folder); - + if (app.has_source(source_id)) { app.remove_attention(source_id); app.remove_source(source_id); diff -Nru geary-0.12.4/src/client/notification/libnotify.vala geary-3.32.0/src/client/notification/libnotify.vala --- geary-0.12.4/src/client/notification/libnotify.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/notification/libnotify.vala 2019-03-17 13:39:29.000000000 +0000 @@ -8,10 +8,13 @@ public class Libnotify : Geary.BaseObject { public const Geary.Email.Field REQUIRED_FIELDS = Geary.Email.Field.ORIGINATORS | Geary.Email.Field.SUBJECT; - + + private const int AVATAR_SIZE = 32; + private static Canberra.Context? sound_context = null; - - private NewMessagesMonitor monitor; + + private weak NewMessagesMonitor monitor; + private weak Application.AvatarStore avatars; private Notify.Notification? current_notification = null; private Notify.Notification? error_notification = null; private Geary.Folder? folder = null; @@ -19,34 +22,32 @@ private List? caps = null; public signal void invoked(Geary.Folder? folder, Geary.Email? email); - - public Libnotify(NewMessagesMonitor monitor) { + + public Libnotify(NewMessagesMonitor monitor, + Application.AvatarStore avatars) { this.monitor = monitor; - + this.avatars = avatars; + monitor.add_required_fields(REQUIRED_FIELDS); - + if (!Notify.is_initted()) { if (!Notify.init(GearyApplication.PRGNAME)) message("Failed to initialize libnotify."); } - + init_sound(); - + // This will return null if no notification server is present this.caps = Notify.get_server_caps(); monitor.new_messages_arrived.connect(on_new_messages_arrived); } - - ~Libnotify() { - monitor.new_messages_arrived.disconnect(on_new_messages_arrived); - } - + private static void init_sound() { if (sound_context == null) Canberra.Context.create(out sound_context); } - + private void on_new_messages_arrived(Geary.Folder folder, int total, int added) { if (added == 1 && monitor.last_new_message_folder != null && monitor.last_new_message != null) { @@ -56,21 +57,21 @@ notify_new_mail(folder, added); } } - + private void on_default_action(Notify.Notification notification, string action) { invoked(folder, email); GearyApplication.instance.activate(); } - + private void notify_new_mail(Geary.Folder folder, int added) { // don't pass email if invoked this.folder = null; email = null; - + if (!GearyApplication.instance.config.show_notifications || !monitor.should_notify_new_messages(folder)) return; - + string body = ngettext("%d new message", "%d new messages", added).printf(added); int total = monitor.get_new_message_count(folder); if (total > added) { @@ -84,56 +85,45 @@ private async void notify_one_message_async(Geary.Folder folder, Geary.Email email, GLib.Cancellable? cancellable) throws GLib.Error { assert(email.fields.fulfills(REQUIRED_FIELDS)); - + // used if notification is invoked this.folder = folder; this.email = email; - + if (!GearyApplication.instance.config.show_notifications || !monitor.should_notify_new_messages(folder)) return; - + // possible to receive email with no originator - Geary.RFC822.MailboxAddress? primary = email.get_primary_originator(); + Geary.RFC822.MailboxAddress? primary = + Util.Email.get_primary_originator(email); if (primary == null) { notify_new_mail(folder, 1); - + return; } - + string body; int count = monitor.get_new_message_count(folder); if (count <= 1) { - body = EmailUtil.strip_subject_prefixes(email); + body = Util.Email.strip_subject_prefixes(email); } else { - body = ngettext("%s\n(%d other new message for %s)", "%s\n(%d other new messages for %s)", count - 1).printf( - EmailUtil.strip_subject_prefixes(email), count - 1, folder.account.information.display_name); + body = ngettext( + "%s\n(%d other new message for %s)", + "%s\n(%d other new messages for %s)", count - 1).printf( + Util.Email.strip_subject_prefixes(email), + count - 1, + folder.account.information.display_name + ); } - // get the avatar - Gdk.Pixbuf? avatar = null; - InputStream? ins = null; - File file = File.new_for_uri(Gravatar.get_image_uri(primary, Gravatar.Default.MYSTERY_MAN)); - try { - ins = yield file.read_async(GLib.Priority.DEFAULT, cancellable); - avatar = yield new Gdk.Pixbuf.from_stream_async(ins, cancellable); - } catch (Error err) { - debug("Failed to get avatar for notification: %s", err.message); - } - - if (ins != null) { - try { - yield ins.close_async(Priority.DEFAULT, cancellable); - } catch (Error close_err) { - // ignored - } - - ins = null; - } - - issue_current_notification(primary.get_short_address(), body, avatar); + Gdk.Pixbuf? avatar = yield this.avatars.load( + primary, AVATAR_SIZE, cancellable + ); + + issue_current_notification(primary.to_short_display(), body, avatar); } - + private void issue_current_notification(string summary, string body, Gdk.Pixbuf? icon) { // only one outstanding notification at a time if (current_notification != null) { @@ -142,14 +132,14 @@ } catch (Error err) { debug("Unable to close current libnotify notification: %s", err.message); } - + current_notification = null; } - + current_notification = issue_notification("email.arrived", summary, body, icon, "message-new_email"); - + } - + private Notify.Notification? issue_notification(string category, string summary, string body, Gdk.Pixbuf? icon, string? sound) { if (this.caps == null) @@ -163,38 +153,38 @@ notification.set_hint_string("desktop-entry", "org.gnome.Geary"); if (caps.find_custom("actions", GLib.strcmp) != null) notification.add_action("default", _("Open"), on_default_action); - + notification.set_category(category); notification.set("summary", summary); notification.set("body", body); - + if (icon != null) notification.set_image_from_pixbuf(icon); - + if (sound != null) { if (caps.find("sound") != null) notification.set_hint_string("sound-name", sound); else play_sound(sound); } - + try { notification.show(); } catch (Error err) { message("Unable to show notification: %s", err.message); } - + return notification; } - + public static void play_sound(string sound) { if (!GearyApplication.instance.config.play_sounds) return; - + init_sound(); sound_context.play(0, Canberra.PROP_EVENT_ID, sound); } - + public void set_error_notification(string summary, string body) { // Only one error at a time, guys. (This means subsequent errors will // be dropped. Since this is only used for one thing now, that's ok, @@ -212,7 +202,7 @@ } catch (Error err) { debug("Unable to close libnotify error notification: %s", err.message); } - + error_notification = null; } } diff -Nru geary-0.12.4/src/client/notification/new-messages-indicator.vala geary-3.32.0/src/client/notification/new-messages-indicator.vala --- geary-0.12.4/src/client/notification/new-messages-indicator.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/notification/new-messages-indicator.vala 2019-03-17 13:39:29.000000000 +0000 @@ -9,36 +9,36 @@ public abstract class NewMessagesIndicator : Geary.BaseObject { protected NewMessagesMonitor monitor; - + public signal void application_activated(uint32 timestamp); - + public signal void inbox_activated(Geary.Folder folder, uint32 timestamp); - + public signal void composer_activated(uint32 timestamp); - + protected NewMessagesIndicator(NewMessagesMonitor monitor) { this.monitor = monitor; } - + public static NewMessagesIndicator create(NewMessagesMonitor monitor) { NewMessagesIndicator? indicator = null; - + // Indicators are ordered from most to least prefered. If more than one is available, // use the first. - + #if HAVE_LIBMESSAGINGMENU if (indicator == null) indicator = new Libmessagingmenu(monitor); #endif - + if (indicator == null) indicator = new NullIndicator(monitor); - + assert(indicator != null); - + return indicator; } - + // Returns time as a uint32 (suitable for signals if event doesn't supply it) protected uint32 now() { return (uint32) TimeVal().tv_sec; diff -Nru geary-0.12.4/src/client/notification/new-messages-monitor.vala geary-3.32.0/src/client/notification/new-messages-monitor.vala --- geary-0.12.4/src/client/notification/new-messages-monitor.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/notification/new-messages-monitor.vala 2019-03-17 13:39:29.000000000 +0000 @@ -13,142 +13,150 @@ public class NewMessagesMonitor : Geary.BaseObject { public delegate bool ShouldNotifyNewMessages(Geary.Folder folder); - + private class MonitorInformation : Geary.BaseObject { public Geary.Folder folder; public Cancellable? cancellable = null; public int count = 0; public Gee.HashSet new_ids = new Gee.HashSet(); - + public MonitorInformation(Geary.Folder folder, Cancellable? cancellable) { this.folder = folder; this.cancellable = cancellable; } } - + public Geary.Email.Field required_fields { get; private set; default = Geary.Email.Field.FLAGS; } public int total_new_messages { get; private set; default = 0; } public Geary.Folder? last_new_message_folder { get; private set; default = null; } public Geary.Email? last_new_message { get; private set; default = null; } - + private Gee.HashMap folder_information = new Gee.HashMap(); private unowned ShouldNotifyNewMessages? _should_notify_new_messages; - + public signal void folder_added(Geary.Folder folder); - + public signal void folder_removed(Geary.Folder folder); - + /** * Fired when the monitor finds new messages on a folder. */ public signal void new_messages_arrived(Geary.Folder folder, int total, int added); - + /** * Fired when the monitor clears the "new" status of some messages in the * folder. */ public signal void new_messages_retired(Geary.Folder folder, int total); - + public NewMessagesMonitor(ShouldNotifyNewMessages? should_notify_new_messages) { _should_notify_new_messages = should_notify_new_messages; } - + public bool should_notify_new_messages(Geary.Folder folder) { return (_should_notify_new_messages == null ? true : _should_notify_new_messages(folder)); } - + public void add_folder(Geary.Folder folder, Cancellable? cancellable = null) { assert(!folder_information.has_key(folder)); - + folder.email_locally_appended.connect(on_email_locally_appended); folder.email_flags_changed.connect(on_email_flags_changed); folder.email_removed.connect(on_email_removed); - + folder_information.set(folder, new MonitorInformation(folder, cancellable)); - + folder_added(folder); } - + public void remove_folder(Geary.Folder folder) { if (!folder_information.has_key(folder)) return; - + folder.email_locally_appended.disconnect(on_email_locally_appended); folder.email_flags_changed.disconnect(on_email_flags_changed); folder.email_removed.disconnect(on_email_removed); - + total_new_messages -= folder_information.get(folder).count; - + folder_information.unset(folder); - + folder_removed(folder); } - + + /** Releases all monitored folders. */ + public void clear_folders() { + // Get an array so the loop does not blow up when removing values. + foreach (Geary.Folder monitored in this.folder_information.keys.to_array()) { + remove_folder(monitored); + } + } + public Gee.Collection get_folders() { return folder_information.keys; } - + public int get_new_message_count(Geary.Folder folder) { assert(folder_information.has_key(folder)); - + return folder_information.get(folder).count; } - + public void add_required_fields(Geary.Email.Field fields) { required_fields |= fields; } - + public bool are_any_new_messages(Geary.Folder folder, Gee.Collection ids) { assert(folder_information.has_key(folder)); MonitorInformation info = folder_information.get(folder); - + foreach (Geary.EmailIdentifier id in ids) { if (info.new_ids.contains(id)) return true; } - + return false; } - + private void on_email_locally_appended(Geary.Folder folder, Gee.Collection email_ids) { do_process_new_email.begin(folder, email_ids); } - + private void on_email_flags_changed(Geary.Folder folder, Gee.Map ids) { retire_new_messages(folder, ids.keys); } - + private void on_email_removed(Geary.Folder folder, Gee.Collection ids) { retire_new_messages(folder, ids); } - + private async void do_process_new_email(Geary.Folder folder, Gee.Collection email_ids) { MonitorInformation info = folder_information.get(folder); - + try { Gee.List? list = yield folder.list_email_by_sparse_id_async(email_ids, required_fields, Geary.Folder.ListFlags.NONE, info.cancellable); if (list == null || list.size == 0) { debug("Warning: %d new emails, but none could be listed", email_ids.size); - + return; } - + new_messages(info, list); - + debug("do_process_new_email: %d messages listed, %d unread in folder %s", list.size, info.count, folder.to_string()); } catch (Error err) { debug("Unable to notify of new email: %s", err.message); } } - + private void new_messages(MonitorInformation info, Gee.Collection emails) { int appended_count = 0; foreach (Geary.Email email in emails) { @@ -156,69 +164,69 @@ debug("Warning: new message %s (%Xh) does not fulfill NewMessagesMonitor required fields of %Xh", email.id.to_string(), email.fields, required_fields); } - + if (info.new_ids.contains(email.id)) continue; - + if (!email.email_flags.is_unread()) continue; - + last_new_message_folder = info.folder; last_new_message = email; - + info.new_ids.add(email.id); appended_count++; } - + update_count(info, true, appended_count); } - + private void retire_new_messages(Geary.Folder folder, Gee.Collection email_ids) { MonitorInformation info = folder_information.get(folder); - + int removed_count = 0; foreach (Geary.EmailIdentifier email_id in email_ids) { if (last_new_message != null && last_new_message.id.equal_to(email_id)) { last_new_message_folder = null; last_new_message = null; } - + if (info.new_ids.remove(email_id)) removed_count++; } - + update_count(info, false, removed_count); } - + public void clear_new_messages(Geary.Folder folder) { assert(folder_information.has_key(folder)); MonitorInformation info = folder_information.get(folder); - + info.new_ids.clear(); last_new_message_folder = null; last_new_message = null; - + update_count(info, false, 0); } - + public void clear_all_new_messages() { foreach(Geary.Folder folder in folder_information.keys) clear_new_messages(folder); } - + private void update_count(MonitorInformation info, bool arrived, int delta) { int new_size = info.new_ids.size; - + // Documentation for "notify" signal seems to suggest that it's possible for the signal to // fire even if the value of the property doesn't change. Since this signal can trigger // big events, want to avoid firing it unless necessary if (info.count == new_size) return; - + total_new_messages += new_size - info.count; info.count = new_size; - + if (arrived) new_messages_arrived(info.folder, info.count, delta); else diff -Nru geary-0.12.4/src/client/notification/null-indicator.vala geary-3.32.0/src/client/notification/null-indicator.vala --- geary-0.12.4/src/client/notification/null-indicator.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/notification/null-indicator.vala 2019-03-17 13:39:29.000000000 +0000 @@ -9,7 +9,7 @@ public class NullIndicator : NewMessagesIndicator { public NullIndicator(NewMessagesMonitor monitor) { base (monitor); - + debug("No messaging menu support in this build"); } } diff -Nru geary-0.12.4/src/client/notification/unity-launcher.vala geary-3.32.0/src/client/notification/unity-launcher.vala --- geary-0.12.4/src/client/notification/unity-launcher.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/notification/unity-launcher.vala 2019-03-17 13:39:29.000000000 +0000 @@ -8,24 +8,24 @@ #if HAVE_LIBUNITY private NewMessagesMonitor? monitor = null; private Unity.LauncherEntry? entry = null; - + public UnityLauncher(NewMessagesMonitor monitor) { this.monitor = monitor; - + entry = Unity.LauncherEntry.get_for_desktop_id("org.gnome.Geary.desktop"); set_count(0); - + monitor.folder_removed.connect(on_folder_removed); monitor.new_messages_arrived.connect(on_new_messages_changed); monitor.new_messages_retired.connect(on_new_messages_changed); } - + ~UnityLauncher() { monitor.folder_removed.disconnect(on_folder_removed); monitor.new_messages_arrived.disconnect(on_new_messages_changed); monitor.new_messages_retired.disconnect(on_new_messages_changed); } - + private void update_count() { // This is the dead-simple approach. It could be optimized, but // doesn't seem like it's worth too much effort. @@ -34,20 +34,20 @@ if (monitor.should_notify_new_messages(folder)) count += monitor.get_new_message_count(folder); } - + set_count(count); } - + private void set_count(int count) { entry.count = count; entry.count_visible = (count != 0); debug("set unity launcher entry count to %s", entry.count.to_string()); } - + private void on_new_messages_changed() { update_count(); } - + private void on_folder_removed() { update_count(); } diff -Nru geary-0.12.4/src/client/sidebar/sidebar-branch.vala geary-3.32.0/src/client/sidebar/sidebar-branch.vala --- geary-0.12.4/src/client/sidebar/sidebar-branch.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/sidebar/sidebar-branch.vala 2019-03-17 13:39:29.000000000 +0000 @@ -14,68 +14,68 @@ AUTO_OPEN_ON_NEW_CHILD, STARTUP_EXPAND_TO_FIRST_CHILD, STARTUP_OPEN_GROUPING; - + public bool is_hide_if_empty() { return (this & HIDE_IF_EMPTY) != 0; } - + public bool is_auto_open_on_new_child() { return (this & AUTO_OPEN_ON_NEW_CHILD) != 0; } - + public bool is_startup_expand_to_first_child() { return (this & STARTUP_EXPAND_TO_FIRST_CHILD) != 0; } - + public bool is_startup_open_grouping() { return (this & STARTUP_OPEN_GROUPING) != 0; } } - + private class Node { public delegate void PruneCallback(Node node); - + public delegate void ChildrenReorderedCallback(Node node); - + public Sidebar.Entry entry; public weak Node? parent; public CompareFunc comparator; public Gee.SortedSet? children = null; - + public Node(Sidebar.Entry entry, Node? parent, CompareFunc comparator) { this.entry = entry; this.parent = parent; this.comparator = comparator; } - + private static int comparator_wrapper(Node anode, Node bnode) { if (anode == bnode) return 0; - + assert(anode.parent == bnode.parent); - + return anode.parent.comparator(anode.entry, bnode.entry); } - + public bool has_children() { return (children != null && children.size > 0); } - + public void add_child(Node child) { child.parent = this; if (children == null) children = new Gee.TreeSet(comparator_wrapper); - + bool added = children.add(child); assert(added); } - + public void remove_child(Node child) { assert(children != null); - + Gee.SortedSet new_children = new Gee.TreeSet(comparator_wrapper); - + // For similar reasons as in reorder_child(), can't rely on TreeSet to locate this // node because we need reference equality. bool found = false; @@ -85,58 +85,58 @@ else found = true; } - + assert(found); - + if (new_children.size != 0) children = new_children; else children = null; - + child.parent = null; } - + public void prune_children(PruneCallback cb) { if (children == null) return; - + foreach (Node child in children) child.prune_children(cb); - + Gee.SortedSet old_children = children; children = null; - + // Although this could've been done in the prior loop, it means notifying that // a child has been removed prior to it being removed; this can cause problem // if a signal handler calls back into the Tree to examine/add/remove nodes. foreach (Node child in old_children) cb(child); } - + // This returns the index of the Node purely by reference equality, making it useful if // the criteria the Node is sorted upon has changed. public int index_of_by_reference(Node child) { if (children == null) return -1; - + int index = 0; foreach (Node c in children) { if (child == c) return index; - + index++; } - + return -1; } - + // Returns true if child moved when reordered. public bool reorder_child(Node child) { assert(children != null); - + int old_index = index_of_by_reference(child); assert(old_index >= 0); - + // Because Gee.SortedSet uses the comparator for equality, if the Node's entry state // has changed in such a way that the item is no longer sorted properly, the SortedSet's // search and remove methods are useless. Makes no difference if children.remove() is @@ -146,287 +146,287 @@ Gee.SortedSet new_children = new Gee.TreeSet(comparator_wrapper); bool added = new_children.add_all(children); assert(added); - + children = new_children; - + int new_index = index_of_by_reference(child); assert(new_index >= 0); - + return (old_index != new_index); } - + public void reorder_children(bool recursive, ChildrenReorderedCallback cb) { if (children == null) return; - + Gee.SortedSet reordered = new Gee.TreeSet(comparator_wrapper); reordered.add_all(children); children = reordered; - + if (recursive) { foreach (Node child in children) child.reorder_children(true, cb); } - + cb(this); } - + public void change_comparator(CompareFunc comparator, bool recursive, ChildrenReorderedCallback cb) { this.comparator = comparator; - + // reorder children, but need to do manual recursion to set comparator reorder_children(false, cb); - + if (recursive) { foreach (Node child in children) child.change_comparator(comparator, true, cb); } } } - + private Node root; private Options options; private bool shown = true; private CompareFunc default_comparator; private Gee.HashMap map = new Gee.HashMap(); - + public signal void entry_added(Sidebar.Entry entry); - + public signal void entry_removed(Sidebar.Entry entry); - + public signal void entry_moved(Sidebar.Entry entry); - + public signal void entry_reparented(Sidebar.Entry entry, Sidebar.Entry old_parent); - + public signal void children_reordered(Sidebar.Entry entry); - + public signal void show_branch(bool show); - + public Branch(Sidebar.Entry root, Options options, CompareFunc default_comparator, CompareFunc? root_comparator = null) { this.default_comparator = default_comparator; this.root = new Node(root, null, (root_comparator != null) ? root_comparator : default_comparator); this.options = options; - + map.set(root, this.root); - + if (options.is_hide_if_empty()) set_show_branch(false); } - + public Sidebar.Entry get_root() { return root.entry; } - + public void set_show_branch(bool shown) { if (this.shown == shown) return; - + this.shown = shown; show_branch(shown); } - + public bool get_show_branch() { return shown; } - + public bool is_auto_open_on_new_child() { return options.is_auto_open_on_new_child(); } - + public bool is_startup_expand_to_first_child() { return options.is_startup_expand_to_first_child(); } - + public bool is_startup_open_grouping() { return options.is_startup_open_grouping(); } - + public void graft(Sidebar.Entry parent, Sidebar.Entry entry, CompareFunc? comparator = null) { assert(map.has_key(parent)); assert(!map.has_key(entry)); - + if (options.is_hide_if_empty()) set_show_branch(true); - + Node parent_node = map.get(parent); Node entry_node = new Node(entry, parent_node, (comparator != null) ? comparator : default_comparator); - + parent_node.add_child(entry_node); map.set(entry, entry_node); - + entry_added(entry); } - + // Cannot prune the root. The Branch should simply be removed from the Tree. public void prune(Sidebar.Entry entry) { assert(entry != root.entry); assert(map.has_key(entry)); - + Node entry_node = map.get(entry); - + entry_node.prune_children(prune_callback); - + assert(entry_node.parent != null); entry_node.parent.remove_child(entry_node); - + bool removed = map.unset(entry); assert(removed); - + entry_removed(entry); - + if (options.is_hide_if_empty() && !root.has_children()) set_show_branch(false); } - + // Cannot reparent the root. public void reparent(Sidebar.Entry new_parent, Sidebar.Entry entry) { assert(entry != root.entry); assert(map.has_key(entry)); assert(map.has_key(new_parent)); - + Node entry_node = map.get(entry); Node new_parent_node = map.get(new_parent); - + assert(entry_node.parent != null); Sidebar.Entry old_parent = entry_node.parent.entry; - + entry_node.parent.remove_child(entry_node); new_parent_node.add_child(entry_node); - + entry_reparented(entry, old_parent); } - + public bool has_entry(Sidebar.Entry entry) { return (root.entry == entry || map.has_key(entry)); } - - // Call when a value related to the comparison of this entry has changed. The root cannot be + + // Call when a value related to the comparison of this entry has changed. The root cannot be // reordered. public void reorder(Sidebar.Entry entry) { assert(entry != root.entry); - + Node? entry_node = map.get(entry); assert(entry_node != null); - + assert(entry_node.parent != null); if (entry_node.parent.reorder_child(entry_node)) entry_moved(entry); } - + // Call when the entire tree needs to be reordered. public void reorder_all() { root.reorder_children(true, children_reordered_callback); } - + // Call when the children of the entry need to be reordered. public void reorder_children(Sidebar.Entry entry, bool recursive) { Node? entry_node = map.get(entry); assert(entry_node != null); - + entry_node.reorder_children(recursive, children_reordered_callback); } - + public void change_all_comparators(CompareFunc? comparator) { root.change_comparator(comparator, true, children_reordered_callback); } - + public void change_comparator(Sidebar.Entry entry, bool recursive, CompareFunc? comparator) { Node? entry_node = map.get(entry); assert(entry_node != null); - + entry_node.change_comparator(comparator, recursive, children_reordered_callback); } - + public int get_child_count(Sidebar.Entry parent) { Node? parent_node = map.get(parent); assert(parent_node != null); - + return (parent_node.children != null) ? parent_node.children.size : 0; } - + // Gets a snapshot of the children of the entry; this list will not be changed as the // branch is updated. public Gee.List? get_children(Sidebar.Entry parent) { assert(map.has_key(parent)); - + Node parent_node = map.get(parent); if (parent_node.children == null) return null; - + Gee.List child_entries = new Gee.ArrayList(); foreach (Node child in parent_node.children) child_entries.add(child.entry); - + return child_entries; } - + public Sidebar.Entry? find_first_child(Sidebar.Entry parent, Locator locator) { Node? parent_node = map.get(parent); assert(parent_node != null); - + if (parent_node.children == null) return null; - + foreach (Node child in parent_node.children) { if (locator(child.entry)) return child.entry; } - + return null; } - + // Returns null if entry is root; public Sidebar.Entry? get_parent(Sidebar.Entry entry) { if (entry == root.entry) return null; - + Node? entry_node = map.get(entry); assert(entry_node != null); assert(entry_node.parent != null); - + return entry_node.parent.entry; } - + // Returns null if entry is root; public Sidebar.Entry? get_previous_sibling(Sidebar.Entry entry) { if (entry == root.entry) return null; - + Node? entry_node = map.get(entry); assert(entry_node != null); assert(entry_node.parent != null); assert(entry_node.parent.children != null); - + Node? sibling = entry_node.parent.children.lower(entry_node); - + return (sibling != null) ? sibling.entry : null; } - + // Returns null if entry is root; public Sidebar.Entry? get_next_sibling(Sidebar.Entry entry) { if (entry == root.entry) return null; - + Node? entry_node = map.get(entry); assert(entry_node != null); assert(entry_node.parent != null); assert(entry_node.parent.children != null); - + Node? sibling = entry_node.parent.children.higher(entry_node); - + return (sibling != null) ? sibling.entry : null; } - + private void prune_callback(Node node) { entry_removed(node.entry); } - + private void children_reordered_callback(Node node) { children_reordered(node.entry); } diff -Nru geary-0.12.4/src/client/sidebar/sidebar-common.vala geary-3.32.0/src/client/sidebar/sidebar-common.vala --- geary-0.12.4/src/client/sidebar/sidebar-common.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/sidebar/sidebar-common.vala 2019-03-17 13:39:29.000000000 +0000 @@ -7,46 +7,46 @@ // A simple grouping Entry that is only expandable public class Sidebar.Grouping : Geary.BaseObject, Sidebar.Entry, Sidebar.ExpandableEntry, Sidebar.RenameableEntry { - + private string name; private string? tooltip; private string? icon; - + public Grouping(string name, string? icon, string? tooltip = null) { this.name = name; this.icon = icon; this.tooltip = tooltip; } - + public void rename(string name) { this.name = name; sidebar_name_changed(name); } - + public bool is_user_renameable() { return false; } - + public string get_sidebar_name() { return name; } - + public string? get_sidebar_tooltip() { return tooltip; } - + public string? get_sidebar_icon() { return icon; } - + public int get_count() { return -1; } - + public string to_string() { return name; } - + public bool expand_on_select() { return true; } @@ -57,7 +57,7 @@ public RootOnlyBranch(Sidebar.Entry root) { base (root, Sidebar.Branch.Options.NONE, null_comparator); } - + private static int null_comparator(Sidebar.Entry a, Sidebar.Entry b) { return (a != b) ? -1 : 0; } @@ -72,12 +72,12 @@ */ public class Sidebar.Header : Sidebar.Grouping, Sidebar.EmphasizableEntry { private bool emphasized; - + public Header(string name, bool emphasized = true) { base(name, null); this.emphasized = emphasized; } - + public bool is_emphasized() { return emphasized; } diff -Nru geary-0.12.4/src/client/sidebar/sidebar-count-cell-renderer.vala geary-3.32.0/src/client/sidebar/sidebar-count-cell-renderer.vala --- geary-0.12.4/src/client/sidebar/sidebar-count-cell-renderer.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/sidebar/sidebar-count-cell-renderer.vala 2019-03-17 13:39:29.000000000 +0000 @@ -9,36 +9,36 @@ */ public class SidebarCountCellRenderer : Gtk.CellRenderer { private const int HORIZONTAL_MARGIN = 4; - + public int counter { get; set; } - + private CountBadge unread_count = new CountBadge(1); - + public SidebarCountCellRenderer() { } - + public override Gtk.SizeRequestMode get_request_mode() { return Gtk.SizeRequestMode.WIDTH_FOR_HEIGHT; } - + public override void get_preferred_width(Gtk.Widget widget, out int minimum_size, out int natural_size) { unread_count.count = counter; minimum_size = unread_count.get_width(widget) + FormattedConversationData.LINE_SPACING; natural_size = minimum_size; } - - public override void render(Cairo.Context ctx, Gtk.Widget widget, Gdk.Rectangle background_area, + + public override void render(Cairo.Context ctx, Gtk.Widget widget, Gdk.Rectangle background_area, Gdk.Rectangle cell_area, Gtk.CellRendererState flags) { unread_count.count = counter; - + // Compute x and y locations to right-align and vertically center the count. int x = cell_area.x + (cell_area.width - unread_count.get_width(widget)) - HORIZONTAL_MARGIN; int y = cell_area.y + ((cell_area.height - unread_count.get_height(widget)) / 2); unread_count.render(widget, ctx, x, y, false); } - + // This is implemented because it's required; ignore it and look at get_preferred_width() instead. - public override void get_size(Gtk.Widget widget, Gdk.Rectangle? cell_area, out int x_offset, + public override void get_size(Gtk.Widget widget, Gdk.Rectangle? cell_area, out int x_offset, out int y_offset, out int width, out int height) { // Set values to avoid compiler warning. x_offset = 0; diff -Nru geary-0.12.4/src/client/sidebar/sidebar-entry.vala geary-3.32.0/src/client/sidebar/sidebar-entry.vala --- geary-0.12.4/src/client/sidebar/sidebar-entry.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/sidebar/sidebar-entry.vala 2019-03-17 13:39:29.000000000 +0000 @@ -6,24 +6,24 @@ public interface Sidebar.Entry : Object { public signal void sidebar_name_changed(string name); - + public signal void sidebar_tooltip_changed(string? tooltip); - + public signal void sidebar_count_changed(int count); - + public abstract string get_sidebar_name(); - + public abstract string? get_sidebar_tooltip(); - + public abstract string? get_sidebar_icon(); - + public abstract int get_count(); - + public abstract string to_string(); - + internal virtual void grafted(Sidebar.Tree tree) { } - + internal virtual void pruned(Sidebar.Tree tree) { } } @@ -37,16 +37,16 @@ public interface Sidebar.RenameableEntry : Sidebar.Entry { public signal void sidebar_name_changed(string name); - + public abstract void rename(string new_name); - + // Return true to allow the user to rename the sidebar entry in the UI. public abstract bool is_user_renameable(); } public interface Sidebar.EmphasizableEntry : Sidebar.Entry { public signal void is_emphasized_changed(bool emphasized); - + public abstract bool is_emphasized(); } diff -Nru geary-0.12.4/src/client/sidebar/sidebar-tree.vala geary-3.32.0/src/client/sidebar/sidebar-tree.vala --- geary-0.12.4/src/client/sidebar/sidebar-tree.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/sidebar/sidebar-tree.vala 2019-03-17 13:39:29.000000000 +0000 @@ -6,44 +6,44 @@ public class Sidebar.Tree : Gtk.TreeView { public const int ICON_SIZE = 16; - + // Only one ExternalDropHandler can be registered with the Tree; it's responsible for completing // the "drag-data-received" signal properly. public delegate void ExternalDropHandler(Gdk.DragContext context, Sidebar.Entry? entry, Gtk.SelectionData data, uint info, uint time); - + private class EntryWrapper : Object { public Sidebar.Entry entry; public Gtk.TreeRowReference row; - + public EntryWrapper(Gtk.TreeModel model, Sidebar.Entry entry, Gtk.TreePath path) { this.entry = entry; this.row = new Gtk.TreeRowReference(model, path); } - + public Gtk.TreePath get_path() { return row.get_path(); } - + public Gtk.TreeIter get_iter() { Gtk.TreeIter iter; bool valid = row.get_model().get_iter(out iter, get_path()); assert(valid); - + return iter; } } - + private class RootWrapper : EntryWrapper { public int root_position; - + public RootWrapper(Gtk.TreeModel model, Sidebar.Entry entry, Gtk.TreePath path, int root_position) { base (model, entry, path); - + this.root_position = root_position; } } - + private enum Columns { NAME, TOOLTIP, @@ -52,7 +52,7 @@ COUNTER, N_COLUMNS } - + private Gtk.TreeStore store = new Gtk.TreeStore(Columns.N_COLUMNS, typeof (string), // NAME typeof (string?), // TOOLTIP @@ -60,7 +60,7 @@ typeof (string?), // ICON typeof (int) // COUNTER ); - + private Gtk.IconTheme? icon_theme; private Gtk.CellRendererText text_renderer; private unowned ExternalDropHandler drop_handler; @@ -77,27 +77,26 @@ private bool is_internal_drag_in_progress = false; private Sidebar.Entry? internal_drag_source_entry = null; private Gtk.TreeRowReference? old_path_ref = null; - + public signal void entry_selected(Sidebar.SelectableEntry selectable); - + public signal void selected_entry_removed(Sidebar.SelectableEntry removed); - + public signal void branch_added(Sidebar.Branch branch); - + public signal void branch_removed(Sidebar.Branch branch); - + public signal void branch_shown(Sidebar.Branch branch, bool shown); - + public Tree(Gtk.TargetEntry[] target_entries, Gdk.DragAction actions, ExternalDropHandler drop_handler, Gtk.IconTheme? theme = null) { set_model(store); icon_theme = theme; get_style_context().add_class("sidebar"); - + Gtk.TreeViewColumn text_column = new Gtk.TreeViewColumn(); text_column.set_expand(true); Gtk.CellRendererPixbuf icon_renderer = new Gtk.CellRendererPixbuf(); - icon_renderer.follow_state = true; text_column.pack_start(icon_renderer, false); text_column.add_attribute(icon_renderer, "icon_name", Columns.ICON); text_column.set_cell_data_func(icon_renderer, icon_renderer_function); @@ -108,36 +107,35 @@ text_column.pack_start(text_renderer, true); text_column.add_attribute(text_renderer, "markup", Columns.NAME); append_column(text_column); - + // Count column. Gtk.TreeViewColumn count_column = new Gtk.TreeViewColumn(); SidebarCountCellRenderer unread_renderer = new SidebarCountCellRenderer(); count_column.pack_start(unread_renderer, false); count_column.add_attribute(unread_renderer, "counter", Columns.COUNTER); append_column(count_column); - + set_headers_visible(false); set_enable_search(false); set_search_column(-1); - set_rules_hint(false); set_show_expanders(true); set_reorderable(false); set_enable_tree_lines(false); set_grid_lines(Gtk.TreeViewGridLines.NONE); set_tooltip_column(Columns.TOOLTIP); - + Gtk.TreeSelection selection = get_selection(); selection.set_mode(Gtk.SelectionMode.BROWSE); selection.set_select_function(on_selection); - + test_expand_row.connect(on_toggle_row); test_collapse_row.connect(on_toggle_row); - - // It Would Be Nice if the target entries and actions were gleaned by querying each + + // It Would Be Nice if the target entries and actions were gleaned by querying each // Sidebar.Entry as it was added, but that's a tad too complicated for our needs // currently enable_model_drag_dest(target_entries, actions); - + // Drag source removed as per http://redmine.yorba.org/issues/4701 // // Reason: this isn't working correctly (Sidebar.InternalDragSourceEntry should be @@ -145,21 +143,21 @@ // the entire mechanism shifted somehow under GTK 3; see Gtk.TreeDragSource and // Gtk.TreeDragDest for more information on how Sidebar should implement this // properly - + this.drop_handler = drop_handler; - + popup_menu.connect(on_context_menu_keypress); - + drag_begin.connect(on_drag_begin); drag_end.connect(on_drag_end); drag_motion.connect(on_drag_motion); } - + ~Tree() { text_renderer.editing_canceled.disconnect(on_editing_canceled); text_renderer.editing_started.disconnect(on_editing_started); } - + public void icon_renderer_function(Gtk.CellLayout layout, Gtk.CellRenderer renderer, Gtk.TreeModel model, Gtk.TreeIter iter) { EntryWrapper? wrapper = get_wrapper_at_iter(iter); if (wrapper == null) { @@ -167,7 +165,7 @@ } renderer.visible = !(wrapper.entry is Sidebar.Header); } - + public void counter_renderer_function(Gtk.CellLayout layout, Gtk.CellRenderer renderer, Gtk.TreeModel model, Gtk.TreeIter iter) { EntryWrapper? wrapper = get_wrapper_at_iter(iter); if (wrapper == null) { @@ -175,101 +173,101 @@ } renderer.visible = !(wrapper.entry is Sidebar.Header); } - + private void on_drag_begin(Gdk.DragContext ctx) { is_internal_drag_in_progress = true; } - + private void on_drag_end(Gdk.DragContext ctx) { is_internal_drag_in_progress = false; internal_drag_source_entry = null; } - + private bool on_drag_motion (Gdk.DragContext context, int x, int y, uint time_) { if (is_internal_drag_in_progress && internal_drag_source_entry == null) { Gtk.TreePath? path; Gtk.TreeViewDropPosition position; get_dest_row_at_pos(x, y, out path, out position); - + if (path != null) { EntryWrapper wrapper = get_wrapper_at_path(path); if (wrapper != null) internal_drag_source_entry = wrapper.entry; } } - + return false; } - + private bool has_wrapper(Sidebar.Entry entry) { return entry_map.has_key(entry); } - + private EntryWrapper? get_wrapper(Sidebar.Entry entry) { EntryWrapper? wrapper = entry_map.get(entry); if (wrapper == null) - warning("Entry %s not found in sidebar", entry.to_string()); - + debug("Entry %s not found in sidebar", entry.to_string()); + return wrapper; } - + private EntryWrapper? get_wrapper_at_iter(Gtk.TreeIter iter) { Value val; store.get_value(iter, Columns.WRAPPER, out val); - + EntryWrapper? wrapper = (EntryWrapper?) val; if (wrapper == null) message("No entry found in sidebar at %s", store.get_path(iter).to_string()); - + return wrapper; } - + private EntryWrapper? get_wrapper_at_path(Gtk.TreePath path) { Gtk.TreeIter iter; if (!store.get_iter(out iter, path)) { message("No entry found in sidebar at %s", path.to_string()); - + return null; } - + return get_wrapper_at_iter(iter); } - + public void set_default_context_menu(Gtk.Menu context_menu) { default_context_menu = context_menu; } - + // Note that this method will result in the "entry-selected" signal to fire if mask_signal // is set to false. public bool place_cursor(Sidebar.Entry entry, bool mask_signal) { if (!expand_to_entry(entry)) return false; - + EntryWrapper? wrapper = get_wrapper(entry); if (wrapper == null) return false; - + get_selection().select_path(wrapper.get_path()); - + mask_entry_selected_signal = mask_signal; set_cursor(wrapper.get_path(), null, false); mask_entry_selected_signal = false; - + return scroll_to_entry(entry); } - + public bool is_selected(Sidebar.Entry entry) { EntryWrapper? wrapper = get_wrapper(entry); - + // Even though get_selection() does not report its return type as nullable, it can be null // if the window has been destroyed. Gtk.TreeSelection selection = get_selection(); if (selection == null) return false; - + return (wrapper != null) ? selection.path_is_selected(wrapper.get_path()) : false; } - + public bool is_any_selected() { return get_selection().count_selected_rows() != 0; } @@ -288,18 +286,18 @@ private string get_name_for_entry(Sidebar.Entry entry) { string name = Geary.HTML.escape_markup(entry.get_sidebar_name()); - + Sidebar.EmphasizableEntry? emphasizable_entry = entry as Sidebar.EmphasizableEntry; if (emphasizable_entry != null && emphasizable_entry.is_emphasized()) name = "%s".printf(name); - + return name; } - + public virtual bool accept_cursor_changed() { return true; } - + public override void cursor_changed() { Gtk.TreePath? path = get_selected_path(); if (path == null) { @@ -307,16 +305,16 @@ base.cursor_changed(); return; } - + EntryWrapper? wrapper = get_wrapper_at_path(path); - + if (selected_wrapper != wrapper) { EntryWrapper old_wrapper = selected_wrapper; selected_wrapper = wrapper; - + if (editing_disabled == 0 && wrapper != null && wrapper.entry is Sidebar.RenameableEntry) text_renderer.editable = ((Sidebar.RenameableEntry) wrapper.entry).is_user_renameable(); - + if (wrapper != null && !mask_entry_selected_signal) { Sidebar.SelectableEntry? selectable = wrapper.entry as Sidebar.SelectableEntry; if (selectable != null) { @@ -328,16 +326,16 @@ } } } - + if (base.cursor_changed != null) base.cursor_changed(); } - + public void disable_editing() { if (editing_disabled++ == 0) text_renderer.editable = false; } - + public void enable_editing() { Gtk.TreePath? path = get_selected_path(); if (path != null && editing_disabled > 0 && --editing_disabled == 0) { @@ -347,7 +345,7 @@ is_user_renameable(); } } - + public void toggle_branch_expansion(Gtk.TreePath path, bool expand_all) { expander_called_manually = true; if (is_row_expanded(path)) @@ -355,290 +353,290 @@ else expand_row(path, expand_all); } - + public bool expand_to_entry(Sidebar.Entry entry) { expander_called_manually = true; EntryWrapper? wrapper = get_wrapper(entry); if (wrapper == null) return false; - + expand_to_path(wrapper.get_path()); - + return true; } - + public void expand_to_first_child(Sidebar.Entry entry) { expander_called_manually = true; EntryWrapper? wrapper = get_wrapper(entry); if (wrapper == null) return; - + Gtk.TreePath path = wrapper.get_path(); - + Gtk.TreeIter iter; while (store.get_iter(out iter, path)) { if (!store.iter_has_child(iter)) break; - + path.down(); } - + expand_to_path(path); } - + public bool has_branch(Sidebar.Branch branch) { return branches.has_key(branch); } - + public void graft(Sidebar.Branch branch, int position) { assert(!branches.has_key(branch)); - + branches.set(branch, position); - + if (branch.get_show_branch()) { associate_branch(branch); - + if (branch.is_startup_expand_to_first_child()) expand_to_first_child(branch.get_root()); if (branch.is_startup_open_grouping()) expand_to_entry(branch.get_root()); } - + branch.entry_added.connect(on_branch_entry_added); branch.entry_removed.connect(on_branch_entry_removed); branch.entry_moved.connect(on_branch_entry_moved); branch.entry_reparented.connect(on_branch_entry_reparented); branch.children_reordered.connect(on_branch_children_reordered); branch.show_branch.connect(on_show_branch); - + branch_added(branch); } - + public int get_position_for_branch(Sidebar.Branch branch) { if (branches.has_key(branch)) return branches.get(branch); - + return int.MIN; } - + // This is used to associate a known branch with the TreeView. private void associate_branch(Sidebar.Branch branch) { assert(branches.has_key(branch)); - + int position = branches.get(branch); - + Gtk.TreeIter? insertion_iter = null; - + // search current roots for insertion point Gtk.TreeIter iter; bool found = store.get_iter_first(out iter); while (found) { RootWrapper? root_wrapper = get_wrapper_at_iter(iter) as RootWrapper; assert(root_wrapper != null); - + if (position < root_wrapper.root_position) { store.insert_before(out insertion_iter, null, iter); - + break; } - + found = store.iter_next(ref iter); } - + // if not found, append if (insertion_iter == null) store.append(out insertion_iter, null); - + associate_wrapper(insertion_iter, new RootWrapper(store, branch.get_root(), store.get_path(insertion_iter), position)); - + // mirror the branch's initial contents from below the root down, let the signals handle // future work associate_children(branch, branch.get_root(), insertion_iter); } - + private void associate_children(Sidebar.Branch branch, Sidebar.Entry parent, Gtk.TreeIter parent_iter) { Gee.List? children = branch.get_children(parent); if (children == null) return; - + foreach (Sidebar.Entry child in children) { Gtk.TreeIter append_iter; store.append(out append_iter, parent_iter); - + associate_entry(append_iter, child); associate_children(branch, child, append_iter); } } - + private void associate_entry(Gtk.TreeIter assoc_iter, Sidebar.Entry entry) { associate_wrapper(assoc_iter, new EntryWrapper(store, entry, store.get_path(assoc_iter))); } - + private void associate_wrapper(Gtk.TreeIter assoc_iter, EntryWrapper wrapper) { Sidebar.Entry entry = wrapper.entry; - + assert(!entry_map.has_key(entry)); entry_map.set(entry, wrapper); - + store.set(assoc_iter, Columns.NAME, get_name_for_entry(entry)); store.set(assoc_iter, Columns.TOOLTIP, entry.get_sidebar_tooltip() != null ? Geary.HTML.escape_markup(entry.get_sidebar_tooltip()) : null); store.set(assoc_iter, Columns.WRAPPER, wrapper); store.set(assoc_iter, Columns.COUNTER, entry.get_count()); load_entry_icons(assoc_iter); - + entry.sidebar_tooltip_changed.connect(on_sidebar_tooltip_changed); entry.sidebar_name_changed.connect(on_sidebar_name_changed); entry.sidebar_count_changed.connect(on_sidebar_count_changed); - + Sidebar.EmphasizableEntry? emphasizable = entry as Sidebar.EmphasizableEntry; if (emphasizable != null) emphasizable.is_emphasized_changed.connect(on_is_emphasized_changed); - + entry.grafted(this); } - + private EntryWrapper reparent_wrapper(Gtk.TreeIter new_iter, EntryWrapper current_wrapper) { Sidebar.Entry entry = current_wrapper.entry; - + bool removed = entry_map.unset(entry); assert(removed); - + EntryWrapper new_wrapper = new EntryWrapper(store, entry, store.get_path(new_iter)); entry_map.set(entry, new_wrapper); - + store.set(new_iter, Columns.NAME, get_name_for_entry(entry)); store.set(new_iter, Columns.TOOLTIP, Geary.HTML.escape_markup(entry.get_sidebar_tooltip())); store.set(new_iter, Columns.COUNTER, entry.get_count()); store.set(new_iter, Columns.WRAPPER, new_wrapper); load_entry_icons(new_iter); - + return new_wrapper; } - + protected void prune_all() { while (branches.keys.size > 0) { Gee.Iterator iterator = branches.keys.iterator(); if (!iterator.next()) break; - + prune(iterator.get()); } } - + public void prune(Sidebar.Branch branch) { assert(branches.has_key(branch)); - + if (has_wrapper(branch.get_root())) disassociate_branch(branch); - + branch.entry_added.disconnect(on_branch_entry_added); branch.entry_removed.disconnect(on_branch_entry_removed); branch.entry_moved.disconnect(on_branch_entry_moved); branch.entry_reparented.disconnect(on_branch_entry_reparented); branch.children_reordered.disconnect(on_branch_children_reordered); branch.show_branch.disconnect(on_show_branch); - + bool removed = branches.unset(branch); assert(removed); - + branch_removed(branch); } - + private void disassociate_branch(Sidebar.Branch branch) { RootWrapper? root_wrapper = get_wrapper(branch.get_root()) as RootWrapper; assert(root_wrapper != null); - + disassociate_wrapper_and_signal(root_wrapper, false); } - + // A wrapper for disassociate_wrapper() (?!?) that fires the "selected-entry-removed" signal if // condition exists private void disassociate_wrapper_and_signal(EntryWrapper wrapper, bool only_children) { bool selected = is_selected(wrapper.entry); - + disassociate_wrapper(wrapper, only_children); - + if (selected) { Sidebar.SelectableEntry? selectable = wrapper.entry as Sidebar.SelectableEntry; assert(selectable != null); - + selected_entry_removed(selectable); } } - + private void disassociate_wrapper(EntryWrapper wrapper, bool only_children) { Gee.ArrayList children = new Gee.ArrayList(); - + Gtk.TreeIter child_iter; bool found = store.iter_children(out child_iter, wrapper.get_iter()); while (found) { EntryWrapper? child_wrapper = get_wrapper_at_iter(child_iter); assert(child_wrapper != null); - + children.add(child_wrapper); - + found = store.iter_next(ref child_iter); } - + foreach (EntryWrapper child_wrapper in children) disassociate_wrapper(child_wrapper, false); - + if (only_children) return; - + Gtk.TreeIter iter = wrapper.get_iter(); store.remove(ref iter); - + if (selected_wrapper == wrapper) selected_wrapper = null; - + Sidebar.Entry entry = wrapper.entry; - + entry.pruned(this); - + entry.sidebar_tooltip_changed.disconnect(on_sidebar_tooltip_changed); entry.sidebar_name_changed.disconnect(on_sidebar_name_changed); entry.sidebar_count_changed.disconnect(on_sidebar_count_changed); - + Sidebar.EmphasizableEntry? emphasizable = entry as Sidebar.EmphasizableEntry; if (emphasizable != null) emphasizable.is_emphasized_changed.disconnect(on_is_emphasized_changed); - + bool removed = entry_map.unset(entry); assert(removed); } - + private void on_branch_entry_added(Sidebar.Branch branch, Sidebar.Entry entry) { Sidebar.Entry? parent = branch.get_parent(entry); assert(parent != null); - + EntryWrapper? parent_wrapper = get_wrapper(parent); assert(parent_wrapper != null); - + Gtk.TreeIter insertion_iter; Sidebar.Entry? next = branch.get_next_sibling(entry); if (next != null) { EntryWrapper next_wrapper = get_wrapper(next); - + // insert before the next sibling in this branch level store.insert_before(out insertion_iter, parent_wrapper.get_iter(), next_wrapper.get_iter()); } else { // append to the bottom of this branch level store.append(out insertion_iter, parent_wrapper.get_iter()); } - + associate_entry(insertion_iter, entry); associate_children(branch, entry, insertion_iter); - + if (branch.is_auto_open_on_new_child()) expand_to_entry(entry); } - + private void on_branch_entry_removed(Sidebar.Branch branch, Sidebar.Entry entry) { EntryWrapper? wrapper = get_wrapper(entry); if (wrapper != null) { @@ -646,120 +644,120 @@ disassociate_wrapper_and_signal(wrapper, false); } } - + private void on_branch_entry_moved(Sidebar.Branch branch, Sidebar.Entry entry) { EntryWrapper? wrapper = get_wrapper(entry); assert(wrapper != null); assert(!(wrapper is RootWrapper)); - + // null means entry is now at the top of the sibling list Gtk.TreeIter? prev_iter = null; Sidebar.Entry? prev = branch.get_previous_sibling(entry); if (prev != null) { EntryWrapper? prev_wrapper = get_wrapper(prev); assert(prev_wrapper != null); - + prev_iter = prev_wrapper.get_iter(); } - + Gtk.TreeIter entry_iter = wrapper.get_iter(); store.move_after(ref entry_iter, prev_iter); } - + private void on_branch_entry_reparented(Sidebar.Branch branch, Sidebar.Entry entry, Sidebar.Entry old_parent) { EntryWrapper? wrapper = get_wrapper(entry); assert(wrapper != null); assert(!(wrapper is RootWrapper)); - + bool selected = (get_current_path().compare(wrapper.get_path()) == 0); - + // remove from current position in tree Gtk.TreeIter iter = wrapper.get_iter(); store.remove(ref iter); - + Sidebar.Entry? parent = branch.get_parent(entry); assert(parent != null); - + EntryWrapper? parent_wrapper = get_wrapper(parent); assert(parent_wrapper != null); - + // null means entry is now at the top of the sibling list Gtk.TreeIter? prev_iter = null; Sidebar.Entry? prev = branch.get_previous_sibling(entry); if (prev != null) { EntryWrapper? prev_wrapper = get_wrapper(prev); assert(prev_wrapper != null); - + prev_iter = prev_wrapper.get_iter(); } - + Gtk.TreeIter new_iter; store.insert_after(out new_iter, parent_wrapper.get_iter(), prev_iter); - + EntryWrapper new_wrapper = reparent_wrapper(new_iter, wrapper); - + if (selected) { expand_to_entry(new_wrapper.entry); place_cursor(new_wrapper.entry, false); } } - + private void on_branch_children_reordered(Sidebar.Branch branch, Sidebar.Entry entry) { Gee.List? children = branch.get_children(entry); if (children == null) return; - + // This works by moving the entries to the bottom of the tree's list in the order they // are presented in the Sidebar.Branch list. foreach (Sidebar.Entry child in children) { EntryWrapper? child_wrapper = get_wrapper(child); assert(child_wrapper != null); - + Gtk.TreeIter child_iter = child_wrapper.get_iter(); store.move_before(ref child_iter, null); } } - + private void on_show_branch(Sidebar.Branch branch, bool shown) { if (shown) associate_branch(branch); else disassociate_branch(branch); - + branch_shown(branch, shown); } - + private void on_sidebar_tooltip_changed(Sidebar.Entry entry, string? tooltip) { EntryWrapper? wrapper = get_wrapper(entry); assert(wrapper != null); - - store.set(wrapper.get_iter(), Columns.TOOLTIP, tooltip != null ? + + store.set(wrapper.get_iter(), Columns.TOOLTIP, tooltip != null ? Geary.HTML.escape_markup(tooltip) : null); } - + private void rename_entry(Sidebar.Entry entry) { EntryWrapper? wrapper = get_wrapper(entry); assert(wrapper != null); - + store.set(wrapper.get_iter(), Columns.NAME, get_name_for_entry(entry)); } - + private void on_sidebar_name_changed(Sidebar.Entry entry, string name) { rename_entry(entry); } - + private void on_is_emphasized_changed(Sidebar.EmphasizableEntry entry, bool is_emphasized) { rename_entry(entry); } - + private void on_sidebar_count_changed(Sidebar.Entry entry, int coun) { EntryWrapper? wrapper = get_wrapper(entry); assert(wrapper != null); - + store.set(wrapper.get_iter(), Columns.COUNTER, entry.get_count()); } - + private void load_entry_icons(Gtk.TreeIter iter) { EntryWrapper? wrapper = get_wrapper_at_iter(iter); if (wrapper == null) @@ -767,10 +765,10 @@ string? icon = wrapper.entry.get_sidebar_icon(); store.set(iter, Columns.ICON, icon); } - + private void load_branch_icons(Gtk.TreeIter iter) { load_entry_icons(iter); - + Gtk.TreeIter child_iter; if (store.iter_children(out child_iter, iter)) { do { @@ -778,89 +776,86 @@ } while (store.iter_next(ref child_iter)); } } - + private bool on_selection(Gtk.TreeSelection selection, Gtk.TreeModel model, Gtk.TreePath path, bool path_currently_selected) { // only allow selection if a page is selectable EntryWrapper? wrapper = get_wrapper_at_path(path); - + return (wrapper != null) ? (wrapper.entry is Sidebar.SelectableEntry) : false; } - + private Gtk.TreePath? get_path_from_event(Gdk.EventButton event) { int x, y; Gdk.ModifierType mask; - event.window.get_device_position(Gdk.Display.get_default().get_device_manager() - .get_client_pointer(), out x, out y, out mask); - + event.window.get_device_position( + event.get_seat().get_pointer(), + out x, out y, out mask + ); + int cell_x, cell_y; Gtk.TreePath path; return get_path_at_pos(x, y, out path, null, out cell_x, out cell_y) ? path : null; } - + private Gtk.TreePath? get_current_path() { Gtk.TreeModel model; GLib.List rows = get_selection().get_selected_rows(out model); assert(rows.length() == 0 || rows.length() == 1); - + return rows.length() != 0 ? rows.nth_data(0) : null; } - + private bool on_context_menu_keypress() { GLib.List rows = get_selection().get_selected_rows(null); if (rows == null) return false; - + Gtk.TreePath? path = rows.data; if (path == null) return false; - + scroll_to_cell(path, null, false, 0, 0); - + return popup_context_menu(path); } - + private bool popup_context_menu(Gtk.TreePath path, Gdk.EventButton? event = null) { EntryWrapper? wrapper = get_wrapper_at_path(path); if (wrapper == null) return false; - + Sidebar.Contextable? contextable = wrapper.entry as Sidebar.Contextable; if (contextable == null) return false; - + Gtk.Menu? context_menu = contextable.get_sidebar_context_menu(event); if (context_menu == null) return false; - - if (event != null) - context_menu.popup(null, null, null, event.button, event.time); - else - context_menu.popup(null, null, null, 0, Gtk.get_current_event_time()); - + + context_menu.popup_at_pointer(event); return true; } - + private bool popup_default_context_menu(Gdk.EventButton event) { if (default_context_menu != null) - default_context_menu.popup(null, null, null, event.button, event.time); - + default_context_menu.popup_at_pointer(event); return true; } - + public bool on_toggle_row(Gtk.TreeIter iter, Gtk.TreePath path) { // Determine whether to allow the row to toggle EntryWrapper? wrapper = get_wrapper_at_iter(iter); if (wrapper == null) { return false; // don't affect things } - + // Most of the time, only allow manual toggles bool should_allow_toggle = expander_called_manually; - + // Cancel out the manual flag expander_called_manually = false; - + // If we are an expanded parent entry with content if (is_row_expanded(path) && store.iter_has_child(iter) && wrapper.entry is Sidebar.SelectableEntry) { // We are taking a special action @@ -876,17 +871,17 @@ // Reset the special behavior count expander_special_count = 0; } - + if (should_allow_toggle) { return false; } // Prevent branch expansion toggle return true; } - + public override bool button_press_event(Gdk.EventButton event) { Gtk.TreePath? path = get_path_from_event(event); - + if (event.button == 3 && event.type == Gdk.EventType.BUTTON_PRESS) { // single right click if (path != null) @@ -898,44 +893,44 @@ old_path_ref = null; return base.button_press_event(event); } - + EntryWrapper? wrapper = get_wrapper_at_path(path); - + if (wrapper == null) { old_path_ref = null; return base.button_press_event(event); } - + // Enable single click to toggle tree entries (bug 4985) if (wrapper.entry is Sidebar.ExpandableEntry || wrapper.entry is Sidebar.InternalDropTargetEntry) { // all labels are InternalDropTargetEntries toggle_branch_expansion(path, false); } - + // Is this a click on an already-highlighted tree item? if ((old_path_ref != null) && (old_path_ref.get_path() != null) && (old_path_ref.get_path().compare(path) == 0)) { - // yes, don't allow single-click editing, but + // yes, don't allow single-click editing, but // pass the event on for dragging. text_renderer.editable = false; return base.button_press_event(event); } - + // Got click on different tree item, make sure it is editable // if it needs to be. if (wrapper.entry is Sidebar.RenameableEntry && ((Sidebar.RenameableEntry) wrapper.entry).is_user_renameable()) { text_renderer.editable = true; } - + // Remember what tree item is highlighted for next time. old_path_ref = new Gtk.TreeRowReference(store, path); } return base.button_press_event(event); } - + public bool is_keypress_interpreted(Gdk.EventKey event) { switch (Gdk.keyval_name(event.keyval)) { case "F2": @@ -943,12 +938,12 @@ case "Return": case "KP_Enter": return true; - + default: return false; } } - + public override bool key_press_event(Gdk.EventKey event) { switch (Gdk.keyval_name(event.keyval)) { case "Return": @@ -956,59 +951,59 @@ Gtk.TreePath? path = get_current_path(); if (path != null) toggle_branch_expansion(path, false); - + return true; - + case "F2": return rename_in_place(); - + case "Delete": Gtk.TreePath? path = get_current_path(); - + return (path != null) ? destroy_path(path) : false; } - + return base.key_press_event(event); } - + public bool rename_entry_in_place(Sidebar.Entry entry) { if (!expand_to_entry(entry)) return false; - + if (!place_cursor(entry, false)) return false; - + return rename_in_place(); } - + private bool rename_in_place() { Gtk.TreePath? cursor_path; Gtk.TreeViewColumn? cursor_column; get_cursor(out cursor_path, out cursor_column); - + if (can_rename_path(cursor_path)) { set_cursor(cursor_path, cursor_column, true); - + return true; } - + return false; } - + public bool scroll_to_entry(Sidebar.Entry entry) { EntryWrapper? wrapper = get_wrapper(entry); if (wrapper == null) return false; - + scroll_to_cell(wrapper.get_path(), null, false, 0, 0); - + return true; } public override void drag_data_get(Gdk.DragContext context, Gtk.SelectionData selection_data, uint info, uint time) { InternalDragSourceEntry? drag_source = null; - + if (internal_drag_source_entry != null) { Sidebar.SelectableEntry selectable = internal_drag_source_entry as Sidebar.SelectableEntry; @@ -1016,7 +1011,7 @@ drag_source = internal_drag_source_entry as InternalDragSourceEntry; } } - + if (drag_source == null) { Gtk.TreePath? selected_path = get_selected_path(); if (selected_path == null) @@ -1025,18 +1020,18 @@ EntryWrapper? wrapper = get_wrapper_at_path(selected_path); if (wrapper == null) return; - + drag_source = wrapper.entry as InternalDragSourceEntry; if (drag_source == null) return; } - + drag_source.prepare_selection_data(selection_data); } - + public override void drag_data_received(Gdk.DragContext context, int x, int y, Gtk.SelectionData selection_data, uint info, uint time) { - + Gtk.TreePath path; Gtk.TreeViewDropPosition pos; if (!get_dest_row_at_pos(x, y, out path, out pos)) { @@ -1045,37 +1040,37 @@ drop_handler(context, null, selection_data, info, time); else Gtk.drag_finish(context, false, false, time); - + return; } - + // Note that a drop outside a sidebar entry is legal if an external drop. EntryWrapper? wrapper = get_wrapper_at_path(path); - + // If an external drop, hand it off to the handler if (Gtk.drag_get_source_widget(context) == null) { drop_handler(context, (wrapper != null) ? wrapper.entry : null, selection_data, info, time); - + return; } - + // An internal drop only applies to DropTargetEntry's if (wrapper == null) { Gtk.drag_finish(context, false, false, time); - + return; } - + Sidebar.InternalDropTargetEntry? targetable = wrapper.entry as Sidebar.InternalDropTargetEntry; if (targetable == null) { Gtk.drag_finish(context, false, false, time); - + return; } - + bool success = targetable.internal_drop_received(context, selection_data); - + Gtk.drag_finish(context, success, false, time); } @@ -1086,36 +1081,36 @@ Gtk.TreePath path; Gtk.TreeViewDropPosition pos; bool has_dest = get_dest_row_at_pos(x, y, out path, out pos); - + // we don't want to insert between rows, only select the rows themselves if (!has_dest || pos == Gtk.TreeViewDropPosition.BEFORE) set_drag_dest_row(path, Gtk.TreeViewDropPosition.INTO_OR_BEFORE); else if (pos == Gtk.TreeViewDropPosition.AFTER) set_drag_dest_row(path, Gtk.TreeViewDropPosition.INTO_OR_AFTER); - + Gdk.drag_status(context, context.get_suggested_action(), time); - + return has_dest; } - + // Returns true if path is renameable, and selects the path as well. private bool can_rename_path(Gtk.TreePath path) { if (editing_disabled > 0) return false; - + EntryWrapper? wrapper = get_wrapper_at_path(path); if (wrapper == null) return false; - + Sidebar.RenameableEntry? renameable = wrapper.entry as Sidebar.RenameableEntry; if (renameable == null) return false; - + if (wrapper.entry is Sidebar.Grouping) return false; - + get_selection().select_path(path); - + return true; } @@ -1123,13 +1118,13 @@ EntryWrapper? wrapper = get_wrapper_at_path(path); if (wrapper == null) return false; - + Sidebar.DestroyableEntry? destroyable = wrapper.entry as Sidebar.DestroyableEntry; if (destroyable == null) return false; - + destroyable.destroy_source(); - + return true; } @@ -1141,24 +1136,24 @@ text_entry.editable = true; } } - + private void on_editing_canceled() { text_entry.editable = false; - + text_entry.editing_done.disconnect(on_editing_done); text_entry.focus_out_event.disconnect(on_editing_focus_out); } - + private void on_editing_done() { text_entry.editable = false; - + EntryWrapper? wrapper = get_wrapper_at_path(get_current_path()); if (wrapper != null) { Sidebar.RenameableEntry? renameable = wrapper.entry as Sidebar.RenameableEntry; if (renameable != null) renameable.rename(text_entry.get_text()); } - + text_entry.editing_done.disconnect(on_editing_done); text_entry.focus_out_event.disconnect(on_editing_focus_out); } diff -Nru geary-0.12.4/src/client/util/util-avatar.vala geary-3.32.0/src/client/util/util-avatar.vala --- geary-0.12.4/src/client/util/util-avatar.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/src/client/util/util-avatar.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,160 @@ +/* + * Copyright 2019 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +namespace Util.Avatar { + + // The following was based on code written by Felipe Borges for + // gnome-control-enter in panels/user-accounts/user-utils.c commit + // 02c288ab6f069a0c106323a93400f192a63cb67e. The copyright in that + // file is: "Copyright 2009-2010 Red Hat, Inc," + + public Gdk.Pixbuf generate_user_picture(string name, int size) { + Cairo.Surface surface = new Cairo.ImageSurface( + Cairo.Format.ARGB32, size, size + ); + Cairo.Context cr = new Cairo.Context(surface); + cr.rectangle(0, 0, size, size); + + /* Fill the background with a colour for the name */ + Gdk.RGBA color = get_color_for_name(name); + cr.set_source_rgb( + color.red / 255.0, color.green / 255.0, color.blue / 255.0 + ); + cr.fill(); + + /* Draw the initials on top */ + string? initials = extract_initials_from_name(name); + if (initials != null) { + string font = "Sans %d".printf((int) GLib.Math.ceil(size / 2.5)); + + cr.set_source_rgb(1.0, 1.0, 1.0); + Pango.Layout layout = Pango.cairo_create_layout(cr); + layout.set_text(initials, -1); + layout.set_font_description(Pango.FontDescription.from_string(font)); + + int width, height; + layout.get_size(out width, out height); + cr.translate(size / 2, size / 2); + cr.move_to( + -((double) width / Pango.SCALE) / 2, + -((double) height / Pango.SCALE) / 2 + ); + Pango.cairo_show_layout(cr, layout); + } + + return Gdk.pixbuf_get_from_surface( + surface, 0, 0, size, size + ); + } + + public Gdk.Pixbuf round_image(Gdk.Pixbuf source) { + int size = source.width; + Cairo.Surface surface = new Cairo.ImageSurface( + Cairo.Format.ARGB32, size, size + ); + Cairo.Context cr = new Cairo.Context(surface); + + /* Clip a circle */ + cr.arc(size / 2, size / 2, size / 2, 0, 2 * GLib.Math.PI); + cr.clip(); + cr.new_path(); + + Gdk.cairo_set_source_pixbuf(cr, source, 0, 0); + cr.paint(); + + return Gdk.pixbuf_get_from_surface( + surface, 0, 0, size, size + ); + } + + public string? extract_initials_from_name(string name) { + string normalized = name.strip().up().normalize(); + string? initials = null; + if (normalized != "") { + GLib.StringBuilder buf = new GLib.StringBuilder(); + unichar c = 0; + int index = 0; + + // Get the first alphanumeric char of the string + for (int i = 0; normalized.get_next_char(ref index, out c); i++) { + if (c.isalnum()) { + buf.append_unichar(c); + break; + } + } + + // Get the first alphanumeric char of the last word of the string + index = normalized.last_index_of_char(' '); + for (int i = 0; normalized.get_next_char(ref index, out c); i++) { + if (c.isalnum()) { + buf.append_unichar(c); + break; + } + } + + if (buf.data.length > 0) { + initials = (string) buf.data; + } + } + return initials; + } + + + public Gdk.RGBA get_color_for_name(string name) { + // https://gitlab.gnome.org/Community/Design/HIG-app-icons/blob/master/GNOME%20HIG.gpl + const double[,3] GNOME_COLOR_PALETTE = { + { 98, 160, 234 }, + { 53, 132, 228 }, + { 28, 113, 216 }, + { 26, 95, 180 }, + { 87, 227, 137 }, + { 51, 209, 122 }, + { 46, 194, 126 }, + { 38, 162, 105 }, + { 248, 228, 92 }, + { 246, 211, 45 }, + { 245, 194, 17 }, + { 229, 165, 10 }, + { 255, 163, 72 }, + { 255, 120, 0 }, + { 230, 97, 0 }, + { 198, 70, 0 }, + { 237, 51, 59 }, + { 224, 27, 36 }, + { 192, 28, 40 }, + { 165, 29, 45 }, + { 192, 97, 203 }, + { 163, 71, 186 }, + { 129, 61, 156 }, + { 97, 53, 131 }, + { 181, 131, 90 }, + { 152, 106, 68 }, + { 134, 94, 60 }, + { 99, 69, 44 } + }; + + Gdk.RGBA color = { 255, 255, 255, 1.0 }; + uint hash; + uint number_of_colors; + uint idx; + + if (name == "") { + return color; + } + + hash = name.hash(); + number_of_colors = GNOME_COLOR_PALETTE.length[0]; + idx = hash % number_of_colors; + + color.red = GNOME_COLOR_PALETTE[idx,0]; + color.green = GNOME_COLOR_PALETTE[idx,1]; + color.blue = GNOME_COLOR_PALETTE[idx,2]; + + return color; + } + +} diff -Nru geary-0.12.4/src/client/util/util-date.vala geary-3.32.0/src/client/util/util-date.vala --- geary-0.12.4/src/client/util/util-date.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/util/util-date.vala 2019-03-17 13:39:29.000000000 +0000 @@ -10,9 +10,9 @@ TWELVE_HOURS, TWENTY_FOUR_HOURS, LOCALE_DEFAULT, - + TOTAL; - + internal int to_index() { // clamp to array boundaries return ((int) this).clamp(0, ClockFormat.TOTAL - 1); @@ -41,27 +41,27 @@ public void init() { if (init_count++ != 0) return; - + // Ripped from Shotwell proposed patch for localizing time (http://redmine.yorba.org/issues/2462) // courtesy Marcel Stimberg. Another example may be found here: // http://bazaar.launchpad.net/~indicator-applet-developers/indicator-datetime/trunk.12.10/view/head:/src/utils.c - + // Because setlocale() is a process-wide setting, need to cache strings at startup, otherwise // risk problems with threading - + string? messages_locale = Intl.setlocale(LocaleCategory.MESSAGES, null); string? time_locale = Intl.setlocale(LocaleCategory.TIME, null); - + // LANGUAGE must be unset before changing locales, as it trumps all the LC_* variables string? language_env = Environment.get_variable("LANGUAGE"); if (language_env != null) Environment.unset_variable("LANGUAGE"); - + // Swap LC_TIME's setting into LC_MESSAGE's. This allows performinglookups of time-based values // from a different translation file, useful in mixed-locale settings if (time_locale != null) Intl.setlocale(LocaleCategory.MESSAGES, time_locale); - + xlat_pretty_dates = new string[ClockFormat.TOTAL]; /// Datetime format for 12-hour time, i.e. 8:31 am /// See http://developer.gnome.org/glib/2.32/glib-GDateTime.html#g-date-time-format @@ -72,16 +72,16 @@ /// Datetime format for the locale default, i.e. 8:31 am or 16:35, /// See http://developer.gnome.org/glib/2.32/glib-GDateTime.html#g-date-time-format xlat_pretty_dates[ClockFormat.LOCALE_DEFAULT] = C_("Default clock format", "%l:%M %P"); - + /// Date format for dates within the current year, i.e. Nov 8 /// See http://developer.gnome.org/glib/2.32/glib-GDateTime.html#g-date-time-format xlat_same_year = _("%b %-e"); - + /// Date format for dates within a different year, i.e. 02/04/10 /// See http://developer.gnome.org/glib/2.32/glib-GDateTime.html#g-date-time-format /* xgettext:no-c-format */ xlat_diff_year = _("%x"); - + xlat_pretty_verbose_dates = new string[ClockFormat.TOTAL]; /// Verbose datetime format for 12-hour time, i.e. November 8, 2010 8:42 am /// See http://developer.gnome.org/glib/2.32/glib-GDateTime.html#g-date-time-format @@ -92,7 +92,7 @@ /// Verbose datetime format for the locale default (full month, day and time) /// See http://developer.gnome.org/glib/2.32/glib-GDateTime.html#g-date-time-format xlat_pretty_verbose_dates[ClockFormat.LOCALE_DEFAULT] = C_("Default full date", "%B %-e, %Y %-l:%M %P"); - + // return LC_MESSAGES back to proper locale and return LANGUAGE environment variable if (messages_locale != null) Intl.setlocale(LocaleCategory.MESSAGES, messages_locale); @@ -104,7 +104,7 @@ private void terminate() { if (--init_count != 0) return; - + xlat_pretty_dates = null; xlat_same_year = null; xlat_diff_year = null; @@ -114,10 +114,10 @@ private bool same_day(DateTime a, DateTime b) { int year1, month1, day1; a.get_ymd(out year1, out month1, out day1); - + int year2, month2, day2; b.get_ymd(out year2, out month2, out day2); - + return year1 == year2 && month1 == month2 && day1 == day2; } @@ -162,38 +162,38 @@ switch (coarse_date) { case CoarseDate.NOW: return _("Now"); - + case CoarseDate.MINUTES: return ngettext("%dm ago", "%dm ago", (ulong) (diff / TimeSpan.MINUTE)).printf((int) (diff / TimeSpan.MINUTE)); - + case CoarseDate.HOURS: int rounded = (int) Math.round((double) diff / TimeSpan.HOUR); return ngettext("%dh ago", "%dh ago", (ulong) rounded).printf(rounded); - + case CoarseDate.TODAY: fmt = xlat_pretty_dates[clock_format.to_index()]; break; - + case CoarseDate.YESTERDAY: return _("Yesterday"); - + case CoarseDate.THIS_WEEK: /// Date format that shows the weekday (Monday, Tuesday, ...) /// See http://developer.gnome.org/glib/2.32/glib-GDateTime.html#g-date-time-format fmt = _("%A"); break; - + case CoarseDate.THIS_YEAR: fmt = xlat_same_year; break; - + case CoarseDate.YEARS: case CoarseDate.FUTURE: default: fmt = xlat_diff_year; break; } - + return datetime.format(fmt); } @@ -201,7 +201,7 @@ DateTime to_local = datetime.to_local(); DateTime now = new DateTime.now_local(); TimeSpan diff = now.difference(to_local); - + return pretty_print_coarse(as_coarse_date(to_local, now, diff), clock_format, to_local, diff); } diff -Nru geary-0.12.4/src/client/util/util-email.vala geary-3.32.0/src/client/util/util-email.vala --- geary-0.12.4/src/client/util/util-email.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/util/util-email.vala 2019-03-17 13:39:29.000000000 +0000 @@ -4,31 +4,133 @@ * (version 2.1 or later). See the COPYING file in this distribution. */ -public int compare_conversation_ascending(Geary.App.Conversation a, Geary.App.Conversation b) { - Geary.Email? a_latest = a.get_latest_recv_email(Geary.App.Conversation.Location.IN_FOLDER_OUT_OF_FOLDER); - Geary.Email? b_latest = b.get_latest_recv_email(Geary.App.Conversation.Location.IN_FOLDER_OUT_OF_FOLDER); - - if (a_latest == null) - return (b_latest == null) ? 0 : -1; - else if (b_latest == null) - return 1; - - // use date-received so newly-arrived messages float to the top, even if they're send date - // was earlier (think of mailing lists that batch up forwarded mail) - return Geary.Email.compare_recv_date_ascending(a_latest, b_latest); -} - -public int compare_conversation_descending(Geary.App.Conversation a, Geary.App.Conversation b) { - return compare_conversation_ascending(b, a); -} +namespace Util.Email { -namespace EmailUtil { + public int compare_conversation_ascending(Geary.App.Conversation a, + Geary.App.Conversation b) { + Geary.Email? a_latest = a.get_latest_recv_email( + Geary.App.Conversation.Location.IN_FOLDER_OUT_OF_FOLDER + ); + Geary.Email? b_latest = b.get_latest_recv_email( + Geary.App.Conversation.Location.IN_FOLDER_OUT_OF_FOLDER + ); + + if (a_latest == null) { + return (b_latest == null) ? 0 : -1; + } else if (b_latest == null) { + return 1; + } + + // use date-received so newly-arrived messages float to the + // top, even if they're send date was earlier (think of + // mailing lists that batch up forwarded mail) + return Geary.Email.compare_recv_date_ascending(a_latest, b_latest); + } + + public int compare_conversation_descending(Geary.App.Conversation a, + Geary.App.Conversation b) { + return compare_conversation_ascending(b, a); + } + + /** Returns the stripped subject line, or a placeholder if none. */ + public string strip_subject_prefixes(Geary.Email email) { + string? cleaned = (email.subject != null) ? email.subject.strip_prefixes() : null; + return !Geary.String.is_empty(cleaned) ? cleaned : _("(no subject)"); + } + + /** + * Returns a mailbox for the primary originator of an email. + * + * RFC 822 allows multiple and absent From header values, and + * software such as Mailman and GitLab will mangle the names in + * From mailboxes. This provides a canonical means to obtain a + * mailbox (that is, name and email address) for the first + * originator, and with the mailbox's name having been fixed up + * where possible. + * + * The first From mailbox is used and de-mangled if found, if not + * the Sender mailbox is used if present, else the first Reply-To + * mailbox is used. + */ + public Geary.RFC822.MailboxAddress? + get_primary_originator(Geary.EmailHeaderSet email) { + Geary.RFC822.MailboxAddress? primary = null; + if (email.from != null && email.from.size > 0) { + // We have a From address, so attempt to de-mangle it + Geary.RFC822.MailboxAddresses? from = email.from; + + string from_name = ""; + if (from != null && from.size > 0) { + primary = from[0]; + from_name = primary.name ?? ""; + } + + Geary.RFC822.MailboxAddresses? reply_to = email.reply_to; + Geary.RFC822.MailboxAddress? primary_reply_to = null; + string reply_to_name = ""; + if (reply_to != null && reply_to.size > 0) { + primary_reply_to = reply_to[0]; + reply_to_name = primary_reply_to.name ?? ""; + } + + // Spaces are important + const string VIA = " via "; + + if (reply_to_name != "" && from_name.has_prefix(reply_to_name)) { + // Mailman sometimes sends the true originator as the + // Reply-To for the email + primary = primary_reply_to; + } else if (VIA in from_name) { + // Mailman, GitLib, Discourse and others send the + // originator's name prefixing something starting with + // "via". + primary = new Geary.RFC822.MailboxAddress( + from_name.split(VIA, 2)[0], primary.address + ); + } + } else if (email.sender != null) { + primary = email.sender; + } else if (email.reply_to != null && email.reply_to.size > 0) { + primary = email.reply_to[0]; + } + + return primary; + } + + /** + * Returns a shortened recipient list suitable for display. + * + * This is useful in case there are a lot of recipients, or there + * is little room for the display. + * + * @return a string containing at least the first mailbox + * serialised by {@link MailboxAddress.to_short_display}, if the + * list contains more mailboxes then an indication of how many + * additional are present. + */ + public string to_short_recipient_display(Geary.RFC822.MailboxAddresses mailboxes) { + if (mailboxes.size == 0) { + // Translators: This is shown for displaying a list of + // email recipients that happens to be empty, + // i.e. contains no email addresses. + return _("(No recipients)"); + } + + // Always mention the first recipient + string first_recipient = mailboxes.get(0).to_short_display(); + if (mailboxes.size == 1) + return first_recipient; + + // Translators: This is used for displaying a short list of + // email recipients lists with two or more addresses. The + // first (string) substitution is address of the first, the + // second substitution is the number of n - 1 remaining + // recipients. + return GLib.ngettext( + "%s and %d other", + "%s and %d others", + mailboxes.size - 1 + ).printf(first_recipient, mailboxes.size - 1); + } -public string strip_subject_prefixes(Geary.Email email) { - string? cleaned = (email.subject != null) ? email.subject.strip_prefixes() : null; - - return !Geary.String.is_empty(cleaned) ? cleaned : _("(no subject)"); } - -} - diff -Nru geary-0.12.4/src/client/util/util-gravatar.vala geary-3.32.0/src/client/util/util-gravatar.vala --- geary-0.12.4/src/client/util/util-gravatar.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/util/util-gravatar.vala 1970-01-01 00:00:00.000000000 +0000 @@ -1,64 +0,0 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. - * - * This software is licensed under the GNU Lesser General Public License - * (version 2.1 or later). See the COPYING file in this distribution. - */ - -namespace Gravatar { - -public const int MIN_SIZE = 1; -public const int MAX_SIZE = 512; -public const int DEFAULT_SIZE = 80; - -public enum Default { - NOT_FOUND, - MYSTERY_MAN, - IDENTICON, - MONSTER_ID, - WAVATAR, - RETRO; - - public string to_param() { - switch (this) { - case NOT_FOUND: - return "404"; - - case MYSTERY_MAN: - return "mm"; - - case IDENTICON: - return "identicon"; - - case MONSTER_ID: - return "monsterid"; - - case WAVATAR: - return "wavatar"; - - case RETRO: - return "retro"; - - default: - assert_not_reached(); - } - } -} - -/** - * Returns a URI for the mailbox address specified. size may be any value from MIN_SIZE to - * MAX_SIZE, representing pixels. This function does not attempt to clamp size to this range or - * return an error of any kind if it's outside this range. - * - * TODO: More parameters are available and could be incorporated. See - * https://en.gravatar.com/site/implement/images/ - */ -public string get_image_uri(Geary.RFC822.MailboxAddress addr, Default def, int size = DEFAULT_SIZE) { - // Gravatar spec for preparing address and hashing: - // http://en.gravatar.com/site/implement/hash/ - string md5 = Checksum.compute_for_string(ChecksumType.MD5, addr.address.strip().down()); - - return "https://secure.gravatar.com/avatar/%s?d=%s&s=%d".printf(md5, def.to_param(), size); -} - -} - diff -Nru geary-0.12.4/src/client/util/util-gtk.vala geary-3.32.0/src/client/util/util-gtk.vala --- geary-0.12.4/src/client/util/util-gtk.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/util/util-gtk.vala 2019-03-17 13:39:29.000000000 +0000 @@ -6,97 +6,6 @@ namespace GtkUtil { -// Use this MenuPositionFunc to position a popup menu relative to a widget -// with Gtk.Menu.popup(). -// -// You *must* attach the button widget with Gtk.Menu.attach_to_widget() before -// this function can be used. -public void menu_popup_relative(Gtk.Menu menu, out int x, out int y, out bool push_in) { - menu.realize(); - - int rx, ry; - menu.get_attach_widget().get_window().get_origin(out rx, out ry); - - Gtk.Allocation menu_button_allocation; - menu.get_attach_widget().get_allocation(out menu_button_allocation); - - x = rx + menu_button_allocation.x; - y = ry + menu_button_allocation.y + menu_button_allocation.height; - - push_in = false; -} - -public void add_proxy_menu(Gtk.ToolItem tool_item, string label, Gtk.Menu proxy_menu) { - Gtk.MenuItem proxy_menu_item = new Gtk.MenuItem.with_label(label); - proxy_menu_item.submenu = proxy_menu; - tool_item.create_menu_proxy.connect((sender) => { - sender.set_proxy_menu_item("proxy", proxy_menu_item); - return true; - }); -} - -public void add_accelerator(Gtk.UIManager ui_manager, Gtk.ActionGroup action_group, - string accelerator, string action) { - // Parse the accelerator. - uint key = 0; - Gdk.ModifierType modifiers = 0; - Gtk.accelerator_parse(accelerator, out key, out modifiers); - if (key == 0) { - debug("Failed to parse accelerator '%s'", accelerator); - return; - } - - // Connect the accelerator to the action. - ui_manager.get_accel_group().connect(key, modifiers, Gtk.AccelFlags.VISIBLE, - (group, obj, key, modifiers) => { - action_group.get_action(action).activate(); - return true; - }); -} - -public void show_menuitem_accel_labels(Gtk.Widget widget) { - Gtk.MenuItem? item = widget as Gtk.MenuItem; - if (item == null) { - return; - } - - string? path = item.get_accel_path(); - if (path == null) { - return; - } - Gtk.AccelKey? key = null; - Gtk.AccelMap.lookup_entry(path, out key); - if (key == null) { - return; - } - item.foreach( - (widget) => { add_accel_to_label(widget, key); } - ); -} - -private void add_accel_to_label(Gtk.Widget widget, Gtk.AccelKey key) { - Gtk.AccelLabel? label = widget as Gtk.AccelLabel; - if (label == null) { - return; - } - - // We should check for (key.accel_flags & Gtk.AccelFlags.VISIBLE) before - // running the following code. However, there appears to be some - // funny business going on because key.accel_flags always turns up as 0, - // even though we explicitly set it to Gtk.AccelFlags.VISIBLE before. - label.set_accel(key.accel_key, key.accel_mods); - label.refetch(); -} - -/** - * Removes all items from a menu. - */ -public void clear_menu(Gtk.Menu menu) { - GLib.List children = menu.get_children(); - foreach (weak Gtk.Widget child in children) - menu.remove(child); -} - /** * Given an HTML-style color spec, parses the color and sets it to the source RGB of the Cairo context. * (Borrowed from Shotwell.) @@ -109,18 +18,6 @@ } /** - * Set xalign property on Gtk.Label in a compatible way. - * - * GtkMisc is being deprecated in GTK+ 3 and the "xalign" property has been moved to GtkLabel. This - * causes compatibility problems with newer versions of Vala generating code that won't link with - * older versions of GTK+. This is a convenience method until Geary requires GTK+ 3.16 as its - * minimum GTK+ version. - */ -public void set_label_xalign(Gtk.Label label, float xalign) { - label.set("xalign", xalign); -} - -/** * Returns whether the close button is at the end of the headerbar. */ bool close_button_at_end() { @@ -168,4 +65,18 @@ */ delegate void MenuForeachFunc(string? label, string? action_name, Variant? target, Menu? section); +/** + * Returns the CSS border box height for a widget. + * + * This adjusts the GTK widget's allocated height to exclude extra + * space added by the CSS margin property, if any. + */ +public inline int get_border_box_height(Gtk.Widget widget) { + Gtk.StyleContext style = widget.get_style_context(); + Gtk.StateFlags flags = style.get_state(); + Gtk.Border margin = style.get_margin(flags); + + return widget.get_allocated_height() - margin.top - margin.bottom; +} + } diff -Nru geary-0.12.4/src/client/util/util-migrate.vala geary-3.32.0/src/client/util/util-migrate.vala --- geary-0.12.4/src/client/util/util-migrate.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/util/util-migrate.vala 2019-03-17 13:39:29.000000000 +0000 @@ -7,7 +7,7 @@ namespace Migrate { private const string GROUP = "AccountInformation"; private const string PRIMARY_EMAIL_KEY = "primary_email"; - private const string SETTINGS_FILENAME = Geary.AccountInformation.SETTINGS_FILENAME; + private const string SETTINGS_FILENAME = Accounts.Manager.SETTINGS_FILENAME; private const string MIGRATED_FILENAME = ".config_migrated"; /** diff -Nru geary-0.12.4/src/client/util/util-webkit.vala geary-3.32.0/src/client/util/util-webkit.vala --- geary-0.12.4/src/client/util/util-webkit.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/util/util-webkit.vala 2019-03-17 13:39:29.000000000 +0000 @@ -18,8 +18,8 @@ */ public bool to_bool(WebKit.JavascriptResult result) throws Geary.JS.Error { - JS.GlobalContext context = result.get_global_context(); - JS.Value value = result.get_value(); + unowned JS.GlobalContext context = result.get_global_context(); + unowned JS.Value value = result.get_value(); if (!value.is_boolean(context)) { throw new Geary.JS.Error.TYPE("Result is not a JS Boolean object"); } @@ -59,12 +59,12 @@ */ public string as_string(WebKit.JavascriptResult result) throws Geary.JS.Error { - JS.GlobalContext context = result.get_global_context(); - JS.Value js_str_value = result.get_value(); + unowned JS.GlobalContext context = result.get_global_context(); + unowned JS.Value js_str_value = result.get_value(); JS.Value? err = null; JS.String js_str = js_str_value.to_string_copy(context, out err); Geary.JS.check_exception(context, err); - return Geary.JS.to_string_released(js_str); + return Geary.JS.to_native_string(js_str); } /** diff -Nru geary-0.12.4/src/client/web-process/web-process-extension.vala geary-3.32.0/src/client/web-process/web-process-extension.vala --- geary-0.12.4/src/client/web-process/web-process-extension.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/client/web-process/web-process-extension.vala 2019-03-17 13:39:29.000000000 +0000 @@ -87,10 +87,10 @@ bool should_load = false; WebKit.Frame frame = page.get_main_frame(); // Explicit cast fixes build on s390x/ppc64. Bug 783882 - JS.GlobalContext context = (JS.GlobalContext) + unowned JS.GlobalContext context = (JS.GlobalContext) frame.get_javascript_global_context(); try { - JS.Value ret = execute_script( + unowned JS.Value ret = execute_script( context, "geary.allowRemoteImages", int.parse("__LINE__") ); should_load = ret.to_boolean(context); @@ -106,7 +106,7 @@ private void remote_image_load_blocked(WebKit.WebPage page) { WebKit.Frame frame = page.get_main_frame(); // Explicit cast fixes build on s390x/ppc64. Bug 783882 - JS.GlobalContext context = (JS.GlobalContext) + unowned JS.GlobalContext context = (JS.GlobalContext) frame.get_javascript_global_context(); try { execute_script( @@ -123,7 +123,7 @@ private void selection_changed(WebKit.WebPage page) { WebKit.Frame frame = page.get_main_frame(); // Explicit cast fixes build on s390x/ppc64. Bug 783882 - JS.GlobalContext context = (JS.GlobalContext) + unowned JS.GlobalContext context = (JS.GlobalContext) frame.get_javascript_global_context(); try { execute_script( @@ -136,20 +136,18 @@ // Return type is nullable as a workaround for Bug 778046, it will // never actually be null. - private JS.Value? execute_script(JS.Context context, string script, int line) + private unowned JS.Value? execute_script(JS.Context context, string script, int line) throws Geary.JS.Error { JS.String js_script = new JS.String.create_with_utf8_cstring(script); JS.String js_source = new JS.String.create_with_utf8_cstring("__FILE__"); JS.Value? err = null; try { - JS.Value ret = context.evaluate_script( + unowned JS.Value ret = context.evaluate_script( js_script, null, js_source, line, out err ); Geary.JS.check_exception(context, err); return ret; } finally { - js_script.release(); - js_source.release(); } } diff -Nru geary-0.12.4/src/CMakeLists.txt geary-3.32.0/src/CMakeLists.txt --- geary-0.12.4/src/CMakeLists.txt 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/CMakeLists.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,824 +0,0 @@ -# Geary build script -# Copyright 2016 Software Freedom Conservancy Inc. - -set(ENGINE_SRC - -engine/api/geary.vala -engine/api/geary-abstract-local-folder.vala -engine/api/geary-account.vala -engine/api/geary-account-information.vala -engine/api/geary-aggregated-folder-properties.vala -engine/api/geary-attachment.vala -engine/api/geary-base-object.vala -engine/api/geary-composed-email.vala -engine/api/geary-contact.vala -engine/api/geary-contact-flags.vala -engine/api/geary-contact-importance.vala -engine/api/geary-contact-store.vala -engine/api/geary-credentials.vala -engine/api/geary-credentials-mediator.vala -engine/api/geary-email-flags.vala -engine/api/geary-email-identifier.vala -engine/api/geary-email-properties.vala -engine/api/geary-email.vala -engine/api/geary-endpoint.vala -engine/api/geary-engine-error.vala -engine/api/geary-engine.vala -engine/api/geary-folder.vala -engine/api/geary-folder-path.vala -engine/api/geary-folder-properties.vala -engine/api/geary-folder-supports-archive.vala -engine/api/geary-folder-supports-copy.vala -engine/api/geary-folder-supports-create.vala -engine/api/geary-folder-supports-empty.vala -engine/api/geary-folder-supports-mark.vala -engine/api/geary-folder-supports-move.vala -engine/api/geary-folder-supports-remove.vala -engine/api/geary-logging.vala -engine/api/geary-named-flag.vala -engine/api/geary-named-flags.vala -engine/api/geary-progress-monitor.vala -engine/api/geary-revokable.vala -engine/api/geary-search-folder.vala -engine/api/geary-search-query.vala -engine/api/geary-service.vala -engine/api/geary-service-provider.vala -engine/api/geary-special-folder-type.vala - -engine/app/app-conversation.vala -engine/app/app-conversation-monitor.vala -engine/app/app-draft-manager.vala -engine/app/app-email-store.vala - -engine/app/conversation-monitor/app-append-operation.vala -engine/app/conversation-monitor/app-conversation-operation-queue.vala -engine/app/conversation-monitor/app-conversation-operation.vala -engine/app/conversation-monitor/app-conversation-set.vala -engine/app/conversation-monitor/app-external-append-operation.vala -engine/app/conversation-monitor/app-fill-window-operation.vala -engine/app/conversation-monitor/app-local-load-operation.vala -engine/app/conversation-monitor/app-local-search-operation.vala -engine/app/conversation-monitor/app-remove-operation.vala -engine/app/conversation-monitor/app-reseed-operation.vala -engine/app/conversation-monitor/app-terminate-operation.vala - -engine/app/email-store/app-async-folder-operation.vala -engine/app/email-store/app-copy-operation.vala -engine/app/email-store/app-fetch-operation.vala -engine/app/email-store/app-list-operation.vala -engine/app/email-store/app-mark-operation.vala - -engine/common/common-message-data.vala - -engine/db/db.vala -engine/db/db-connection.vala -engine/db/db-context.vala -engine/db/db-database.vala -engine/db/db-database-error.vala -engine/db/db-result.vala -engine/db/db-statement.vala -engine/db/db-synchronous-mode.vala -engine/db/db-transaction-async-job.vala -engine/db/db-transaction-outcome.vala -engine/db/db-transaction-type.vala -engine/db/db-versioned-database.vala - -engine/imap/imap.vala -engine/imap/imap-error.vala -engine/imap/api/imap-account.vala -engine/imap/api/imap-email-flags.vala -engine/imap/api/imap-email-properties.vala -engine/imap/api/imap-folder-properties.vala -engine/imap/api/imap-folder.vala -engine/imap/api/imap-folder-root.vala -engine/imap/command/imap-append-command.vala -engine/imap/command/imap-capability-command.vala -engine/imap/command/imap-close-command.vala -engine/imap/command/imap-command.vala -engine/imap/command/imap-compress-command.vala -engine/imap/command/imap-copy-command.vala -engine/imap/command/imap-create-command.vala -engine/imap/command/imap-examine-command.vala -engine/imap/command/imap-expunge-command.vala -engine/imap/command/imap-fetch-command.vala -engine/imap/command/imap-id-command.vala -engine/imap/command/imap-idle-command.vala -engine/imap/command/imap-list-command.vala -engine/imap/command/imap-list-return-parameter.vala -engine/imap/command/imap-login-command.vala -engine/imap/command/imap-logout-command.vala -engine/imap/command/imap-message-set.vala -engine/imap/command/imap-noop-command.vala -engine/imap/command/imap-search-command.vala -engine/imap/command/imap-search-criteria.vala -engine/imap/command/imap-search-criterion.vala -engine/imap/command/imap-select-command.vala -engine/imap/command/imap-starttls-command.vala -engine/imap/command/imap-status-command.vala -engine/imap/command/imap-store-command.vala -engine/imap/message/imap-data-format.vala -engine/imap/message/imap-envelope.vala -engine/imap/message/imap-fetch-body-data-specifier.vala -engine/imap/message/imap-fetch-data-specifier.vala -engine/imap/message/imap-flag.vala -engine/imap/message/imap-flags.vala -engine/imap/message/imap-internal-date.vala -engine/imap/message/imap-mailbox-specifier.vala -engine/imap/message/imap-message-data.vala -engine/imap/message/imap-message-flag.vala -engine/imap/message/imap-message-flags.vala -engine/imap/message/imap-sequence-number.vala -engine/imap/message/imap-status-data-type.vala -engine/imap/message/imap-tag.vala -engine/imap/message/imap-uid.vala -engine/imap/message/imap-uid-validity.vala -engine/imap/parameter/imap-atom-parameter.vala -engine/imap/parameter/imap-list-parameter.vala -engine/imap/parameter/imap-literal-parameter.vala -engine/imap/parameter/imap-nil-parameter.vala -engine/imap/parameter/imap-number-parameter.vala -engine/imap/parameter/imap-parameter.vala -engine/imap/parameter/imap-quoted-string-parameter.vala -engine/imap/parameter/imap-root-parameters.vala -engine/imap/parameter/imap-string-parameter.vala -engine/imap/parameter/imap-unquoted-string-parameter.vala -engine/imap/response/imap-capabilities.vala -engine/imap/response/imap-continuation-response.vala -engine/imap/response/imap-fetch-data-decoder.vala -engine/imap/response/imap-fetched-data.vala -engine/imap/response/imap-mailbox-attribute.vala -engine/imap/response/imap-mailbox-attributes.vala -engine/imap/response/imap-mailbox-information.vala -engine/imap/response/imap-response-code.vala -engine/imap/response/imap-response-code-type.vala -engine/imap/response/imap-server-data.vala -engine/imap/response/imap-server-data-type.vala -engine/imap/response/imap-server-response.vala -engine/imap/response/imap-status.vala -engine/imap/response/imap-status-data.vala -engine/imap/response/imap-status-response.vala -engine/imap/transport/imap-client-connection.vala -engine/imap/transport/imap-client-session-manager.vala -engine/imap/transport/imap-client-session.vala -engine/imap/transport/imap-deserializer.vala -engine/imap/transport/imap-serializer.vala - -engine/imap-db/imap-db-account.vala -engine/imap-db/imap-db-attachment.vala -engine/imap-db/imap-db-contact.vala -engine/imap-db/imap-db-database.vala -engine/imap-db/imap-db-email-identifier.vala -engine/imap-db/imap-db-folder.vala -engine/imap-db/imap-db-gc.vala -engine/imap-db/imap-db-message-addresses.vala -engine/imap-db/imap-db-message-row.vala -engine/imap-db/search/imap-db-search-email-identifier.vala -engine/imap-db/search/imap-db-search-folder.vala -engine/imap-db/search/imap-db-search-folder-properties.vala -engine/imap-db/search/imap-db-search-folder-root.vala -engine/imap-db/search/imap-db-search-query.vala -engine/imap-db/search/imap-db-search-term.vala -engine/imap-db/outbox/smtp-outbox-email-identifier.vala -engine/imap-db/outbox/smtp-outbox-email-properties.vala -engine/imap-db/outbox/smtp-outbox-folder.vala -engine/imap-db/outbox/smtp-outbox-folder-properties.vala -engine/imap-db/outbox/smtp-outbox-folder-root.vala - -engine/imap-engine/imap-engine.vala -engine/imap-engine/imap-engine-account-synchronizer.vala -engine/imap-engine/imap-engine-batch-operations.vala -engine/imap-engine/imap-engine-contact-store.vala -engine/imap-engine/imap-engine-email-flag-watcher.vala -engine/imap-engine/imap-engine-email-prefetcher.vala -engine/imap-engine/imap-engine-generic-account.vala -engine/imap-engine/imap-engine-generic-folder.vala -engine/imap-engine/imap-engine-minimal-folder.vala -engine/imap-engine/imap-engine-replay-operation.vala -engine/imap-engine/imap-engine-replay-queue.vala -engine/imap-engine/imap-engine-revokable-move.vala -engine/imap-engine/imap-engine-revokable-committed-move.vala -engine/imap-engine/imap-engine-send-replay-operation.vala -engine/imap-engine/gmail/imap-engine-gmail-account.vala -engine/imap-engine/gmail/imap-engine-gmail-all-mail-folder.vala -engine/imap-engine/gmail/imap-engine-gmail-drafts-folder.vala -engine/imap-engine/gmail/imap-engine-gmail-folder.vala -engine/imap-engine/gmail/imap-engine-gmail-search-folder.vala -engine/imap-engine/gmail/imap-engine-gmail-spam-trash-folder.vala -engine/imap-engine/other/imap-engine-other-account.vala -engine/imap-engine/other/imap-engine-other-folder.vala -engine/imap-engine/outlook/imap-engine-outlook-account.vala -engine/imap-engine/outlook/imap-engine-outlook-folder.vala -engine/imap-engine/outlook/imap-engine-outlook-drafts-folder.vala -engine/imap-engine/replay-ops/imap-engine-abstract-list-email.vala -engine/imap-engine/replay-ops/imap-engine-copy-email.vala -engine/imap-engine/replay-ops/imap-engine-create-email.vala -engine/imap-engine/replay-ops/imap-engine-empty-folder.vala -engine/imap-engine/replay-ops/imap-engine-fetch-email.vala -engine/imap-engine/replay-ops/imap-engine-list-email-by-id.vala -engine/imap-engine/replay-ops/imap-engine-list-email-by-sparse-id.vala -engine/imap-engine/replay-ops/imap-engine-mark-email.vala -engine/imap-engine/replay-ops/imap-engine-move-email-commit.vala -engine/imap-engine/replay-ops/imap-engine-move-email-prepare.vala -engine/imap-engine/replay-ops/imap-engine-move-email-revoke.vala -engine/imap-engine/replay-ops/imap-engine-remove-email.vala -engine/imap-engine/replay-ops/imap-engine-replay-append.vala -engine/imap-engine/replay-ops/imap-engine-replay-disconnect.vala -engine/imap-engine/replay-ops/imap-engine-replay-removal.vala -engine/imap-engine/replay-ops/imap-engine-server-search-email.vala -engine/imap-engine/replay-ops/imap-engine-user-close.vala -engine/imap-engine/yahoo/imap-engine-yahoo-account.vala -engine/imap-engine/yahoo/imap-engine-yahoo-folder.vala - -engine/memory/memory-buffer.vala -engine/memory/memory-byte-buffer.vala -engine/memory/memory-empty-buffer.vala -engine/memory/memory-file-buffer.vala -engine/memory/memory-growable-buffer.vala -engine/memory/memory-offset-buffer.vala -engine/memory/memory-string-buffer.vala -engine/memory/memory-unowned-byte-array-buffer.vala -engine/memory/memory-unowned-bytes-buffer.vala -engine/memory/memory-unowned-string-buffer.vala - -engine/mime/mime-content-disposition.vala -engine/mime/mime-content-parameters.vala -engine/mime/mime-content-type.vala -engine/mime/mime-data-format.vala -engine/mime/mime-disposition-type.vala -engine/mime/mime-error.vala -engine/mime/mime-multipart-subtype.vala - -engine/nonblocking/nonblocking-abstract-semaphore.vala -engine/nonblocking/nonblocking-batch.vala -engine/nonblocking/nonblocking-concurrent.vala -engine/nonblocking/nonblocking-counting-semaphore.vala -engine/nonblocking/nonblocking-error.vala -engine/nonblocking/nonblocking-mailbox.vala -engine/nonblocking/nonblocking-mutex.vala -engine/nonblocking/nonblocking-reporting-semaphore.vala -engine/nonblocking/nonblocking-variants.vala - -engine/rfc822/rfc822.vala -engine/rfc822/rfc822-error.vala -engine/rfc822/rfc822-gmime-filter-flowed.vala -engine/rfc822/rfc822-gmime-filter-blockquotes.vala -engine/rfc822/rfc822-gmime-filter-plain.vala -engine/rfc822/rfc822-mailbox-addresses.vala -engine/rfc822/rfc822-mailbox-address.vala -engine/rfc822/rfc822-message.vala -engine/rfc822/rfc822-message-data.vala -engine/rfc822/rfc822-utils.vala - -engine/smtp/smtp-authenticator.vala -engine/smtp/smtp-capabilities.vala -engine/smtp/smtp-client-connection.vala -engine/smtp/smtp-client-session.vala -engine/smtp/smtp-command.vala -engine/smtp/smtp-data-format.vala -engine/smtp/smtp-error.vala -engine/smtp/smtp-greeting.vala -engine/smtp/smtp-login-authenticator.vala -engine/smtp/smtp-plain-authenticator.vala -engine/smtp/smtp-request.vala -engine/smtp/smtp-response.vala -engine/smtp/smtp-response-code.vala -engine/smtp/smtp-response-line.vala - -engine/state/state-machine-descriptor.vala -engine/state/state-machine.vala -engine/state/state-mapping.vala - -engine/util/util-ascii.vala -engine/util/util-collection.vala -engine/util/util-connectivity-manager.vala -engine/util/util-converter.vala -engine/util/util-files.vala -engine/util/util-generic-capabilities.vala -engine/util/util-html.vala -engine/util/util-idle-manager.vala -engine/util/util-imap-utf7.vala -engine/util/util-inet.vala -engine/util/util-iterable.vala -engine/util/util-js.vala -engine/util/util-numeric.vala -engine/util/util-object.vala -engine/util/util-reference-semantics.vala -engine/util/util-scheduler.vala -engine/util/util-stream.vala -engine/util/util-string.vala -engine/util/util-synchronization.vala -engine/util/util-time.vala -engine/util/util-timeout-manager.vala -engine/util/util-trillian.vala - -${CMAKE_BINARY_DIR}/geary-version.vala -) - -set(CLIENT_SRC -client/application/autostart-manager.vala -client/application/geary-application.vala -client/application/geary-args.vala -client/application/geary-config.vala -client/application/geary-controller.vala -client/application/secret-mediator.vala - -client/accounts/account-dialog.vala -client/accounts/account-dialog-account-list-pane.vala -client/accounts/account-dialog-add-edit-pane.vala -client/accounts/account-dialog-edit-alternate-emails-pane.vala -client/accounts/account-dialog-pane.vala -client/accounts/account-dialog-remove-confirm-pane.vala -client/accounts/account-dialog-remove-fail-pane.vala -client/accounts/account-dialog-spinner-pane.vala -client/accounts/account-spinner-page.vala -client/accounts/add-edit-page.vala -client/accounts/login-dialog.vala - -client/components/client-web-view.vala -client/components/count-badge.vala -client/components/empty-placeholder.vala -client/components/folder-popover.vala -client/components/icon-factory.vala -client/components/main-toolbar.vala -client/components/main-window.vala -client/components/monitored-progress-bar.vala -client/components/monitored-spinner.vala -client/components/search-bar.vala -client/components/status-bar.vala -client/components/stock.vala - -client/composer/composer-box.vala -client/composer/composer-container.vala -client/composer/composer-embed.vala -client/composer/composer-headerbar.vala -client/composer/composer-link-popover.vala -client/composer/composer-web-view.vala -client/composer/composer-widget.vala -client/composer/composer-window.vala -client/composer/contact-entry-completion.vala -client/composer/contact-list-store.vala -client/composer/email-entry.vala -client/composer/spell-check-popover.vala - -client/conversation-list/conversation-list-cell-renderer.vala -client/conversation-list/conversation-list-store.vala -client/conversation-list/conversation-list-view.vala -client/conversation-list/formatted-conversation-data.vala - -client/conversation-viewer/conversation-email.vala -client/conversation-viewer/conversation-list-box.vala -client/conversation-viewer/conversation-message.vala -client/conversation-viewer/conversation-viewer.vala -client/conversation-viewer/conversation-web-view.vala - -client/dialogs/alert-dialog.vala -client/dialogs/attachment-dialog.vala -client/dialogs/certificate-warning-dialog.vala -client/dialogs/password-dialog.vala -client/dialogs/preferences-dialog.vala -client/dialogs/upgrade-dialog.vala - -client/folder-list/folder-list-abstract-folder-entry.vala -client/folder-list/folder-list-account-branch.vala -client/folder-list/folder-list-folder-entry.vala -client/folder-list/folder-list-tree.vala -client/folder-list/folder-list-inboxes-branch.vala -client/folder-list/folder-list-inbox-folder-entry.vala -client/folder-list/folder-list-search-branch.vala -client/folder-list/folder-list-special-grouping.vala - -client/notification/libmessagingmenu.vala -client/notification/libnotify.vala -client/notification/new-messages-indicator.vala -client/notification/new-messages-monitor.vala -client/notification/null-indicator.vala -client/notification/unity-launcher.vala - -client/sidebar/sidebar-branch.vala -client/sidebar/sidebar-common.vala -client/sidebar/sidebar-count-cell-renderer.vala -client/sidebar/sidebar-entry.vala -client/sidebar/sidebar-tree.vala - -client/util/util-date.vala -client/util/util-email.vala -client/util/util-files.vala -client/util/util-gio.vala -client/util/util-gravatar.vala -client/util/util-gtk.vala -client/util/util-international.vala -client/util/util-migrate.vala -client/util/util-webkit.vala -) - -set(WEB_PROCESS_SRC -client/web-process/web-process-extension.vala -) - -set(CONSOLE_SRC -console/main.vala -) - -set(MAILER_SRC -mailer/main.vala -) - -# Vala -find_package(Vala REQUIRED) -include(ValaVersion) -ensure_vala_version("0.26.0" MINIMUM) -include(ValaPrecompile) - -# Vapigen -find_program(VAPIGEN vapigen) -if (VAPIGEN STREQUAL "VAPIGEN-NOTFOUND") - message(FATAL_ERROR "vapigen must be installed to build Geary.") -else () - message(STATUS "Found vapigen: " ${VAPIGEN}) -endif () - -# Packages -if (LIBMESSAGINGMENU_FOUND) - message(STATUS "Unity messaging menu support: ON") - set(EXTRA_CLIENT_PKG_CONFIG - ${EXTRA_CLIENT_PKG_CONFIG} - messaging-menu - ) - - set(EXTRA_CLIENT_PACKAGES - ${EXTRA_CLIENT_PACKAGES} - MessagingMenu-1.0 - ) - - set(EXTRA_VALA_OPTIONS - ${EXTRA_VALA_OPTIONS} - -D HAVE_LIBMESSAGINGMENU - ) -else () - message(STATUS "Unity messaging menu support: OFF") -endif () - -if (LIBUNITY_FOUND) - message(STATUS "Unity launcher support: ON") - set(EXTRA_CLIENT_PKG_CONFIG - ${EXTRA_CLIENT_PKG_CONFIG} - unity - ) - - set(EXTRA_CLIENT_PACKAGES - ${EXTRA_CLIENT_PACKAGES} - unity - ) - - set(EXTRA_VALA_OPTIONS - ${EXTRA_VALA_OPTIONS} - -D HAVE_LIBUNITY - ) -else () - message(STATUS "Unity launcher support: OFF") -endif () - -if (NO_FATAL_WARNINGS) - message(STATUS "Vala fatal warnings: OFF") -else () - message(STATUS "Vala fatal warnings: ON") - - set (EXTRA_VALA_OPTIONS - ${EXTRA_VALA_OPTIONS} - --fatal-warnings - ) -endif () - -pkg_check_modules(DEPS REQUIRED - gthread-2.0 - glib-2.0>=${TARGET_GLIB}.0 - gio-2.0>=${TARGET_GLIB}.0 - gtk+-3.0>=${TARGET_GTK} - libsoup-2.4>=2.48 - gee-0.8>=0.8.5 - libnotify>=0.7.5 - libcanberra>=0.28 - sqlite3>=3.7.4 - gmime-2.6>=2.6.17 - libsecret-1>=0.11 - libxml-2.0>=2.7.8 - gcr-3>=3.10.1 - gobject-introspection-1.0 - webkit2gtk-4.0>=${TARGET_WEBKIT} - webkit2gtk-web-extension-4.0>=${TARGET_WEBKIT} - javascriptcoregtk-4.0>=${TARGET_WEBKIT} - enchant>=1.6 - ${EXTRA_CLIENT_PKG_CONFIG} -) - -add_custom_target(git-version - COMMAND ${CMAKE_COMMAND} - -DSRC_DIR=${CMAKE_CURRENT_SOURCE_DIR} - -DDST_DIR=${CMAKE_BINARY_DIR} - -DVERSION=${VERSION} - -P ${CMAKE_CURRENT_SOURCE_DIR}/../cmake/gitversion.cmake - BYPRODUCTS ${CMAKE_BINARY_DIR}/geary-version.vala -) - -set(ENGINE_PACKAGES - gee-0.8 - gio-2.0 - glib-2.0 - gmime-2.6 - javascriptcore-4.0 - libxml-2.0 - posix - sqlite3 -) - -set(CLIENT_PACKAGES - enchant - gcr-3 - geary-engine - gio-2.0 - gtk+-3.0 - libcanberra - libnotify - libsecret-1 - libsoup-2.4 - webkit2gtk-4.0 - ${EXTRA_CLIENT_PACKAGES} -) - -# webkit2gtk-web-extension-4.0 is included as custom VAPI below -set(WEB_PROCESS_PACKAGES - geary-engine - gee-0.8 - gtk+-3.0 - javascriptcore-4.0 - libsoup-2.4 - webkit2gtk-web-extension-4.0 -) - -set(CONSOLE_PACKAGES - geary-engine - gtk+-3.0 -) - -set(GSETTINGS_DIR ${CMAKE_SOURCE_DIR}/desktop) - -set(CFLAGS - ${DEPS_CFLAGS} - ${DEPS_CFLAGS_OTHER} - -D_INSTALL_PREFIX=\"${CMAKE_INSTALL_PREFIX}\" - -D_BUILD_ROOT_DIR=\"${CMAKE_BINARY_DIR}\" - -D_SOURCE_ROOT_DIR=\"${CMAKE_SOURCE_DIR}\" - -D_GSETTINGS_DIR=\"${CMAKE_BINARY_DIR}/gsettings\" - -DGETTEXT_PACKAGE=\"${GETTEXT_PACKAGE}\" - -DLANGUAGE_SUPPORT_DIRECTORY=\"${LANGUAGE_SUPPORT_DIRECTORY}\" - -DISO_CODE_639_XML=\"${ISO_CODE_639_XML}\" - -DISO_CODE_3166_XML=\"${ISO_CODE_3166_XML}\" - -DGCR_API_SUBJECT_TO_CHANGE -) - -if (REF_TRACKING) - message(STATUS "Reference tracking: ON") - set(EXTRA_VALA_OPTIONS - ${EXTRA_VALA_OPTIONS} - --define=REF_TRACKING - ) -else () - message(STATUS "Reference tracking: OFF") -endif () - -if (NOT DEPS_gtk+-3.0_VERSION VERSION_LESS 3.20) - set(EXTRA_VALA_OPTIONS - ${EXTRA_VALA_OPTIONS} - -D GTK_3_20 - ) -endif() - -if (NOT DEPS_gtk+-3.0_VERSION VERSION_LESS 3.22) - set(EXTRA_VALA_OPTIONS - ${EXTRA_VALA_OPTIONS} - -D GTK_3_22 - ) -endif() - -if (DISABLE_POODLE) - message(STATUS "POODLE SSLv3 fix: OFF") - set(EXTRA_VALA_OPTIONS - ${EXTRA_VALA_OPTIONS} - --define=DISABLE_POODLE - ) -else () - message(STATUS "POODLE SSLv3 fix: ON") -endif () - -set(LIB_PATHS ${DEPS_LIBRARY_DIRS}) -link_directories(${LIB_PATHS}) -add_definitions(${CFLAGS}) - -set(VALAC_OPTIONS - --vapidir=${CMAKE_BINARY_DIR}/src - --vapidir=${CMAKE_SOURCE_DIR}/bindings/vapi - --metadatadir=${CMAKE_SOURCE_DIR}/bindings/metadata - --target-glib=${TARGET_GLIB} - --thread - --debug - --enable-checking - --enable-deprecated - --fatal-warnings - ${EXTRA_VALA_OPTIONS} -) - -# Engine (static library used for building apps and unit test) -################################################# -vala_precompile(ENGINE_VALA_C geary-engine - ${ENGINE_SRC} -PACKAGES - ${ENGINE_PACKAGES} -GENERATE_VAPI - geary-engine -OPTIONS - ${VALAC_OPTIONS} -) - -add_library(geary-engine STATIC ${ENGINE_VALA_C}) -add_dependencies(geary-engine git-version) -# Build the library statically so we can it statically, but make it -# relocatable so we can link it to the web extension dynamic lib. -set_property( - TARGET geary-engine - PROPERTY POSITION_INDEPENDENT_CODE TRUE -) -target_link_libraries(geary-engine m ${DEPS_LIBRARIES} sqlite3-unicodesn) - -# WebKit2GTK VAPI generation -################################################# -add_custom_target(webkit2gtk-vapi - DEPENDS - "${CMAKE_BINARY_DIR}/src/webkit2gtk-4.0.vapi" - "${CMAKE_BINARY_DIR}/src/webkit2gtk-web-extension-4.0.vapi" - "${CMAKE_SOURCE_DIR}/bindings/vapi/javascriptcore-4.0.vapi" -) -add_custom_command( - OUTPUT - ${CMAKE_BINARY_DIR}/src/webkit2gtk-4.0.vapi - DEPENDS - "${CMAKE_SOURCE_DIR}/bindings/metadata/WebKit2-4.0.metadata" - "${CMAKE_SOURCE_DIR}/bindings/vapi/javascriptcore-4.0.vapi" - WORKING_DIRECTORY - "${CMAKE_SOURCE_DIR}/bindings/metadata" - COMMAND - vapigen --library=webkit2gtk-4.0 --pkg gtk+-3.0 --pkg libsoup-2.4 --pkg javascriptcore-4.0 --vapidir=${CMAKE_SOURCE_DIR}/bindings/vapi --metadatadir=${CMAKE_SOURCE_DIR}/bindings/metadata --directory=${CMAKE_BINARY_DIR}/src `${PKG_CONFIG_EXECUTABLE} --variable=girdir gobject-introspection-1.0`/WebKit2-4.0.gir -) -add_custom_command( - OUTPUT - "${CMAKE_BINARY_DIR}/src/webkit2gtk-web-extension-4.0.vapi" - DEPENDS - "${CMAKE_SOURCE_DIR}/bindings/metadata/WebKit2WebExtension-4.0.metadata" - "${CMAKE_SOURCE_DIR}/bindings/metadata/WebKit2WebExtension-4.0-custom.vala" - "${CMAKE_SOURCE_DIR}/bindings/vapi/javascriptcore-4.0.vapi" - WORKING_DIRECTORY - "${CMAKE_SOURCE_DIR}/bindings/metadata" - COMMAND - vapigen --library=webkit2gtk-web-extension-4.0 --pkg gtk+-3.0 --pkg libsoup-2.4 --pkg javascriptcore-4.0 --vapidir=${CMAKE_SOURCE_DIR}/bindings/vapi --metadatadir=${CMAKE_SOURCE_DIR}/bindings/metadata --directory=${CMAKE_BINARY_DIR}/src `${PKG_CONFIG_EXECUTABLE} --variable=girdir gobject-introspection-1.0`/WebKit2WebExtension-4.0.gir WebKit2WebExtension-4.0-custom.vala -) - -# Client library (static lib used for building client and unit tests) -################################################# - -vala_precompile(CLIENT_VALA_C geary-client - ${CLIENT_SRC} -PACKAGES - ${ENGINE_PACKAGES} - ${CLIENT_PACKAGES} -GENERATE_VAPI - geary-client -OPTIONS - ${VALAC_OPTIONS} - --gresources=${RESOURCES_XML} -) - -add_library(geary-client STATIC ${CLIENT_VALA_C}) -add_dependencies(geary-client resource_copy webkit2gtk-vapi) -target_link_libraries(geary-client m ${DEPS_LIBRARIES} geary-engine) - -# Main client application binary -################################################# -set_property(SOURCE ${RESOURCES_C} PROPERTY GENERATED TRUE) - -vala_precompile(GEARY_VALA_C geary - "client/application/main.vala" -PACKAGES - ${ENGINE_PACKAGES} - ${CLIENT_PACKAGES} - geary-client -OPTIONS - ${VALAC_OPTIONS} -) - -add_executable(geary ${GEARY_VALA_C} ${RESOURCES_C}) -target_link_libraries(geary ${DEPS_LIBRARIES} geary-client) -install(TARGETS geary RUNTIME DESTINATION bin) -add_custom_command( - TARGET - geary - POST_BUILD - COMMAND - ${CMAKE_COMMAND} -E copy geary ${CMAKE_BINARY_DIR}/ -) - -# GSettings -# This needs to be here and not in desktop/CMakeLists.txt in order for Geary to run in the build -# directory -include(GSettings) -add_schemas(geary ${GSETTINGS_DIR} ${CMAKE_INSTALL_PREFIX}) - -# Client web process extension library -################################################# -vala_precompile(WEB_PROCESS_VALA_C geary-web-process - ${WEB_PROCESS_SRC} -PACKAGES - ${WEB_PROCESS_PACKAGES} - ${ENGINE_PACKAGES} ## XXX REMOVE ME -OPTIONS - ${VALAC_OPTIONS} -) - -add_library(geary-web-process MODULE ${WEB_PROCESS_VALA_C}) -target_link_libraries(geary-web-process PRIVATE ${DEPS_LIBRARIES} geary-engine) -install(TARGETS geary-web-process LIBRARY DESTINATION lib/geary/web-extensions) - -# Console app -################################################# -vala_precompile(CONSOLE_VALA_C geary-console - ${CONSOLE_SRC} -PACKAGES - ${CONSOLE_PACKAGES} - ${ENGINE_PACKAGES} -OPTIONS - ${VALAC_OPTIONS} -) - -add_executable(geary-console ${CONSOLE_VALA_C}) -target_link_libraries(geary-console ${DEPS_LIBRARIES} geary-engine) -add_custom_command( - TARGET - geary-console - POST_BUILD - COMMAND - ${CMAKE_COMMAND} -E copy geary-console ${CMAKE_BINARY_DIR}/ -) - -# Mailer app -################################################# -vala_precompile(MAILER_VALA_C geary-mailer - ${MAILER_SRC} -PACKAGES - geary-engine - ${ENGINE_PACKAGES} -OPTIONS - ${VALAC_OPTIONS} -) - -add_executable(geary-mailer ${MAILER_VALA_C}) -target_link_libraries(geary-mailer ${DEPS_LIBRARIES} geary-engine) -add_custom_command( - TARGET - geary-mailer - POST_BUILD - COMMAND - ${CMAKE_COMMAND} -E copy geary-mailer ${CMAKE_BINARY_DIR}/ -) - -# Valadoc -################################################# -foreach(pkg ${ENGINE_PACKAGES}) - list(APPEND VALADOC_PKG_OPTS "--pkg=${pkg}") -endforeach(pkg ${ENGINE_PACKAGES}) - -include(FindValadoc) -add_custom_target(valadoc - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/src - COMMAND ${VALADOC_EXECUTABLE} - --verbose --force --deps - --package-name=geary-${VERSION} - --package-version=${VERSION} - --target-glib=${TARGET_GLIB} - -b ${CMAKE_CURRENT_SOURCE_DIR} - -o ${CMAKE_SOURCE_DIR}/valadoc - --vapidir=${CMAKE_BINARY_DIR}/src - --vapidir=${CMAKE_SOURCE_DIR}/bindings/vapi - ${VALADOC_PKG_OPTS} ${ENGINE_SRC} -) -add_dependencies(valadoc git-version) - -## Make clean: remove copied files -################################################## -set_property( - DIRECTORY .. - APPEND - PROPERTY ADDITIONAL_MAKE_CLEAN_FILES - geary - geary-console - geary-mailer -) - -add_subdirectory(sqlite3-unicodesn) diff -Nru geary-0.12.4/src/console/main.vala geary-3.32.0/src/console/main.vala --- geary-0.12.4/src/console/main.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/console/main.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,4 +1,5 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* +n * Copyright 2016 Software Freedom Conservancy Inc. * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. @@ -10,72 +11,74 @@ STATE } +const int IMAP_TIMEOUT_SEC = 60 * 15; + class ImapConsole : Gtk.Window { private const int KEEPALIVE_SEC = 60 * 10; - + private Gtk.TextView console = new Gtk.TextView(); private Gtk.Entry cmdline = new Gtk.Entry(); private Gtk.Statusbar statusbar = new Gtk.Statusbar(); - + private uint statusbar_ctx = 0; private uint statusbar_msg_id = 0; - + private Geary.Imap.ClientConnection? cx = null; private Gee.HashMap status_responses = new Gee.HashMap< Geary.Imap.Tag, Geary.Imap.StatusResponse>(); private Geary.Nonblocking.Event recvd_response_event = new Geary.Nonblocking.Event(); - + public ImapConsole() { title = "IMAP Console"; destroy.connect(() => { Gtk.main_quit(); }); set_default_size(800, 600); - + Gtk.Box layout = new Gtk.Box(Gtk.Orientation.VERTICAL, 4); - + console.editable = false; Gtk.ScrolledWindow scrolled_console = new Gtk.ScrolledWindow(null, null); scrolled_console.add(console); scrolled_console.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC); layout.pack_start(scrolled_console, true, true, 0); - + cmdline.activate.connect(on_activate); layout.pack_start(cmdline, false, false, 0); - + statusbar_ctx = statusbar.get_context_id("status"); layout.pack_end(statusbar, false, false, 0); - + add(layout); - + cmdline.grab_focus(); } - + private void on_activate() { exec(cmdline.buffer.text); cmdline.buffer.delete_text(0, -1); } - + private void clear_status() { if (statusbar_msg_id == 0) return; - + statusbar.remove(statusbar_ctx, statusbar_msg_id); statusbar_msg_id = 0; } - + private void status(string text) { clear_status(); - + string msg = text; if (!msg.has_suffix(".") && !msg.has_prefix("usage")) msg += "."; - + statusbar_msg_id = statusbar.push(statusbar_ctx, msg); } - + private void exception(Error err) { status(err.message); } - + private static string[] cmdnames = { "noop", "nop", @@ -88,9 +91,12 @@ "logout", "id", "bye", + "namespace", "list", "xlist", "examine", + "create", + "delete", "fetch", "uid-fetch", "fetch-fields", @@ -107,25 +113,27 @@ "preview", "close" }; - + private void exec(string input) { string[] lines = input.strip().split(";"); foreach (string line in lines) { string[] tokens = line.strip().split(" "); if (tokens.length == 0) continue; - + string cmd = tokens[0].strip().down(); - string[] args = new string[0]; for (int ctr = 1; ctr < tokens.length; ctr++) { string arg = tokens[ctr].strip(); - if (!Geary.String.is_empty(arg)) + if (arg == "\"\"") { + args += ""; + } else if (!Geary.String.is_empty(arg)) { args += arg; + } } - + clear_status(); - + // TODO: Need to break out the command delegates into their own objects with the // human command-names and usage and exec()'s and such; this isn't a long-term approach try { @@ -134,104 +142,116 @@ case "nop": noop(cmd, args); break; - + case "capabilities": case "caps": capabilities(cmd, args); break; - + case "connect": case "unsecure": connect_cmd(cmd, args); break; - + case "disconnect": disconnect_cmd(cmd, args); break; - + case "starttls": starttls(cmd, args); break; - + case "login": login(cmd, args); break; - + case "logout": case "bye": case "kthxbye": logout(cmd, args); break; - + case "id": id(cmd, args); break; - + + case "namespace": + namespace(cmd, args); + break; + case "list": case "xlist": list(cmd, args); break; - + case "examine": examine(cmd, args); break; - + + case "create": + create(cmd, args); + break; + + case "delete": + @delete(cmd, args); + break; + case "fetch": case "uid-fetch": fetch(cmd, args); break; - + case "close": close_cmd(cmd, args); break; - + case "fetch-fields": fetch_fields(cmd, args); break; - + case "append": append(cmd, args); break; - + case "search": case "uid-search": search(cmd, args); break; - + case "help": foreach (string cmdname in cmdnames) print_console_line(cmdname); break; - + case "exit": case "quit": quit(cmd, args); break; - + case "gmail": string[] fake_args = new string[1]; fake_args[0] = "imap.gmail.com:993"; connect_cmd("connect", fake_args); break; - + case "yahoo": string[] fake_args = new string[1]; fake_args[0] = "imap.mail.yahoo.com:993"; connect_cmd("connect", fake_args); break; - + case "keepalive": keepalive(cmd, args); break; - + case "status": folder_status(cmd, args); break; - + case "preview": preview(cmd, args); break; - + default: status("Unknown command \"%s\"".printf(cmd)); break; @@ -241,129 +261,116 @@ } } } - + private void check_args(string cmd, string[] args, int count, string? usage) throws CommandException { if (args.length != count) throw new CommandException.USAGE("usage: %s %s", cmd, usage != null ? usage : ""); } - + private void check_connected(string cmd, string[] args, int count, string? usage) throws CommandException { if (cx == null) throw new CommandException.STATE("'connect' required"); - + check_args(cmd, args, count, usage); } - + private void check_min_args(string cmd, string[] args, int min_count, string? usage) throws CommandException { if (args.length < min_count) throw new CommandException.USAGE("usage: %s %s", cmd, usage != null ? usage : ""); } - + private void check_min_connected(string cmd, string[] args, int min_count, string? usage) throws CommandException { if (cx == null) throw new CommandException.STATE("'connect' required"); - + check_min_args(cmd, args, min_count, usage); } - + private void capabilities(string cmd, string[] args) throws Error { check_connected(cmd, args, 0, null); - - cx.send_async.begin(new Geary.Imap.CapabilityCommand(), null, on_capabilities); - } - - private void on_capabilities(Object? source, AsyncResult result) { - try { - cx.send_async.end(result); - status("Success"); - } catch (Error err) { - exception(err); - } + + this.cx.send_command(new Geary.Imap.CapabilityCommand()); } - + private void noop(string cmd, string[] args) throws Error { check_connected(cmd, args, 0, null); - - cx.send_async.begin(new Geary.Imap.NoopCommand(), null, on_noop); - } - - private void on_noop(Object? source, AsyncResult result) { - try { - cx.send_async.end(result); - status("Success"); - } catch (Error err) { - exception(err); - } + + this.cx.send_command(new Geary.Imap.NoopCommand()); } - + private void connect_cmd(string cmd, string[] args) throws Error { if (cx != null) throw new CommandException.STATE("'logout' required"); - + check_args(cmd, args, 1, "hostname[:port]"); - - Geary.Endpoint.Flags flags = Geary.Endpoint.Flags.NONE; - if (cmd != "unsecure") - flags |= Geary.Endpoint.Flags.SSL; - + + Geary.TlsNegotiationMethod method = Geary.TlsNegotiationMethod.TRANSPORT; + if (cmd == "unsecure") { + method = Geary.TlsNegotiationMethod.START_TLS; + } + cx = new Geary.Imap.ClientConnection( - new Geary.Endpoint(args[0], Geary.Imap.ClientConnection.DEFAULT_PORT_SSL, - flags, Geary.Imap.ClientConnection.DEFAULT_TIMEOUT_SEC)); - + new Geary.Endpoint( + new GLib.NetworkAddress(args[0], Geary.Imap.IMAP_TLS_PORT), + method, + IMAP_TIMEOUT_SEC + ) + ); + cx.sent_command.connect(on_sent_command); cx.received_status_response.connect(on_received_status_response); cx.received_server_data.connect(on_received_server_data); cx.received_bad_response.connect(on_received_bad_response); - + status("Connecting to %s...".printf(args[0])); cx.connect_async.begin(null, on_connected); } - + private void on_connected(Object? source, AsyncResult result) { try { cx.connect_async.end(result); status("Connected"); } catch (Error err) { cx = null; - + exception(err); } } - + private void disconnect_cmd(string cmd, string[] args) throws Error { check_connected(cmd, args, 0, null); - + status("Disconnecting..."); cx.disconnect_async.begin(null, on_disconnected); } - + private void on_disconnected(Object? source, AsyncResult result) { try { cx.disconnect_async.end(result); status("Disconnected"); - + cx.sent_command.disconnect(on_sent_command); cx.received_status_response.disconnect(on_received_status_response); cx.received_server_data.connect(on_received_server_data); cx.received_bad_response.disconnect(on_received_bad_response); - + cx = null; } catch (Error err) { exception(err); } } - + private void starttls(string cmd, string[] args) throws Error { check_connected(cmd, args, 0, ""); - + status("Starting TLS..."); do_starttls_async.begin(on_do_starttls_async_completed); } - + private async void do_starttls_async() throws Error { Geary.Imap.StarttlsCommand cmd = new Geary.Imap.StarttlsCommand(); - yield cx.send_async(cmd, null); - + this.cx.send_command(cmd); + Geary.Imap.StatusResponse response = yield wait_for_response_async(cmd.tag); if (response.status == Geary.Imap.Status.OK) { yield cx.starttls_async(null); @@ -372,7 +379,7 @@ status("STARTTLS denied: %s".printf(response.to_string())); } } - + private void on_do_starttls_async_completed(Object? source, AsyncResult result) { try { do_starttls_async.end(result); @@ -380,321 +387,249 @@ exception(err); } } - + private void login(string cmd, string[] args) throws Error { check_connected(cmd, args, 2, "user pass"); - + status("Logging in..."); - cx.send_async.begin(new Geary.Imap.LoginCommand(args[0], args[1]), null, on_logged_in); + this.cx.send_command(new Geary.Imap.LoginCommand(args[0], args[1])); } - - private void on_logged_in(Object? source, AsyncResult result) { - try { - cx.send_async.end(result); - status("Login completed"); - } catch (Error err) { - exception(err); - } - } - + private void logout(string cmd, string[] args) throws Error { check_connected(cmd, args, 0, null); - + status("Logging out..."); - cx.send_async.begin(new Geary.Imap.LogoutCommand(), null, on_logout); + this.cx.send_command(new Geary.Imap.LogoutCommand()); } - - private void on_logout(Object? source, AsyncResult result) { - try { - cx.send_async.end(result); - status("Logged out"); - } catch (Error err) { - exception(err); - } - } - + private void id(string cmd, string[] args) throws Error { check_connected(cmd, args, 0, null); - + status("Retrieving ID..."); - + Gee.HashMap fields = new Gee.HashMap(); fields.set("name", "geary-console"); fields.set("version", Geary.Version.GEARY_VERSION); - cx.send_async.begin(new Geary.Imap.IdCommand(fields), null, on_id); + this.cx.send_command(new Geary.Imap.IdCommand(fields)); } - - private void on_id(Object? source, AsyncResult result) { - try { - cx.send_async.end(result); - status("Retrieved ID"); - } catch (Error err) { - exception(err); - } + + private void namespace(string cmd, string[] args) throws Error { + check_connected(cmd, args, 0, null); + + status("Retrieving NAMESPACE..."); + this.cx.send_command(new Geary.Imap.NamespaceCommand()); } - + private void list(string cmd, string[] args) throws Error { check_connected(cmd, args, 2, " "); - + status("Listing..."); - cx.send_async.begin(new Geary.Imap.ListCommand.wildcarded(args[0], - new Geary.Imap.MailboxSpecifier(args[1]), (cmd.down() == "xlist"), null), null, on_list); + this.cx.send_command(new Geary.Imap.ListCommand.wildcarded(args[0], + new Geary.Imap.MailboxSpecifier(args[1]), (cmd.down() == "xlist"), null)); } - - private void on_list(Object? source, AsyncResult result) { - try { - cx.send_async.end(result); - status("Listed"); - } catch (Error err) { - exception(err); - } - } - + private void examine(string cmd, string[] args) throws Error { check_connected(cmd, args, 1, ""); - + status("Opening %s read-only".printf(args[0])); - cx.send_async.begin(new Geary.Imap.ExamineCommand(new Geary.Imap.MailboxSpecifier(args[0])), - null, on_examine); + this.cx.send_command(new Geary.Imap.ExamineCommand(new Geary.Imap.MailboxSpecifier(args[0]))); } - - private void on_examine(Object? source, AsyncResult result) { - try { - cx.send_async.end(result); - status("Opened read-only"); - } catch (Error err) { - exception(err); - } + + private void create(string cmd, string[] args) throws Error { + check_connected(cmd, args, 1, ""); + + status("Creating %s".printf(args[0])); + this.cx.send_command( + new Geary.Imap.CreateCommand( + new Geary.Imap.MailboxSpecifier(args[0]) + ) + ); } - + + private void @delete(string cmd, string[] args) throws Error { + check_connected(cmd, args, 1, ""); + + status("Deleting %s".printf(args[0])); + this.cx.send_command( + new Geary.Imap.DeleteCommand( + new Geary.Imap.MailboxSpecifier(args[0]) + ) + ); + } + private void fetch(string cmd, string[] args) throws Error { check_min_connected(cmd, args, 2, " "); - + status("Fetching %s".printf(args[0])); - + Geary.Imap.MessageSet msg_set = (cmd.down() == "fetch") ? new Geary.Imap.MessageSet.custom(args[0]) : new Geary.Imap.MessageSet.uid_custom(args[0]); - + Gee.ArrayList data_items = new Gee.ArrayList(); for (int ctr = 1; ctr < args.length; ctr++) { Geary.Imap.StringParameter stringp = Geary.Imap.StringParameter.get_best_for(args[ctr]); Geary.Imap.FetchDataSpecifier data_type = Geary.Imap.FetchDataSpecifier.from_parameter(stringp); data_items.add(data_type); } - - cx.send_async.begin(new Geary.Imap.FetchCommand(msg_set, data_items, null), null, on_fetch); + + this.cx.send_command(new Geary.Imap.FetchCommand(msg_set, data_items, null)); } - + private void fetch_fields(string cmd, string[] args) throws Error { check_min_connected(cmd, args, 2, " "); - + status("Fetching fields %s".printf(args[0])); - + Geary.Imap.FetchBodyDataSpecifier fields = new Geary.Imap.FetchBodyDataSpecifier( Geary.Imap.FetchBodyDataSpecifier.SectionPart.HEADER_FIELDS, null, -1, -1, args[1:args.length]); - + Gee.List list = new Gee.ArrayList(); list.add(fields); - - cx.send_async.begin(new Geary.Imap.FetchCommand( - new Geary.Imap.MessageSet.custom(args[0]), null, list), null, on_fetch); - } - - private void on_fetch(Object? source, AsyncResult result) { - try { - cx.send_async.end(result); - status("Fetched"); - } catch (Error err) { - exception(err); - } + + this.cx.send_command(new Geary.Imap.FetchCommand( + new Geary.Imap.MessageSet.custom(args[0]), null, list)); } - + private void append(string cmd, string[] args) throws Error { check_connected(cmd, args, 2, " "); - + status("Appending %s to %s".printf(args[1], args[0])); - - cx.send_async.begin(new Geary.Imap.AppendCommand(new Geary.Imap.MailboxSpecifier(args[0]), - null, null, new Geary.Memory.FileBuffer(File.new_for_path(args[1]), true)), null, - on_appended); - } - - private void on_appended(Object? source, AsyncResult result) { - try { - cx.send_async.end(result); - status("Appended"); - } catch (Error err) { - exception(err); - } + + this.cx.send_command(new Geary.Imap.AppendCommand(new Geary.Imap.MailboxSpecifier(args[0]), + null, null, new Geary.Memory.FileBuffer(File.new_for_path(args[1]), true))); } - + private void search(string cmd, string[] args) throws Error { check_min_connected(cmd, args, 1, " ..."); - + status("Searching"); - + Geary.Imap.SearchCriteria criteria = new Geary.Imap.SearchCriteria(); foreach (string arg in args) criteria.and(new Geary.Imap.SearchCriterion.simple(arg)); - + Geary.Imap.SearchCommand search; if (cmd == "uid-search") search = new Geary.Imap.SearchCommand.uid(criteria); else search = new Geary.Imap.SearchCommand(criteria); - - cx.send_async.begin(search, null, on_searched); - } - - private void on_searched(Object? source, AsyncResult result) { - try { - cx.send_async.end(result); - status("Searched"); - } catch (Error err) { - exception(err); - } + + this.cx.send_command(search); } - + private void close_cmd(string cmd, string[] args) throws Error { check_connected(cmd, args, 0, null); - + status("Closing"); - - cx.send_async.begin(new Geary.Imap.CloseCommand(), null, on_closed); - } - - private void on_closed(Object? source, AsyncResult result) { - try { - cx.send_async.end(result); - status("Closed"); - } catch (Error err) { - exception(err); - } + + this.cx.send_command(new Geary.Imap.CloseCommand()); } - + private void folder_status(string cmd, string[] args) throws Error { check_min_connected(cmd, args, 2, " "); - + status("Status %s".printf(args[0])); - + Geary.Imap.StatusDataType[] data_items = new Geary.Imap.StatusDataType[0]; for (int ctr = 1; ctr < args.length; ctr++) { Geary.Imap.StringParameter stringp = Geary.Imap.StringParameter.get_best_for(args[ctr]); data_items += Geary.Imap.StatusDataType.from_parameter(stringp); } - - cx.send_async.begin(new Geary.Imap.StatusCommand(new Geary.Imap.MailboxSpecifier(args[0]), - data_items), null, on_get_status); - } - - private void on_get_status(Object? source, AsyncResult result) { - try { - cx.send_async.end(result); - status("Get status"); - } catch (Error err) { - exception(err); - } + + this.cx.send_command(new Geary.Imap.StatusCommand(new Geary.Imap.MailboxSpecifier(args[0]), + data_items)); } - + private void preview(string cmd, string[] args) throws Error { check_min_connected(cmd, args, 1, ""); - + status("Preview %s".printf(args[0])); - + Geary.Imap.FetchBodyDataSpecifier preview_data_type = new Geary.Imap.FetchBodyDataSpecifier.peek( Geary.Imap.FetchBodyDataSpecifier.SectionPart.NONE, { 1 }, 0, Geary.Email.MAX_PREVIEW_BYTES, null); - + Gee.ArrayList list = new Gee.ArrayList(); list.add(preview_data_type); - - cx.send_async.begin(new Geary.Imap.FetchCommand( - new Geary.Imap.MessageSet.custom(args[0]), null, list), null, on_preview_completed); - } - - private void on_preview_completed(Object? source, AsyncResult result) { - try { - cx.send_async.end(result); - status("Preview fetched"); - } catch (Error err) { - exception(err); - } + + this.cx.send_command(new Geary.Imap.FetchCommand( + new Geary.Imap.MessageSet.custom(args[0]), null, list)); } - + private void quit(string cmd, string[] args) throws Error { Gtk.main_quit(); } - + private bool keepalive_on = false; - + private void keepalive(string cmd, string[] args) throws Error { if (keepalive_on) { status("Keepalive already active."); - + return; } - + check_connected(cmd, args, 0, null); - + keepalive_on = true; Timeout.add_seconds(KEEPALIVE_SEC, on_keepalive); - + status("Keepalive on."); } - + private bool on_keepalive() { try { noop("noop", new string[0]); } catch (Error err) { status("Keepalive failed, halted: %s".printf(err.message)); - + keepalive_on = false; } - + return keepalive_on; } - + private void print_console_line(string text) { append_to_console("[C] "); append_to_console(text); append_to_console("\n"); } - + private void on_sent_command(Geary.Imap.Command cmd) { append_to_console("[L] "); append_to_console(cmd.to_string()); append_to_console("\n"); } - + private void on_received_status_response(Geary.Imap.StatusResponse status_response) { append_to_console("[R] "); append_to_console(status_response.to_string()); append_to_console("\n"); - + // TODO: This means that responses will grow without bounds without some process of // timing-out old unharvested responses status_responses.set(status_response.tag, status_response); recvd_response_event.blind_notify(); } - + private async Geary.Imap.StatusResponse wait_for_response_async(Geary.Imap.Tag tag) throws Error { for (;;) { Geary.Imap.StatusResponse? response = status_responses.get(tag); if (response != null) return response; - + yield recvd_response_event.wait_async(); } } - + private void on_received_server_data(Geary.Imap.ServerData server_data) { append_to_console("[D] "); append_to_console(server_data.to_string()); append_to_console("\n"); } - + private void on_received_bad_response(Geary.Imap.RootParameters root, Geary.ImapError err) { append_to_console("[E] "); append_to_console(err.message); @@ -702,7 +637,7 @@ append_to_console(root.to_string()); append_to_console("\n"); } - + private void append_to_console(string text) { Gtk.TextIter iter; console.buffer.get_iter_at_offset(out iter, -1); @@ -712,14 +647,13 @@ void main(string[] args) { Gtk.init(ref args); - + Geary.Logging.init(); Geary.Logging.enable_flags(Geary.Logging.Flag.NETWORK); Geary.Logging.log_to(stdout); - + ImapConsole console = new ImapConsole(); console.show_all(); - + Gtk.main(); } - diff -Nru geary-0.12.4/src/console/meson.build geary-3.32.0/src/console/meson.build --- geary-0.12.4/src/console/meson.build 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/src/console/meson.build 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,19 @@ +geary_console_sources = files( + 'main.vala', +) + +geary_console_dependencies = [ + gtk, + gee, + gmime, + webkit2gtk, + geary_engine_dep, +] + +geary_console = executable('geary-console', + geary_console_sources, + dependencies: geary_console_dependencies, + include_directories: config_h_dir, + vala_args: geary_vala_options, + c_args: geary_c_options, +) diff -Nru geary-0.12.4/src/engine/api/geary-abstract-local-folder.vala geary-3.32.0/src/engine/api/geary-abstract-local-folder.vala --- geary-0.12.4/src/engine/api/geary-abstract-local-folder.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/api/geary-abstract-local-folder.vala 2019-03-17 13:39:29.000000000 +0000 @@ -10,55 +10,53 @@ public abstract class Geary.AbstractLocalFolder : Geary.Folder { private ProgressMonitor _opening_monitor = new Geary.ReentrantProgressMonitor(Geary.ProgressType.ACTIVITY); public override Geary.ProgressMonitor opening_monitor { get { return _opening_monitor; } } - + private int open_count = 0; private Nonblocking.Semaphore closed_semaphore = new Nonblocking.Semaphore(); - + protected AbstractLocalFolder() { + // Notify now to ensure that wait_for_close_async does not + // block if never opened. + this.closed_semaphore.blind_notify(); } - + public override Geary.Folder.OpenState get_open_state() { return open_count > 0 ? Geary.Folder.OpenState.LOCAL : Geary.Folder.OpenState.CLOSED; } - + protected void check_open() throws EngineError { if (open_count == 0) throw new EngineError.OPEN_REQUIRED("%s not open", to_string()); } - + protected bool is_open() { return open_count > 0; } - - public override async void wait_for_open_async(Cancellable? cancellable = null) throws Error { - if (open_count == 0) - throw new EngineError.OPEN_REQUIRED("%s not open", to_string()); - } - + public override async bool open_async(Geary.Folder.OpenFlags open_flags, Cancellable? cancellable = null) throws Error { if (open_count++ > 0) return false; - + closed_semaphore.reset(); - + notify_opened(Geary.Folder.OpenState.LOCAL, properties.email_total); - + return true; } - + public override async bool close_async(Cancellable? cancellable = null) throws Error { if (open_count == 0 || --open_count > 0) return false; - + closed_semaphore.blind_notify(); - + notify_closed(Geary.Folder.CloseReason.LOCAL_CLOSE); notify_closed(Geary.Folder.CloseReason.FOLDER_CLOSED); - + return false; } - + public override async void wait_for_close_async(Cancellable? cancellable = null) throws Error { yield closed_semaphore.wait_async(cancellable); } diff -Nru geary-0.12.4/src/engine/api/geary-account-information.vala geary-3.32.0/src/engine/api/geary-account-information.vala --- geary-0.12.4/src/engine/api/geary-account-information.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/api/geary-account-information.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,82 +1,84 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. + * Copyright 2018 Michael Gratton * * This software is licensed under the GNU Lesser General Public License - * (version 2.1 or later). See the COPYING file in this distribution. + * (version 2.1 or later). See the COPYING file in this distribution. */ public class Geary.AccountInformation : BaseObject { - public const string PROP_NICKNAME = "nickname"; // Name of nickname property. - - private const string GROUP = "AccountInformation"; - private const string REAL_NAME_KEY = "real_name"; - private const string NICKNAME_KEY = "nickname"; - private const string PRIMARY_EMAIL_KEY = "primary_email"; - private const string ALTERNATE_EMAILS_KEY = "alternate_emails"; - private const string SERVICE_PROVIDER_KEY = "service_provider"; - private const string ORDINAL_KEY = "ordinal"; - private const string PREFETCH_PERIOD_DAYS_KEY = "prefetch_period_days"; - private const string IMAP_USERNAME_KEY = "imap_username"; - private const string IMAP_REMEMBER_PASSWORD_KEY = "imap_remember_password"; - private const string SMTP_USERNAME_KEY = "smtp_username"; - private const string SMTP_REMEMBER_PASSWORD_KEY = "smtp_remember_password"; - private const string IMAP_HOST = "imap_host"; - private const string IMAP_PORT = "imap_port"; - private const string IMAP_SSL = "imap_ssl"; - private const string IMAP_STARTTLS = "imap_starttls"; - private const string SMTP_HOST = "smtp_host"; - private const string SMTP_PORT = "smtp_port"; - private const string SMTP_SSL = "smtp_ssl"; - private const string SMTP_STARTTLS = "smtp_starttls"; - private const string SMTP_USE_IMAP_CREDENTIALS = "smtp_use_imap_credentials"; - private const string SMTP_NOAUTH = "smtp_noauth"; - private const string SAVE_SENT_MAIL_KEY = "save_sent_mail"; - private const string DRAFTS_FOLDER_KEY = "drafts_folder"; - private const string SENT_MAIL_FOLDER_KEY = "sent_mail_folder"; - private const string SPAM_FOLDER_KEY = "spam_folder"; - private const string TRASH_FOLDER_KEY = "trash_folder"; - private const string ARCHIVE_FOLDER_KEY = "archive_folder"; - private const string SAVE_DRAFTS_KEY = "save_drafts"; - private const string USE_EMAIL_SIGNATURE_KEY = "use_email_signature"; - private const string EMAIL_SIGNATURE_KEY = "email_signature"; - - // - // "Retired" keys - // - - /* - * key: "imap_pipeline" - * value: bool - */ - - public const string SETTINGS_FILENAME = "geary.ini"; + + public const int DEFAULT_PREFETCH_PERIOD_DAYS = 14; - - public static int default_ordinal = 0; - - private static Gee.HashMap? known_endpoints = null; - - /** - * Location account information is stored (as well as other data, including database and - * attachment files. - */ - public File? config_dir { get; private set; default = null; } - public File? data_dir { get; private set; default = null; } - internal File? file = null; - // - // IMPORTANT: When adding new properties, be sure to add them to the copy method. - // + /** The next ordinal that should be allocated for an account. */ + public static int next_ordinal = 0; + + /** Comparator for account info objects based on their ordinals. */ + public static int compare_ascending(AccountInformation a, AccountInformation b) { + int diff = a.ordinal - b.ordinal; + if (diff != 0) + return diff; + + // Stabilize on display name, which should always be unique. + return a.display_name.collate(b.display_name); + } + + public static Geary.FolderPath? build_folder_path(Gee.List? parts) { + if (parts == null || parts.size == 0) + return null; + + Geary.FolderPath path = new Imap.FolderRoot(); + foreach (string basename in parts) { + path = path.get_child(basename); + } + return path; + } + + + /** A unique (engine-wide), opaque identifier for the account. */ + public string id { get; private set; } + + /** A unique (engine-wide) ordering for the account. */ + public int ordinal { + get; set; default = AccountInformation.next_ordinal++; + } + + /** Specifies the email provider for this account. */ + public Geary.ServiceProvider service_provider { get; private set; } /** - * A unique, immutable, machine-readable identifier for this account. + * A human-readable label describing the email service provider. * - * This string's value should be treated as an opaque, private - * implementation detail and not parsed at all. For older accounts - * it will be an email address, for newer accounts it will be - * something else. Once created, this string will never change. - */ - public string id { get; private set; } + * Known providers such as Gmail will have a label specified by + * clients, but other accounts can only really be identified by + * their server names. This attempts to extract a 'nice' value for + * label based on the service's host names. + */ + public string service_label { + owned get { + string? value = this._service_label; + if (value == null) { + string email_domain = this.primary_mailbox.domain; + if (this.incoming.host.has_suffix(email_domain)) { + value = email_domain; + } else { + string[] host_parts = this.incoming.host.split("."); + if (host_parts.length > 2) { + host_parts = host_parts[1:host_parts.length]; + } + value = string.joinv(".", host_parts); + } + // Don't stash the calculated value in _service_label + // since we want it updated if the service host names + // change + } + return value; + } + set { this._service_label = value; } + } + private string? _service_label = null; /** * A unique human-readable display name for this account. @@ -88,8 +90,8 @@ */ public string display_name { get { - return (!String.is_empty_or_whitespace(this.nickname)) - ? this.nickname + return (!String.is_empty_or_whitespace(this.label)) + ? this.label : this.primary_mailbox.address; } } @@ -100,922 +102,452 @@ * This is not to be used in the UI (use `display_name` instead) * and not transmitted on the wire or used in correspondence. */ - public string nickname { get; set; default = ""; } + public string label { get; set; default = ""; } /** - * The default email address for the account. + * The default sender mailbox address for the account. + * + * This is the first mailbox in the {@link sender_mailboxes} list. */ public Geary.RFC822.MailboxAddress primary_mailbox { - get; set; default = new RFC822.MailboxAddress("", ""); + owned get { return this.sender_mailboxes.get(0); } } /** - * A list of additional email addresses this account accepts. - * - * Use {@link add_alternate_mailbox} or {@link replace_alternate_mailboxes} rather than edit - * this collection directly. + * A read-only list of sender mailbox address for the account. * - * @see get_all_mailboxes + * The first address in the list is the default address, others + * are essentially aliases. */ - public Gee.List? alternate_mailboxes { get; private set; default = null; } + public Gee.List? sender_mailboxes { + owned get { return this.mailboxes.read_only_view; } + } - public Geary.ServiceProvider service_provider { - get; set; default = Geary.ServiceProvider.GMAIL; + /** Determines if the account has more than one sender mailbox. */ + public bool has_sender_aliases { + get { return this.sender_mailboxes.size > 1; } } + + /** Specifies the number of days to be fetched by the account sync. */ public int prefetch_period_days { get; set; default = DEFAULT_PREFETCH_PERIOD_DAYS; } - /** - * Whether the user has requested that sent mail be saved. Note that Geary - * will only actively push sent mail when this AND allow_save_sent_mail() - * are both true. - */ - public bool save_sent_mail { - // If we aren't allowed to save sent mail due to account type, we want - // to return true here on the assumption that the account will save - // sent mail for us, and thus the user can't disable sent mail from - // being saved. - get { return (allow_save_sent_mail() ? _save_sent_mail : true); } - set { _save_sent_mail = value; } + /** Specifies if sent email should be saved to the Sent folder. */ + public bool save_sent { + get { + bool save = _save_sent; + switch (this.service_provider) { + case GMAIL: + case OUTLOOK: + save = false; + break; + } + return save; + } + set { this._save_sent = value; } } + private bool _save_sent = true; - // Order for display purposes. - public int ordinal { - get; set; default = AccountInformation.default_ordinal++; - } + /** Determines if drafts should be saved on the server. */ + public bool save_drafts { get; set; default = true; } - // These properties are only used if the service provider's - // account type does not override them. + /** + * The source of authentication credentials for this account. + */ + public CredentialsMediator mediator { get; private set; } - public string default_imap_server_host { - get; set; default = ""; - } - public uint16 default_imap_server_port { - get; set; default = Imap.ClientConnection.DEFAULT_PORT_SSL; - } - public bool default_imap_server_ssl { get; set; default = true; } - public bool default_imap_server_starttls { get; set; default = false; } + /* Incoming email service configuration. */ + public ServiceInformation incoming { get; set; } - public string default_smtp_server_host { - get; set; default = ""; - } - public uint16 default_smtp_server_port { - get; set; default = Geary.Smtp.ClientConnection.DEFAULT_PORT_SSL; + /* Outgoing email service configuration. */ + public ServiceInformation outgoing { get; set; } + + /** A lock that can be used to ensure saving is serialised. */ + public Nonblocking.Mutex write_lock { + get; private set; default = new Nonblocking.Mutex(); } - public bool default_smtp_server_ssl { get; set; default = true; } - public bool default_smtp_server_starttls { get; set; default = false; } - public bool default_smtp_use_imap_credentials { get; set; default = false; } - public bool default_smtp_server_noauth { get; set; default = false; } - public bool use_email_signature { get; set; default = false; } - public string email_signature { get; set; default = ""; } + /** Specifies if an email sig should be appended to new messages. */ + public bool use_signature { get; set; default = false; } + + /** Specifies the email sig to be appended to new messages. */ + public string signature { get; set; default = ""; } + /** Draft special folder path. */ public Geary.FolderPath? drafts_folder_path { get; set; default = null; } - public Geary.FolderPath? sent_mail_folder_path { get; set; default = null; } + + /** Sent special folder path. */ + public Geary.FolderPath? sent_folder_path { get; set; default = null; } + + /** Spam special folder path. */ public Geary.FolderPath? spam_folder_path { get; set; default = null; } + + /** Trash special folder path. */ public Geary.FolderPath? trash_folder_path { get; set; default = null; } + + /** Archive special folder path. */ public Geary.FolderPath? archive_folder_path { get; set; default = null; } - public Geary.Credentials imap_credentials { get; set; default = new Geary.Credentials(null, null); } - public bool imap_remember_password { get; set; default = true; } - public Geary.Credentials? smtp_credentials { get; set; default = new Geary.Credentials(null, null); } - public bool smtp_remember_password { get; set; default = true; } - - public bool save_drafts { get; set; default = true; } - - private bool _save_sent_mail = true; - private Endpoint? imap_endpoint = null; - private Endpoint? smtp_endpoint = null; - /** - * Indicates the supplied {@link Endpoint} has reported TLS certificate warnings during - * connection. + * Location of the account's config directory. * - * Since this {@link Endpoint} persists for the lifetime of the {@link AccountInformation}, - * marking it as trusted once will survive the application session. It is up to the caller to - * pin the certificate appropriately if the user does not want to receive these warnings in - * the future. + * This directory is used to store small, per-account + * configuration files, including the account's settings key file. */ - public signal void untrusted_host(Endpoint endpoint, Endpoint.SecurityType security, - TlsConnection cx, Service service); + public File? config_dir { get; private set; default = null; } - // Used to create temporary AccountInformation objects. (Note that these cannot be saved.) - public AccountInformation.temp_copy(AccountInformation copy) { - copy_from(copy); + /** + * Location of the account's data directory. + * + * This directory is used to store large, per-account data files + * such as the account database. + */ + public File? data_dir { get; private set; default = null; } + + private Gee.List mailboxes { + get; private set; + default = new Gee.LinkedList(); } + + /** + * Emitted when a service has reported an authentication failure. + * + * No further connection attempts will be made after this signal + * has been fired until the associated {@link ClientService} has + * been restarted. It is up to the client to prompt the user for + * updated credentials and restart the service. + */ + public signal void authentication_failure(ServiceInformation service); + + /** + * Emitted when an endpoint has reported TLS certificate warnings. + * + * This signal is emitted when either of the incoming or outgoing + * endpoints emit the signal with the same name. It may be more + * convenient for clients to connect to this instead. + * + * No further connection attempts will be made after this signal + * has been fired until the associated {@link ClientService} has + * been restarted. It is up to the client to prompt the user to + * take action about the certificate (e.g. decide to pin it) then + * restart the service. + * + * @see Endpoint.untrusted_host + */ + public signal void untrusted_host(ServiceInformation service, + Endpoint endpoint, + GLib.TlsConnection cx); + + /** Emitted when the account settings have changed. */ + public signal void changed(); + /** - * Creates a new, empty account info file. + * Creates a new account with default settings. */ - public AccountInformation(string id, File config_directory, File data_directory) { + public AccountInformation(string id, + ServiceProvider provider, + CredentialsMediator mediator, + RFC822.MailboxAddress primary_mailbox) { this.id = id; - this.config_dir = config_directory; - this.data_dir = data_directory; - this.file = config_dir.get_child(SETTINGS_FILENAME); + this.mediator = mediator; + this.service_provider = provider; + this.incoming = new ServiceInformation(Protocol.IMAP, provider); + this.outgoing = new ServiceInformation(Protocol.SMTP, provider); + + provider.set_account_defaults(this); + + append_sender(primary_mailbox); } /** - * Loads an account info from a config directory. - * - * Throws an error if the config file was not found, could not be - * parsed, or doesn't have all required fields. + * Creates a copy of an existing config. */ - internal AccountInformation.from_file(string id, - File config_directory, - File data_directory) - throws Error { - this(id, config_directory, data_directory); + public AccountInformation.copy(AccountInformation other) { + this( + other.id, + other.service_provider, + other.mediator, + other.primary_mailbox + ); + this.service_label = other.service_label; + this.label = other.label; + if (other.mailboxes.size > 1) { + this.mailboxes.add_all( + other.mailboxes.slice(1, other.mailboxes.size) + ); + } + this.prefetch_period_days = other.prefetch_period_days; + this.save_sent = other.save_sent; + this.save_drafts = other.save_drafts; + this.use_signature = other.use_signature; + this.signature = other.signature; - KeyFile key_file = new KeyFile(); - key_file.load_from_file(file.get_path() ?? "", KeyFileFlags.NONE); + this.incoming = new ServiceInformation.copy(other.incoming); + this.outgoing = new ServiceInformation.copy(other.outgoing); - // This is the only required value at the moment? - string primary_email = key_file.get_value(GROUP, PRIMARY_EMAIL_KEY); - string real_name = get_string_value(key_file, GROUP, REAL_NAME_KEY); + this.drafts_folder_path = other.drafts_folder_path; + this.sent_folder_path = other.sent_folder_path; + this.spam_folder_path = other.spam_folder_path; + this.trash_folder_path = other.trash_folder_path; + this.archive_folder_path = other.archive_folder_path; - this.primary_mailbox = new RFC822.MailboxAddress(real_name, primary_email); - this.nickname = get_string_value(key_file, GROUP, NICKNAME_KEY); + this.config_dir = other.config_dir; + this.data_dir = other.data_dir; + } - // Store alternate emails in a list of case-insensitive strings - Gee.List alt_email_list = get_string_list_value(key_file, GROUP, ALTERNATE_EMAILS_KEY); - if (alt_email_list.size != 0) { - foreach (string alt_email in alt_email_list) { - RFC822.MailboxAddresses mailboxes = new RFC822.MailboxAddresses.from_rfc822_string(alt_email); - foreach (RFC822.MailboxAddress mailbox in mailboxes.get_all()) - add_alternate_mailbox(mailbox); - } + /** Sets the location of the account's storage directories. */ + public void set_account_directories(GLib.File config, GLib.File data) { + this.config_dir = config; + this.data_dir = data; + } + + /** + * Determines if a mailbox is in the sender mailbox list. + * + * Returns true if the given address is equal to one of the + * addresses in {@link sender_mailboxes}, by case-insensitive + * matching the address parts. + * + * @see Geary.RFC822.MailboxAddress.equal_to + */ + public bool has_sender_mailbox(Geary.RFC822.MailboxAddress email) { + return this.mailboxes.any_match((alt) => alt.equal_to(email)); + } + + /** + * Appends a mailbox to the list of sender mailboxes. + * + * Mailboxes with duplicate addresses will not be added. + * + * Returns true if the mailbox was appended. + */ + public bool append_sender(Geary.RFC822.MailboxAddress mailbox) { + bool add = !has_sender_mailbox(mailbox); + if (add) { + this.mailboxes.add(mailbox); } + return add; + } - this.imap_credentials.user = get_string_value( - key_file, GROUP, IMAP_USERNAME_KEY, primary_email); - this.imap_remember_password = get_bool_value( - key_file, GROUP, IMAP_REMEMBER_PASSWORD_KEY, this.imap_remember_password); - this.smtp_credentials.user = get_string_value( - key_file, GROUP, SMTP_USERNAME_KEY, primary_email); - this.smtp_remember_password = get_bool_value( - key_file, GROUP, SMTP_REMEMBER_PASSWORD_KEY, this.smtp_remember_password); - this.service_provider = Geary.ServiceProvider.from_string( - get_string_value( - key_file, GROUP, SERVICE_PROVIDER_KEY, Geary.ServiceProvider.GMAIL.to_string())); - this.prefetch_period_days = get_int_value( - key_file, GROUP, PREFETCH_PERIOD_DAYS_KEY, this.prefetch_period_days); - this.save_sent_mail = get_bool_value( - key_file, GROUP, SAVE_SENT_MAIL_KEY, this.save_sent_mail); - this.ordinal = get_int_value( - key_file, GROUP, ORDINAL_KEY, this.ordinal); - this.use_email_signature = get_bool_value( - key_file, GROUP, USE_EMAIL_SIGNATURE_KEY, this.use_email_signature); - this.email_signature = get_escaped_string( - key_file, GROUP, EMAIL_SIGNATURE_KEY, this.email_signature); - - if (this.ordinal >= AccountInformation.default_ordinal) - AccountInformation.default_ordinal = this.ordinal + 1; - - if (service_provider == ServiceProvider.OTHER) { - this.default_imap_server_host = get_string_value( - key_file, GROUP, IMAP_HOST, this.default_imap_server_host); - this.default_imap_server_port = get_uint16_value( - key_file, GROUP, IMAP_PORT, this.default_imap_server_port); - this.default_imap_server_ssl = get_bool_value( - key_file, GROUP, IMAP_SSL, this.default_imap_server_ssl); - this.default_imap_server_starttls = get_bool_value( - key_file, GROUP, IMAP_STARTTLS, this.default_imap_server_starttls); - - this.default_smtp_server_host = get_string_value( - key_file, GROUP, SMTP_HOST, this.default_smtp_server_host); - this.default_smtp_server_port = get_uint16_value( - key_file, GROUP, SMTP_PORT, this.default_smtp_server_port); - this.default_smtp_server_ssl = get_bool_value( - key_file, GROUP, SMTP_SSL, this.default_smtp_server_ssl); - this.default_smtp_server_starttls = get_bool_value( - key_file, GROUP, SMTP_STARTTLS, this.default_smtp_server_starttls); - this.default_smtp_use_imap_credentials = get_bool_value( - key_file, GROUP, SMTP_USE_IMAP_CREDENTIALS, this.default_smtp_use_imap_credentials); - this.default_smtp_server_noauth = get_bool_value( - key_file, GROUP, SMTP_NOAUTH, this.default_smtp_server_noauth); - - if (default_smtp_server_noauth) { - this.smtp_credentials = null; - } else if (default_smtp_use_imap_credentials) { - this.smtp_credentials.user = imap_credentials.user; - this.smtp_credentials.pass = imap_credentials.pass; - } + /** + * Inserts a mailbox into the list of sender mailboxes. + * + * Mailboxes with duplicate addresses will not be added. + * + * Returns true if the mailbox was inserted. + */ + public bool insert_sender(int index, Geary.RFC822.MailboxAddress mailbox) { + bool add = !has_sender_mailbox(mailbox); + if (add) { + this.mailboxes.insert(index, mailbox); } + return add; + } - this.drafts_folder_path = build_folder_path( - get_string_list_value(key_file, GROUP, DRAFTS_FOLDER_KEY)); - this.sent_mail_folder_path = build_folder_path( - get_string_list_value(key_file, GROUP, SENT_MAIL_FOLDER_KEY)); - this.spam_folder_path = build_folder_path( - get_string_list_value(key_file, GROUP, SPAM_FOLDER_KEY)); - this.trash_folder_path = build_folder_path( - get_string_list_value(key_file, GROUP, TRASH_FOLDER_KEY)); - this.archive_folder_path = build_folder_path( - get_string_list_value(key_file, GROUP, ARCHIVE_FOLDER_KEY)); - - this.save_drafts = get_bool_value(key_file, GROUP, SAVE_DRAFTS_KEY, true); - } - - ~AccountInformation() { - if (imap_endpoint != null) - imap_endpoint.untrusted_host.disconnect(on_imap_untrusted_host); - - if (smtp_endpoint != null) - smtp_endpoint.untrusted_host.disconnect(on_smtp_untrusted_host); - } - - internal static void init() { - known_endpoints = new Gee.HashMap(); - } - - private static Geary.Endpoint get_shared_endpoint(Service service, Endpoint endpoint) { - string key = "%s/%s:%u".printf(service.user_label(), endpoint.remote_address.hostname, - endpoint.remote_address.port); - - // if already known, prefer it over this one - if (known_endpoints.has_key(key)) - return known_endpoints.get(key); - - // save for future use and return this one - known_endpoints.set(key, endpoint); - - return endpoint; - } - - // Copies all data from the "from" object into this one. - public void copy_from(AccountInformation from) { - this.id = from.id; - this.nickname = from.nickname; - this.primary_mailbox = from.primary_mailbox; - if (from.alternate_mailboxes != null) { - foreach (RFC822.MailboxAddress alternate_mailbox in from.alternate_mailboxes) - add_alternate_mailbox(alternate_mailbox); - } - this.service_provider = from.service_provider; - this.prefetch_period_days = from.prefetch_period_days; - this.save_sent_mail = from.save_sent_mail; - this.ordinal = from.ordinal; - this.default_imap_server_host = from.default_imap_server_host; - this.default_imap_server_port = from.default_imap_server_port; - this.default_imap_server_ssl = from.default_imap_server_ssl; - this.default_imap_server_starttls = from.default_imap_server_starttls; - this.default_smtp_server_host = from.default_smtp_server_host; - this.default_smtp_server_port = from.default_smtp_server_port; - this.default_smtp_server_ssl = from.default_smtp_server_ssl; - this.default_smtp_server_starttls = from.default_smtp_server_starttls; - this.default_smtp_use_imap_credentials = from.default_smtp_use_imap_credentials; - this.default_smtp_server_noauth = from.default_smtp_server_noauth; - this.imap_credentials = from.imap_credentials; - this.imap_remember_password = from.imap_remember_password; - this.smtp_credentials = from.smtp_credentials; - this.smtp_remember_password = from.smtp_remember_password; - this.drafts_folder_path = from.drafts_folder_path; - this.sent_mail_folder_path = from.sent_mail_folder_path; - this.spam_folder_path = from.spam_folder_path; - this.trash_folder_path = from.trash_folder_path; - this.archive_folder_path = from.archive_folder_path; - this.save_drafts = from.save_drafts; - this.use_email_signature = from.use_email_signature; - this.email_signature = from.email_signature; - } - - /** - * Return a list of the primary and all alternate email addresses. - */ - public Gee.List get_all_mailboxes() { - Gee.ArrayList all = new Gee.ArrayList(); - - all.add(this.primary_mailbox); - - if (alternate_mailboxes != null) - all.add_all(alternate_mailboxes); - - return all; - } - - /** - * Add an alternate email address to the account. - * - * Duplicates will be ignored. - */ - public void add_alternate_mailbox(Geary.RFC822.MailboxAddress mailbox) { - if (alternate_mailboxes == null) - alternate_mailboxes = new Gee.ArrayList(); - - if (!alternate_mailboxes.contains(mailbox)) - alternate_mailboxes.add(mailbox); - } - - /** - * Replaces the list of alternate email addresses with the supplied collection. - * - * Duplicates will be ignored. - */ - public void replace_alternate_mailboxes(Gee.Collection? mailboxes) { - alternate_mailboxes = null; - - if (mailboxes == null || mailboxes.size == 0) - return; - - foreach (RFC822.MailboxAddress mailbox in mailboxes) - add_alternate_mailbox(mailbox); - } - - /** - * Return whether this account allows setting the save_sent_mail option. - * If not, save_sent_mail will always be true and setting it will be - * ignored. - */ - public bool allow_save_sent_mail() { - // We should never push mail to Gmail, since its servers automatically - // push sent mail to the sent mail folder. - return service_provider != ServiceProvider.GMAIL; - } - - /** - * Gets the path used when Geary has found or created a special folder for - * this account. This will be null if Geary has always been told about the - * special folders by the server, and hasn't had to go looking for them. - * Only the DRAFTS, SENT, SPAM, and TRASH special folder types are valid to - * pass to this function. + /** + * Removes a mailbox from the list of sender mailboxes. + * + * The last mailbox cannot be removed. + * + * Returns true if the mailbox was removed. + */ + public bool remove_sender(Geary.RFC822.MailboxAddress mailbox) { + bool removed = false; + if (this.mailboxes.size > 1) { + removed = this.mailboxes.remove(mailbox); + } + return removed; + } + + /** + * Replace a mailbox at the specified index. + */ + public void replace_sender(int index, Geary.RFC822.MailboxAddress mailbox) { + this.mailboxes.set(index, mailbox); + } + + /** + * Returns the configured path for a special folder type. + * + * This is used when Geary has found or created a special folder + * for this account. The path will be null if Geary has always + * been told about the special folders by the server, and hasn't + * had to go looking for them. Only the ARCHIVE, DRAFTS, SENT, + * SPAM, and TRASH special folder types are valid to pass to this + * function. */ public Geary.FolderPath? get_special_folder_path(Geary.SpecialFolderType special) { switch (special) { case Geary.SpecialFolderType.DRAFTS: - return drafts_folder_path; - + return this.drafts_folder_path; + case Geary.SpecialFolderType.SENT: - return sent_mail_folder_path; - + return this.sent_folder_path; + case Geary.SpecialFolderType.SPAM: - return spam_folder_path; - + return this.spam_folder_path; + case Geary.SpecialFolderType.TRASH: - return trash_folder_path; + return this.trash_folder_path; case Geary.SpecialFolderType.ARCHIVE: - return archive_folder_path; - - default: - assert_not_reached(); + return this.archive_folder_path; } + + return null; } - + /** - * Sets the path Geary will look for or create a special folder. This is - * only obeyed if the server doesn't tell Geary which folders are special. - * Only the DRAFTS, SENT, SPAM, TRASH and ARCHIVE special folder types are - * valid to pass to this function. + * Sets the configured path for a special folder type. + * + * This is only obeyed if the server doesn't tell Geary which + * folders are special. Only the DRAFTS, SENT, SPAM, TRASH and + * ARCHIVE special folder types are valid to pass to this + * function. */ - public void set_special_folder_path(Geary.SpecialFolderType special, Geary.FolderPath? path) { + public void set_special_folder_path(Geary.SpecialFolderType special, + Geary.FolderPath? new_path) { + Geary.FolderPath? old_path = null; switch (special) { case Geary.SpecialFolderType.DRAFTS: - drafts_folder_path = path; + old_path = this.drafts_folder_path; + this.drafts_folder_path = new_path; break; - + case Geary.SpecialFolderType.SENT: - sent_mail_folder_path = path; + old_path = this.sent_folder_path; + this.sent_folder_path = new_path; break; - + case Geary.SpecialFolderType.SPAM: - spam_folder_path = path; + old_path = this.spam_folder_path; + this.spam_folder_path = new_path; break; - + case Geary.SpecialFolderType.TRASH: - trash_folder_path = path; + old_path = this.trash_folder_path; + this.trash_folder_path = new_path; break; case Geary.SpecialFolderType.ARCHIVE: - archive_folder_path = path; + old_path = this.archive_folder_path; + this.archive_folder_path = new_path; break; - - default: - assert_not_reached(); - } - } - - /** - * Fetch the passwords for the given services. For each service, if the - * password is unset, use get_passwords_async() first; if the password is - * set or it's not in the key store, use prompt_passwords_async(). Return - * true if all passwords were retrieved from the key store or the user - * proceeded normally if/when prompted, false if the user tried to cancel - * the prompt. - * - * If force_request is set to true, a prompt will appear regardless. - */ - public async bool fetch_passwords_async(ServiceFlag services, - bool force_request = false) throws Error { - if (force_request) { - // Delete the current password(s). - if (services.has_imap()) { - yield Geary.Engine.instance.authentication_mediator.clear_password_async( - Service.IMAP, this); - - if (imap_credentials != null) - imap_credentials.pass = null; - } else if (services.has_smtp()) { - yield Geary.Engine.instance.authentication_mediator.clear_password_async( - Service.SMTP, this); - - if (smtp_credentials != null) - smtp_credentials.pass = null; - } } - - // Only call get_passwords on anything that hasn't been set - // (incorrectly) previously. - ServiceFlag get_services = 0; - if (services.has_imap() && !imap_credentials.is_complete()) - get_services |= ServiceFlag.IMAP; - - if (services.has_smtp() && smtp_credentials != null && !smtp_credentials.is_complete()) - get_services |= ServiceFlag.SMTP; - - ServiceFlag unset_services = services; - if (get_services != 0) - unset_services = yield get_passwords_async(get_services); - else - return true; - - if (unset_services == 0) - return true; - - return yield prompt_passwords_async(unset_services); - } - - private void check_mediator_instance() throws EngineError { - if (Geary.Engine.instance.authentication_mediator == null) - throw new EngineError.OPEN_REQUIRED( - "Geary.Engine instance needs to be open with a valid Geary.CredentialsMediator"); - } - - private void set_imap_password(string imap_password) { - // Don't just update the pass field, because we need imap_credentials - // itself to change so callers can bind to its changed signal. - imap_credentials = new Credentials(imap_credentials.user, imap_password); - } - - private void set_smtp_password(string smtp_password) { - // See above. Same argument. - smtp_credentials = new Credentials(smtp_credentials.user, smtp_password); - } - - /** - * Use Engine's authentication mediator to retrieve the passwords for the - * given services. The passwords will be stored in the appropriate - * credentials in this instance. Return any services that could *not* be - * retrieved from the key store (in which case you may want to call - * prompt_passwords_async() on the return value), or 0 if all were - * retrieved. - */ - public async ServiceFlag get_passwords_async(ServiceFlag services) throws Error { - check_mediator_instance(); - - CredentialsMediator mediator = Geary.Engine.instance.authentication_mediator; - ServiceFlag failed_services = 0; - - if (services.has_imap()) { - string? imap_password = yield mediator.get_password_async(Service.IMAP, this); - - if (imap_password != null) - set_imap_password(imap_password); - else - failed_services |= ServiceFlag.IMAP; - } - - if (services.has_smtp() && smtp_credentials != null) { - string? smtp_password = yield mediator.get_password_async(Service.SMTP, this); - - if (smtp_password != null) - set_smtp_password(smtp_password); - else - failed_services |= ServiceFlag.SMTP; - } - - return failed_services; - } - - /** - * Use the Engine's authentication mediator to prompt for the passwords for - * the given services. The passwords will be stored in the appropriate - * credentials in this instance. After the prompt, the passwords will be - * updated in the key store using update_stored_passwords_async(). Return - * whether the user proceeded normally (false if they tried to cancel the - * prompt). - */ - public async bool prompt_passwords_async(ServiceFlag services) throws Error { - check_mediator_instance(); - - string? imap_password, smtp_password; - bool imap_remember_password, smtp_remember_password; - - if (smtp_credentials == null) - services &= ~ServiceFlag.SMTP; - - if (!yield Geary.Engine.instance.authentication_mediator.prompt_passwords_async( - services, this, out imap_password, out smtp_password, - out imap_remember_password, out smtp_remember_password)) - return false; - - if (services.has_imap()) { - set_imap_password(imap_password); - this.imap_remember_password = imap_remember_password; - } - - if (services.has_smtp()) { - set_smtp_password(smtp_password); - this.smtp_remember_password = smtp_remember_password; - } - - yield update_stored_passwords_async(services); - - return true; - } - - /** - * Use the Engine's authentication mediator to set or clear the passwords - * for the given services in the key store. - */ - public async void update_stored_passwords_async(ServiceFlag services) throws Error { - check_mediator_instance(); - - CredentialsMediator mediator = Geary.Engine.instance.authentication_mediator; - - if (services.has_imap()) { - if (imap_remember_password) - yield mediator.set_password_async(Service.IMAP, this); - else - yield mediator.clear_password_async(Service.IMAP, this); - } - - if (services.has_smtp() && smtp_credentials != null) { - if (smtp_remember_password) - yield mediator.set_password_async(Service.SMTP, this); - else - yield mediator.clear_password_async(Service.SMTP, this); - } - } - - /** - * Returns the {@link Endpoint} for the account's IMAP service. - * - * The Endpoint instance is guaranteed to be the same for the lifetime of the - * {@link AccountInformation} instance, which is in turn guaranteed to be the same for the - * duration of the application session. - */ - public Endpoint get_imap_endpoint() { - if (imap_endpoint != null) - return imap_endpoint; - - switch (service_provider) { - case ServiceProvider.GMAIL: - imap_endpoint = ImapEngine.GmailAccount.generate_imap_endpoint(); - break; - - case ServiceProvider.YAHOO: - imap_endpoint = ImapEngine.YahooAccount.generate_imap_endpoint(); - break; - - case ServiceProvider.OUTLOOK: - imap_endpoint = ImapEngine.OutlookAccount.generate_imap_endpoint(); - break; - - case ServiceProvider.OTHER: - Endpoint.Flags imap_flags = Endpoint.Flags.NONE; - if (default_imap_server_ssl) - imap_flags |= Endpoint.Flags.SSL; - if (default_imap_server_starttls) - imap_flags |= Endpoint.Flags.STARTTLS; - - imap_endpoint = new Endpoint(default_imap_server_host, default_imap_server_port, - imap_flags, Imap.ClientConnection.RECOMMENDED_TIMEOUT_SEC); - break; - - default: - assert_not_reached(); - } - - // look for existing one in the global pool; want to use that because Endpoint is mutable - // and signalled in such a way that it's better to share them - imap_endpoint = get_shared_endpoint(Service.IMAP, imap_endpoint); - - // bind shared Endpoint signal to this AccountInformation's signal - imap_endpoint.untrusted_host.connect(on_imap_untrusted_host); - - return imap_endpoint; - } - - private void on_imap_untrusted_host(Endpoint endpoint, Endpoint.SecurityType security, - TlsConnection cx) { - untrusted_host(endpoint, security, cx, Service.IMAP); - } - - /** - * Returns the {@link Endpoint} for the account's SMTP service. - * - * The Endpoint instance is guaranteed to be the same for the lifetime of the - * {@link AccountInformation} instance, which is in turn guaranteed to be the same for the - * duration of the application session. - */ - public Endpoint get_smtp_endpoint() { - if (smtp_endpoint != null) - return smtp_endpoint; - - switch (service_provider) { - case ServiceProvider.GMAIL: - smtp_endpoint = ImapEngine.GmailAccount.generate_smtp_endpoint(); - break; - - case ServiceProvider.YAHOO: - smtp_endpoint = ImapEngine.YahooAccount.generate_smtp_endpoint(); - break; - - case ServiceProvider.OUTLOOK: - smtp_endpoint = ImapEngine.OutlookAccount.generate_smtp_endpoint(); + + if ((old_path == null && new_path != null) || + (old_path != null && new_path == null) || + (old_path != null && !old_path.equal_to(new_path))) { + changed(); + } + } + + /** + * Returns the best credentials to use for the outgoing service. + * + * This method checks for an outgoing service that use incoming + * service's credentials for authentication and if enabled, + * returns those. If this method returns null, then outgoing + * authentication should not be attempted for this account. + */ + public Credentials? get_outgoing_credentials() { + Credentials? outgoing = null; + switch (this.outgoing.credentials_requirement) { + case USE_INCOMING: + outgoing = this.incoming.credentials; break; - - case ServiceProvider.OTHER: - Endpoint.Flags smtp_flags = Endpoint.Flags.NONE; - if (default_smtp_server_ssl) - smtp_flags |= Endpoint.Flags.SSL; - if (default_smtp_server_starttls) - smtp_flags |= Endpoint.Flags.STARTTLS; - - smtp_endpoint = new Endpoint(default_smtp_server_host, default_smtp_server_port, - smtp_flags, Smtp.ClientConnection.DEFAULT_TIMEOUT_SEC); + case CUSTOM: + outgoing = this.outgoing.credentials; break; - - default: - assert_not_reached(); - } - - // look for existing one in the global pool; want to use that because Endpoint is mutable - // and signalled in such a way that it's better to share them - smtp_endpoint = get_shared_endpoint(Service.SMTP, smtp_endpoint); - - // bind shared Endpoint signal to this AccountInformation's signal - smtp_endpoint.untrusted_host.connect(on_smtp_untrusted_host); - - return smtp_endpoint; - } - - private void on_smtp_untrusted_host(Endpoint endpoint, Endpoint.SecurityType security, - TlsConnection cx) { - untrusted_host(endpoint, security, cx, Service.SMTP); - } - - public Geary.Endpoint get_endpoint_for_service(Geary.Service service) { - switch (service) { - case Service.IMAP: - return get_imap_endpoint(); - - case Service.SMTP: - return get_smtp_endpoint(); - - default: - assert_not_reached(); } + return outgoing; } - - private Geary.FolderPath? build_folder_path(Gee.List? parts) { - if (parts == null || parts.size == 0) - return null; - - Geary.FolderPath path = new Imap.FolderRoot(parts[0]); - for (int i = 1; i < parts.size; i++) - path = path.get_child(parts.get(i)); - return path; - } - - private string get_string_value(KeyFile key_file, string group, string key, string def = "") { - try { - return key_file.get_value(group, key); - } catch(KeyFileError err) { - // Ignore. - } - - return def; - } - - private string get_escaped_string(KeyFile key_file, string group, string key, string def = "") { - try { - return key_file.get_string(group, key); - } catch (KeyFileError err) { - // ignore - } - - return def; - } - - private Gee.List get_string_list_value(KeyFile key_file, string group, string key) { - try { - string[] list = key_file.get_string_list(group, key); - if (list.length > 0) - return Geary.Collection.array_list_wrap(list); - } catch(KeyFileError err) { - // Ignore. - } - - return new Gee.ArrayList(); - } - - private bool get_bool_value(KeyFile key_file, string group, string key, bool def = false) { - try { - return key_file.get_boolean(group, key); - } catch(KeyFileError err) { - // Ignore. - } - - return def; - } - - private int get_int_value(KeyFile key_file, string group, string key, int def = 0) { - try { - return key_file.get_integer(group, key); - } catch(KeyFileError err) { - // Ignore. - } - - return def; - } - - private uint16 get_uint16_value(KeyFile key_file, string group, string key, uint16 def = 0) { - return (uint16) get_int_value(key_file, group, key); - } - - public async void store_async(Cancellable? cancellable = null) { - if (file == null || config_dir == null) { - warning("Cannot save account, no file set.\n"); - return; - } - - if (!config_dir.query_exists(cancellable)) { - try { - config_dir.make_directory_with_parents(); - } catch (Error err) { - error("Error creating configuration directory for account '%s': %s", - this.id, err.message); - } - } - if (!data_dir.query_exists(cancellable)) { - try { - data_dir.make_directory_with_parents(); - } catch (Error err) { - error("Error creating storage directory for account '%s': %s", - this.id, err.message); + /** + * Loads the authentication token for the outgoing service. + * + * Credentials are loaded from the mediator, thus it may yield for + * some time. + * + * Returns true if the credential's token was successfully loaded + * or are not needed (that is, if the credentials are null), or + * false if the token could not be loaded and the service's + * credentials are invalid. + */ + public async bool load_outgoing_credentials(GLib.Cancellable? cancellable) + throws GLib.Error { + Credentials? creds = get_outgoing_credentials(); + bool loaded = true; + if (creds != null) { + if (this.outgoing.credentials_requirement == USE_INCOMING) { + loaded = yield this.mediator.load_token( + this, this.incoming, cancellable + ); + } else { + loaded = yield this.mediator.load_token( + this, this.outgoing, cancellable + ); } } + return loaded; + } - if (!file.query_exists(cancellable)) { - try { - yield file.create_async(FileCreateFlags.REPLACE_DESTINATION); - } catch (Error err) { - debug("Error creating account info file: %s", err.message); - } + /** + * Loads the authentication token for the incoming service. + * + * Credentials are loaded from the mediator, thus it may yield for + * some time. + * + * Returns true if the credential's token was successfully loaded + * or are not needed (that is, if the credentials are null), or + * false if the token could not be loaded and the service's + * credentials are invalid. + */ + public async bool load_incoming_credentials(GLib.Cancellable? cancellable) + throws GLib.Error { + Credentials? creds = this.incoming.credentials; + bool loaded = true; + if (creds != null) { + loaded = yield this.mediator.load_token( + this, this.incoming, cancellable + ); } + return loaded; + } - KeyFile key_file = new KeyFile(); - - key_file.set_value(GROUP, REAL_NAME_KEY, this.primary_mailbox.name); - key_file.set_value(GROUP, PRIMARY_EMAIL_KEY, this.primary_mailbox.address); - key_file.set_value(GROUP, NICKNAME_KEY, this.nickname); - key_file.set_value(GROUP, SERVICE_PROVIDER_KEY, this.service_provider.to_string()); - key_file.set_integer(GROUP, ORDINAL_KEY, this.ordinal); - key_file.set_value(GROUP, IMAP_USERNAME_KEY, this.imap_credentials.user); - key_file.set_boolean(GROUP, IMAP_REMEMBER_PASSWORD_KEY, this.imap_remember_password); - if (smtp_credentials != null) - key_file.set_value(GROUP, SMTP_USERNAME_KEY, this.smtp_credentials.user); - key_file.set_boolean(GROUP, SMTP_REMEMBER_PASSWORD_KEY, this.smtp_remember_password); - key_file.set_integer(GROUP, PREFETCH_PERIOD_DAYS_KEY, this.prefetch_period_days); - key_file.set_boolean(GROUP, SAVE_SENT_MAIL_KEY, this.save_sent_mail); - key_file.set_boolean(GROUP, USE_EMAIL_SIGNATURE_KEY, this.use_email_signature); - key_file.set_string(GROUP, EMAIL_SIGNATURE_KEY, this.email_signature); - if (alternate_mailboxes != null && this.alternate_mailboxes.size > 0) { - string[] list = new string[this.alternate_mailboxes.size]; - for (int ctr = 0; ctr < this.alternate_mailboxes.size; ctr++) - list[ctr] = this.alternate_mailboxes[ctr].to_rfc822_string(); - - key_file.set_string_list(GROUP, ALTERNATE_EMAILS_KEY, list); - } - - if (service_provider == ServiceProvider.OTHER) { - key_file.set_value(GROUP, IMAP_HOST, this.default_imap_server_host); - key_file.set_integer(GROUP, IMAP_PORT, this.default_imap_server_port); - key_file.set_boolean(GROUP, IMAP_SSL, this.default_imap_server_ssl); - key_file.set_boolean(GROUP, IMAP_STARTTLS, this.default_imap_server_starttls); - - key_file.set_value(GROUP, SMTP_HOST, this.default_smtp_server_host); - key_file.set_integer(GROUP, SMTP_PORT, this.default_smtp_server_port); - key_file.set_boolean(GROUP, SMTP_SSL, this.default_smtp_server_ssl); - key_file.set_boolean(GROUP, SMTP_STARTTLS, this.default_smtp_server_starttls); - key_file.set_boolean(GROUP, SMTP_USE_IMAP_CREDENTIALS, this.default_smtp_use_imap_credentials); - key_file.set_boolean(GROUP, SMTP_NOAUTH, this.default_smtp_server_noauth); - } - - key_file.set_string_list(GROUP, DRAFTS_FOLDER_KEY, (this.drafts_folder_path != null - ? this.drafts_folder_path.as_list().to_array() : new string[] {})); - key_file.set_string_list(GROUP, SENT_MAIL_FOLDER_KEY, (this.sent_mail_folder_path != null - ? this.sent_mail_folder_path.as_list().to_array() : new string[] {})); - key_file.set_string_list(GROUP, SPAM_FOLDER_KEY, (this.spam_folder_path != null - ? this.spam_folder_path.as_list().to_array() : new string[] {})); - key_file.set_string_list(GROUP, TRASH_FOLDER_KEY, (this.trash_folder_path != null - ? this.trash_folder_path.as_list().to_array() : new string[] {})); - key_file.set_string_list(GROUP, ARCHIVE_FOLDER_KEY, (this.archive_folder_path != null - ? this.archive_folder_path.as_list().to_array() : new string[] {})); - - key_file.set_boolean(GROUP, SAVE_DRAFTS_KEY, this.save_drafts); - - string data = key_file.to_data(); - string new_etag; - - try { - yield file.replace_contents_async(data.data, null, false, FileCreateFlags.NONE, - cancellable, out new_etag); - - Geary.Engine.instance.add_account(this, true); - } catch (Error err) { - debug("Error writing to account info file: %s", err.message); - } - } - - public async void clear_stored_passwords_async(ServiceFlag services) throws Error { - Error? return_error = null; - check_mediator_instance(); - CredentialsMediator mediator = Geary.Engine.instance.authentication_mediator; - - try { - if (services.has_imap()) - yield mediator.clear_password_async(Service.IMAP, this); - } catch (Error e) { - return_error = e; - } - - try { - if (services.has_smtp() && smtp_credentials != null) - yield mediator.clear_password_async(Service.SMTP, this); - } catch (Error e) { - return_error = e; - } - - if (return_error != null) - throw return_error; - } - - /** - * Deletes an account from disk. This is used by Geary.Engine and should not - * normally be invoked directly. - */ - internal async void remove_async(Cancellable? cancellable = null) { - if (data_dir == null) { - warning("Cannot remove account storage directory; nothing to remove"); - } else { - yield Files.recursive_delete_async(data_dir, cancellable); - } - - if (config_dir == null) { - warning("Cannot remove account configuration directory; nothing to remove"); - } else { - yield Files.recursive_delete_async(config_dir, cancellable); - } - - try { - yield clear_stored_passwords_async(ServiceFlag.IMAP | ServiceFlag.SMTP); - } catch (Error e) { - debug("Error clearing SMTP password: %s", e.message); - } - } - - /** - * Determines if this account contains a specific email address. - * - * Returns true if the address part of `email` is equal to (case - * insensitive) the address part of this account's primary mailbox - * or any of its secondary mailboxes. - */ - public bool has_email_address(Geary.RFC822.MailboxAddress email) { + public bool equal_to(AccountInformation other) { return ( - this.primary_mailbox.equal_to(email) || - (this.alternate_mailboxes != null && - this.alternate_mailboxes.fold((alt) => { - return alt.equal_to(email); - }, false)) + this == other || ( + // This is probably overkill, but handy for testing. + this.id == other.id && + this.ordinal == other.ordinal && + this.mediator == other.mediator && + this.service_provider == other.service_provider && + this.service_label == other.service_label && + this.label == other.label && + this.primary_mailbox.equal_to(other.primary_mailbox) && + this.sender_mailboxes.size == other.sender_mailboxes.size && + traverse(this.sender_mailboxes).all( + addr => other.sender_mailboxes.contains(addr) + ) && + this.prefetch_period_days == other.prefetch_period_days && + this.save_sent == other.save_sent && + this.save_drafts == other.save_drafts && + this.use_signature == other.use_signature && + this.signature == other.signature && + this.incoming.equal_to(other.incoming) && + this.outgoing.equal_to(other.outgoing) && + this.drafts_folder_path == other.drafts_folder_path && + this.sent_folder_path == other.sent_folder_path && + this.spam_folder_path == other.spam_folder_path && + this.trash_folder_path == other.trash_folder_path && + this.archive_folder_path == other.archive_folder_path && + this.config_dir == other.config_dir && + this.data_dir == other.data_dir + ) ); } - public static int compare_ascending(AccountInformation a, AccountInformation b) { - int diff = a.ordinal - b.ordinal; - if (diff != 0) - return diff; - - // Stabilize on nickname, which should always be unique. - return a.display_name.collate(b.display_name); - } - - // Returns true if this is a copy. - public bool is_copy() { - return file == null; - } } diff -Nru geary-0.12.4/src/engine/api/geary-account.vala geary-3.32.0/src/engine/api/geary-account.vala --- geary-0.12.4/src/engine/api/geary-account.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/api/geary-account.vala 2019-03-17 13:39:29.000000000 +0000 @@ -21,33 +21,136 @@ */ public abstract class Geary.Account : BaseObject { - public enum Problem { - RECV_EMAIL_LOGIN_FAILED, - SEND_EMAIL_LOGIN_FAILED, - HOST_UNREACHABLE, - NETWORK_UNAVAILABLE, - DATABASE_FAILURE, - EMAIL_DELIVERY_FAILURE, - SAVE_SENT_MAIL_FAILED, - CONNECTION_FAILURE, - } - - public Geary.AccountInformation information { get; protected set; } - + + + /** Number of times to attempt re-authentication. */ + internal const uint AUTH_ATTEMPTS_MAX = 3; + + + /** + * Denotes the account's current status. + * + * @see Account.current_status + * @see ClientService.current_status + */ + [Flags] + public enum Status { + + /** + * The account is currently online and operating normally. + * + * This flags will be set when the account's {@link incoming} + * service's {@link ClientService.current_status} is {@link + * ClientService.Status.CONNECTED}. + */ + ONLINE, + + /** + * One or of the account's services is degraded. + * + * This flag will be set when one or both of its services has + * encountered a problem. Consult the {@link + * ClientService.current_status} to determine which and the + * exact problem. + */ + SERVICE_PROBLEM; + + + /** Determines if the {@link ONLINE} flag is set. */ + public bool is_online() { + return (this & ONLINE) == ONLINE; + } + + /** Determines if the {@link SERVICE_PROBLEM} flag is set. */ + public bool has_service_problem() { + return (this & SERVICE_PROBLEM) == SERVICE_PROBLEM; + } + + } + + /** + * A utility method to sort a Gee.Collection of {@link Folder}s by + * their {@link FolderPath}s to ensure they comport with {@link + * folders_available_unavailable}, {@link folders_created}, {@link + * folders_deleted} signals' contracts. + */ + public static Gee.BidirSortedSet + sort_by_path(Gee.Collection folders) { + Gee.TreeSet sorted = + new Gee.TreeSet(Account.folder_path_comparator); + sorted.add_all(folders); + return sorted; + } + + /** + * Comparator used to sort folders. + * + * @see sort_by_path + */ + public static int folder_path_comparator(Geary.Folder a, Geary.Folder b) { + return a.path.compare_to(b.path); + } + + + /** + * The account's current configuration. + */ + public AccountInformation information { get; protected set; } + + /** + * The account's current status. + * + * This property's value is set based on the {@link + * ClientService.current_status} of the account's {@link incoming} + * and {@link outgoing} services. See {@link Status} for more + * information. + * + * The initial value for this property is {@link Status.ONLINE}, + * which may or may not be incorrect. However the once the account + * has been opened, its services will begin checking connectivity + * and the value will be updated to match in due course. + * + * @see ClientService.current_status + */ + public Status current_status { get; protected set; default = ONLINE; } + + /** + * The service manager for the incoming email service. + */ + public ClientService incoming { get; private set; } + + /** + * The service manager for the outgoing email service. + */ + public ClientService outgoing { get; private set; } + public Geary.ProgressMonitor search_upgrade_monitor { get; protected set; } public Geary.ProgressMonitor db_upgrade_monitor { get; protected set; } public Geary.ProgressMonitor db_vacuum_monitor { get; protected set; } public Geary.ProgressMonitor opening_monitor { get; protected set; } public Geary.ProgressMonitor sending_monitor { get; protected set; } + protected string id { get; private set; } + + public signal void opened(); - + public signal void closed(); - + public signal void email_sent(Geary.RFC822.Message rfc822); - - public signal void report_problem(Geary.Account.Problem problem, Error? err); - + + /** + * Emitted to notify the client that some problem has occurred. + * + * The engine uses this signal to report internal errors and other + * issues that the client should notify the user about. The {@link + * ProblemReport} class provides context about the nature of the + * problem itself. + */ + public signal void report_problem(Geary.ProblemReport problem); + + public signal void contacts_loaded(); + /** * Fired when folders become available or unavailable in the account. * @@ -55,153 +158,112 @@ * they're created later; they become unavailable when the account is * closed or they're deleted later. * - * Folders are ordered for the convenience of the caller from the top of the hierarchy to - * lower in the hierarchy. In other words, parents are listed before children, assuming the - * lists are traversed in natural order. + * Folders are ordered for the convenience of the caller from the + * top of the hierarchy to lower in the hierarchy. In other + * words, parents are listed before children, assuming the + * collections are traversed in natural order. * * @see sort_by_path */ - public signal void folders_available_unavailable(Gee.List? available, - Gee.List? unavailable); + public signal void + folders_available_unavailable(Gee.BidirSortedSet? available, + Gee.BidirSortedSet? unavailable); /** - * Fired when folders are created or deleted. + * Fired when new folders have been created. * - * Folders are ordered for the convenience of the caller from the top of the hierarchy to - * lower in the hierarchy. In other words, parents are listed before children, assuming the - * lists are traversed in natural order. + * This is fired in response to new folders appearing, for example + * the user created a new folder. It will be fired after {@link + * folders_available_unavailable} has been fired to mark the + * folders as having been made available. + * + * Folders are ordered for the convenience of the caller from the + * top of the hierarchy to lower in the hierarchy. In other + * words, parents are listed before children, assuming the + * collection is traversed in natural order. + */ + public signal void folders_created(Gee.BidirSortedSet created); + + /** + * Fired when existing folders are deleted. * - * @see sort_by_path + * This is fired in response to existing folders being removed, + * for example if the user deleted a folder. it will be fired + * after {@link folders_available_unavailable} has been fired to + * mark the folders as having been made unavailable. + * + * Folders are ordered for the convenience of the caller from the + * top of the hierarchy to lower in the hierarchy. In other + * words, parents are listed before children, assuming the + * collection is traversed in natural order. */ - public signal void folders_added_removed(Gee.List? added, - Gee.List? removed); - + public signal void folders_deleted(Gee.BidirSortedSet deleted); + /** * Fired when a Folder's contents is detected having changed. */ public signal void folders_contents_altered(Gee.Collection altered); - + /** - * Fired when a Folder's contents is detected having changed. + * Fired when a Folder's type is detected having changed. */ public signal void folders_special_type(Gee.Collection altered); - + /** * Fired when emails are appended to a folder in this account. */ public signal void email_appended(Geary.Folder folder, Gee.Collection ids); - + /** * Fired when emails are inserted to a folder in this account. * * @see Folder.email_inserted */ public signal void email_inserted(Geary.Folder folder, Gee.Collection ids); - + /** * Fired when emails are removed from a folder in this account. */ public signal void email_removed(Geary.Folder folder, Gee.Collection ids); - + /** * Fired when one or more emails have been locally saved to a folder with * the full set of Fields. */ public signal void email_locally_complete(Geary.Folder folder, Gee.Collection ids); - + /** * Fired when one or more emails have been discovered (added) to the Folder, but not necessarily * appended (i.e. old email pulled down due to user request or background fetching). */ public signal void email_discovered(Geary.Folder folder, Gee.Collection ids); - + /** * Fired when the supplied email flags have changed from any folder. */ public signal void email_flags_changed(Geary.Folder folder, Gee.Map map); - - private string name; - - protected Account(string name, AccountInformation information) { - this.name = name; + + + protected Account(AccountInformation information, + ClientService incoming, + ClientService outgoing) { this.information = information; - } - - protected virtual void notify_folders_available_unavailable(Gee.List? available, - Gee.List? unavailable) { - folders_available_unavailable(available, unavailable); - } + this.incoming = incoming; + this.outgoing = outgoing; + this.id = "%s[%s]".printf( + information.id, information.service_provider.to_value() + ); - protected virtual void notify_folders_added_removed(Gee.List? added, - Gee.List? removed) { - folders_added_removed(added, removed); + incoming.notify["current-status"].connect( + on_service_status_notify + ); + outgoing.notify["current-status"].connect( + on_service_status_notify + ); } - - protected virtual void notify_folders_contents_altered(Gee.Collection altered) { - folders_contents_altered(altered); - } - - protected virtual void notify_email_appended(Geary.Folder folder, Gee.Collection ids) { - email_appended(folder, ids); - } - - protected virtual void notify_email_inserted(Geary.Folder folder, Gee.Collection ids) { - email_inserted(folder, ids); - } - - protected virtual void notify_email_removed(Geary.Folder folder, Gee.Collection ids) { - email_removed(folder, ids); - } - - protected virtual void notify_email_locally_complete(Geary.Folder folder, - Gee.Collection ids) { - email_locally_complete(folder, ids); - } - - protected virtual void notify_email_discovered(Geary.Folder folder, - Gee.Collection ids) { - email_discovered(folder, ids); - } - - protected virtual void notify_email_flags_changed(Geary.Folder folder, - Gee.Map flag_map) { - email_flags_changed(folder, flag_map); - } - - protected virtual void notify_opened() { - opened(); - } - - protected virtual void notify_closed() { - closed(); - } - - protected virtual void notify_email_sent(RFC822.Message message) { - email_sent(message); - } - - protected virtual void notify_report_problem(Geary.Account.Problem problem, Error? err) { - report_problem(problem, err); - } - - /** - * A utility method to sort a Gee.Collection of {@link Folder}s by their {@link FolderPath}s - * to ensure they comport with {@link folders_available_unavailable} and - * {@link folders_added_removed} signals' contracts. - */ - protected Gee.List sort_by_path(Gee.Collection folders) { - Gee.TreeSet sorted = new Gee.TreeSet(folder_path_comparator); - sorted.add_all(folders); - - return Collection.to_array_list(sorted); - } - - private int folder_path_comparator(Geary.Folder a, Geary.Folder b) { - return a.path.compare_to(b.path); - } - + /** * Opens the {@link Account} and makes it and its {@link Folder}s available for use. * @@ -211,7 +273,7 @@ * version of Geary. */ public abstract async void open_async(Cancellable? cancellable = null) throws Error; - + /** * Closes the {@link Account}, which makes most its operations unavailable. * @@ -220,12 +282,12 @@ * Returns without error if the Account is already closed. */ public abstract async void close_async(Cancellable? cancellable = null) throws Error; - + /** * Returns true if this account is open, else false. */ public abstract bool is_open(); - + /** * Rebuild the local data stores for this {@link Account}. * @@ -241,7 +303,7 @@ * Unlike most methods in Account, this should only be called when the Account is closed. */ public abstract async void rebuild_async(Cancellable? cancellable = null) throws Error; - + /** * Lists all the currently-available folders found under the parent path * unless it's null, in which case it lists all the root folders. If the @@ -257,13 +319,13 @@ */ public abstract Gee.Collection list_matching_folders(Geary.FolderPath? parent) throws Error; - + /** * Lists all currently-available folders. See caveats under * list_matching_folders(). */ public abstract Gee.Collection list_folders() throws Error; - + /** * Gets a perpetually update-to-date collection of autocompletion contacts. */ @@ -276,7 +338,7 @@ */ public abstract async bool folder_exists_async(Geary.FolderPath path, Cancellable? cancellable = null) throws Error; - + /** * Fetches a Folder object corresponding to the supplied path. If the backing medium does * not have a record of a folder at the path, EngineError.NOT_FOUND will be thrown. @@ -288,7 +350,7 @@ */ public abstract async Geary.Folder fetch_folder_async(Geary.FolderPath path, Cancellable? cancellable = null) throws Error; - + /** * Returns the folder representing the given special folder type. If no such folder exists, * null is returned. @@ -297,7 +359,7 @@ return traverse(list_folders()) .first_matching(f => f.special_folder_type == special); } - + /** * Returns the Folder object with the given special folder type. The folder will be * created on the server if it doesn't already exist. An error will be thrown if the @@ -306,7 +368,7 @@ */ public abstract async Geary.Folder get_required_special_folder_async(Geary.SpecialFolderType special, Cancellable? cancellable = null) throws Error; - + /** * Submits a ComposedEmail for delivery. Messages may be scheduled for later delivery or immediately * sent. Subscribe to the "email-sent" signal to be notified of delivery. Note that that signal @@ -315,7 +377,7 @@ */ public abstract async void send_email_async(Geary.ComposedEmail composed, Cancellable? cancellable = null) throws Error; - + /** * Search the local account for emails referencing a Message-ID value * (which can appear in the Message-ID header itself, as well as the @@ -330,7 +392,7 @@ Geary.RFC822.MessageID message_id, Geary.Email.Field requested_fields, bool partial_ok, Gee.Collection? folder_blacklist, Geary.EmailFlags? flag_blacklist, Cancellable? cancellable = null) throws Error; - + /** * Return a single email fulfilling the required fields. The email to pull * is identified by an EmailIdentifier from a previous call to @@ -340,7 +402,7 @@ */ public abstract async Geary.Email local_fetch_email_async(Geary.EmailIdentifier email_id, Geary.Email.Field required_fields, Cancellable? cancellable = null) throws Error; - + /** * Create a new {@link SearchQuery} for this {@link Account}. * @@ -357,7 +419,7 @@ * Dropping the last reference to the SearchQuery will close it. */ public abstract Geary.SearchQuery open_search(string query, Geary.SearchQuery.Strategy strategy); - + /** * Performs a search with the given query. Optionally, a list of folders not to search * can be passed as well as a list of email identifiers to restrict the search to only those messages. @@ -369,13 +431,13 @@ public abstract async Gee.Collection? local_search_async(Geary.SearchQuery query, int limit = 100, int offset = 0, Gee.Collection? folder_blacklist = null, Gee.Collection? search_ids = null, Cancellable? cancellable = null) throws Error; - + /** * Given a list of mail IDs, returns a set of casefolded words that match for the query. */ public abstract async Gee.Set? get_search_matches_async(Geary.SearchQuery query, Gee.Collection ids, Cancellable? cancellable = null) throws Error; - + /** * Return a map of each passed-in email identifier to the set of folders * that contain it. If an email id doesn't appear in the resulting map, @@ -385,12 +447,119 @@ */ public abstract async Gee.MultiMap? get_containing_folders_async( Gee.Collection ids, Cancellable? cancellable) throws Error; - + /** * Used only for debugging. Should not be used for user-visible strings. */ public virtual string to_string() { - return name; + return this.id; + } + + /** Fires a {@link opened} signal. */ + protected virtual void notify_opened() { + opened(); + } + + /** Fires a {@link closed} signal. */ + protected virtual void notify_closed() { + closed(); + } + + /** Fires a {@link folders_available_unavailable} signal. */ + protected virtual void + notify_folders_available_unavailable(Gee.BidirSortedSet? available, + Gee.BidirSortedSet? unavailable) { + folders_available_unavailable(available, unavailable); + } + + /** Fires a {@link folders_created} signal. */ + protected virtual void notify_folders_created(Gee.BidirSortedSet created) { + folders_created(created); + } + + /** Fires a {@link folders_deleted} signal. */ + protected virtual void notify_folders_deleted(Gee.BidirSortedSet deleted) { + folders_deleted(deleted); } -} + /** Fires a {@link folders_contents_altered} signal. */ + protected virtual void notify_folders_contents_altered(Gee.Collection altered) { + folders_contents_altered(altered); + } + + /** Fires a {@link email_appended} signal. */ + protected virtual void notify_email_appended(Geary.Folder folder, Gee.Collection ids) { + email_appended(folder, ids); + } + + /** Fires a {@link email_inserted} signal. */ + protected virtual void notify_email_inserted(Geary.Folder folder, Gee.Collection ids) { + email_inserted(folder, ids); + } + + /** Fires a {@link email_removed} signal. */ + protected virtual void notify_email_removed(Geary.Folder folder, Gee.Collection ids) { + email_removed(folder, ids); + } + + /** Fires a {@link email_locally_complete} signal. */ + protected virtual void notify_email_locally_complete(Geary.Folder folder, + Gee.Collection ids) { + email_locally_complete(folder, ids); + } + + /** Fires a {@link email_discovered} signal. */ + protected virtual void notify_email_discovered(Geary.Folder folder, + Gee.Collection ids) { + email_discovered(folder, ids); + } + + /** Fires a {@link email_flags_changed} signal. */ + protected virtual void notify_email_flags_changed(Geary.Folder folder, + Gee.Map flag_map) { + email_flags_changed(folder, flag_map); + } + + protected virtual void notify_email_sent(RFC822.Message message) { + email_sent(message); + } + + /** Fires a {@link report_problem} signal for this account. */ + protected virtual void notify_report_problem(ProblemReport report) { + report_problem(report); + } + + /** + * Fires a {@link report_problem} signal for this account. + */ + protected virtual void notify_account_problem(ProblemType type, Error? err) { + report_problem(new AccountProblemReport(type, this.information, err)); + } + + /** Fires a {@link report_problem} signal for a service for this account. */ + protected virtual void notify_service_problem(ProblemType type, + ServiceInformation service, + Error? err) { + report_problem( + new ServiceProblemReport(type, this.information, service, err) + ); + } + + private void on_service_status_notify() { + Status new_status = 0; + // Don't consider service status UNKNOWN to indicate being + // offline, since clients will indicate offline status, but + // not indicate online status. So when at startup, or when + // restarting services, we don't want to cause them to + // spuriously indicate being offline. + if (incoming.current_status != UNREACHABLE) { + new_status |= ONLINE; + } + if (incoming.current_status.is_error() || + outgoing.current_status.is_error()) { + new_status |= SERVICE_PROBLEM; + } + this.current_status = new_status; + } + +} diff -Nru geary-0.12.4/src/engine/api/geary-aggregated-folder-properties.vala geary-3.32.0/src/engine/api/geary-aggregated-folder-properties.vala --- geary-0.12.4/src/engine/api/geary-aggregated-folder-properties.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/api/geary-aggregated-folder-properties.vala 2019-03-17 13:39:29.000000000 +0000 @@ -17,7 +17,7 @@ // Map of child FolderProperties to their bindings. private Gee.Map> child_bindings = new Gee.HashMap>(); - + /** * Creates an aggregate FolderProperties. */ @@ -25,7 +25,7 @@ // Set defaults. base(0, 0, Trillian.UNKNOWN, Trillian.UNKNOWN, Trillian.UNKNOWN, is_local_only, is_virtual, false); } - + /** * Adds a child FolderProperties. The child's property values will overwrite * this class's property values. @@ -36,7 +36,7 @@ assert(bindings != null); child_bindings.set(child, bindings); } - + /** * Removes a child FolderProperties. */ @@ -44,10 +44,10 @@ Gee.List bindings; if (child_bindings.unset(child, out bindings)) { Geary.ObjectUtils.unmirror_properties(bindings); - + return true; } - + return false; } } diff -Nru geary-0.12.4/src/engine/api/geary-attachment.vala geary-3.32.0/src/engine/api/geary-attachment.vala --- geary-0.12.4/src/engine/api/geary-attachment.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/api/geary-attachment.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,4 +1,5 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. @@ -6,21 +7,11 @@ /** * An attachment that was a part of an {@link Email}. - * - * @see Email.get_attachment */ - public abstract class Geary.Attachment : BaseObject { /** - * An identifier that can be used to locate the {@link Attachment} in an {@link Email}. - * - * @see Email.get_attachment - */ - public string id { get; private set; } - - /** * The {@link Mime.ContentType} of the {@link Attachment}. */ public Mime.ContentType content_type { get; private set; } @@ -29,6 +20,8 @@ * The Content-ID of the attachment. * * See [[https://tools.ietf.org/html/rfc2111]] + * + * @see Email.get_attachment_by_content_id */ public string? content_id { get; private set; } @@ -62,31 +55,30 @@ public string? content_filename { get; private set; } /** - * The on-disk File of the {@link Attachment}. + * The attachment's on-disk File, if any. + * + * This will be null if the attachment has not been saved to disk. */ - public File file { get; private set; } + public GLib.File? file { get; private set; default = null; } /** * The file size (in bytes) if the {@link file}. + * + * This will be -1 if the attachment has not been saved to disk. */ - public int64 filesize { get; private set; } + public int64 filesize { get; private set; default = -1; } + - protected Attachment(string id, - Mime.ContentType content_type, + protected Attachment(Mime.ContentType content_type, string? content_id, string? content_description, Mime.ContentDisposition content_disposition, - string? content_filename, - File file, - int64 filesize) { - this.id = id; + string? content_filename) { this.content_type = content_type; this.content_id = content_id; this.content_description = content_description; this.content_disposition = content_disposition; this.content_filename = content_filename; - this.file = file; - this.filesize = filesize; } /** @@ -111,7 +103,7 @@ string[] others = { alt_file_name, this.content_id, - this.id ?? "attachment", + "attachment", }; int i = 0; @@ -135,12 +127,12 @@ } if (name_type == null || - name_type.is_default() || + name_type.is_same(Mime.ContentType.ATTACHMENT_DEFAULT) || !name_type.is_same(mime_type)) { // Substitute file name either is of unknown type // (e.g. it does not have an extension) or is not the // same type as the declared type, so try to fix it. - if (mime_type.is_default()) { + if (mime_type.is_same(Mime.ContentType.ATTACHMENT_DEFAULT)) { // Declared type is unknown, see if we can guess // it. Don't use GFile.query_info however since // that will attempt to use the filename, which is @@ -162,4 +154,12 @@ return file_name; } + /** + * Sets the attachment's on-disk location and size. + */ + protected void set_file_info(GLib.File file, int64 file_size) { + this.file = file; + this.filesize = file_size; + } + } diff -Nru geary-0.12.4/src/engine/api/geary-client-service.vala geary-3.32.0/src/engine/api/geary-client-service.vala --- geary-0.12.4/src/engine/api/geary-client-service.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/src/engine/api/geary-client-service.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,459 @@ +/* + * Copyright 2018 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +/** + * Manages client connections to a specific network service. + * + * Client service object are used by accounts to manage client + * connections to a specific network service, such as IMAP or SMTP + * services. This abstract class does not connect to the service + * itself, rather manages the configuration, status tracking, and + * life-cycle of concrete implementations. + */ +public abstract class Geary.ClientService : BaseObject { + + + private const int BECAME_REACHABLE_TIMEOUT_SEC = 1; + private const int BECAME_UNREACHABLE_TIMEOUT_SEC = 3; + + + /** + * Denotes the service's current status. + * + * @see ClientService.current_status + * @see Account.current_status + */ + public enum Status { + + /** + * The service status is currently unknown. + * + * This is the initial state, and will only change after the + * service has performed initial connectivity testing and/or + * successfully connected to the remote host. + */ + UNKNOWN, + + /** + * The service is currently unreachable. + * + * This typically indicates the local computer is offline. The + * service will attempt to determine if the remote host is + * reachable once the service has been started. If determined + * to be reachable, the service will attempt to connect to the + * host, otherwise it will be marked as unreachable. + */ + UNREACHABLE, + + /** + * The service is connected and working normally. + * + * A connection to the remote host has been established and is + * operating normally. + */ + CONNECTED, + + /** + * A network problem occurred connecting to the service. + * + * This is caused by DNS lookup failures, connectivity + * failures, the service rejecting the connection due to + * connection limits, or the service configuration being out + * of date. + * + * The {@link connection_error} signal will be fired with an + * error (if any), and an attempt to re-connect will be made + * when the connectivity manager indicates the network has + * changed. It may also require manual intervention to update + * the service's configuration to successfully re-connect, + * however. + */ + CONNECTION_FAILED, + + /** + * The service's credentials were rejected by the remote service. + * + * The {@link AccountInformation.authentication_failure} + * signal on the service's account configuration will be fired + * and no more connection attempts will be made until the + * service is restarted. + */ + AUTHENTICATION_FAILED, + + /** + * The remote service's TLS certificate was rejected. + * + * The {@link AccountInformation.untrusted_host} signal on the + * service's account configuration will be fired and no more + * connection attempts will be made until the service is + * restarted. + */ + TLS_VALIDATION_FAILED, + + /** + * A general problem occurred with the remote service. + * + * A network connection was successfully established, but some + * problem other than authentication or TLS certificate + * validation has prevented a successful connection. This may + * be because of an unsupported protocol version or other + * general incompatibility. + * + * The {@link unrecoverable_error} signal will be fired with + * an error (if any), and no more connection attempts will be + * made until the service is restarted. + */ + UNRECOVERABLE_ERROR; + + + /** + * Determines if re-connection should be attempted from this state. + * + * If the service is in this state, it will automatically + * attempt to reconnect when connectivity changes have been + * detected. + */ + public bool automatically_reconnect() { + return ( + this == UNKNOWN || + this == UNREACHABLE || + this == CONNECTED || + this == CONNECTION_FAILED + ); + } + + /** + * Determines the current status is an error condition. + * + * Returns true if not offline or connected. + */ + public bool is_error() { + return ( + this != UNKNOWN && + this != UNREACHABLE && + this != CONNECTED + ); + } + + public string to_value() { + return ObjectUtils.to_enum_nick(typeof(Status), this); + } + + } + + + /** + * Fired when the service encounters a connection error. + * + * @see Status.CONNECTION_FAILED + */ + public signal void connection_error(ErrorContext err); + + /** + * Fired when the service encounters an unrecoverable error. + * + * @see Status.UNRECOVERABLE_ERROR + */ + public signal void unrecoverable_error(ErrorContext err); + + + /** The service's account. */ + public AccountInformation account { get; private set; } + + /** The configuration for the service. */ + public ServiceInformation configuration { get; private set; } + + /** + * The service's current status. + * + * The current state of certain aspects of the service + * (e.g. online/offline state may not be fully known, and hence + * the value of this property reflects the engine's current + * understanding of the service's status, not necessarily that of + * actual reality. + * + * The initial value for this property is {@link Status.UNKNOWN}. + * + * @see Account.current_status + */ + public Status current_status { get; protected set; default = UNKNOWN; } + + /** The network endpoint the service will connect to. */ + public Endpoint remote { get; private set; } + + /** Determines if this service has been started. */ + public bool is_running { get; private set; default = false; } + + // Since the connectivity manager can flip-flop rapidly, introduce + // some hysteresis on connectivity changes to smooth out the + // transitions. + private TimeoutManager became_reachable_timer; + private TimeoutManager became_unreachable_timer; + + /** The last reported error, if any. */ + public ErrorContext? last_error { get; private set; default = null; } + + + protected ClientService(AccountInformation account, + ServiceInformation configuration, + Endpoint remote) { + this.account = account; + this.configuration = configuration; + this.remote = remote; + + this.became_reachable_timer = new TimeoutManager.seconds( + BECAME_REACHABLE_TIMEOUT_SEC, became_reachable + ); + this.became_unreachable_timer = new TimeoutManager.seconds( + BECAME_UNREACHABLE_TIMEOUT_SEC, became_unreachable + ); + + connect_handlers(); + + this.notify["is-running"].connect(on_running_notify); + this.notify["current-status"].connect(on_current_status_notify); + } + + ~ClientService() { + disconnect_handlers(); + } + + /** + * Updates the configuration for the service. + * + * The service will be restarted if it is already running, and if + * so will be stopped before the old configuration and endpoint is + * replaced by the new one, then started again. + */ + public async void update_configuration(ServiceInformation configuration, + Endpoint remote, + GLib.Cancellable? cancellable) + throws GLib.Error { + disconnect_handlers(); + + bool do_restart = this.is_running; + if (do_restart) { + yield stop(cancellable); + } + + this.configuration = configuration; + this.remote = remote; + connect_handlers(); + + if (do_restart) { + yield start(cancellable); + } + } + + /** + * Starts the service running. + * + * This may cause the manager to establish connections to the + * network service. + */ + public abstract async void start(GLib.Cancellable? cancellable = null) + throws GLib.Error; + + /** + * Stops the service running. + * + * Any existing connections to the network service will be closed. + */ + public abstract async void stop(GLib.Cancellable? cancellable = null) + throws GLib.Error; + + /** + * Starts the service, stopping it first if running. + * + * An error will be thrown if the service could not be stopped or + * could not be started again. If an error is thrown while + * stopping the service, no attempt will be made to start it + * again. + */ + public async void restart(GLib.Cancellable? cancellable = null) + throws GLib.Error { + if (this.is_running) { + yield stop(cancellable); + } + + yield start(cancellable); + } + + /** + * Called when the network service has become reachable. + * + * Derived classes may wish to attempt to establish a network + * connection to the remote service when this is called. + */ + protected abstract void became_reachable(); + + /** + * Called when the network service has become unreachable. + * + * Derived classes should close any network connections that are + * being established, or have been established with remote + * service. + */ + protected abstract void became_unreachable(); + + /** + * Notifies the network service has been started. + * + * Derived classes must call this when they consider the service + * to has been successfully started, to update service status and + * start reachable checking. + */ + protected void notify_started() { + this.is_running = true; + if (this.remote.connectivity.is_reachable.is_certain()) { + became_reachable(); + } else if (this.remote.connectivity.is_reachable.is_impossible()) { + this.current_status = UNREACHABLE; + } else { + this.remote.connectivity.check_reachable.begin(); + } + } + + /** + * Notifies when the network service has been stopped. + * + * Derived classes must call this before stopping the service to + * update service status and cancel any pending reachable checks. + */ + protected void notify_stopped() { + this.is_running = false; + this.current_status = UNKNOWN; + this.became_reachable_timer.reset(); + this.became_unreachable_timer.reset(); + } + + /** + * Notifies that the service has successfully connected. + * + * Derived classes should call this when a connection to the + * network service has been successfully negotiated and appears to + * be operating normally. + */ + protected void notify_connected() { + this.current_status = CONNECTED; + } + + /** + * Notifies that a connection error occurred. + * + * Derived classes should call this when a connection to the + * network service encountered some network error other than a + * login failure or TLS certificate validation error. + */ + protected void notify_connection_failed(ErrorContext? error) { + // Set the error first so it is up to date when any + // current-status notify handlers fire + this.last_error = error; + this.current_status = CONNECTION_FAILED; + connection_error(error); + } + + /** + * Notifies that an authentication failure has occurred. + * + * Derived classes should call this when they have detected that + * authentication has failed because the service rejected the + * supplied credentials, but not when login failed for other + * reasons (for example, connection limits being reached, service + * temporarily unavailable, etc). + */ + protected void notify_authentication_failed() { + this.current_status = AUTHENTICATION_FAILED; + this.account.authentication_failure(this.configuration); + } + + /** + * Notifies that an unrecoverable error has occurred. + * + * Derived classes should call this when they have detected that + * some unrecoverable error has occurred when connecting to the + * service, such as an unsupported protocol or version. + */ + protected void notify_unrecoverable_error(ErrorContext error) { + // Set the error first so it is up to date when any + // current-status notify handlers fire + this.last_error = error; + this.current_status = UNRECOVERABLE_ERROR; + unrecoverable_error(error); + } + + private void connect_handlers() { + this.remote.connectivity.notify["is-reachable"].connect( + on_connectivity_change + ); + this.remote.connectivity.remote_error_reported.connect( + on_connectivity_error + ); + this.remote.untrusted_host.connect(on_untrusted_host); + } + + private void disconnect_handlers() { + this.remote.connectivity.notify["is-reachable"].disconnect( + on_connectivity_change + ); + this.remote.connectivity.remote_error_reported.disconnect( + on_connectivity_error + ); + this.remote.untrusted_host.disconnect(on_untrusted_host); + } + + private void on_running_notify() { + debug( + "%s:%s %s", + this.account.id, + this.configuration.protocol.to_value(), + this.is_running ? "started" : "stopped" + ); + } + + private void on_current_status_notify() { + debug( + "%s:%s: status changed to: %s", + this.account.id, + this.configuration.protocol.to_value(), + this.current_status.to_value() + ); + } + + private void on_connectivity_change() { + if (this.is_running && this.current_status.automatically_reconnect()) { + if (this.remote.connectivity.is_reachable.is_certain()) { + this.became_reachable_timer.start(); + this.became_unreachable_timer.reset(); + } else { + this.current_status = UNREACHABLE; + this.became_unreachable_timer.start(); + this.became_reachable_timer.reset(); + } + } + } + + private void on_connectivity_error(Error error) { + if (this.is_running) { + this.became_reachable_timer.reset(); + this.became_unreachable_timer.reset(); + notify_connection_failed(new ErrorContext(error)); + } + } + + private void on_untrusted_host(Endpoint remote, + GLib.TlsConnection cx) { + if (this.is_running) { + this.current_status = TLS_VALIDATION_FAILED; + this.became_reachable_timer.reset(); + this.became_unreachable_timer.reset(); + became_unreachable(); + this.account.untrusted_host(this.configuration, remote, cx); + } + } + +} diff -Nru geary-0.12.4/src/engine/api/geary-composed-email.vala geary-3.32.0/src/engine/api/geary-composed-email.vala --- geary-0.12.4/src/engine/api/geary-composed-email.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/api/geary-composed-email.vala 2019-03-17 13:39:29.000000000 +0000 @@ -21,7 +21,7 @@ | Geary.Email.Field.REFERENCES | Geary.Email.Field.SUBJECT | Geary.Email.Field.DATE; - + public DateTime date { get; set; } // TODO: sender goes here, but not beyond, as it's not properly supported by GMime yet. public RFC822.MailboxAddress? sender { get; set; default = null; } @@ -47,7 +47,7 @@ public string img_src_prefix { get; set; default = ""; } - public ComposedEmail(DateTime date, RFC822.MailboxAddresses from, + public ComposedEmail(DateTime date, RFC822.MailboxAddresses from, RFC822.MailboxAddresses? to = null, RFC822.MailboxAddresses? cc = null, RFC822.MailboxAddresses? bcc = null, string? subject = null, string? body_text = null, string? body_html = null) { @@ -60,7 +60,7 @@ this.body_text = body_text; this.body_html = body_html; } - + public Geary.RFC822.Message to_rfc822_message(string? message_id = null) { return new RFC822.Message.from_composed_email(this, message_id); } diff -Nru geary-0.12.4/src/engine/api/geary-contact-flags.vala geary-3.32.0/src/engine/api/geary-contact-flags.vala --- geary-0.12.4/src/engine/api/geary-contact-flags.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/api/geary-contact-flags.vala 2019-03-17 13:39:29.000000000 +0000 @@ -15,35 +15,35 @@ public static NamedFlag ALWAYS_LOAD_REMOTE_IMAGES { get { if (_always_load_remote_images == null) _always_load_remote_images = new NamedFlag("ALWAYSLOADREMOTEIMAGES"); - + return _always_load_remote_images; } } - + public ContactFlags() { } - + public static ContactFlags deserialize(string? flags) { if (String.is_empty(flags)) return new ContactFlags(); - + ContactFlags result = new ContactFlags(); - + string[] tokens = flags.split(" "); foreach (string flag in tokens) result.add(new NamedFlag(flag)); - + return result; } - + public inline bool always_load_remote_images() { return contains(ALWAYS_LOAD_REMOTE_IMAGES); } - + public string serialize() { string ret = ""; foreach (NamedFlag flag in list) ret += flag.serialize() + " "; - + return ret.strip(); } } diff -Nru geary-0.12.4/src/engine/api/geary-contact-importance.vala geary-3.32.0/src/engine/api/geary-contact-importance.vala --- geary-0.12.4/src/engine/api/geary-contact-importance.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/api/geary-contact-importance.vala 2019-03-17 13:39:29.000000000 +0000 @@ -17,7 +17,7 @@ * || CC || appeared in the 'CC' or 'BCC' fields OR did not appear in any field (assuming BCC) || * * "Examples:" - * + * * || "Enum Value" || "Account Owner" || "Contact" || * || FROM_TO || Appeared in 'from' or 'sender' || Appeared in 'to' || * || CC_FROM || Appeared in 'CC', 'BCC', or did not appear || Appeared in 'from' or 'sender'. || diff -Nru geary-0.12.4/src/engine/api/geary-contact-store.vala geary-3.32.0/src/engine/api/geary-contact-store.vala --- geary-0.12.4/src/engine/api/geary-contact-store.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/api/geary-contact-store.vala 2019-03-17 13:39:29.000000000 +0000 @@ -3,42 +3,48 @@ * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ - + public abstract class Geary.ContactStore : BaseObject { public Gee.Collection contacts { owned get { return contact_map.values; } } - + private Gee.Map contact_map; - - public signal void contact_added(Contact contact); - - public signal void contact_updated(Contact contact); - - internal ContactStore() { + + public signal void contacts_added(Gee.Collection contacts); + + public signal void contacts_updated(Gee.Collection contacts); + + protected ContactStore() { contact_map = new Gee.HashMap(); } - + public void update_contacts(Gee.Collection new_contacts) { - foreach (Contact contact in new_contacts) - update_contact(contact); + Gee.LinkedList added = new Gee.LinkedList(); + Gee.LinkedList updated = new Gee.LinkedList(); + + foreach (Contact contact in new_contacts) { + Contact? old_contact = contact_map[contact.normalized_email]; + if (old_contact == null) { + contact_map[contact.normalized_email] = contact; + added.add(contact); + } else if (old_contact.highest_importance < contact.highest_importance) { + old_contact.highest_importance = contact.highest_importance; + updated.add(contact); + } + } + + if (!added.is_empty) + contacts_added(added); + + if (!updated.is_empty) + contacts_updated(updated); } - + public abstract async void mark_contacts_async(Gee.Collection contacts, ContactFlags? to_add, ContactFlags? to_remove) throws Error; - + public Contact? get_by_rfc822(Geary.RFC822.MailboxAddress address) { return contact_map[address.address.normalize().casefold()]; } - - private void update_contact(Contact contact) { - Contact? old_contact = contact_map[contact.normalized_email]; - if (old_contact == null) { - contact_map[contact.normalized_email] = contact; - contact_added(contact); - } else if (old_contact.highest_importance < contact.highest_importance) { - old_contact.highest_importance = contact.highest_importance; - contact_updated(old_contact); - } - } } diff -Nru geary-0.12.4/src/engine/api/geary-contact.vala geary-3.32.0/src/engine/api/geary-contact.vala --- geary-0.12.4/src/engine/api/geary-contact.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/api/geary-contact.vala 2019-03-17 13:39:29.000000000 +0000 @@ -10,7 +10,7 @@ public string? real_name { get; private set; } public int highest_importance { get; set; } public ContactFlags? contact_flags { get; set; default = null; } - + public Contact(string email, string? real_name, int highest_importance, string? normalized_email = null, ContactFlags? contact_flags = null) { this.normalized_email = normalized_email ?? email.normalize().casefold(); @@ -19,15 +19,15 @@ this.highest_importance = highest_importance; this.contact_flags = contact_flags; } - + public Contact.from_rfc822_address(RFC822.MailboxAddress address, int highest_importance) { this(address.address, address.name, highest_importance); } - + public RFC822.MailboxAddress get_rfc822_address() { return new RFC822.MailboxAddress(real_name, email); } - + public inline bool always_load_remote_images() { return contact_flags != null && contact_flags.always_load_remote_images(); } diff -Nru geary-0.12.4/src/engine/api/geary-credentials-mediator.vala geary-3.32.0/src/engine/api/geary-credentials-mediator.vala --- geary-0.12.4/src/engine/api/geary-credentials-mediator.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/api/geary-credentials-mediator.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,45 +1,24 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. + * Copyright 2018 Michael Gratton * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ -public interface Geary.CredentialsMediator : Object { - /** - * Query the key store for the password of the given username for the given - * service. Return null if the password wasn't in the key store, or the - * password if it was. - */ - public abstract async string? get_password_async(Service service, - AccountInformation account_information, - Cancellable? cancellable = null) throws Error; - - /** - * Add or update the key store's password entry for the given credentials - * for the given service. - */ - public abstract async void set_password_async(Service service, - AccountInformation account_information, - Cancellable? cancellable = null) throws Error; - - /** - * Deletes the key store's password entry for the given credentials for the - * given service. Do nothing (and do *not* throw an error) if the - * credentials weren't in the key store. - */ - public abstract async void clear_password_async(Service service, - AccountInformation account_information, - Cancellable? cancellable = null) throws Error; - +/** + * A store for authentication tokens. + */ +public interface Geary.CredentialsMediator : GLib.Object { + /** - * Prompt the user to enter passwords for the given services in the given - * account. Set the out parameters for the services to the values entered - * by the user (out parameters for services not being prompted for are - * ignored). Return false if the user tried to cancel the interaction, or - * true if they tried to proceed. + * Updates the token for a service's credential from the store. + * + * Returns true if the token was present and loaded, else false. */ - public abstract async bool prompt_passwords_async(ServiceFlag services, - AccountInformation account_information, - out string? imap_password, out string? smtp_password, - out bool imap_remember_password, out bool smtp_remember_password) throws Error; + public abstract async bool load_token(AccountInformation account, + ServiceInformation service, + GLib.Cancellable? cancellable) + throws GLib.Error; + } diff -Nru geary-0.12.4/src/engine/api/geary-credentials.vala geary-3.32.0/src/engine/api/geary-credentials.vala --- geary-0.12.4/src/engine/api/geary-credentials.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/api/geary-credentials.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,53 +1,147 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. + * Copyright 2018 Michael Gratton * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ /** - * Credentials represent a username and a password authenticating a user for access to a resource. - * More sophisticated schemes exist; this suffices for now. + * Credentials provide a user's access details for authentication. * - * Either property (user, pass) may be null. This indicates the Credentials are incomplete and - * need further information (i.e. prompt user for username, fetch password from keyring, etc.) - * Either field may be a non-null zero-length string; this is considered valid and is_complete() - * will return true in this case. + * The {@link user} property specifies the user's log in name, and the + * {@link token} property is a shared secret between the user and a + * service. For password-based schemes, this would be a password. + + * The token property may be null. This indicates the Credentials are + * incomplete and need further information (i.e. prompt user for + * username, fetch password from keyring, etc.). The token may be a + * non-null zero-length string; this is considered valid and + * is_complete() will return true in this case. * - * Note that Geary will hold Credentials in memory for the long-term, usually the duration of the - * application. This is because network resources often have to be connected (or reconnected) to - * in the background and asking the user to reauthenticate each time is deemed inconvenient. + * Note that Geary will hold Credentials in memory for the long-term, + * usually the duration of the application. This is because network + * resources often have to be connected (or reconnected) to in the + * background and asking the user to reauthenticate each time is + * deemed inconvenient. */ - public class Geary.Credentials : BaseObject, Gee.Hashable { - public string? user { get; set; } - public string? pass { get; set; } - - public Credentials(string? user, string? pass) { + + + /** + * Authentication methods supported by the Engine. + */ + public enum Method { + + /** Password-based authentication, such as SASL PLAIN. */ + PASSWORD, + + /** OAuth2-based authentication. */ + OAUTH2; + + + public string to_string() { + switch (this) { + case PASSWORD: + return "password"; + + case OAUTH2: + return "oauth2"; + + default: + assert_not_reached(); + } + } + + public static Method from_string(string str) throws Error { + switch (str) { + case "password": + return PASSWORD; + + case "oauth2": + return OAUTH2; + + default: + throw new KeyFileError.INVALID_VALUE( + "Unknown credentials method type: %s", str + ); + } + } + } + + + /** The requirements for a service's credentials. */ + public enum Requirement { + /** No credentials are required. */ + NONE, + + /** The incoming service's credentials should be used. */ + USE_INCOMING, + + /** Custom credentials are required. */ + CUSTOM; + + public static Requirement for_value(string value) + throws EngineError { + return ObjectUtils.from_enum_nick( + typeof(Requirement), value.ascii_down() + ); + } + + public string to_value() { + return ObjectUtils.to_enum_nick( + typeof(Requirement), this + ); + } + + } + + + public Method supported_method { get; private set; } + public string user { get; private set; } + public string? token { get; private set; } + + public Credentials(Method supported_method, string user, string? token = null) { + this.supported_method = supported_method; this.user = user; - this.pass = pass; + this.token = token; } - + + /** Determines if a token has been provided. */ public bool is_complete() { - return (user != null) && (pass != null); + return this.token != null; + } + + public Credentials copy_with_user(string user) { + return new Credentials(this.supported_method, user, this.token); + } + + public Credentials copy_with_token(string? token) { + return new Credentials(this.supported_method, this.user, token); } - + public Credentials copy() { - return new Credentials(user, pass); + return new Credentials(this.supported_method, this.user, this.token); } - + public string to_string() { - return user; + return "%s:%s".printf(this.user, this.supported_method.to_string()); } - + public bool equal_to(Geary.Credentials c) { if (this == c) return true; - - return user == c.user && pass == c.pass; + + return ( + this.supported_method == c.supported_method && + this.user == c.user && + this.token == c.token + ); } - + public uint hash() { - return "%s%s".printf(user ?? "", pass ?? "").hash(); + return "%d%s%s".printf( + this.supported_method, this.user, this.token ?? "" + ).hash(); } } - diff -Nru geary-0.12.4/src/engine/api/geary-email-flags.vala geary-3.32.0/src/engine/api/geary-email-flags.vala --- geary-0.12.4/src/engine/api/geary-email-flags.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/api/geary-email-flags.vala 2019-03-17 13:39:29.000000000 +0000 @@ -26,33 +26,37 @@ public static NamedFlag LOAD_REMOTE_IMAGES { owned get { return new NamedFlag("LOADREMOTEIMAGES"); } } - + public static NamedFlag DRAFT { owned get { return new NamedFlag("DRAFT"); } } - + + public static NamedFlag DELETED { owned get { + return new NamedFlag("DELETED"); + } } + /// Signifies a message in our outbox that has been sent but we're still /// keeping around for other purposes, i.e. pushing up to Sent Mail. public static NamedFlag OUTBOX_SENT { owned get { // This shouldn't ever touch the wire, so make it invalid IMAP. return new NamedFlag(" OUTBOX SENT "); } } - + public EmailFlags() { } - + /** * Create a new {@link EmailFlags} container initialized with one or more flags. */ public EmailFlags.with(Geary.NamedFlag flag1, ...) { va_list args = va_list(); NamedFlag? flag = flag1; - + do { add(flag); } while((flag = args.arg()) != null); } - + // Convenience method to check if the unread flag is set. public inline bool is_unread() { return contains(UNREAD); @@ -61,17 +65,21 @@ public inline bool is_flagged() { return contains(FLAGGED); } - + public inline bool load_remote_images() { return contains(LOAD_REMOTE_IMAGES); } - + public inline bool is_draft() { return contains(DRAFT); } - + public inline bool is_outbox_sent() { return contains(OUTBOX_SENT); } + + public inline bool is_deleted() { + return contains(DELETED); + } } diff -Nru geary-0.12.4/src/engine/api/geary-email-header-set.vala geary-3.32.0/src/engine/api/geary-email-header-set.vala --- geary-0.12.4/src/engine/api/geary-email-header-set.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/src/engine/api/geary-email-header-set.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,46 @@ +/* + * Copyright 2019 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +/** + * Denotes an object that has a set of RFC822 headers. + */ +public interface Geary.EmailHeaderSet : BaseObject { + + /** Value of the RFC 822 Date header. */ + public abstract RFC822.Date? date { get; protected set; } + + /** Value of the RFC 822 From header, an originator field. */ + public abstract RFC822.MailboxAddresses? from { get; protected set; } + + /** Value of the RFC 822 Sender header, an originator field. */ + public abstract RFC822.MailboxAddress? sender { get; protected set; } + + /** Value of the RFC 822 Reply-To header, an originator field. */ + public abstract RFC822.MailboxAddresses? reply_to { get; protected set; } + + /** Value of the RFC 822 To header, a recipient field. */ + public abstract RFC822.MailboxAddresses? to { get; protected set; } + + /** Value of the RFC 822 Cc header, a recipient field. */ + public abstract RFC822.MailboxAddresses? cc { get; protected set; } + + /** Value of the RFC 822 Bcc header, a recipient field. */ + public abstract RFC822.MailboxAddresses? bcc { get; protected set; } + + /** Value of the RFC 822 Message-Id header, a reference field. */ + public abstract RFC822.MessageID? message_id { get; protected set; } + + /** Value of the RFC 822 In-Reply-To header, a reference field. */ + public abstract RFC822.MessageIDList? in_reply_to { get; protected set; } + + /** Value of the RFC 822 References header, a reference field. */ + public abstract RFC822.MessageIDList? references { get; protected set; } + + /** Value of the RFC 822 Subject header. */ + public abstract RFC822.Subject? subject { get; protected set; } + +} diff -Nru geary-0.12.4/src/engine/api/geary-email-identifier.vala geary-3.32.0/src/engine/api/geary-email-identifier.vala --- geary-0.12.4/src/engine/api/geary-email-identifier.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/api/geary-email-identifier.vala 2019-03-17 13:39:29.000000000 +0000 @@ -19,22 +19,22 @@ public abstract class Geary.EmailIdentifier : BaseObject, Gee.Hashable { // Warning: only change this if you know what you are doing. protected string unique; - + protected EmailIdentifier(string unique) { this.unique = unique; } - + public virtual uint hash() { return unique.hash(); } - + public virtual bool equal_to(Geary.EmailIdentifier other) { if (this == other) return true; - + return unique == other.unique; } - + /** * A comparator for stabilizing sorts. * @@ -44,10 +44,10 @@ public virtual int stable_sort_comparator(Geary.EmailIdentifier other) { if (this == other) return 0; - + return strcmp(unique, other.unique); } - + /** * A comparator for finding which {@link EmailIdentifier} is earliest in the "natural" * sorting of a {@link Folder}'s list. @@ -68,7 +68,7 @@ * @see Folder.list_email_by_id_async */ public abstract int natural_sort_comparator(Geary.EmailIdentifier other); - + /** * Sorts the supplied Collection of {@link EmailIdentifier} by their natural sort order. * @@ -82,14 +82,14 @@ int cmp = a.natural_sort_comparator(b); if (cmp == 0) cmp = a.stable_sort_comparator(b); - + return cmp; }); sorted.add_all(ids); - + return sorted; } - + /** * Sorts the supplied Collection of {@link EmailIdentifier} by their natural sort order. * @@ -103,14 +103,14 @@ int cmp = a.id.natural_sort_comparator(b.id); if (cmp == 0) cmp = a.id.stable_sort_comparator(b.id); - + return cmp; }); sorted.add_all(emails); - + return sorted; } - + public virtual string to_string() { return "[%s]".printf(unique.to_string()); } diff -Nru geary-0.12.4/src/engine/api/geary-email-properties.vala geary-3.32.0/src/engine/api/geary-email-properties.vala --- geary-0.12.4/src/engine/api/geary-email-properties.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/api/geary-email-properties.vala 2019-03-17 13:39:29.000000000 +0000 @@ -20,17 +20,17 @@ * the INTERNALDATE supplied by the server. */ public DateTime date_received { get; protected set; } - + /** * Total size of the email (header and body) in bytes. */ public int64 total_bytes { get; protected set; } - - public EmailProperties(DateTime date_received, int64 total_bytes) { + + protected EmailProperties(DateTime date_received, int64 total_bytes) { this.date_received = date_received; this.total_bytes = total_bytes; } - + public abstract string to_string(); } diff -Nru geary-0.12.4/src/engine/api/geary-email.vala geary-3.32.0/src/engine/api/geary-email.vala --- geary-0.12.4/src/engine/api/geary-email.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/api/geary-email.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,16 +1,32 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. + * Copyright 2018 Michael Gratton * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ /** - * Representation of a single email message in the engine. + * An Email represents a single RFC 822 style email message. * - * This class encapsulates an email, attachments and its related - * server and database state. + * This class provides a common abstraction over different + * representations of email messages, allowing messages from different + * mail systems, from both local and remote sources, and locally + * composed email messages to all be represented by a single + * object. While this object represents a RFC 822 message, it also + * holds additional metadata about an email not specified by that + * format, such as its unique {@link id}, and unread state and other + * {@link email_flags}. + * + * Email objects may by constructed in many ways, but are usually + * obtained via a {@link Folder}. Email objects may be partial + * representations of messages, in cases where a remote message has + * not been fully downloaded, or a local message not fully loaded from + * a database. This can be checked via an email's {@link fields} + * property, and if the currently loaded fields are not sufficient, + * then additional fields can be loaded via a folder. */ -public class Geary.Email : BaseObject { +public class Geary.Email : BaseObject, EmailHeaderSet { /** * The maximum expected length of message body preview text. @@ -18,35 +34,82 @@ public const int MAX_PREVIEW_BYTES = 256; /** - * Currently only one field is mutable: FLAGS. All others never change once stored in the + * Indicates email fields that may change over time. + * + * The mutable fields are: FLAGS -- since these change as for + * example messages are marked as read, and PREVIEW -- since the + * preview is updated when the full message body is + * available. All others never change once stored in the * database. */ - public const Field MUTABLE_FIELDS = Geary.Email.Field.FLAGS; - + public const Field MUTABLE_FIELDS = ( + Geary.Email.Field.FLAGS | Geary.Email.Field.PREVIEW + ); + /** - * The fields required to build an RFC822.Message for get_message() and - * any attachments. + * Indicates the email fields required to build an RFC822.Message. + * + * @see get_message + */ + public const Field REQUIRED_FOR_MESSAGE = ( + Geary.Email.Field.HEADER | Geary.Email.Field.BODY + ); + + /** + * Specifies specific parts of an email message. + * + * See the {@link Email.fields} property to determine which parts + * an email object currently contains. */ - public const Field REQUIRED_FOR_MESSAGE = Geary.Email.Field.HEADER | Geary.Email.Field.BODY; - - // THESE VALUES ARE PERSISTED. Change them only if you know what you're doing. public enum Field { + // THESE VALUES ARE PERSISTED. Change them only if you know what you're doing. + + /** Denotes no fields. */ NONE = 0, + + /** The RFC 822 Date header. */ DATE = 1 << 0, + + /** The RFC 822 From, Sender, and Reply-To headers. */ ORIGINATORS = 1 << 1, + + /** The RFC 822 To, Cc, and Bcc headers. */ RECEIVERS = 1 << 2, + + /** The RFC 822 Message-Id, In-Reply-To, and References headers. */ REFERENCES = 1 << 3, + + /** The RFC 822 Subject header. */ SUBJECT = 1 << 4, + + /** The list of all RFC 822 headers. */ HEADER = 1 << 5, + + /** The RFC 822 message body and attachments. */ BODY = 1 << 6, + + /** The {@link Email.properties} object. */ PROPERTIES = 1 << 7, + + /** The plain text preview. */ PREVIEW = 1 << 8, + + /** The {@link Email.email_flags} object. */ FLAGS = 1 << 9, - - ENVELOPE = DATE | ORIGINATORS | RECEIVERS | REFERENCES | SUBJECT, - ALL = DATE | ORIGINATORS | RECEIVERS | REFERENCES | SUBJECT | HEADER | BODY - | PROPERTIES | PREVIEW | FLAGS; - + + /** + * The union of the primary headers of a message. + * + * The envelope includes the {@link DATE}, {@link + * ORIGINATORS}, {@link RECEIVERS}, {@link REFERENCES}, and + * {@link SUBJECT} fields. + */ + ENVELOPE = DATE | ORIGINATORS | RECEIVERS | REFERENCES | SUBJECT, + + /** The union of all email fields. */ + ALL = DATE | ORIGINATORS | RECEIVERS | REFERENCES | SUBJECT | + HEADER | BODY | PROPERTIES | PREVIEW | FLAGS; + public static Field[] all() { return { DATE, @@ -61,123 +124,243 @@ FLAGS }; } - + public inline bool is_all_set(Field required_fields) { return (this & required_fields) == required_fields; } - + public inline bool is_any_set(Field required_fields) { return (this & required_fields) != 0; } - + public inline Field set(Field field) { return (this | field); } - + public inline Field clear(Field field) { return (this & ~(field)); } - + public inline bool fulfills(Field required_fields) { return is_all_set(required_fields); } - + public inline bool fulfills_any(Field required_fields) { return is_any_set(required_fields); } - + public inline bool require(Field required_fields) { return is_all_set(required_fields); } - + public inline bool requires_any(Field required_fields) { return is_any_set(required_fields); } - + public string to_list_string() { StringBuilder builder = new StringBuilder(); foreach (Field f in all()) { if (is_all_set(f)) { if (!String.is_empty(builder.str)) builder.append(", "); - + builder.append(f.to_string()); } } - + return builder.str; } } - + /** - * id is a unique identifier for the Email in the Folder. It is guaranteed to be unique for - * as long as the Folder is open. Once closed, guarantees are no longer made. + * A unique identifier for the Email in the Folder. + * + * This is is guaranteed to be unique for as long as the Folder is + * open. Once closed, guarantees are no longer made. * - * This field is always returned, no matter what Fields are used to retrieve the Email. + * This field is always returned, no matter what Fields are used + * to retrieve the Email. */ public Geary.EmailIdentifier id { get; private set; } - - // DATE - public Geary.RFC822.Date? date { get; private set; default = null; } - - // ORIGINATORS - public Geary.RFC822.MailboxAddresses? from { get; private set; default = null; } - public Geary.RFC822.MailboxAddress? sender { get; private set; default = null; } - public Geary.RFC822.MailboxAddresses? reply_to { get; private set; default = null; } - - // RECEIVERS - public Geary.RFC822.MailboxAddresses? to { get; private set; default = null; } - public Geary.RFC822.MailboxAddresses? cc { get; private set; default = null; } - public Geary.RFC822.MailboxAddresses? bcc { get; private set; default = null; } - - // REFERENCES - public Geary.RFC822.MessageID? message_id { get; private set; default = null; } - public Geary.RFC822.MessageIDList? in_reply_to { get; private set; default = null; } - public Geary.RFC822.MessageIDList? references { get; private set; default = null; } - - // SUBJECT - public Geary.RFC822.Subject? subject { get; private set; default = null; } - - // HEADER - public RFC822.Header? header { get; private set; default = null; } - - // BODY + + /** + * {@inheritDoc} + * + * Value will be valid if {@link Field.DATE} is set. + */ + public Geary.RFC822.Date? date { get; protected set; default = null; } + + /** + * {@inheritDoc} + * + * Value will be valid if {@link Field.ORIGINATORS} is set. + */ + public Geary.RFC822.MailboxAddresses? from { get; protected set; default = null; } + + /** + * {@inheritDoc} + * + * Value will be valid if {@link Field.ORIGINATORS} is set. + */ + public Geary.RFC822.MailboxAddress? sender { get; protected set; default = null; } + + /** + * {@inheritDoc} + * + * Value will be valid if {@link Field.ORIGINATORS} is set. + */ + public Geary.RFC822.MailboxAddresses? reply_to { get; protected set; default = null; } + + /** + * {@inheritDoc} + * + * Value will be valid if {@link Field.RECEIVERS} is set. + */ + public Geary.RFC822.MailboxAddresses? to { get; protected set; default = null; } + + /** + * {@inheritDoc} + * + * Value will be valid if {@link Field.RECEIVERS} is set. + */ + public Geary.RFC822.MailboxAddresses? cc { get; protected set; default = null; } + + /** + * {@inheritDoc} + * + * Value will be valid if {@link Field.RECEIVERS} is set. + */ + public Geary.RFC822.MailboxAddresses? bcc { get; protected set; default = null; } + + /** + * {@inheritDoc} + * + * Value will be valid if {@link Field.REFERENCES} is set. + */ + public Geary.RFC822.MessageID? message_id { get; protected set; default = null; } + + /** + * {@inheritDoc} + * + * Value will be valid if {@link Field.REFERENCES} is set. + */ + public Geary.RFC822.MessageIDList? in_reply_to { get; protected set; default = null; } + + /** + * {@inheritDoc} + * + * Value will be valid if {@link Field.REFERENCES} is set. + */ + public Geary.RFC822.MessageIDList? references { get; protected set; default = null; } + + /** + * {@inheritDoc} + * + * Value will be valid if {@link Field.SUBJECT} is set. + */ + public Geary.RFC822.Subject? subject { get; protected set; default = null; } + + /** + * {@inheritDoc} + * + * Value will be valid if {@link Field.HEADER} is set. + */ + public RFC822.Header? header { get; protected set; default = null; } + + /** + * The complete RFC 822 message body. + * + * Value will be valid if {@link Field.BODY} is set. + */ public RFC822.Text? body { get; private set; default = null; } + + /** + * MIME multipart body parts. + * + * Value will be valid if {@link Field.BODY} is set. + */ public Gee.List attachments { get; private set; default = new Gee.ArrayList(); } - - // PROPERTIES - public Geary.EmailProperties? properties { get; private set; default = null; } - - // PREVIEW + + /** + * A plain text prefix of the email's message body. + * + * Value will be valid if {@link Field.PREVIEW} is set. + */ public RFC822.PreviewText? preview { get; private set; default = null; } - - // FLAGS + + /** + * Set of immutable properties for the email. + * + * Value will be valid if {@link Field.PROPERTIES} is set. + */ + public Geary.EmailProperties? properties { get; private set; default = null; } + + /** + * Set of mutable flags for the email. + * + * Value will be valid if {@link Field.FLAGS} is set. + */ public Geary.EmailFlags? email_flags { get; private set; default = null; } - + + /** + * Specifies the properties that have been populated for this email. + * + * Since this email object may be a partial representation of a + * complete email message, this property lists all parts of the + * object that have actually been loaded, as opposed to parts that + * are simply missing from the email it represents. + * + * For example, if this property includes the {@link + * Field.SUBJECT} flag, then the {@link subject} property has been + * set to reflect the Subject header of the message. Of course, + * the subject may then still may be null or empty, if the email + * did not specify a subject header. + */ public Geary.Email.Field fields { get; private set; default = Field.NONE; } - + private Geary.RFC822.Message? message = null; - + public Email(Geary.EmailIdentifier id) { this.id = id; } - + + /** + * Determines if this message is unread from its flags. + * + * If {@link email_flags} is not null, returns the value of {@link + * EmailFlags.is_unread}, otherwise returns {@link + * Trillian.UNKNOWN}. + */ public inline Trillian is_unread() { return email_flags != null ? Trillian.from_boolean(email_flags.is_unread()) : Trillian.UNKNOWN; } + /** + * Determines if this message is flagged from its flags. + * + * If {@link email_flags} is not null, returns the value of {@link + * EmailFlags.is_flagged}, otherwise returns {@link + * Trillian.UNKNOWN}. + */ public inline Trillian is_flagged() { return email_flags != null ? Trillian.from_boolean(email_flags.is_flagged()) : Trillian.UNKNOWN; } - + + /** + * Determines if this message is flagged from its flags. + * + * If {@link email_flags} is not null, returns the value of {@link + * EmailFlags.load_remote_images}, otherwise returns {@link + * Trillian.UNKNOWN}. + */ public inline Trillian load_remote_images() { return email_flags != null ? Trillian.from_boolean(email_flags.load_remote_images()) : Trillian.UNKNOWN; } public void set_send_date(Geary.RFC822.Date? date) { this.date = date; - + fields |= Field.DATE; } @@ -206,69 +389,69 @@ this.to = to; this.cc = cc; this.bcc = bcc; - + fields |= Field.RECEIVERS; } - + public void set_full_references(Geary.RFC822.MessageID? message_id, Geary.RFC822.MessageIDList? in_reply_to, Geary.RFC822.MessageIDList? references) { this.message_id = message_id; this.in_reply_to = in_reply_to; this.references = references; - + fields |= Field.REFERENCES; } - + public void set_message_subject(Geary.RFC822.Subject? subject) { this.subject = subject; - + fields |= Field.SUBJECT; } - + public void set_message_header(Geary.RFC822.Header header) { this.header = header; - + // reset the message object, which is built from this text message = null; - + fields |= Field.HEADER; } - + public void set_message_body(Geary.RFC822.Text body) { this.body = body; - + // reset the message object, which is built from this text message = null; - + fields |= Field.BODY; } - + public void set_email_properties(Geary.EmailProperties properties) { this.properties = properties; - + fields |= Field.PROPERTIES; } - + public void set_message_preview(Geary.RFC822.PreviewText preview) { this.preview = preview; - + fields |= Field.PREVIEW; } public void set_flags(Geary.EmailFlags email_flags) { this.email_flags = email_flags; - + fields |= Field.FLAGS; } public void add_attachment(Geary.Attachment attachment) { attachments.add(attachment); } - + public void add_attachments(Gee.Collection attachments) { this.attachments.add_all(attachments); } - + public string get_searchable_attachment_list() { StringBuilder search = new StringBuilder(); foreach (Geary.Attachment attachment in attachments) { @@ -279,40 +462,24 @@ } return search.str; } - + /** - * This method requires the REQUIRED_FOR_MESSAGE fields be present. - * If not, EngineError.INCOMPLETE_MESSAGE is thrown. + * Constructs a new RFC 822 message from this email. + * + * This method requires the {@link REQUIRED_FOR_MESSAGE} fields be + * present. If not, {@link EngineError.INCOMPLETE_MESSAGE} is + * thrown. */ public Geary.RFC822.Message get_message() throws EngineError, RFC822Error { if (message != null) return message; - - if (!fields.fulfills(REQUIRED_FOR_MESSAGE)) - throw new EngineError.INCOMPLETE_MESSAGE("Parsed email requires HEADER and BODY"); - - message = new Geary.RFC822.Message.from_parts(header, body); - - return message; - } - /** - * Returns the attachment with the given {@link Geary.Attachment.id}. - * - * Requires the REQUIRED_FOR_MESSAGE fields be present; else - * EngineError.INCOMPLETE_MESSAGE is thrown. - */ - public Geary.Attachment? get_attachment_by_id(string attachment_id) - throws EngineError { if (!fields.fulfills(REQUIRED_FOR_MESSAGE)) throw new EngineError.INCOMPLETE_MESSAGE("Parsed email requires HEADER and BODY"); - foreach (Geary.Attachment attachment in attachments) { - if (attachment.id == attachment_id) { - return attachment; - } - } - return null; + message = new Geary.RFC822.Message.from_parts(header, body); + + return message; } /** @@ -342,50 +509,31 @@ */ public Gee.Set? get_ancestors() { Gee.Set ancestors = new Gee.HashSet(); - + // the email's Message-ID counts as its lineage if (message_id != null) ancestors.add(message_id); - + // References list the email trail back to its source if (references != null) ancestors.add_all(references.list); - + // RFC822 requires the In-Reply-To Message-ID be prepended to the References list, but // this ensures that's the case if (in_reply_to != null) ancestors.add_all(in_reply_to.list); - + return (ancestors.size > 0) ? ancestors : null; } - + public string get_preview_as_string() { return (preview != null) ? preview.buffer.to_string() : ""; } - - /** - * Returns the primary originator of an email, which is defined as the first mailbox address - * in From:, Sender:, or Reply-To:, in that order, depending on availability. - * - * Returns null if no originators are present. - */ - public RFC822.MailboxAddress? get_primary_originator() { - if (from != null && from.size > 0) - return from[0]; - - if (sender != null) - return sender; - - if (reply_to != null && reply_to.size > 0) - return reply_to[0]; - - return null; - } public string to_string() { return "[%s] ".printf(id.to_string()); } - + /** * Converts a Collection of {@link Email}s to a Map of Emails keyed by {@link EmailIdentifier}s. * @@ -394,15 +542,15 @@ public static Gee.Map? emails_to_map(Gee.Collection? emails) { if (emails == null || emails.size == 0) return null; - + Gee.Map map = new Gee.HashMap(); foreach (Email email in emails) map.set(email.id, email); - + return map; } - + /** * CompareFunc to sort {@link Email} by {@link date} ascending. * @@ -412,16 +560,16 @@ public static int compare_sent_date_ascending(Geary.Email aemail, Geary.Email bemail) { if (aemail.date == null || bemail.date == null) { GLib.message("Warning: comparing email for sent date but no Date: field loaded"); - + return compare_id_ascending(aemail, bemail); } - + int compare = aemail.date.value.compare(bemail.date.value); - + // stabilize sort by using the mail identifier's stable sort ordering return (compare != 0) ? compare : compare_id_ascending(aemail, bemail); } - + /** * CompareFunc to sort {@link Email} by {@link date} descending. * @@ -431,7 +579,7 @@ public static int compare_sent_date_descending(Geary.Email aemail, Geary.Email bemail) { return compare_sent_date_ascending(bemail, aemail); } - + /** * CompareFunc to sort {@link Email} by {@link EmailProperties.date_received} ascending. * @@ -441,16 +589,16 @@ public static int compare_recv_date_ascending(Geary.Email aemail, Geary.Email bemail) { if (aemail.properties == null || bemail.properties == null) { GLib.message("Warning: comparing email for received date but email properties not loaded"); - + return compare_id_ascending(aemail, bemail); } - + int compare = aemail.properties.date_received.compare(bemail.properties.date_received); - + // stabilize sort with identifiers return (compare != 0) ? compare : compare_id_ascending(aemail, bemail); } - + /** * CompareFunc to sort {@link Email} by {@link EmailProperties.date_received} descending. * @@ -460,12 +608,12 @@ public static int compare_recv_date_descending(Geary.Email aemail, Geary.Email bemail) { return compare_recv_date_ascending(bemail, aemail); } - + // only used to stabilize a sort private static int compare_id_ascending(Geary.Email aemail, Geary.Email bemail) { return aemail.id.stable_sort_comparator(bemail.id); } - + /** * CompareFunc to sort Email by EmailProperties.total_bytes. If not available, emails are * compared by EmailIdentifier. @@ -473,18 +621,18 @@ public static int compare_size_ascending(Geary.Email aemail, Geary.Email bemail) { Geary.EmailProperties? aprop = (Geary.EmailProperties) aemail.properties; Geary.EmailProperties? bprop = (Geary.EmailProperties) bemail.properties; - + if (aprop == null || bprop == null) { GLib.message("Warning: comparing email by size but email properties not loaded"); - + return compare_id_ascending(aemail, bemail); } - + int cmp = (int) (aprop.total_bytes - bprop.total_bytes).clamp(-1, 1); - + return (cmp != 0) ? cmp : compare_id_ascending(aemail, bemail); } - + /** * CompareFunc to sort Email by EmailProperties.total_bytes. If not available, emails are * compared by EmailIdentifier. diff -Nru geary-0.12.4/src/engine/api/geary-endpoint.vala geary-3.32.0/src/engine/api/geary-endpoint.vala --- geary-0.12.4/src/engine/api/geary-endpoint.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/api/geary-endpoint.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,49 +1,75 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. + * Copyright 2018 Michael Gratton * * This software is licensed under the GNU Lesser General Public License - * (version 2.1 or later). See the COPYING file in this distribution. + * (version 2.1 or later). See the COPYING file in this distribution. */ /** - * An Endpoint represents the location of an Internet TCP connection as represented by a host, - * a port, and flags and other parameters that specify the nature of the connection itself. + * Encapsulates network configuration and state for remote service. */ - public class Geary.Endpoint : BaseObject { - public const string PROP_TRUST_UNTRUSTED_HOST = "trust-untrusted-host"; - - [Flags] - public enum Flags { - NONE = 0, - SSL, - STARTTLS; - - public inline bool is_all_set(Flags flags) { - return (this & flags) == flags; - } - - public inline bool is_any_set(Flags flags) { - return (this & flags) != 0; + + + /** + * The default TLS certificate database to use when connecting. + * + * If not null, this will be set as the database on new TLS + * connections. + */ + public static GLib.TlsDatabase? default_tls_database = null; + + + /** Returns {@link GLib.TlsCertificateFlags} as a string. */ + public static string tls_flag_to_string(GLib.TlsCertificateFlags flag) { + // Vala to_string() for Flags enums currently doesn't work -- + // bummer... Should only be called when a single flag is set, + // otherwise returns a string indicating an unknown value + switch (flag) { + case TlsCertificateFlags.BAD_IDENTITY: + return "BAD_IDENTITY"; + + case TlsCertificateFlags.EXPIRED: + return "EXPIRED"; + + case TlsCertificateFlags.GENERIC_ERROR: + return "GENERIC_ERROR"; + + case TlsCertificateFlags.INSECURE: + return "INSECURE"; + + case TlsCertificateFlags.NOT_ACTIVATED: + return "NOT_ACTIVATED"; + + case TlsCertificateFlags.REVOKED: + return "REVOKED"; + + case TlsCertificateFlags.UNKNOWN_CA: + return "UNKNOWN_CA"; + + default: + return "(unknown=%Xh)".printf(flag); } } - - public enum SecurityType { - NONE, - SSL, - STARTTLS - } - - public enum AttemptStarttls { - YES, - NO, - HALT - } - - public NetworkAddress remote_address { get; private set; } - public Flags flags { get; private set; } + + + /** Specifies how to connect to the remote endpoint. */ + public GLib.SocketConnectable remote { get; private set; } + + /** A connectivity manager for this endpoint. */ + public ConnectivityManager connectivity { get; private set; } + + /** Timeout for connection attempts, in seconds. */ public uint timeout_sec { get; private set; } - public TlsCertificateFlags tls_validation_flags { get; set; default = TlsCertificateFlags.VALIDATE_ALL; } - public bool force_ssl3 { get; set; default = false; } + + /** Transport security method to use when connecting. */ + public TlsNegotiationMethod tls_method { get; private set; } + + /** Transport security certificate validation requirements. */ + public TlsCertificateFlags tls_validation_flags { + get; set; default = TlsCertificateFlags.VALIDATE_ALL; + } /** * The maximum number of commands that will be pipelined at once. @@ -56,154 +82,142 @@ /** * When set, TLS has reported certificate issues. * - * @see trust_untrusted_host * @see untrusted_host */ public TlsCertificateFlags tls_validation_warnings { get; private set; default = 0; } - + /** * The TLS certificate for an invalid or untrusted connection. */ public TlsCertificate? untrusted_certificate { get; private set; default = null; } - - /** - * When set, indicates the user has acceded to trusting the host even though TLS has reported - * certificate issues. - * - * Initialized to {@link Trillian.UNKNOWN}, meaning the user must decide when warnings are - * detected. - * - * @see untrusted_host - * @see tls_validation_warnings - */ - public Trillian trust_untrusted_host { get; set; default = Trillian.UNKNOWN; } - - /** - * Returns true if (a) no TLS warnings have been detected or (b) user has explicitly acceded - * to ignoring them and continuing the connection. - * - * This returns true if no connection has been attempted or connected and STARTTLS has not - * been issued. It's only when a connection is attempted can the certificate be examined - * and this can accurately return false. This behavior allows for a single code path to - * first attempt a connection and thereafter only attempt connections when TLS issues have - * been resolved by the user. - * - * @see tls_validation_warnings - * @see trust_untrusted_host - */ - public bool is_trusted_or_never_connected { - get { - return (tls_validation_warnings != 0) - ? trust_untrusted_host.is_certain() - : trust_untrusted_host.is_possible(); - } - } - - public bool is_ssl { get { - return flags.is_all_set(Flags.SSL); - } } - - public bool use_starttls { get { - return flags.is_all_set(Flags.STARTTLS); - } } - + private SocketClient? socket_client = null; - + + /** - * Fired when TLS certificate warnings are detected and the caller has not marked this - * {@link Endpoint} as trusted via {@link trust_untrusted_host}. + * Emitted when unexpected TLS certificate warnings are detected. * - * The connection will be closed when this is fired. The caller should query the user about - * how to deal with the situation. If user wants to proceed, set {@link trust_untrusted_host} - * to {@link Trillian.TRUE} and retry connection. + * This occurs when a connection receives a TLS certificate + * warning. The connection will be closed when this is fired. The + * caller should query the user about how to deal with the + * situation. If user wants to proceed, pin the certificate in a + * way such that it accessible to the connection via {@link + * default_tls_database}. * + * @see AccountInformation.untrusted_host * @see tls_validation_warnings */ - public signal void untrusted_host(SecurityType security, TlsConnection cx); - - public Endpoint(string host_specifier, uint16 default_port, Flags flags, uint timeout_sec) { - this.remote_address = new NetworkAddress(host_specifier, default_port); - this.flags = flags; + public signal void untrusted_host(GLib.TlsConnection cx); + + + public Endpoint(GLib.SocketConnectable remote, + TlsNegotiationMethod method, + uint timeout_sec) { + this.remote = remote; + this.connectivity = new ConnectivityManager((NetworkAddress) this.remote); this.timeout_sec = timeout_sec; + this.tls_method = method; } - + + public async GLib.SocketConnection connect_async(GLib.Cancellable? cancellable = null) + throws GLib.Error { + GLib.SocketClient client = get_socket_client(); + GLib.IOError? connect_error = null; + try { + return yield client.connect_async(this.remote, cancellable); + } catch (GLib.IOError.NETWORK_UNREACHABLE err) { + connect_error = err; + } + + // Ubuntu 18.04 for some reason started throwing + // NETWORK_UNREACHABLE when an AAAA record was resolved for + // host name but no valid IPv6 network was available. Work + // around by re-attempting manually resolving and selecting an + // address to use. See issue #217. + GLib.SocketAddressEnumerator addrs = this.remote.enumerate(); + GLib.SocketAddress? addr = yield addrs.next_async(cancellable); + while (addr != null) { + GLib.InetSocketAddress? inet_addr = addr as GLib.InetSocketAddress; + if (inet_addr != null) { + try { + return yield client.connect_async( + new GLib.InetSocketAddress( + inet_addr.address, (uint16) inet_addr.port + ), + cancellable + ); + } catch (GLib.IOError.NETWORK_UNREACHABLE err) { + // Keep going + } + } + addr = yield addrs.next_async(cancellable); + } + + throw connect_error; + } + + public async TlsClientConnection starttls_handshake_async(IOStream base_stream, + Cancellable? cancellable = null) throws Error { + TlsClientConnection tls_cx = TlsClientConnection.new( + base_stream, this.remote + ); + prepare_tls_cx(tls_cx); + + yield tls_cx.handshake_async(Priority.DEFAULT, cancellable); + + return tls_cx; + } + + public string to_string() { + return this.remote.to_string(); + } + private SocketClient get_socket_client() { if (socket_client != null) return socket_client; - + socket_client = new SocketClient(); - - if (is_ssl) { + + if (this.tls_method == TlsNegotiationMethod.TRANSPORT) { socket_client.set_tls(true); socket_client.set_tls_validation_flags(tls_validation_flags); socket_client.event.connect(on_socket_client_event); } - + socket_client.set_timeout(timeout_sec); - + return socket_client; } - public async SocketConnection connect_async(Cancellable? cancellable = null) throws Error { - return yield get_socket_client().connect_async(remote_address, cancellable); - } - - public async TlsClientConnection starttls_handshake_async(IOStream base_stream, - Cancellable? cancellable = null) throws Error { - TlsClientConnection tls_cx = TlsClientConnection.new(base_stream, remote_address); - prepare_tls_cx(tls_cx, true); - - yield tls_cx.handshake_async(Priority.DEFAULT, cancellable); - - return tls_cx; + private void prepare_tls_cx(GLib.TlsClientConnection tls_cx) { + // Setting this on Ubuntu 18.04 breaks some TLS + // connections. See issue #217. + // tls_cx.server_identity = this.remote; + tls_cx.validation_flags = this.tls_validation_flags; + if (Endpoint.default_tls_database != null) { + tls_cx.set_database(Endpoint.default_tls_database); + } + + tls_cx.accept_certificate.connect(on_accept_certificate); } - - private void on_socket_client_event(SocketClientEvent event, SocketConnectable? connectable, - IOStream? ios) { - // get TlsClientConnection to bind signals and set flags prior to handshake - if (event == SocketClientEvent.TLS_HANDSHAKING) - prepare_tls_cx((TlsClientConnection) ios, false); - } - - private void prepare_tls_cx(TlsClientConnection tls_cx, bool starttls) { - tls_cx.use_ssl3 = force_ssl3; - tls_cx.set_validation_flags(tls_validation_flags); - - // Vala doesn't do delegates in a ternary operator very well - if (starttls) - tls_cx.accept_certificate.connect(on_accept_starttls_certificate); - else - tls_cx.accept_certificate.connect(on_accept_ssl_certificate); - } - - private bool on_accept_starttls_certificate(TlsConnection cx, TlsCertificate cert, TlsCertificateFlags flags) { - return report_tls_warnings(SecurityType.STARTTLS, cx, cert, flags); - } - - private bool on_accept_ssl_certificate(TlsConnection cx, TlsCertificate cert, TlsCertificateFlags flags) { - return report_tls_warnings(SecurityType.SSL, cx, cert, flags); - } - - private bool report_tls_warnings(SecurityType security, TlsConnection cx, TlsCertificate cert, - TlsCertificateFlags warnings) { - // TODO: Report or verify flags with user, but for now merely log for informational/debugging - // reasons and accede - message("%s TLS warnings connecting to %s: %Xh (%s)", security.to_string(), to_string(), warnings, - tls_flags_to_string(warnings)); - + + private void report_tls_warnings(GLib.TlsConnection cx, + GLib.TlsCertificate cert, + GLib.TlsCertificateFlags warnings) { + // TODO: Report or verify flags with user, but for now merely + // log for informational/debugging reasons and accede + message( + "%s TLS warnings connecting to %s: %Xh (%s)", + this.tls_method.to_string(), to_string(), warnings, + tls_flags_to_string(warnings) + ); + tls_validation_warnings = warnings; untrusted_certificate = cert; - - // if user has marked this untrusted host as trusted already, accept warnings and move on - if (trust_untrusted_host == Trillian.TRUE) - return true; - - // signal an issue has been detected and return false to deny the connection - untrusted_host(security, cx); - - return false; + + untrusted_host(cx); } - + private string tls_flags_to_string(TlsCertificateFlags flags) { StringBuilder builder = new StringBuilder(); for (int pos = 0; pos < sizeof (TlsCertificateFlags) * 8; pos++) { @@ -211,67 +225,36 @@ if (flag != 0) { if (!String.is_empty(builder.str)) builder.append(" | "); - + builder.append(tls_flag_to_string(flag)); } } - + return !String.is_empty(builder.str) ? builder.str : "(none)"; } - - // Vala to_string() for Flags enums currently doesn't work -- bummer... - // Should only be called when a single flag is set, otherwise returns a string indicating an - // unknown value - public string tls_flag_to_string(TlsCertificateFlags flag) { - switch (flag) { - case TlsCertificateFlags.BAD_IDENTITY: - return "BAD_IDENTITY"; - - case TlsCertificateFlags.EXPIRED: - return "EXPIRED"; - - case TlsCertificateFlags.GENERIC_ERROR: - return "GENERIC_ERROR"; - - case TlsCertificateFlags.INSECURE: - return "INSECURE"; - - case TlsCertificateFlags.NOT_ACTIVATED: - return "NOT_ACTIVATED"; - - case TlsCertificateFlags.REVOKED: - return "REVOKED"; - - case TlsCertificateFlags.UNKNOWN_CA: - return "UNKNOWN_CA"; - - default: - return "(unknown=%Xh)".printf(flag); + + private void on_socket_client_event(GLib.SocketClientEvent event, + GLib.SocketConnectable? connectable, + GLib.IOStream? ios) { + // get TlsClientConnection to bind signals and set flags prior + // to handshake + if (event == SocketClientEvent.TLS_HANDSHAKING) { + prepare_tls_cx((TlsClientConnection) ios); } } - - /** - * Returns true if a STARTTLS command should be attempted on the connection: - * (a) STARTTLS is reported available (a parameter specified by the caller to this method), - * (b) not using SSL (so TLS is not required), and (c) STARTTLS is specified as a flag on - * the Endpoint. - * - * If AttemptStarttls.HALT is returned, the caller should not proceed to pass any - * authentication information down the connection; this situation indicates the connection is - * insecure and the Endpoint is configured otherwise. - */ - public AttemptStarttls attempt_starttls(bool starttls_available) { - if (is_ssl || !use_starttls) - return AttemptStarttls.NO; - - if (!starttls_available) - return AttemptStarttls.HALT; - - return AttemptStarttls.YES; - } - - public string to_string() { - return "%s/default:%u".printf(remote_address.hostname, remote_address.port); + + private bool on_accept_certificate(GLib.TlsConnection cx, + GLib.TlsCertificate cert, + GLib.TlsCertificateFlags flags) { + // Per the docs for GTlsConnection.accept-certificate, + // handling this signal must not block, so do this when idle + GLib.Idle.add(() => { + report_tls_warnings(cx, cert, flags); + return GLib.Source.REMOVE; + }, + GLib.Priority.HIGH + ); + return false; } -} +} diff -Nru geary-0.12.4/src/engine/api/geary-engine-error.vala geary-3.32.0/src/engine/api/geary-engine-error.vala --- geary-0.12.4/src/engine/api/geary-engine-error.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/api/geary-engine-error.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,23 +1,49 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. + * Copyright 2018 Michael Gratton * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ public errordomain Geary.EngineError { + + /** An account, folder or other object has not been opened. */ OPEN_REQUIRED, + + /** An account, folder or other object has already been opened. */ ALREADY_OPEN, + + /** An object with the same name or id already exists. */ ALREADY_EXISTS, + + /** An account, folder or other object has already been closed. */ + ALREADY_CLOSED, + + /** An account, folder or other object must be closed first. */ + CLOSE_REQUIRED, + + /** An object with the given name or id does not exist. */ NOT_FOUND, + + /** The parameters for a function or method call are somehow invalid. */ BAD_PARAMETERS, - BAD_RESPONSE, + + /** An email did not contain all required fields. */ INCOMPLETE_MESSAGE, + + /** A remote resource is no longer available. */ SERVER_UNAVAILABLE, - ALREADY_CLOSED, - CLOSE_REQUIRED, + + /** The account database or other local resource is corrupted. */ CORRUPT, + + /** The account database or other local resource cannot be accessed. */ PERMISSIONS, + + /** The account database or other local resource has a bad version. */ VERSION, + + /** A remote resource does not support a given operation. */ UNSUPPORTED } - diff -Nru geary-0.12.4/src/engine/api/geary-engine.vala geary-3.32.0/src/engine/api/geary-engine.vala --- geary-0.12.4/src/engine/api/geary-engine.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/api/geary-engine.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,66 +1,66 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. + * Copyright 2018 Michael Gratton * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ /** - * The Geary email engine initial entry points. + * Manages email account instances and their life-cycle. * - * Engine represents and contains interfaces into the rest of the email library. It's a singleton - * class (see {@link instance}) with various signals for event notification. Engine is initialized - * by calling {@link open_async} and closed with {@link close_async}. - * - * Engine can list existing {@link Account} objects and create/delete them. It can also validate - * changes to Accounts prior to saving those changes. + * An engine represents and contains interfaces into the rest of the + * email library. Instances are initialized by calling {@link + * open_async} and closed with {@link close_async}. Use this class for + * verifying and adding {@link AccountInformation} objects to check + * and start using email accounts. */ public class Geary.Engine : BaseObject { - private const string ID_PREFIX = "account_"; - private const string ID_FORMAT = "account_%02u"; - [Flags] - public enum ValidationOption { - NONE = 0, - CHECK_CONNECTIONS, - UPDATING_EXISTING; - - public inline bool is_all_set(ValidationOption options) { - return (options & this) == options; - } - } - - [Flags] - public enum ValidationResult { - OK = 0, - INVALID_NICKNAME, - EMAIL_EXISTS, - IMAP_CONNECTION_FAILED, - IMAP_CREDENTIALS_INVALID, - SMTP_CONNECTION_FAILED, - SMTP_CREDENTIALS_INVALID; - - public inline bool is_all_set(ValidationResult result) { - return (result & this) == result; - } - } - - private static Engine? _instance = null; + // Set low to avoid leaving the user hanging too long when + // validating a service. + private const uint VALIDATION_TIMEOUT = 15; + + public static Engine instance { get { return (_instance != null) ? _instance : (_instance = new Engine()); } } + private static Engine? _instance = null; + + + // Workaround for Vala issue #659. See shared_endpoints below. + private class EndpointWeakRef { + + GLib.WeakRef weak_ref; + + public EndpointWeakRef(Endpoint endpoint) { + this.weak_ref = GLib.WeakRef(endpoint); + } + + public Endpoint? get() { + return this.weak_ref.get() as Endpoint; + } + + } + - public File? user_data_dir { get; private set; default = null; } - public File? user_config_dir { get; private set; default = null; } + /** Location of the directory containing shared resource files. */ public File? resource_dir { get; private set; default = null; } - public Geary.CredentialsMediator? authentication_mediator { get; private set; default = null; } - - private bool is_initialized = false; - private bool is_open = false; + private Gee.HashMap? accounts = null; private Gee.HashMap? account_instances = null; + private bool is_initialized = false; + private bool is_open = false; + + // Would use a `weak Endpoint` value type for this map instead of + // the custom class, but we can't currently reassign built-in + // weak refs back to a strong ref at the moment, nor use a + // GLib.WeakRef as a generics param. See Vala issue #659. + private Gee.Map shared_endpoints = + new Gee.HashMap(); /** * Fired when the engine is opened and all the existing accounts are loaded. @@ -73,38 +73,21 @@ public signal void closed(); /** - * Fired when an account becomes available in the engine. Opening the - * engine makes all existing accounts available; newly created accounts are - * also made available as soon as they're stored. + * Fired when an account becomes available in the engine. + * + * Accounts are made available as soon as they're added to the + * engine. */ public signal void account_available(AccountInformation account); /** - * Fired when an account becomes unavailable in the engine. Closing the - * engine makes all accounts unavailable; deleting an account also makes it - * unavailable. + * Fired when an account becomes unavailable in the engine. + * + * Accounts are become available as soon when they are removed from the + * engine or the engine is closed. */ public signal void account_unavailable(AccountInformation account); - /** - * Fired when a new account is created. - */ - public signal void account_added(AccountInformation account); - - /** - * Fired when an account is deleted. - */ - public signal void account_removed(AccountInformation account); - - /** - * Fired when an {@link Endpoint} associated with the {@link AccountInformation} reports - * TLS certificate warnings during connection. - * - * This may be fired during normal operation or while validating the AccountInformation, in - * which case there is no {@link Account} associated with it. - */ - public signal void untrusted_host(Geary.AccountInformation account_information, - Endpoint endpoint, Endpoint.SecurityType security, TlsConnection cx, Service service); // Public so it can be tested public Engine() { @@ -114,7 +97,7 @@ if (!is_open) throw new EngineError.OPEN_REQUIRED("Geary.Engine instance not open"); } - + // This can't be called from within the ctor, as initialization code may want to access the // Engine instance to make their own calls and, in particular, subscribe to signals. // @@ -124,118 +107,67 @@ private void initialize_library() { if (is_initialized) return; - + is_initialized = true; - - AccountInformation.init(); + Logging.init(); RFC822.init(); - ImapEngine.init(); Imap.init(); HTML.init(); } - + /** - * Initializes the engine, and makes all existing accounts available. The - * given authentication mediator will be used to retrieve all passwords - * when necessary. + * Initializes the engine so that accounts can be added to it. */ - public async void open_async(File user_config_dir, File user_data_dir, File resource_dir, - Geary.CredentialsMediator? authentication_mediator, Cancellable? cancellable = null) throws Error { + public async void open_async(GLib.File resource_dir, + GLib.Cancellable? cancellable = null) + throws GLib.Error { // initialize *before* opening the Engine ... all initialize code should assume the Engine // is closed initialize_library(); - + if (is_open) throw new EngineError.ALREADY_OPEN("Geary.Engine instance already open"); - - this.user_config_dir = user_config_dir; - this.user_data_dir = user_data_dir; + this.resource_dir = resource_dir; - this.authentication_mediator = authentication_mediator; accounts = new Gee.HashMap(); account_instances = new Gee.HashMap(); is_open = true; - yield add_existing_accounts_async(cancellable); - opened(); } - private async void add_existing_accounts_async(Cancellable? cancellable = null) throws Error { - try { - user_data_dir.make_directory_with_parents(cancellable); - } catch (IOError e) { - if (!(e is IOError.EXISTS)) - throw e; - } - - FileEnumerator enumerator - = yield user_config_dir.enumerate_children_async("standard::*", - FileQueryInfoFlags.NONE, Priority.DEFAULT, cancellable); - - Gee.List account_list = new Gee.ArrayList(); - - for (;;) { - List info_list; - try { - info_list = yield enumerator.next_files_async(1, Priority.DEFAULT, cancellable); - } catch (Error e) { - debug("Error enumerating existing accounts: %s", e.message); - break; - } - - if (info_list.length() == 0) - break; - - FileInfo info = info_list.nth_data(0); - if (info.get_file_type() == FileType.DIRECTORY) { - try { - string id = info.get_name(); - account_list.add( - new AccountInformation.from_file( - id, - user_config_dir.get_child(id), - user_data_dir.get_child(id) - ) - ); - } catch (Error err) { - warning("Ignoring empty/bad config in %s: %s", - info.get_name(), err.message); - } - } - } - - foreach(AccountInformation info in account_list) - add_account(info); - } - /** - * Uninitializes the engine, and makes all accounts unavailable. + * Uninitializes the engine, and removes all known accounts. */ public async void close_async(Cancellable? cancellable = null) throws Error { if (!is_open) return; - + Gee.Collection unavailable_accounts = accounts.values; accounts.clear(); - + foreach(AccountInformation account in unavailable_accounts) account_unavailable(account); - - user_data_dir = null; + resource_dir = null; - authentication_mediator = null; accounts = null; account_instances = null; - + is_open = false; closed(); } /** + * Determines if an account with a specific id has added. + */ + public bool has_account(string id) { + return (this.accounts != null && this.accounts.has_key(id)); + } + + /** * Returns a current account given its id. * * Throws an error if the engine has not been opened or if the @@ -263,229 +195,289 @@ } /** - * Returns a new account, not yet stored on disk. - * - * Throws an error if the engine has not been opened or if an - * invalid account id is generated. + * Determines if an account's IMAP service can be connected to. */ - public AccountInformation create_orphan_account() throws Error { + public async void validate_imap(AccountInformation account, + ServiceInformation service, + GLib.Cancellable? cancellable = null) + throws GLib.Error { check_opened(); - // We might want to allow the client to specify the id, but - // just generate one here for now: Use a common prefix and a - // numeric suffix, starting at 1. To generate the next id, - // find the last account and increment its suffix. - - string? last_account = this.accounts.keys.fold((next, last) => { - string? result = last; - if (next.has_prefix(ID_PREFIX)) { - result = (last == null || strcmp(last, next) < 0) ? next : last; - } - return result; - }, - null); - uint next_id = 1; - if (last_account != null) { - next_id = int.parse(last_account.substring(ID_PREFIX.length)) + 1; + // Use a new endpoint since we use a different socket timeout + Endpoint endpoint = new_endpoint( + account.service_provider, service, VALIDATION_TIMEOUT + ); + ulong untrusted_id = endpoint.untrusted_host.connect( + (security, cx) => account.untrusted_host(service, security, cx) + ); + + Geary.Imap.ClientSession client = new Imap.ClientSession(endpoint); + GLib.Error? imap_err = null; + try { + yield client.connect_async(cancellable); + } catch (GLib.Error err) { + imap_err = err; } - string id = ID_FORMAT.printf(next_id); - if (this.accounts.has_key(id)) - throw new EngineError.ALREADY_EXISTS("Account %s already exists", id); + if (imap_err == null) { + try { + yield client.initiate_session_async( + service.credentials, cancellable + ); + } catch (GLib.Error err) { + imap_err = err; + } - return new AccountInformation( - id, user_config_dir.get_child(id), user_data_dir.get_child(id) - ); + try { + yield client.disconnect_async(cancellable); + } catch { + // Oh well + } + } + + // This always needs to be disconnected, even when there's an + // error + endpoint.disconnect(untrusted_id); + + if (imap_err != null) { + throw imap_err; + } } /** - * Returns whether the account information "validates." If validate_connection is true, - * we check if we can connect to the endpoints and authenticate using the supplied credentials. + * Determines if an account's SMTP service can be connected to. */ - public async ValidationResult validate_account_information_async(AccountInformation account, - ValidationOption options, Cancellable? cancellable = null) throws Error { + public async void validate_smtp(AccountInformation account, + ServiceInformation service, + Credentials? incoming_credentials, + GLib.Cancellable? cancellable = null) + throws GLib.Error { check_opened(); - - ValidationResult error_code = ValidationResult.OK; - - // Make sure the account nickname and email is not in use. - foreach (AccountInformation a in get_accounts().values) { - // Don't need to check a's alternate_emails since they - // can't be set at account creation time - bool has_email = account.has_email_address(a.primary_mailbox); - - if (!has_email && Geary.String.stri_equal(account.nickname, a.nickname)) - error_code |= ValidationResult.INVALID_NICKNAME; - - // if creating a new Account, don't allow an existing email address - if (!options.is_all_set(ValidationOption.UPDATING_EXISTING) && has_email) - error_code |= ValidationResult.EMAIL_EXISTS; - } - - // If we don't need to validate the connection, exit out here. - if (!options.is_all_set(ValidationOption.CHECK_CONNECTIONS)) - return error_code; - - account.untrusted_host.connect(on_untrusted_host); - - // validate IMAP, which requires logging in and establishing an AUTHORIZED cx state - Geary.Imap.ClientSession? imap_session = new Imap.ClientSession(account.get_imap_endpoint()); - try { - yield imap_session.connect_async(cancellable); - } catch (Error err) { - debug("Error connecting to IMAP server: %s", err.message); - error_code |= ValidationResult.IMAP_CONNECTION_FAILED; - } - - if (!error_code.is_all_set(ValidationResult.IMAP_CONNECTION_FAILED)) { - try { - yield imap_session.initiate_session_async(account.imap_credentials, cancellable); - - // Connected and initiated, still need to be sure connection authorized - Imap.MailboxSpecifier current_mailbox; - if (imap_session.get_protocol_state(out current_mailbox) != Imap.ClientSession.ProtocolState.AUTHORIZED) - error_code |= ValidationResult.IMAP_CREDENTIALS_INVALID; - } catch (Error err) { - debug("Error validating IMAP account info: %s", err.message); - if (err is ImapError.UNAUTHENTICATED) - error_code |= ValidationResult.IMAP_CREDENTIALS_INVALID; - else - error_code |= ValidationResult.IMAP_CONNECTION_FAILED; - } + + // Use a new endpoint since we use a different socket timeout + Endpoint endpoint = new_endpoint( + account.service_provider, service, VALIDATION_TIMEOUT + ); + ulong untrusted_id = endpoint.untrusted_host.connect( + (security, cx) => account.untrusted_host(service, security, cx) + ); + + Credentials? credentials = null; + switch (service.credentials_requirement) { + case USE_INCOMING: + credentials = incoming_credentials; + break; + case CUSTOM: + credentials = service.credentials; + break; } - - try { - yield imap_session.disconnect_async(cancellable); - } catch (Error err) { - // ignored - } finally { - imap_session = null; - } - - // SMTP is simpler, merely see if login works and done (throws an SmtpError if not) - Geary.Smtp.ClientSession? smtp_session = new Geary.Smtp.ClientSession(account.get_smtp_endpoint()); + + Geary.Smtp.ClientSession client = new Geary.Smtp.ClientSession(endpoint); + GLib.Error? login_err = null; try { - yield smtp_session.login_async(account.smtp_credentials, cancellable); - } catch (Error err) { - debug("Error validating SMTP account info: %s", err.message); - if (err is SmtpError.AUTHENTICATION_FAILED) - error_code |= ValidationResult.SMTP_CREDENTIALS_INVALID; - else - error_code |= ValidationResult.SMTP_CONNECTION_FAILED; + yield client.login_async(credentials, cancellable); + } catch (GLib.Error err) { + login_err = err; } - + try { - yield smtp_session.logout_async(true, cancellable); - } catch (Error err) { - // ignored - } finally { - smtp_session = null; - } - - account.untrusted_host.disconnect(on_untrusted_host); - - return error_code; + yield client.logout_async(true, cancellable); + } catch { + // Oh well + } + + // This always needs to be disconnected, even when there's an + // error + endpoint.disconnect(untrusted_id); + + if (login_err != null) { + throw login_err; + } } - + /** * Creates a Geary.Account from a Geary.AccountInformation (which is what * other methods in this interface deal in). */ - public Geary.Account get_account_instance(AccountInformation account_information) + public Geary.Account get_account_instance(AccountInformation config) throws Error { check_opened(); - if (account_instances.has_key(account_information.id)) - return account_instances.get(account_information.id); + if (account_instances.has_key(config.id)) + return account_instances.get(config.id); - ImapDB.Account local_account = new ImapDB.Account(account_information); - Imap.Account remote_account = new Imap.Account(account_information); + ImapDB.Account local = new ImapDB.Account(config); + Endpoint incoming_remote = get_shared_endpoint( + config.service_provider, config.incoming + ); + Endpoint outgoing_remote = get_shared_endpoint( + config.service_provider, config.outgoing + ); Geary.Account account; - switch (account_information.service_provider) { + switch (config.service_provider) { case ServiceProvider.GMAIL: - account = new ImapEngine.GmailAccount("Gmail:%s".printf(account_information.id), - account_information, remote_account, local_account); + account = new ImapEngine.GmailAccount( + config, local, incoming_remote, outgoing_remote + ); break; case ServiceProvider.YAHOO: - account = new ImapEngine.YahooAccount("Yahoo:%s".printf(account_information.id), - account_information, remote_account, local_account); + account = new ImapEngine.YahooAccount( + config, local, incoming_remote, outgoing_remote + ); break; case ServiceProvider.OUTLOOK: - account = new ImapEngine.OutlookAccount("Outlook:%s".printf(account_information.id), - account_information, remote_account, local_account); + account = new ImapEngine.OutlookAccount( + config, local, incoming_remote, outgoing_remote + ); break; case ServiceProvider.OTHER: - account = new ImapEngine.OtherAccount("Other:%s".printf(account_information.id), - account_information, remote_account, local_account); + account = new ImapEngine.OtherAccount( + config, local, incoming_remote, outgoing_remote + ); break; default: assert_not_reached(); } - account_instances.set(account_information.id, account); + account_instances.set(config.id, account); return account; } /** - * Adds the account to be tracked by the engine. Should only be called from - * AccountInformation.store_async() and this class. + * Adds the account to be tracked by the engine. */ - public void add_account(AccountInformation account, bool created = false) throws Error { + public void add_account(AccountInformation account) throws Error { check_opened(); - bool already_added = accounts.has_key(account.id); + if (accounts.has_key(account.id)) { + throw new EngineError.ALREADY_EXISTS( + "Account id '%s' already exists", account.id + ); + } accounts.set(account.id, account); - - if (!already_added) { - account.untrusted_host.connect(on_untrusted_host); - - if (created) - account_added(account); - - account_available(account); - } + account_available(account); } /** - * Deletes the account from disk. + * Removes an account from the engine. */ - public async void remove_account_async(AccountInformation account, - Cancellable? cancellable = null) throws Error { + public void remove_account(AccountInformation account) + throws GLib.Error { check_opened(); - + // Ensure account is closed. - if (account_instances.has_key(account.id) && account_instances.get(account.id).is_open()) { - throw new EngineError.CLOSE_REQUIRED("Account %s must be closed before removal", - account.id); + if (this.account_instances.has_key(account.id) && + this.account_instances.get(account.id).is_open()) { + throw new EngineError.CLOSE_REQUIRED( + "Account %s must be closed before removal", account.id + ); } - if (accounts.unset(account.id)) { - account.untrusted_host.disconnect(on_untrusted_host); - - // Removal *MUST* be done in the following order: - // 1. Send the account-unavailable signal. + if (this.accounts.has_key(account.id)) { + // Send the account-unavailable signal, account will be + // removed client side. account_unavailable(account); - - // 2. Delete the corresponding files. - yield account.remove_async(cancellable); - - // 3. Send the account-removed signal. - account_removed(account); - - // 4. Remove the account data from the engine. - account_instances.unset(account.id); + + // Then remove the account data from the engine. + this.accounts.unset(account.id); + this.account_instances.unset(account.id); } } - private void on_untrusted_host(AccountInformation account_information, Endpoint endpoint, - Endpoint.SecurityType security, TlsConnection cx, Service service) { - untrusted_host(account_information, endpoint, security, cx, service); + /** + * Changes the service configuration for an account. + * + * This updates an account's service configuration with the given + * configuration, by replacing the account's existing + * configuration for that service. The corresponding {@link + * Account.incoming} or {@link Account.outgoing} client service + * will also be updated so that the new configuration will start + * taking effect immediately. + */ + public async void update_account_service(AccountInformation account, + ServiceInformation updated, + GLib.Cancellable? cancellable) + throws GLib.Error { + Account? impl = this.account_instances.get(account.id); + if (impl == null) { + throw new EngineError.BAD_PARAMETERS( + "Account has not been added to the engine: %s", account.id + ); + } + + ClientService? service = null; + switch (updated.protocol) { + case Protocol.IMAP: + account.incoming = updated; + service = impl.incoming; + break; + + case Protocol.SMTP: + account.outgoing = updated; + service = impl.outgoing; + break; + } + + Endpoint remote = get_shared_endpoint(account.service_provider, updated); + yield service.update_configuration(updated, remote, cancellable); + account.changed(); + } + + private Geary.Endpoint get_shared_endpoint(ServiceProvider provider, + ServiceInformation service) { + // Key includes TLS method since endpoints encapsulate + // TLS-specific state + string key = "%s:%u/%s".printf( + service.host, + service.port, + service.transport_security.to_value() + ); + + Endpoint? shared = null; + EndpointWeakRef? cached = this.shared_endpoints.get(key); + if (cached != null) { + shared = cached.get() as Endpoint; + } + if (shared == null) { + uint timeout = service.protocol == Protocol.IMAP + ? Imap.ClientConnection.RECOMMENDED_TIMEOUT_SEC + : Smtp.ClientConnection.DEFAULT_TIMEOUT_SEC; + + shared = new_endpoint(provider, service, timeout); + + // XXX this is pretty hacky, move this back into the + // OutlookAccount somehow + if (provider == ServiceProvider.OUTLOOK) { + // As of June 2016, outlook.com's IMAP servers have a bug + // where a large number (~50) of pipelined STATUS commands on + // mailboxes with many messages will eventually cause it to + // break command parsing and return a BAD response, causing us + // to drop the connection. Limit the number of pipelined + // commands per batch to work around this. See b.g.o Bug + // 766552 + shared.max_pipeline_batch_size = 25; + } + + this.shared_endpoints.set(key, new EndpointWeakRef(shared)); + } + + return shared; } -} + private inline Geary.Endpoint new_endpoint(ServiceProvider provider, + ServiceInformation service, + uint timeout) { + return new Endpoint( + new NetworkAddress(service.host, service.port), + service.transport_security, + timeout + ); + } + +} diff -Nru geary-0.12.4/src/engine/api/geary-folder-path.vala geary-3.32.0/src/engine/api/geary-folder-path.vala --- geary-0.12.4/src/engine/api/geary-folder-path.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/api/geary-folder-path.vala 2019-03-17 13:39:29.000000000 +0000 @@ -13,330 +13,305 @@ * @see FolderRoot */ -public class Geary.FolderPath : BaseObject, Gee.Hashable, - Gee.Comparable { - /** - * The name of this folder (without any child or parent names or delimiters). - */ - public string basename { get; private set; } - +public class Geary.FolderPath : + BaseObject, Gee.Hashable, Gee.Comparable { + + + // Workaround for Vala issue #659. See children below. + private class FolderPathWeakRef { + + GLib.WeakRef weak_ref; + + public FolderPathWeakRef(FolderPath path) { + this.weak_ref = GLib.WeakRef(path); + } + + public FolderPath? get() { + return this.weak_ref.get() as FolderPath; + } + + } + + + /** The base name of this folder, excluding parents. */ + public string name { get; private set; } + + /** The number of children under the root in this path. */ + public uint length { + get { + uint length = 0; + FolderPath parent = this.parent; + while (parent != null) { + length++; + parent = parent.parent; + } + return length; + } + } + /** * Whether this path is lexiographically case-sensitive. * * This has implications, as {@link FolderPath} is Comparable and Hashable. */ public bool case_sensitive { get; private set; } - - private Gee.List? path = null; - private string? fullpath = null; - private string? fullpath_separator = null; - private uint stored_hash = uint.MAX; - - protected FolderPath(string basename, bool case_sensitive) { - assert(this is FolderRoot); - - this.basename = basename; - this.case_sensitive = case_sensitive; + + /** Determines if this path is a root folder path. */ + public bool is_root { + get { return this.parent == null; } } - - private FolderPath.child(Gee.List path, string basename, bool case_sensitive) { - assert(path[0] is FolderRoot); - - this.path = path; - this.basename = basename; - this.case_sensitive = case_sensitive; + + /** Determines if this path is a child of the root folder. */ + public bool is_top_level { + get { + FolderPath? parent = parent; + return parent != null && parent.is_root; + } } - - /** - * Returns true if this {@link FolderPath} is a root folder. - * - * This means that the FolderPath ''should'' be castable into {@link FolderRoot}, which is - * enforced through the constructor and accessor styles of this class. However, this test - * merely checks if this FolderPath has any children. A GObject "is" operation is the - * reliable way to cast to FolderRoot. - */ - public bool is_root() { - return (path == null || path.size == 0); + + /** Returns the parent of this path. */ + public FolderPath? parent { get; private set; } + + private string[] path; + + // Would use a `weak FolderPath` value type for this map instead of + // the custom class, but we can't currently reassign built-in + // weak refs back to a strong ref at the moment, nor use a + // GLib.WeakRef as a generics param. See Vala issue #659. + private Gee.Map children = + new Gee.HashMap(); + + private uint? stored_hash = null; + + + /** Constructor only for use by {@link FolderRoot}. */ + internal FolderPath() { + this.name = ""; + this.parent = null; + this.case_sensitive = false; + this.path = new string[0]; + } + + private FolderPath.child(FolderPath parent, + string name, + bool case_sensitive) { + this.parent = parent; + this.name = name; + this.case_sensitive = case_sensitive; + this.path = parent.path.copy(); + this.path += name; } - + /** * Returns the {@link FolderRoot} of this path. */ public Geary.FolderRoot get_root() { - return (FolderRoot) ((path != null && path.size > 0) ? path[0] : this); - } - - /** - * Returns the parent {@link FolderPath} of this folder or null if this is the root. - * - * @see is_root - */ - public Geary.FolderPath? get_parent() { - return (path != null && path.size > 0) ? path.last() : null; - } - - /** - * Returns the number of folders in this path, not including any children of this object. - */ - public int get_path_length() { - // include self, which is not stored in the path list - return (path != null) ? path.size + 1 : 1; - } - - /** - * Returns the {@link FolderPath} object at the index, with this FolderPath object being - * the farthest child. - * - * Root is at index 0 (zero). - * - * Returns null if index is out of bounds. There is always at least one element in the path, - * namely this one, meaning zero is always acceptable and that index[length - 1] will always - * return this object. - * - * @see get_path_length - */ - public Geary.FolderPath? get_folder_at(int index) { - // include self, which is not stored in the path list ... essentially, this logic makes it - // look like "this" is stored at the end of the path list - if (path == null) - return (index == 0) ? this : null; - - int length = path.size; - if (index < length) - return path[index]; - - if (index == length) - return this; - - return null; - } - - /** - * Returns the {@link FolderPath} as a List of {@link basename} strings, this FolderPath's - * being the last in the list. - * - * Thus, the list should have at least one element. - */ - public Gee.List as_list() { - Gee.List list = new Gee.ArrayList(); - - if (path != null) { - foreach (Geary.FolderPath folder in path) - list.add(folder.basename); - } - - list.add(basename); - - return list; + FolderPath? path = this; + while (path.parent != null) { + path = path.parent; + } + return (FolderRoot) path; } - + /** - * Creates a {@link FolderPath} object that is a child of this folder. - * - * {@link Trillian.TRUE} and {@link Trillian.FALSE} force case-sensitivity. - * {@link Trillian.UNKNOWN} indicates to use {@link FolderRoot.default_case_sensitivity}. + * Returns an array of the names of non-root elements in the path. */ - public Geary.FolderPath get_child(string basename, Trillian child_case_sensitive = Trillian.UNKNOWN) { - // Build the child's path, which is this node's path plus this node - Gee.List child_path = new Gee.ArrayList(); - if (path != null) - child_path.add_all(path); - child_path.add(this); - - return new FolderPath.child(child_path, basename, - child_case_sensitive.to_boolean(get_root().default_case_sensitivity)); + public string[] as_array() { + return this.path; } - + /** - * Returns true if the other {@link FolderPath} has the same parent as this one. + * Creates a path that is a child of this folder. * - * Like {@link equal_to} and {@link compare_to}, this comparison the comparison is - * lexiographic, not by reference. - */ - public bool has_same_parent(FolderPath other) { - FolderPath? parent = get_parent(); - FolderPath? other_parent = other.get_parent(); - - if (parent == other_parent) - return true; - - if (parent != null && other_parent != null) - return parent.equal_to(other_parent); - - return false; + * Specifying {@link Trillian.TRUE} or {@link Trillian.FALSE} for + * `is_case_sensitive` forces case-sensitivity either way. If + * {@link Trillian.UNKNOWN}, then {@link + * FolderRoot.default_case_sensitivity} is used. + */ + public virtual FolderPath + get_child(string name, + Trillian is_case_sensitive = Trillian.UNKNOWN) { + FolderPath? child = null; + FolderPathWeakRef? child_ref = this.children.get(name); + if (child_ref != null) { + child = child_ref.get(); + } + if (child == null) { + child = new FolderPath.child( + this, + name, + is_case_sensitive.to_boolean( + get_root().default_case_sensitivity + ) + ); + this.children.set(name, new FolderPathWeakRef(child)); + } + return child; } - + /** - * Returns the {@link FolderPath} as a single string with the supplied separator used as a - * delimiter. - * - * The separator is not appended to the fullpath. + * Determines if this path is a strict ancestor of another. */ - public string get_fullpath(string separator) { - // use cached copy if the stars align - if (fullpath != null && fullpath_separator == separator) - return fullpath; - - StringBuilder builder = new StringBuilder(); - - if (path != null) { - foreach (Geary.FolderPath folder in path) { - builder.append(folder.basename); - builder.append(separator); + public bool is_descendant(FolderPath target) { + bool is_descendent = false; + FolderPath? path = target.parent; + while (path != null) { + if (path.equal_to(this)) { + is_descendent = true; + break; } + path = path.parent; } - - builder.append(basename); - - fullpath = builder.str; - fullpath_separator = separator; - - return fullpath; - } - - private uint get_basename_hash() { - return case_sensitive ? str_hash(basename) : str_hash(basename.down()); - } - - private int compare_internal(Geary.FolderPath other, bool allow_case_sensitive, bool normalize) { - if (this == other) - return 0; - - // walk elements using as_list() as that includes the basename (whereas path does not), - // avoids the null problem, and makes comparisons straightforward - Gee.List this_list = as_list(); - Gee.List other_list = other.as_list(); - - // if paths exist, do comparison of each parent in order - int min = int.min(this_list.size, other_list.size); - for (int ctr = 0; ctr < min; ctr++) { - string this_element = this_list[ctr]; - string other_element = other_list[ctr]; - - if (normalize) { - this_element = this_element.normalize(); - other_element = other_element.normalize(); - } - if (!allow_case_sensitive - // if either case-sensitive, then comparison is CS - || (!get_folder_at(ctr).case_sensitive && !other.get_folder_at(ctr).case_sensitive)) { - this_element = this_element.casefold(); - other_element = other_element.casefold(); - } - - int result = this_element.collate(other_element); - if (result != 0) - return result; - } - - // paths up to the min element count are equal, shortest path is less-than, otherwise - // equal paths - return this_list.size - other_list.size; + return is_descendent; } - + /** - * Does a Unicode-normalized, case insensitive match. Useful for getting a rough idea if - * a folder matches a name, but shouldn't be used to determine strict equality. + * Does a Unicode-normalized, case insensitive match. Useful for + * getting a rough idea if a folder matches a name, but shouldn't + * be used to determine strict equality. */ - public int compare_normalized_ci(Geary.FolderPath other) { + public int compare_normalized_ci(FolderPath other) { return compare_internal(other, false, true); } - + /** * {@inheritDoc} * - * Comparisons for Geary.FolderPath is defined as (a) empty paths are less-than non-empty paths - * and (b) each element is compared to the corresponding path element of the other FolderPath - * following collation rules for casefolded (case-insensitive) compared, and (c) shorter paths - * are less-than longer paths, assuming the path elements are equal up to the shorter path's + * Comparisons for FolderPath is defined as (a) empty paths + * are less-than non-empty paths and (b) each element is compared + * to the corresponding path element of the other FolderPath + * following collation rules for casefolded (case-insensitive) + * compared, and (c) shorter paths are less-than longer paths, + * assuming the path elements are equal up to the shorter path's * length. * * Note that {@link FolderPath.case_sensitive} affects comparisons. * - * Returns -1 if this path is lexiographically before the other, 1 if its after, and 0 if they - * are equal. + * Returns -1 if this path is lexiographically before the other, 1 + * if its after, and 0 if they are equal. */ - public int compare_to(Geary.FolderPath other) { + public int compare_to(FolderPath other) { return compare_internal(other, true, false); } - + /** * {@inheritDoc} * * Note that {@link FolderPath.case_sensitive} affects comparisons. */ public uint hash() { - if (stored_hash != uint.MAX) - return stored_hash; - - // always one element in path - stored_hash = get_folder_at(0).get_basename_hash(); - - int path_length = get_path_length(); - for (int ctr = 1; ctr < path_length; ctr++) - stored_hash ^= get_folder_at(ctr).get_basename_hash(); - - return stored_hash; - } - - private bool is_basename_equal(string cmp, bool other_cs) { - // case-sensitive comparison if either is sensitive - return (other_cs || case_sensitive) ? (basename == cmp) : (basename.down() == cmp.down()); - } - - /** - * {@inheritDoc} - */ - public bool equal_to(Geary.FolderPath other) { - int path_length = get_path_length(); - if (other.get_path_length() != path_length) - return false; - - for (int ctr = 0; ctr < path_length; ctr++) { - // this should never return null as length is already checked - FolderPath? other_folder = other.get_folder_at(ctr); - assert(other_folder != null); - - if (!get_folder_at(ctr).is_basename_equal(other_folder.basename, other_folder.case_sensitive)) - return false; + if (this.stored_hash == null) { + this.stored_hash = 0; + FolderPath? path = this; + while (path != null) { + this.stored_hash ^= (case_sensitive) + ? str_hash(path.name) : str_hash(path.name.down()); + path = path.parent; + } } - - return true; + return this.stored_hash; } - + + /** {@inheritDoc} */ + public bool equal_to(FolderPath other) { + return this.compare_internal(other, true, false) == 0; + } + /** - * Returns the fullpath using the default separator. + * Returns a string version of the path using a default separator. * - * Use only for debugging and logging. + * Do not use this for obtaining an IMAP mailbox name to send to a + * server, use {@link + * Geary.Imap.MailboxSpecifier.MailboxSpecifier.from_folder_path} + * instead. This method is useful for debugging and logging only. */ public string to_string() { - return get_fullpath(">"); + const char SEP = '>'; + StringBuilder builder = new StringBuilder(); + if (this.is_root) { + builder.append_c(SEP); + } else { + foreach (string name in this.path) { + builder.append_c(SEP); + builder.append(name); + } + } + return builder.str; + } + + private int compare_internal(FolderPath other, + bool allow_case_sensitive, + bool normalize) { + if (this == other) { + return 0; + } + + int a_len = (int) this.length; + int b_len = (int) other.length; + if (a_len != b_len) { + return a_len - b_len; + } + + return compare_names(this, other, allow_case_sensitive, normalize); + } + + private static int compare_names(FolderPath a, FolderPath b, + bool allow_case_sensitive, + bool normalize) { + int cmp = 0; + if (a.parent != null && b.parent != null) { + cmp = compare_names( + a.parent, b.parent, allow_case_sensitive, normalize + ); + } + if (cmp == 0) { + string a_name = a.name; + string b_name = b.name; + + if (normalize) { + a_name = a_name.normalize(); + b_name = b_name.normalize(); + } + + if (!allow_case_sensitive + // if either case-sensitive, then comparison is CS + || (!a.case_sensitive && !b.case_sensitive)) { + a_name = a_name.casefold(); + b_name = b_name.casefold(); + } + + return strcmp(a_name, b_name); + } + return cmp; } + } /** - * The root of a folder heirarchy. - * - * A {@link FolderPath} can only be created by starting with a FolderRoot and adding children - * via {@link FolderPath.get_child}. Because all FolderPaths hold references to their parents, - * this element can be retrieved with {@link FolderPath.get_root}. + * The root of a folder hierarchy. * - * Since each email system may have different requirements for its paths, this is an abstract - * class. + * A {@link FolderPath} can only be created by starting with a + * FolderRoot and adding children via {@link FolderPath.get_child}. + * Because all FolderPaths hold references to their parents, this + * element can be retrieved with {@link FolderPath.get_root}. */ -public abstract class Geary.FolderRoot : Geary.FolderPath { +public class Geary.FolderRoot : FolderPath { + + /** - * The default case sensitivity of each element in the {@link FolderPath}. + * The default case sensitivity of descendant folders. * - * @see FolderRoot.case_sensitive * @see FolderPath.get_child */ public bool default_case_sensitivity { get; private set; } - - protected FolderRoot(string basename, bool case_sensitive, bool default_case_sensitivity) { - base (basename, case_sensitive); - + + + /** + * Constructs a new folder root with given default sensitivity. + */ + public FolderRoot(bool default_case_sensitivity) { + base(); this.default_case_sensitivity = default_case_sensitivity; } -} +} diff -Nru geary-0.12.4/src/engine/api/geary-folder-properties.vala geary-3.32.0/src/engine/api/geary-folder-properties.vala --- geary-0.12.4/src/engine/api/geary-folder-properties.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/api/geary-folder-properties.vala 2019-03-17 13:39:29.000000000 +0000 @@ -12,24 +12,24 @@ public const string PROP_NAME_IS_OPENABLE = "is-openable"; public const string PROP_NAME_IS_LOCAL_ONLY = "is-local-only"; public const string PROP_NAME_IS_VIRTUAL = "is-virtual"; - + /** * The total count of email in the {@link Folder}. */ public int email_total { get; protected set; } - + /** * The total count of unread email in the {@link Folder}. */ public int email_unread { get; protected set; } - + /** * Returns a {@link Trillian} indicating if this {@link Folder} has children. * * has_children == {@link Trillian.TRUE} implies {@link supports_children} == Trilian.TRUE. */ public Trillian has_children { get; protected set; } - + /** * Returns a {@link Trillian} indicating if this {@link Folder} can parent new children * {@link Folder}s. @@ -37,12 +37,12 @@ * This does ''not'' mean creating a sub-folder is guaranteed to succeed. */ public Trillian supports_children { get; protected set; } - + /** * Returns a {@link Trillian} indicating if {@link Folder.open_async} can succeed remotely. */ public Trillian is_openable { get; protected set; } - + /** * Returns true if the {@link Folder} is local-only, that is, has no remote folder backing * it. @@ -52,7 +52,7 @@ * a Folder interface. */ public bool is_local_only { get; private set; } - + /** * Returns true if the {@link Folder} is virtual, that is, it is either generated by some * external criteria and/or is aggregating the content of other Folders. @@ -61,7 +61,7 @@ * copy. */ public bool is_virtual { get; private set; } - + /** * True if the {@link Folder} offers the {@link FolderSupport.Create} interface but is * guaranteed not to return a {@link EmailIdentifier}, even if @@ -71,7 +71,7 @@ * will usually be false. */ public bool create_never_returns_id { get; protected set; } - + protected FolderProperties(int email_total, int email_unread, Trillian has_children, Trillian supports_children, Trillian is_openable, bool is_local_only, bool is_virtual, bool create_never_returns_id) { diff -Nru geary-0.12.4/src/engine/api/geary-folder-supports-archive.vala geary-3.32.0/src/engine/api/geary-folder-supports-archive.vala --- geary-0.12.4/src/engine/api/geary-folder-supports-archive.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/api/geary-folder-supports-archive.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,28 +1,34 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ /** - * The addition of this interface to {@link Geary.Folder} indicates that it supports an archive - * operation (which may or may not be in addition to a remove operation via - * {@link Geary.FolderSupport.Remove}). + * The addition of this interface to {@link Geary.Folder} indicates + * that it supports an archive operation (which may or may not be in + * addition to a remove operation via {@link + * Geary.FolderSupport.Remove}). * - * An archive operation acts like remove except that the mail is still available on the server, - * usually in an All Mail folder and perhaps others. It does not imply that the mail message was - * moved to the Trash folder. + * An archive operation acts like remove except that the mail is still + * available on the server, usually in an All Mail folder and perhaps + * others. It does not imply that the mail message was moved to the + * Trash folder. */ +public interface Geary.FolderSupport.Archive : Folder { -public interface Geary.FolderSupport.Archive : Geary.Folder { /** * Archives the specified emails from the folder. * - * The {@link Geary.Folder} must be opened prior to attempting this operation. + * This folder must be opened prior to attempting this operation. * - * @return A {@link Geary.Revokable} that may be used to revoke (undo) this operation later. + * @return A {@link Geary.Revokable} that may be used to revoke + * (undo) this operation later. */ - public abstract async Geary.Revokable? archive_email_async(Gee.List email_ids, - Cancellable? cancellable = null) throws Error; -} + public abstract async Revokable? + archive_email_async(Gee.Collection email_ids, + GLib.Cancellable? cancellable = null) + throws GLib.Error; +} diff -Nru geary-0.12.4/src/engine/api/geary-folder-supports-copy.vala geary-3.32.0/src/engine/api/geary-folder-supports-copy.vala --- geary-0.12.4/src/engine/api/geary-folder-supports-copy.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/api/geary-folder-supports-copy.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,28 +1,34 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ /** - * The addition of the Geary.FolderSupport.Copy interface to a {@link Geary.Folder} indicates it - * supports a copy email operation. + * The addition of the Geary.FolderSupport.Copy interface to a {@link + * Geary.Folder} indicates it supports a copy email operation. * - * A copied email will not be removed from the current folder but will appear in the destination. + * A copied email will not be removed from the current folder but will + * appear in the destination. * - * Copy does not imply {@link Geary.FolderSupport.Move}, or vice-versa. + * Copy does not imply {@link Geary.FolderSupport.Move}, or + * vice-versa. */ +public interface Geary.FolderSupport.Copy : Folder { -public interface Geary.FolderSupport.Copy : Geary.Folder { /** * Copies messages into another folder. * - * If the destination is this {@link Folder}, the operation will not make a copy of the message - * but will return success. + * If the destination is this {@link Folder}, the operation will + * not make a copy of the message but will return success. * - * The Folder must be opened prior to attempting this operation. + * This folder must be opened prior to attempting this operation. */ - public abstract async void copy_email_async(Gee.List to_copy, - Geary.FolderPath destination, Cancellable? cancellable = null) throws Error; -} + public abstract async void + copy_email_async(Gee.Collection to_copy, + FolderPath destination, + GLib.Cancellable? cancellable = null) + throws GLib.Error; +} diff -Nru geary-0.12.4/src/engine/api/geary-folder-supports-create.vala geary-3.32.0/src/engine/api/geary-folder-supports-create.vala --- geary-0.12.4/src/engine/api/geary-folder-supports-create.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/api/geary-folder-supports-create.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,37 +1,47 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ /** - * The addition of the Geary.FolderSupport.Create interface on a {@link Geary.Folder} indicates it supports - * creating email. + * The addition of the Geary.FolderSupport.Create interface on a + * {@link Geary.Folder} indicates it supports creating email. * * Created emails are uploaded to the Folder and stored there. * - * Note that creating an email in the Outbox will queue it for sending. Thus, it may be removed - * without user interaction at some point in the future. + * Note that creating an email in the Outbox will queue it for + * sending. Thus, it may be removed without user interaction at some + * point in the future. */ +public interface Geary.FolderSupport.Create : Folder { -public interface Geary.FolderSupport.Create : Geary.Folder { /** * Creates (appends) the message to this folder. * * The Folder must be opened prior to attempting this operation. * - * The optional {@link EmailFlags} allows for those flags to be set when saved. Some Folders - * may ignore those flags (i.e. Outbox) if not applicable. + * The optional {@link EmailFlags} allows for those flags to be + * set when saved. Some Folders may ignore those flags + * (i.e. Outbox) if not applicable. * - * The optional DateTime allows for the message's "date received" time to be set when saved. - * Like EmailFlags, this is optional if not applicable. - * - * If an id is passed, this will replace the existing message by deleting it after the new - * message is created. The new message's ID is returned. + * The optional DateTime allows for the message's "date received" + * time to be set when saved. Like EmailFlags, this is optional + * if not applicable. + * + * If an id is passed, this will replace the existing message by + * deleting it after the new message is created. The new + * message's ID is returned. * * @see FolderProperties.create_never_returns_id */ - public abstract async Geary.EmailIdentifier? create_email_async(Geary.RFC822.Message rfc822, EmailFlags? flags, - DateTime? date_received, Geary.EmailIdentifier? id, Cancellable? cancellable = null) throws Error; -} + public abstract async EmailIdentifier? + create_email_async(RFC822.Message rfc822, + EmailFlags? flags, + DateTime? date_received, + EmailIdentifier? id, + GLib.Cancellable? cancellable = null) + throws GLib.Error; +} diff -Nru geary-0.12.4/src/engine/api/geary-folder-supports-empty.vala geary-3.32.0/src/engine/api/geary-folder-supports-empty.vala --- geary-0.12.4/src/engine/api/geary-folder-supports-empty.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/api/geary-folder-supports-empty.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,25 +1,31 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ /** - * The addition of the Geary.FolderSupport.Empty interface to a {@link Geary.Folder} - * indicates that it supports removing (deleting) all email quickly. + * The addition of the Geary.FolderSupport.Empty interface to a {@link + * Geary.Folder} indicates that it supports removing (deleting) all + * email quickly. * - * This generally means that the message is deleted from the server and is not recoverable. - * It does ''not'' mean the messages are moved to a Trash folder where they may or may not be - * automatically deleted some time later. Users invoking empty are expecting all contents on the - * remote to be removed entirely, whether or not any or all of them have been synchronized locally. + * This generally means that the message is deleted from the server + * and is not recoverable. It does ''not'' mean the messages are + * moved to a Trash folder where they may or may not be automatically + * deleted some time later. Users invoking empty are expecting all + * contents on the remote to be removed entirely, whether or not any + * or all of them have been synchronized locally. * * @see FolderSupport.Remove */ +public interface Geary.FolderSupport.Empty : Folder { -public interface Geary.FolderSupport.Empty : Geary.Folder { /** * Removes all email from the folder. */ - public abstract async void empty_folder_async(Cancellable? cancellable = null) throws Error; -} + public abstract async void + empty_folder_async(GLib.Cancellable? cancellable = null) + throws GLib.Error; +} diff -Nru geary-0.12.4/src/engine/api/geary-folder-supports-mark.vala geary-3.32.0/src/engine/api/geary-folder-supports-mark.vala --- geary-0.12.4/src/engine/api/geary-folder-supports-mark.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/api/geary-folder-supports-mark.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,22 +1,27 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ /** - * The addition of the Geary.FolderSupport.Mark interface indicates the {@link Geary.Folder} - * supports marking and unmarking messages with system and user-defined flags. + * The addition of the Geary.FolderSupport.Mark interface indicates + * the {@link Geary.Folder} supports marking and unmarking messages + * with system and user-defined flags. */ - public interface Geary.FolderSupport.Mark : Geary.Folder { + /** * Adds and removes flags from a list of messages. * - * The {@link Geary.Folder} must be opened prior to attempting this operation. + * This folder must be opened prior to attempting this operation. */ - public abstract async void mark_email_async(Gee.List to_mark, - Geary.EmailFlags? flags_to_add, Geary.EmailFlags? flags_to_remove, - Cancellable? cancellable = null) throws Error; -} + public abstract async void + mark_email_async(Gee.Collection to_mark, + EmailFlags? flags_to_add, + EmailFlags? flags_to_remove, + GLib.Cancellable? cancellable = null) + throws GLib.Error; +} diff -Nru geary-0.12.4/src/engine/api/geary-folder-supports-move.vala geary-3.32.0/src/engine/api/geary-folder-supports-move.vala --- geary-0.12.4/src/engine/api/geary-folder-supports-move.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/api/geary-folder-supports-move.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,29 +1,35 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + *Copyright 2016 Software Freedom Conservancy Inc. * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ /** - * The addition of the Geary.FolderSupport.Move interface indicates that the - * {@link Geary.Folder} supports a move email operation. Moved messages are - * removed from this Folder. + * The addition of the Geary.FolderSupport.Move interface indicates + * that the {@link Geary.Folder} supports a move email operation. + * Moved messages are removed from this Folder. * - * Move does not imply {@link Geary.FolderSupport.Copy}, or vice-versa. + * Move does not imply {@link Geary.FolderSupport.Copy}, or + * vice-versa. */ +public interface Geary.FolderSupport.Move : Folder { -public interface Geary.FolderSupport.Move : Geary.Folder { /** * Moves messages to another folder. * - * If the destination is this {@link Folder}, the operation will not move the message in any - * way but will return success. + * If the destination is this {@link Folder}, the operation will + * not move the message in any way but will return success. * - * The {@link Geary.Folder} must be opened prior to attempting this operation. + * This folder must be opened prior to attempting this operation. * - * @return A {@link Geary.Revokable} that may be used to revoke (undo) this operation later. + * @return A {@link Geary.Revokable} that may be used to revoke + * (undo) this operation later. */ - public abstract async Geary.Revokable? move_email_async(Gee.List to_move, - Geary.FolderPath destination, Cancellable? cancellable = null) throws Error; -} + public abstract async Revokable? + move_email_async(Gee.Collection to_move, + FolderPath destination, + GLib.Cancellable? cancellable = null) + throws GLib.Error; +} diff -Nru geary-0.12.4/src/engine/api/geary-folder-supports-remove.vala geary-3.32.0/src/engine/api/geary-folder-supports-remove.vala --- geary-0.12.4/src/engine/api/geary-folder-supports-remove.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/api/geary-folder-supports-remove.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,32 +1,37 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ /** - * The addition of the Geary.FolderSupport.Remove interface to a {@link Geary.Folder} - * indicates that it supports removing (deleting) email. + * The addition of the Geary.FolderSupport.Remove interface to a + * {@link Geary.Folder} indicates that it supports removing (deleting) + * email. * - * This generally means that the message is deleted from the server and is not recoverable. - * It _may_ mean the message is moved to a Trash folder where it may or may not be - * automatically deleted some time later; this behavior is server-specific and not always + * This generally means that the message is deleted from the server + * and is not recoverable. It _may_ mean the message is moved to a + * Trash folder where it may or may not be automatically deleted some + * time later; this behavior is server-specific and not always * determinable by Geary (or worked around, either). * - * The remove operation is distinct from the archive operation, available via - * {@link Geary.FolderSupport.Archive}. + * The remove operation is distinct from the archive operation, + * available via {@link Geary.FolderSupport.Archive}. * - * A Folder that does not support Remove does not imply that email might not be removed later, - * such as by the server. + * A Folder that does not support Remove does not imply that email + * might not be removed later, such as by the server. */ +public interface Geary.FolderSupport.Remove : Folder { -public interface Geary.FolderSupport.Remove : Geary.Folder { /** * Removes the specified emails from the folder. * - * The {@link Geary.Folder} must be opened prior to attempting this operation. + * This folder must be opened prior to attempting this operation. */ - public abstract async void remove_email_async(Gee.List email_ids, - Cancellable? cancellable = null) throws Error; -} + public abstract async void + remove_email_async(Gee.Collection email_ids, + GLib.Cancellable? cancellable = null) + throws GLib.Error; +} diff -Nru geary-0.12.4/src/engine/api/geary-folder.vala geary-3.32.0/src/engine/api/geary-folder.vala --- geary-0.12.4/src/engine/api/geary-folder.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/api/geary-folder.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,44 +1,107 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. + * Copyright 2018 Michael Gratton * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ /** - * Folder represents the basic unit of organization for email. + * A Folder represents the basic unit of organization for email. * - * Each {@link Account} offers a hierarcichal listing of Folders. Folders must be opened (with - * {@link open_async} before using most of its methods and should be closed with - * {@link close_async} when completed, even if a method has failed with an IOError. + * Each {@link Account} provides a hierarchical listing of Folders. + * Note that while most folders are able to store email messages, some + * folders may not and may exist purely to group together folders + * below it in the account's folder hierarchy. Folders that can + * contain email messages either store these messages purely locally + * (for example, in the case of an ''outbox'' for mail queued for + * sending), or as a representation of those found in a mailbox on a + * remote mail server, such as those provided by an IMAP server. Email + * messages are represented by the {@link Email} class, and many + * folder methods will return collections of these. For folders that + * represent a remote mailbox, the mailbox's email are cached locally, + * and the set of cached messages may be a subset of those available + * in the mailbox, depending on an account's settings. Email messages + * may be partially cached, in the case of a new message having just + * arrived or a message with many large attachments that was not + * completely downloaded. * - * Folder offers various open states indicating when its "local" (disk or database) connection and - * "remote" (network) connections are ready. Generally the local connection opens first and the - * remote connection takes time to establish. When in this state, Folder's methods still operate, - * but will only return locally stored information. + * Folder objects must be opened (with {@link open_async} before using + * most of its methods and should be closed with {@link close_async} + * when completed, even if a previous method call has failed with an + * IOError. Folders offer various open states indicating when its + * "local" (disk or database) connection and "remote" (network) + * connections are ready. Generally the local connection opens first + * and the remote connection takes time to establish. When in this + * state, Folder's methods still operate, but will only return locally + * stored information. * - * Folder only offers a small selection of guaranteed functionality (in particular, the ability - * to list its {@link Email}). Additional functionality for Folders is indicated by the presence - * of {@link FolderSupport} interfaces, include {@link FolderSupport.Remove}, - * {@link FolderSupport.Copy}, and so forth. + * The set of locally stored messages is called the folder's + * ''vector'', and contains generally the most recent message in the + * mailbox at the upper end, back through to some older message at the + * start or lower end of the vector. Thus the ordering of the vector + * is the ''natural'' ordering, based on the order in which messages + * were appended to the folder, not when messages were sent or some + * other criteria. For remote-backed folders, the engine will maintain + * the vector in accordance with the value of {@link + * AccountInformation.prefetch_period_days}, however the start of the + * vector will be extended back past that over time and in response to + * certain operations that cause the vector to be ''expanded'' --- + * that is for additional messages to be loaded from the remote + * server, extending the vector. The upper end of the vector is + * similarly extended as new messages are appended to the folder by + * another on the server or in response to user operations such as + * moving a message. + * + * This class only offers a small selection of guaranteed + * functionality (in particular, the ability to list its {@link + * Email}). Additional functionality for Folders is indicated by the + * presence of {@link FolderSupport} interfaces, include {@link + * FolderSupport.Remove}, {@link FolderSupport.Copy}, and so forth. * * @see Geary.SpecialFolderType */ - public abstract class Geary.Folder : BaseObject { + + /** + * Indicates if a folder has been opened, and if so in which way. + */ public enum OpenState { + + /** + * Indicates the folder has not been opened. + * + * Either no call to {@link open_async} has yet been made, or + * an equal number of calls to {@link close_async} have also + * been made. + */ CLOSED, - OPENING, - REMOTE, + + /** + * Indicates the folder has been opened locally only. + * + * The folder has been opened by a call to {@link open_async}, + * but if the folder is backed by a remote mailbox, a + * connection to the remote mailbox has not yet been + * established. + */ LOCAL, - BOTH + + /** + * Indicates the folder has been opened with a remote connection. + * + * The folder has been opened by a call to {@link open_async}, + * and a connection to the remote mailbox has also been + * made. Local-only folders will never reach this state. + */ + REMOTE; } - + public enum OpenFailed { - LOCAL_FAILED, - REMOTE_FAILED, - CANCELLED + LOCAL_ERROR, + REMOTE_ERROR, } - + /** * Provides the reason why the folder is closing or closed when the {@link closed} signal * is fired. @@ -60,12 +123,12 @@ REMOTE_CLOSE, REMOTE_ERROR, FOLDER_CLOSED; - + public bool is_error() { return (this == LOCAL_ERROR) || (this == REMOTE_ERROR); } } - + [Flags] public enum CountChangeReason { NONE = 0, @@ -73,36 +136,37 @@ INSERTED, REMOVED } - + /** - * Flags modifying the behavior of open_async(). + * Flags that modify the behavior of {@link open_async}. */ [Flags] public enum OpenFlags { + /** If only //NONE// is set, the folder will be opened normally. */ NONE = 0, - /** - * Perform the minimal amount of activity possible to open the folder - * and be synchronized with the server. This may mean some attributes of - * the messages (such as their flags or other metadata) may not be up-to-date - * when the folder opens. Not all folders will support this flag. - */ - FAST_OPEN, + /** * Do not delay opening a connection to the server. * + * This has no effect for folders that are not backed by a + * remote server. + * * @see open_async */ NO_DELAY; - + + /** Determines if any one of the given //flags// are set. */ public bool is_any_set(OpenFlags flags) { return (this & flags) != 0; } - + + /** Determines all of the given //flags// are set. */ public bool is_all_set(OpenFlags flags) { return (this & flags) == flags; } + } - + /** * Flags modifying how email is retrieved. */ @@ -124,91 +188,112 @@ /** * Direction of list traversal (if not set, from newest to oldest). */ - OLDEST_TO_NEWEST; - + OLDEST_TO_NEWEST, + /** + * Internal use only, prevents flag changes updating unread count. + */ + NO_UNREAD_UPDATE; + public bool is_any_set(ListFlags flags) { return (this & flags) != 0; } - + public bool is_all_set(ListFlags flags) { return (this & flags) == flags; } - + public bool is_local_only() { return is_all_set(LOCAL_ONLY); } - + public bool is_force_update() { return is_all_set(FORCE_UPDATE); } - + public bool is_including_id() { return is_all_set(INCLUDING_ID); } - + public bool is_oldest_to_newest() { return is_all_set(OLDEST_TO_NEWEST); } - + public bool is_newest_to_oldest() { return !is_oldest_to_newest(); } } - + + /** The account that owns this folder. */ public abstract Geary.Account account { get; } - + + /** Current properties for this folder. */ public abstract Geary.FolderProperties properties { get; } - + + /** The folder path represented by this object. */ public abstract Geary.FolderPath path { get; } - + + /** Determines the type of this folder. */ public abstract Geary.SpecialFolderType special_folder_type { get; } - + + /** Monitor for notifying of progress when opening the folder. */ public abstract Geary.ProgressMonitor opening_monitor { get; } - + + /** - * Fired when the folder is successfully opened by a caller. + * Fired when the folder moves through stages of being opened. * - * It will only fire once until the Folder is closed, with the {@link OpenState} indicating what - * has been opened and the count indicating the number of messages in the folder. In the case - * of {@link OpenState.BOTH} or {@link OpenState.REMOTE}, it refers to the authoritative number. - * For {@link OpenState.LOCAL}, it refers to the number of messages in the local store. + * It will fire at least once if the folder successfully opens, + * with the {@link OpenState} indicating what has been opened and + * the count indicating the number of messages in the folder. it + * may fire additional times as remote sessions are established + * and re-established after being lost. + * + * If //state// is {@link OpenState.LOCAL}, the local store for + * the folder has opened and the count reflects the number of + * messages in the local store. + * + * If //state// is {@link OpenState.REMOTE}, it indicates both the + * local store and a remote session has been established, and the + * count reflects the number of messages on the remote. This + * signal will not be fired with this value for a local-only folder. * - * {@link OpenState.REMOTE} will only be passed if there's no local store, indicating that it's - * not a synchronized folder but rather one entirely backed by a network server. Geary - * currently has no such folder implemented like this. - * - * This signal will never fire with {@link OpenState.CLOSED} as a parameter. + * This signal will never fire with {@link OpenState.CLOSED} as a + * parameter. * * @see get_open_state */ public signal void opened(OpenState state, int count); - + /** * Fired when {@link open_async} fails for one or more reasons. * - * See open_async and {@link opened} for more information on how opening a Folder works, i particular - * how open_async may return immediately although the remote has not completely opened. - * This signal may be called in the context of, or after completion of, open_async. It will - * ''not'' be called after {@link close_async} has completed, however. + * See open_async and {@link opened} for more information on how + * opening a Folder works, in particular how open_async may return + * immediately although the remote has not completely opened. + * This signal may be called in the context of, or after + * completion of, open_async. It will ''not'' be called after + * {@link close_async} has completed, however. * - * Note that this signal may be fired ''and'' open_async throw an Error. + * Note that this signal may be fired ''and'' open_async throw an + * Error. * - * This signal may be fired more than once before the Folder is closed. It will only fire once - * for each type of failure, however. + * This signal may be fired more than once before the Folder is + * closed, especially in the case of a remote session */ public signal void open_failed(OpenFailed failure, Error? err); - + /** - * Fired when the Folder is closed, either by the caller or due to errors in the local - * or remote store(s). + * Fired when the Folder is closed, either by the caller or due to + * errors in the local or remote store(s). * - * It will fire three times: to report how the local store closed - * (gracefully or due to error), how the remote closed (similarly) and finally with - * {@link CloseReason.FOLDER_CLOSED}. The first two may come in either order; the third is - * always the last. + * It will fire a number of times: to report how the local store + * closed (gracefully or due to error), how the remote closed + * (similarly) and finally with {@link CloseReason.FOLDER_CLOSED}. + * The first two may come in either order; the third is always the + * last. */ public signal void closed(CloseReason reason); - + /** * Fired when email has been appended to the list of messages in the folder. * @@ -217,7 +302,7 @@ * @see email_locally_appended */ public signal void email_appended(Gee.Collection ids); - + /** * Fired when previously unknown messages have been appended to the list of email in the folder. * @@ -230,7 +315,7 @@ * @see email_appended */ public signal void email_locally_appended(Gee.Collection ids); - + /** * Fired when email has been inserted into the list of messages in the folder. * @@ -242,7 +327,7 @@ * @see email_locally_inserted */ public signal void email_inserted(Gee.Collection ids); - + /** * Fired when previously unknown messages have been appended to the list of email in the folder. * @@ -256,7 +341,7 @@ * @see email_locally_inserted */ public signal void email_locally_inserted(Gee.Collection ids); - + /** * Fired when email has been removed (deleted or moved) from the folder. * @@ -269,7 +354,7 @@ * signal will ''not'' fire, although {@link email_count_changed} will. */ public signal void email_removed(Gee.Collection ids); - + /** * Fired when the total count of email in a folder has changed in any way. * @@ -277,19 +362,19 @@ * and {@link email_removed} (although see the note at email_removed). */ public signal void email_count_changed(int new_count, CountChangeReason reason); - + /** * Fired when the supplied email flags have changed, whether due to local action or reported by * the server. */ public signal void email_flags_changed(Gee.Map map); - + /** * Fired when one or more emails have been locally saved with the full set * of Fields. */ public signal void email_locally_complete(Gee.Collection ids); - + /** * Fired when the {@link SpecialFolderType} has changed. * @@ -298,62 +383,62 @@ */ public signal void special_folder_type_changed(Geary.SpecialFolderType old_type, Geary.SpecialFolderType new_type); - + /** * Fired when the Folder's display name has changed. * * @see get_display_name */ public signal void display_name_changed(); - + protected Folder() { } - + protected virtual void notify_opened(Geary.Folder.OpenState state, int count) { opened(state, count); } - + protected virtual void notify_open_failed(Geary.Folder.OpenFailed failure, Error? err) { open_failed(failure, err); } - + protected virtual void notify_closed(Geary.Folder.CloseReason reason) { closed(reason); } - + protected virtual void notify_email_appended(Gee.Collection ids) { email_appended(ids); } - + protected virtual void notify_email_locally_appended(Gee.Collection ids) { email_locally_appended(ids); } - + protected virtual void notify_email_inserted(Gee.Collection ids) { email_inserted(ids); } - + protected virtual void notify_email_locally_inserted(Gee.Collection ids) { email_locally_inserted(ids); } - + protected virtual void notify_email_removed(Gee.Collection ids) { email_removed(ids); } - + protected virtual void notify_email_count_changed(int new_count, Folder.CountChangeReason reason) { email_count_changed(new_count, reason); } - + protected virtual void notify_email_flags_changed(Gee.Map flag_map) { email_flags_changed(flag_map); } - + protected virtual void notify_email_locally_complete(Gee.Collection ids) { email_locally_complete(ids); } - + /** * In its default implementation, this will also call {@link notify_display_name_changed} since * that's often the case; if not, subclasses should override. @@ -361,140 +446,173 @@ protected virtual void notify_special_folder_type_changed(Geary.SpecialFolderType old_type, Geary.SpecialFolderType new_type) { special_folder_type_changed(old_type, new_type); - + // in default implementation, this may also mean the display name changed; subclasses may // override this behavior, but no way to detect this, so notify - if (special_folder_type != Geary.SpecialFolderType.NONE) - notify_display_name_changed(); + notify_display_name_changed(); } - + protected virtual void notify_display_name_changed() { display_name_changed(); } - + /** * Returns a name suitable for displaying to the user. * - * Default is to display the basename of the Folder's path, unless it's a special folder, + * Default is to display the name of the Folder's path, unless it's a special folder, * in which case {@link SpecialFolderType.get_display_name} is returned. */ public virtual string get_display_name() { return (special_folder_type == Geary.SpecialFolderType.NONE) - ? path.basename : special_folder_type.get_display_name(); + ? path.name : special_folder_type.get_display_name(); } - - /** - * Returns the state of the Folder's connections to the local and remote stores. - */ + + /** Determines if a folder has been opened, and if so in which way. */ public abstract OpenState get_open_state(); - + /** - * The Folder must be opened before most operations may be performed on it. Depending on the - * implementation this might entail opening a network connection or setting the connection to - * a particular state, opening a file or database, and so on. - * - * In the case of a Folder that is aggregating the contents of synchronized folder, it's possible - * for this method to complete even though all internal opens haven't completed. The "opened" - * signal is the final say on when a Folder is fully opened with its OpenState parameter - * indicating how open it really is. In general, a Folder's local store will open immediately - * while it may take time (if ever) for the remote state to open. Thus, it's possible for - * the "opened" signal to fire some time *after* this method completes. - * - * {@link OpenFlags.NO_DELAY} may be passed to force an immediate opening of the remote folder. - * This still will not occur in the context of the open_async call, but will initiate the - * connection immediately. Use this only when it's known that remote calls or remote - * notifications to the Folder are imminent or monitoring the Folder is vital (such as with the - * Inbox). - * - * However, even if the method returns before the Folder's OpenState is BOTH, this Folder is - * ready for operation if this method returns without error. The messages the folder returns - * may not reflect the full state of the Folder, however, and returned emails may subsequently - * have their state changed (such as their position). Making a call that requires - * accessing the remote store before OpenState.BOTH has been signalled will result in that - * call blocking until the remote is open or an error state has occurred. It's also possible for - * the command to return early without waiting, depending on prior information of the folder. - * See list_email_async() for special notes on its operation. Also see wait_for_open_async(). - * - * If there's an error while opening, "open-failed" will be fired. (See that signal for more - * information on how many times it may fire, and when.) To prevent the Folder from going into - * a halfway state, it will immediately schedule a close_async() to cleanup, and those - * associated signals will be fired as well. - * - * If the Folder has been opened previously, an internal open count is incremented and the - * method returns. There are no other side-effects. This means it's possible for the - * open_flags parameter to be ignored. See the returned result for more information. - * - * A Folder may be reopened after it has been closed. This allows for Folder objects to be - * emitted by the Account object cheaply, but the client should only have a few open at a time, - * as each may represent an expensive resource (such as a network connection). + * Marks the folder's operations as being required for use. + * + * A folder object must be opened before most operations may be + * performed on it. Depending on the folder implementation this + * might entail opening a network connection or setting the + * connection to a particular state, opening a file or database, + * and so on. In general, a Folder's local store should open + * immediately, hence if this call returns with error, {@link + * get_open_state} should return {@link OpenState.LOCAL}. + * + * For folders that are backed by a remote mailbox, it may take + * time for a remote connection to be established (if ever), and + * so it is possible for this method to complete even though a + * remote connection is not available. In this case the folder's + * state and the email messages the its contains are backed by a + * local cache, and may not reflect the full state of the remote + * mailbox. Hence both folder and email state may subsequently be + * changed (such as their position) after the remote connection + * has been established and the local and remote stores have been + * synchronised. Use signals such as {@link email_appended} to be + * notified of such changes. + * + * Connecting to the {@link opened} signal can be used to be + * notified when a remote connection has been established. Making + * a method call on a folder that requires accessing the remote + * mailbox before {@link OpenState.REMOTE} has been sent via this + * signal will result in that call blocking until the remote is + * open, the folder closes, or an error occurs. However it is also + * possible for some methods to return early without waiting, + * depending on prior information of the folder. See {@link + * list_email_by_id_async} for special notes on its + * operation. + * + * In some cases, establishing a remote connection may be + * performed lazily, that is only when first needed. If however + * {@link OpenFlags.NO_DELAY} is passed as an argument it will + * instead force an immediate opening of the remote + * connection. This still will not occur in the context of the + * this method all call, but it will ensure the a connection is + * initiated immediately. Since establishing remote connections is + * costly, use this only when it's known that remote calls or + * remote notifications to the Folder are imminent or monitoring + * the Folder is vital (such as with the Inbox). + * + * If the Folder has been opened by a call to this method + * previously, an internal open count is incremented and the + * method returns. There are no other side-effects. This means + * it's possible for the open_flags parameter to be ignored. See + * the returned result for more information. + * + * A Folder may safely be reopened after it has been closed. This + * allows for Folder objects to be emitted by the Account object + * cheaply, but the client should only have a few open at a time, + * as each may represent an expensive resource (such as a network + * connection). + * + * If there is an error while opening, "open-failed" will be + * fired. (See that signal for more information on how many times + * it may fire, and when.) To prevent the Folder from going into + * a halfway state, it will immediately schedule a close_async() + * to cleanup, and those associated signals will be fired as well. * * Returns false if already opened. */ - public abstract async bool open_async(OpenFlags open_flags, Cancellable? cancellable = null) throws Error; - + public abstract async bool open_async(OpenFlags open_flags, + Cancellable? cancellable = null) + throws Error; + /** - * Wait for the Folder to become fully open or fails to open due to error. If not opened - * due to error, throws EngineError.ALREADY_CLOSED. + * Marks one use of the folder's operations as being completed. + * + * The folder must be closed when operations on it are concluded. + * Depending on the implementation this might entail closing a + * network connection or reverting it to another state, or closing + * file handles or database connections. + * + * If the folder is open, an internal open count is decremented. + * If it remains above zero, the method returns with no other + * side-effects. If it decrements to zero, the folder will start + * to close tearing down network connections, closing files, and + * so-forth. The {@link closed} signal can be used to be notified + * of progress closing the folder. Use {@link + * wait_for_close_async} to block until the folder is completely + * closed. + * + * Returns true if the open count decrements to zero and the + * folder is closing, or if it is already closed. * - * NOTE: The current implementation requirements are only that should be work after an - * open_async() call has completed (i.e. an open is in progress). Calling this method - * otherwise will throw an EngineError.OPEN_REQUIRED. - */ - public abstract async void wait_for_open_async(Cancellable? cancellable = null) throws Error; - - /** - * The Folder should be closed when operations on it are concluded. Depending on the - * implementation this might entail closing a network connection or reverting it to another - * state, or closing file handles or database connections. - * - * If the Folder is open, an internal open count is decremented. If it remains above zero, the - * method returns with no other side-effects. If it decrements to zero, the Folder is closed, - * tearing down network connections, closing files, and so forth. See "closed" for signals - * indicating the closing states. - * - * Returns true if the open count decrements to zero and the folder is ''closing''. Use - * {@link wait_for_close_async} to block until the folder is completely closed. Otherwise, - * returns false. Note that this semantic is slightly different than the result code for - * {@link open_async}. + * @see open_async */ public abstract async bool close_async(Cancellable? cancellable = null) throws Error; - + /** * Wait for the {@link Folder} to fully close. * - * Unlike {@link wait_for_open_async}, this will ''always'' block until a {@link Folder} is - * closed, even if it's not open. + * This will ''always'' block until the folder is closed, even if + * it's not open. */ public abstract async void wait_for_close_async(Cancellable? cancellable = null) throws Error; - - /** - * Find the lowest- and highest-ordered {@link EmailIdentifier}s in the - * folder, among the given set of EmailIdentifiers that may or may not be - * in the folder. If none of the given set are in the folder, return null. - */ - public abstract async void find_boundaries_async(Gee.Collection ids, - out Geary.EmailIdentifier? low, out Geary.EmailIdentifier? high, - Cancellable? cancellable = null) throws Error; - + /** - * List emails from the {@link Folder} starting at a particular location within the vector - * and moving either direction along the mail stack. - * - * If the {@link EmailIdentifier} is null, it indicates the end of the vector. Which end - * depends on the {@link ListFlags.OLDEST_TO_NEWEST} flag. Without, the default is to traverse - * from newest to oldest, with null being the newest email. If set, the direction is reversed - * and null indicates the oldest email. + * List a number of contiguous emails in the folder's vector. * - * If not null, the EmailIdentifier ''must'' have originated from this Folder. - * - * To fetch all available messages in one call, use a count of int.MAX. - * - * Use {@link ListFlags.INCLUDING_ID} to include the {@link Email} for the particular identifier - * in the results. Otherwise, the specified email will not be included. A null - * EmailIdentifier implies that the top most email is included in the result (i.e. + * Emails in the folder are listed starting at a particular + * location within the vector and moving either direction along + * it. For remote-backed folders, the remote server is contacted + * if any messages stored locally do not meet the requirements + * given by `required_fields`, or if `count` extends back past the + * low end of the vector. + * + * If the {@link EmailIdentifier} is null, it indicates the end of + * the vector, not the end of the remote. Which end depends on + * the {@link ListFlags.OLDEST_TO_NEWEST} flag. If not set, the + * default is to traverse from newest to oldest, with null being + * the newest email in the vector. If set, the direction is + * reversed and null indicates the oldest email in the vector, not + * the oldest in the mailbox. + * + * If not null, the EmailIdentifier ''must'' have originated from + * this Folder. + * + * To fetch all available messages in one call, use a count of + * `int.MAX`. If the {@link ListFlags.OLDEST_TO_NEWEST} flag is + * set then the listing will contain all messages in the vector, + * and no expansion will be performed. It may still access the + * remote however in case of any of the messages not meeting the + * given `required_fields`. If {@link ListFlags.OLDEST_TO_NEWEST} + * is not set, the call will cause the vector to be fully expanded + * and the listing will return all messages in the remote + * mailbox. Note that specifying `int.MAX` in either case may be a + * expensive operation (in terms of both computation and memory) + * if the number of messages in the folder or mailbox is large, + * hence should be avoided if possible. + * + * Use {@link ListFlags.INCLUDING_ID} to include the {@link Email} + * for the particular identifier in the results. Otherwise, the + * specified email will not be included. A null EmailIdentifier + * implies that the top most email is included in the result (i.e. * ListFlags.INCLUDING_ID is not required); * - * If the remote connection fails, this call will return locally-available Email without error. + * If the remote connection fails, this call will return + * locally-available Email without error. * * There's no guarantee of the returned messages' order. * @@ -503,8 +621,10 @@ public abstract async Gee.List? list_email_by_id_async(Geary.EmailIdentifier? initial_id, int count, Geary.Email.Field required_fields, ListFlags flags, Cancellable? cancellable = null) throws Error; - + /** + * List a set of non-contiguous emails in the folder's vector. + * * Similar in contract to {@link list_email_by_id_async}, but uses a list of * {@link Geary.EmailIdentifier}s rather than a range. * @@ -519,7 +639,7 @@ public abstract async Gee.List? list_email_by_sparse_id_async( Gee.Collection ids, Geary.Email.Field required_fields, ListFlags flags, Cancellable? cancellable = null) throws Error; - + /** * Returns the locally available Geary.Email.Field fields for the specified emails. If a * list or fetch operation occurs on the emails that specifies a field not returned here, @@ -532,7 +652,7 @@ */ public abstract async Gee.Map? list_local_email_fields_async( Gee.Collection ids, Cancellable? cancellable = null) throws Error; - + /** * Returns a single email that fulfills the required_fields flag at the ordered position in * the folder. If the email_id is invalid for the folder's contents, an EngineError.NOT_FOUND @@ -552,12 +672,11 @@ */ public abstract async Geary.Email fetch_email_async(Geary.EmailIdentifier email_id, Geary.Email.Field required_fields, ListFlags flags, Cancellable? cancellable = null) throws Error; - + /** * Used for debugging. Should not be used for user-visible labels. */ public virtual string to_string() { - return "%s:%s".printf(account.to_string(), path.to_string()); + return "%s:%s".printf(this.account.information.id, this.path.to_string()); } } - diff -Nru geary-0.12.4/src/engine/api/geary-logging.vala geary-3.32.0/src/engine/api/geary-logging.vala --- geary-0.12.4/src/engine/api/geary-logging.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/api/geary-logging.vala 2019-03-17 13:39:29.000000000 +0000 @@ -13,9 +13,11 @@ namespace Geary.Logging { +private const string DOMAIN = "Geary"; + [Flags] public enum Flag { - NONE, + NONE = 0, NETWORK, SERIALIZER, REPLAY, @@ -24,11 +26,11 @@ SQL, FOLDER_NORMALIZATION, DESERIALIZER; - + public inline bool is_all_set(Flag flags) { return (flags & this) == flags; } - + public inline bool is_any_set(Flag flags) { return (flags & this) != 0; } @@ -48,10 +50,7 @@ public void init() { if (init_count++ != 0) return; - entry_timer = new Timer(); - - log_to(null); } /** @@ -90,29 +89,35 @@ return logging_flags.is_all_set(flags); } +[PrintfFormat] public inline void error(Flag flags, string fmt, ...) { if (logging_flags.is_any_set(flags)) - logv(null, LogLevelFlags.LEVEL_ERROR, fmt, va_list()); + logv(DOMAIN, LogLevelFlags.LEVEL_ERROR, fmt, va_list()); } +[PrintfFormat] public inline void critical(Flag flags, string fmt, ...) { if (logging_flags.is_any_set(flags)) - logv(null, LogLevelFlags.LEVEL_CRITICAL, fmt, va_list()); + logv(DOMAIN, LogLevelFlags.LEVEL_CRITICAL, fmt, va_list()); } +[PrintfFormat] public inline void warning(Flag flags, string fmt, ...) { if (logging_flags.is_any_set(flags)) - logv(null, LogLevelFlags.LEVEL_WARNING, fmt, va_list()); + logv(DOMAIN, LogLevelFlags.LEVEL_WARNING, fmt, va_list()); } +[PrintfFormat] public inline void message(Flag flags, string fmt, ...) { if (logging_flags.is_any_set(flags)) - logv(null, LogLevelFlags.LEVEL_MESSAGE, fmt, va_list()); + logv(DOMAIN, LogLevelFlags.LEVEL_MESSAGE, fmt, va_list()); } +[PrintfFormat] public inline void debug(Flag flags, string fmt, ...) { - if (logging_flags.is_any_set(flags)) - logv(null, LogLevelFlags.LEVEL_DEBUG, fmt, va_list()); + if (logging_flags.is_any_set(flags)) { + logv(DOMAIN, LogLevelFlags.LEVEL_DEBUG, fmt, va_list()); + } } /** @@ -125,31 +130,62 @@ */ public void log_to(FileStream? stream) { Logging.stream = stream; - - Log.set_handler(null, LogLevelFlags.LEVEL_DEBUG, - (domain, levels, msg) => { on_log(" [deb]", levels, msg); }); - Log.set_handler(null, LogLevelFlags.LEVEL_INFO, - (domain, levels, msg) => { on_log(" [inf]", levels, msg); }); - Log.set_handler(null, LogLevelFlags.LEVEL_MESSAGE, - (domain, levels, msg) => { on_log(" [msg]", levels, msg); }); - Log.set_handler(null, LogLevelFlags.LEVEL_WARNING, - (domain, levels, msg) => { on_log("*[wrn]", levels, msg); }); - Log.set_handler(null, LogLevelFlags.LEVEL_CRITICAL, - (domain, levels, msg) => { on_log("![crt]", levels, msg); }); - Log.set_handler(null, LogLevelFlags.LEVEL_ERROR, - (domain, levels, msg) => { on_log("![err]", levels, msg); }); } -private void on_log(string prefix, LogLevelFlags log_levels, string message) { - if (stream == null) - return; - - GLib.Time tm = GLib.Time.local(time_t()); - stream.printf("%s %02d:%02d:%02d %lf %s\n", prefix, tm.hour, tm.minute, tm.second, - entry_timer.elapsed(), message); - - entry_timer.start(); +public void default_handler(string? domain, + LogLevelFlags log_levels, + string message) { + unowned FileStream? out = stream; + if (out != null || + ((LogLevelFlags.LEVEL_WARNING & log_levels) > 0) || + ((LogLevelFlags.LEVEL_CRITICAL & log_levels) > 0) || + ((LogLevelFlags.LEVEL_ERROR & log_levels) > 0)) { + + if (out == null) { + out = GLib.stderr; + } + + GLib.Time tm = GLib.Time.local(time_t()); + out.printf( + "%s %02d:%02d:%02d %lf %s: %s\n", + to_prefix(log_levels), + tm.hour, tm.minute, tm.second, + entry_timer.elapsed(), + domain ?? "default", + message + ); + + entry_timer.start(); + } } +private inline string to_prefix(LogLevelFlags level) { + switch (level) { + case LogLevelFlags.LEVEL_ERROR: + return "![err]"; + + case LogLevelFlags.LEVEL_CRITICAL: + return "![crt]"; + + case LogLevelFlags.LEVEL_WARNING: + return "*[wrn]"; + + case LogLevelFlags.LEVEL_MESSAGE: + return " [msg]"; + + case LogLevelFlags.LEVEL_INFO: + return " [inf]"; + + case LogLevelFlags.LEVEL_DEBUG: + return " [deb]"; + + case LogLevelFlags.LEVEL_MASK: + return "![***]"; + + default: + return "![???]"; + + } } +} diff -Nru geary-0.12.4/src/engine/api/geary-named-flags.vala geary-3.32.0/src/engine/api/geary-named-flags.vala --- geary-0.12.4/src/engine/api/geary-named-flags.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/api/geary-named-flags.vala 2019-03-17 13:39:29.000000000 +0000 @@ -11,91 +11,91 @@ public class Geary.NamedFlags : BaseObject, Gee.Hashable { protected Gee.Set list = new Gee.HashSet(); - + public virtual signal void added(Gee.Collection flags) { } - + public virtual signal void removed(Gee.Collection flags) { } - + public NamedFlags() { } - + protected virtual void notify_added(Gee.Collection flags) { added(flags); } - + protected virtual void notify_removed(Gee.Collection flags) { removed(flags); } - + public bool contains(NamedFlag flag) { return list.contains(flag); } - + public bool contains_any(NamedFlags flags) { return Geary.traverse(list).any(f => flags.contains(f)); } - + public Gee.Set get_all() { return list.read_only_view; } - + public virtual void add(NamedFlag flag) { if (!list.contains(flag)) { list.add(flag); notify_added(Geary.iterate(flag).to_array_list()); } } - + public virtual void add_all(NamedFlags flags) { Gee.ArrayList added = Geary.traverse(flags.get_all()) .filter(f => !list.contains(f)) .to_array_list(); - + list.add_all(added); notify_added(added); } - + public virtual bool remove(NamedFlag flag) { bool removed = list.remove(flag); if (removed) notify_removed(Geary.iterate(flag).to_array_list()); - + return removed; } - + public virtual bool remove_all(NamedFlags flags) { Gee.ArrayList removed = Geary.traverse(flags.get_all()) .filter(f => list.contains(f)) .to_array_list(); - + list.remove_all(removed); notify_removed(removed); - + return removed.size > 0; } - + public bool equal_to(Geary.NamedFlags other) { if (this == other) return true; - + if (list.size != other.list.size) return false; - + return Geary.traverse(list).all(f => other.contains(f)); } - + public uint hash() { return Geary.String.stri_hash(to_string()); } - + public string to_string() { string ret = "["; foreach (NamedFlag flag in list) { ret += flag.to_string() + " "; } - + return ret + "]"; } } diff -Nru geary-0.12.4/src/engine/api/geary-named-flag.vala geary-3.32.0/src/engine/api/geary-named-flag.vala --- geary-0.12.4/src/engine/api/geary-named-flag.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/api/geary-named-flag.vala 2019-03-17 13:39:29.000000000 +0000 @@ -12,26 +12,26 @@ public class Geary.NamedFlag : BaseObject, Gee.Hashable { public string name { get; private set; } - + public NamedFlag(string name) { this.name = name; } - + public bool equal_to(Geary.NamedFlag other) { if (this == other) return true; - + return name.down() == other.name.down(); } - + public uint hash() { return name.down().hash(); } - + public string serialize() { return name; } - + public string to_string() { return name; } diff -Nru geary-0.12.4/src/engine/api/geary-problem-report.vala geary-3.32.0/src/engine/api/geary-problem-report.vala --- geary-0.12.4/src/engine/api/geary-problem-report.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/src/engine/api/geary-problem-report.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,141 @@ +/* + * Copyright 2016 Software Freedom Conservancy Inc. + * Copyright 2017-2018 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +/** Describes available problem types. */ +public enum Geary.ProblemType { + + + /** Indicates an engine problem not covered by one of the other types. */ + GENERIC_ERROR, + + /** Indicates an error opening, using or closing the account database. */ + DATABASE_FAILURE, + + /** Indicates a problem establishing a connection. */ + CONNECTION_ERROR, + + /** Indicates a problem caused by a network operation. */ + NETWORK_ERROR, + + /** Indicates a non-network related server error. */ + SERVER_ERROR, + + /** Indicates credentials supplied for authentication were rejected. */ + AUTHENTICATION, + + /** Indicates a remote TLS certificate failed validation. */ + UNTRUSTED, + + /** Indicates an outgoing message was sent, but not saved. */ + SEND_EMAIL_SAVE_FAILED; + + + /** Determines the appropriate problem type for an IOError. */ + public static ProblemType for_ioerror(IOError error) { + if (error is IOError.CONNECTION_REFUSED || + error is IOError.HOST_NOT_FOUND || + error is IOError.HOST_UNREACHABLE || + error is IOError.NETWORK_UNREACHABLE) { + return ProblemType.CONNECTION_ERROR; + } + + if (error is IOError.CONNECTION_CLOSED || + error is IOError.NOT_CONNECTED) { + return ProblemType.NETWORK_ERROR; + } + + return ProblemType.GENERIC_ERROR; + } + +} + +/** + * Describes a error that the engine encountered, for reporting to the client. + */ +public class Geary.ProblemReport : Object { + + + /** Describes the type of being reported. */ + public ProblemType problem_type { get; private set; } + + /** The exception caused the problem, if any. */ + public ErrorContext? error { get; private set; default = null; } + + + public ProblemReport(ProblemType type, Error? error) { + this.problem_type = type; + if (error != null) { + this.error = new ErrorContext(error); + } + } + + /** Returns a string representation of the report, for debugging only. */ + public string to_string() { + return "%s: %s".printf( + this.problem_type.to_string(), + this.error != null + ? this.error.format_full_error() + : "no error reported" + ); + } + +} + +/** + * Describes an account-related error that the engine encountered. + */ +public class Geary.AccountProblemReport : ProblemReport { + + + /** The account related to the problem report. */ + public AccountInformation account { get; private set; } + + + public AccountProblemReport(ProblemType type, AccountInformation account, Error? error) { + base(type, error); + this.account = account; + } + + /** Returns a string representation of the report, for debugging only. */ + public new string to_string() { + return "%s: %s".printf(this.account.id, base.to_string()); + } + +} + +/** + * Describes a service-related error that the engine encountered. + */ +public class Geary.ServiceProblemReport : AccountProblemReport { + + + /** The service related to the problem report. */ + public ServiceInformation service { get; private set; } + + + public ServiceProblemReport(ProblemType type, + AccountInformation account, + ServiceInformation service, + Error? error) { + base(type, account, error); + this.service = service; + } + + /** Returns a string representation of the report, for debugging only. */ + public new string to_string() { + return "%s: %s: %s: %s".printf( + this.account.id, + this.service.protocol.to_string(), + this.problem_type.to_string(), + this.error != null + ? this.error.format_full_error() + : "no error reported" + ); + } + +} diff -Nru geary-0.12.4/src/engine/api/geary-progress-monitor.vala geary-3.32.0/src/engine/api/geary-progress-monitor.vala --- geary-0.12.4/src/engine/api/geary-progress-monitor.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/api/geary-progress-monitor.vala 2019-03-17 13:39:29.000000000 +0000 @@ -21,41 +21,41 @@ public abstract class Geary.ProgressMonitor : BaseObject { public const double MIN = 0.0; public const double MAX = 1.0; - + public double progress { get; protected set; default = MIN; } public bool is_in_progress { get; protected set; default = false; } public Geary.ProgressType progress_type { get; protected set; } - + /** * The start signal is fired just before progress begins. It will not fire again until after * {@link finish} has fired. */ public signal void start(); - + /** * Notifies the user of existing progress. Note that monitor refers to the monitor that * invoked this update, which may not be the same as this object. */ public signal void update(double total_progress, double change, Geary.ProgressMonitor monitor); - + /** * Finish is fired when progress has completed. */ public signal void finish(); - + /** * Users must call this before calling update. * * Must not be called again until {@link ProgressMonitor.notify_finish} has been called. - */ + */ public virtual void notify_start() { assert(!is_in_progress); progress = MIN; is_in_progress = true; - + start(); } - + /** * Users must call this when progress has completed. * @@ -64,7 +64,7 @@ public virtual void notify_finish() { assert(is_in_progress); is_in_progress = false; - + finish(); } } @@ -77,11 +77,11 @@ public class Geary.ReentrantProgressMonitor : Geary.ProgressMonitor { private int start_count = 0; - + public ReentrantProgressMonitor(ProgressType type) { this.progress_type = type; } - + /** * {@inheritDoc} * @@ -95,7 +95,7 @@ if (start_count++ == 0) base.notify_start(); } - + /** * {@inheritDoc} * @@ -106,10 +106,10 @@ */ public override void notify_finish() { bool finished = (--start_count == 0); - + // prevent underflow before signalling start_count = start_count.clamp(0, int.MAX); - + if (finished) base.notify_finish(); } @@ -125,7 +125,7 @@ public SimpleProgressMonitor(ProgressType type) { this.progress_type = type; } - + /** * Updates the progress by the given value. Must be between {@link ProgressMonitor.MIN} and * {@link ProgressMonitor.MAX}. @@ -136,10 +136,10 @@ public void increment(double value) { assert(value > 0); assert(is_in_progress); - + if (progress + value > MAX) value = MAX - progress; - + progress += value; update(progress, value, this); } @@ -152,7 +152,7 @@ private int min_interval; private int max_interval; private int current = 0; - + /** * Creates a new progress monitor with the given interval range. */ @@ -161,7 +161,7 @@ this.min_interval = min; this.max_interval = max; } - + /** * Sets a new interval. Must not be done while in progress. */ @@ -170,26 +170,26 @@ this.min_interval = min; this.max_interval = max; } - + public override void notify_start() { current = 0; base.notify_start(); } - + /** - * Incrememts the progress + * Incrememts the progress */ public void increment(int count = 1) { assert(is_in_progress); assert(count + progress >= min_interval); assert(count + progress <= max_interval); - + current += count; - + double new_progress = (1.0 * current - min_interval) / (1.0 * max_interval - min_interval); double change = new_progress - progress; progress = new_progress; - + update(progress, change, this); } } @@ -200,14 +200,14 @@ */ public class Geary.AggregateProgressMonitor : Geary.ProgressMonitor { private Gee.HashSet monitors = new Gee.HashSet(); - + /** * Creates an aggregate progress monitor. */ public AggregateProgressMonitor() { this.progress_type = Geary.ProgressType.AGGREGATED; } - + /** * Adds a new progress monitor to this aggregate. */ @@ -241,45 +241,45 @@ break; } } - + if (issue_signal) notify_finish(); } } - + private void on_start() { if (!is_in_progress) notify_start(); } - + private void on_update(double total_progress, double change, ProgressMonitor monitor) { assert(is_in_progress); - + double updated_progress = MIN; foreach(Geary.ProgressMonitor pm in monitors) updated_progress += pm.progress; - + updated_progress /= monitors.size; - + double aggregated_change = updated_progress - progress; if (aggregated_change < 0) aggregated_change = 0; - + progress += updated_progress; - + if (progress > MAX) progress = MAX; - + update(progress, aggregated_change, monitor); } - + private void on_finish() { // Only signal completion once all progress monitors are complete. foreach(Geary.ProgressMonitor pm in monitors) { if (pm.is_in_progress) return; } - + notify_finish(); } } diff -Nru geary-0.12.4/src/engine/api/geary-revokable.vala geary-3.32.0/src/engine/api/geary-revokable.vala --- geary-0.12.4/src/engine/api/geary-revokable.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/api/geary-revokable.vala 2019-03-17 13:39:29.000000000 +0000 @@ -16,7 +16,7 @@ public abstract class Geary.Revokable : BaseObject { public const string PROP_VALID = "valid"; public const string PROP_IN_PROCESS = "in-process"; - + /** * Indicates if {@link revoke_async} or {@link commit_async} are valid operations for this * {@link Revokable}. @@ -27,7 +27,7 @@ * @see set_invalid */ public bool valid { get; private set; default = true; } - + /** * Indicates a {@link revoke_async} or {@link commit_async} operation is underway. * @@ -37,16 +37,16 @@ * @see valid */ public bool in_process { get; protected set; default = false; } - + private uint commit_timeout_id = 0; - + /** * Fired when the {@link Revokable} has been revoked. * * {@link valid} will stil be true when this is fired. */ public signal void revoked(); - + /** * Fired when the {@link Revokable} has been committed. * @@ -55,7 +55,7 @@ * {@link valid} will stil be true when this is fired. */ public signal void committed(Geary.Revokable? commit_revokable); - + /** * Create a {@link Revokable} with optional parameters. * @@ -65,11 +65,11 @@ protected Revokable(int commit_timeout_sec = 0) { if (commit_timeout_sec == 0) return; - + // This holds a reference to the Revokable, meaning cancelling the timeout in the dtor is // largely symbolic, but so be it commit_timeout_id = Timeout.add_seconds(commit_timeout_sec, on_timed_commit); - + // various events that cancel the need for a timed commit; this is important to drop the // ref to this object within the event loop revoked.connect(cancel_timed_commit); @@ -79,19 +79,19 @@ cancel_timed_commit(); }); } - + ~Revokable() { cancel_timed_commit(); } - + protected virtual void notify_revoked() { revoked(); } - + protected virtual void notify_committed(Geary.Revokable? commit_revokable) { committed(commit_revokable); } - + /** * Mark the {@link Revokable} as invalid. * @@ -102,7 +102,7 @@ protected void set_invalid() { valid = false; } - + /** * Revoke (undo) the operation. * @@ -115,10 +115,10 @@ public virtual async void revoke_async(Cancellable? cancellable = null) throws Error { if (in_process) throw new EngineError.ALREADY_OPEN("Already revoking or committing operation"); - + if (!valid) throw new EngineError.ALREADY_CLOSED("Revokable not valid"); - + in_process = true; try { yield internal_revoke_async(cancellable); @@ -126,7 +126,7 @@ in_process = false; } } - + /** * The child class's implementation of {@link revoke_async}. * @@ -138,7 +138,7 @@ * if successful. */ protected abstract async void internal_revoke_async(Cancellable? cancellable) throws Error; - + /** * Commits (completes) the operation immediately. * @@ -155,10 +155,10 @@ public virtual async void commit_async(Cancellable? cancellable = null) throws Error { if (in_process) throw new EngineError.ALREADY_OPEN("Already revoking or committing operation"); - + if (!valid) throw new EngineError.ALREADY_CLOSED("Revokable not valid"); - + in_process = true; try { yield internal_commit_async(cancellable); @@ -166,7 +166,7 @@ in_process = false; } } - + /** * The child class's implementation of {@link commit_async}. * @@ -178,20 +178,20 @@ * if successful. */ protected abstract async void internal_commit_async(Cancellable? cancellable) throws Error; - + private bool on_timed_commit() { commit_timeout_id = 0; - + if (valid && !in_process) commit_async.begin(); - + return false; } - + private void cancel_timed_commit() { if (commit_timeout_id == 0) return; - + Source.remove(commit_timeout_id); commit_timeout_id = 0; } diff -Nru geary-0.12.4/src/engine/api/geary-search-folder.vala geary-3.32.0/src/engine/api/geary-search-folder.vala --- geary-0.12.4/src/engine/api/geary-search-folder.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/api/geary-search-folder.vala 2019-03-17 13:39:29.000000000 +0000 @@ -22,37 +22,37 @@ public abstract class Geary.SearchFolder : Geary.AbstractLocalFolder { private weak Account _account; public override Account account { get { return _account; } } - + private FolderProperties _properties; public override FolderProperties properties { get { return _properties; } } - + private FolderPath? _path = null; public override FolderPath path { get { return _path; } } - + public override SpecialFolderType special_folder_type { get { return Geary.SpecialFolderType.SEARCH; } } - + public Geary.SearchQuery? search_query { get; protected set; default = null; } - + /** * Fired when the search query has changed. This signal is fired *after* the search * has completed. */ public signal void search_query_changed(Geary.SearchQuery? query); - + protected SearchFolder(Account account, FolderProperties properties, FolderPath path) { _account = account; _properties = properties; _path = path; } - + protected virtual void notify_search_query_changed(SearchQuery? query) { search_query_changed(query); } - + /** * Sets the keyword string for this search. * @@ -63,14 +63,14 @@ * the {@link search_query} property to change before completion. */ public abstract void search(string query, SearchQuery.Strategy strategy, Cancellable? cancellable = null); - + /** * Clears the search query and results. * * {@link search_query_changed} will be fired and {@link search_query} will be set to null. */ public abstract void clear(); - + /** * Given a list of mail IDs, returns a set of casefolded words that match for the current * search query. diff -Nru geary-0.12.4/src/engine/api/geary-search-query.vala geary-3.32.0/src/engine/api/geary-search-query.vala --- geary-0.12.4/src/engine/api/geary-search-query.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/api/geary-search-query.vala 2019-03-17 13:39:29.000000000 +0000 @@ -49,17 +49,17 @@ */ HORIZON } - + /** * The original user search text. */ public string raw { get; private set; } - + /** * The selected {@link Strategy} quality. */ public Strategy strategy { get; private set; } - + protected SearchQuery(string raw, Strategy strategy) { this.raw = raw; this.strategy = strategy; diff -Nru geary-0.12.4/src/engine/api/geary-service-information.vala geary-3.32.0/src/engine/api/geary-service-information.vala --- geary-0.12.4/src/engine/api/geary-service-information.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/src/engine/api/geary-service-information.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,195 @@ +/* + * Copyright 2017 Software Freedom Conservancy Inc. + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +/** + * The network protocols supported by the engine for email services. + */ +public enum Geary.Protocol { + IMAP, + SMTP; + + + public static Protocol for_value(string value) + throws EngineError { + return ObjectUtils.from_enum_nick( + typeof(Protocol), value.ascii_down() + ); + } + + public string to_value() { + return ObjectUtils.to_enum_nick( + typeof(Protocol), this + ); + } + +} + +namespace Geary.Smtp { + + /** Default clear-text SMTP network port */ + public const uint16 SMTP_PORT = 25; + + /** Default clear-text SMTP submission network port */ + public const uint16 SUBMISSION_PORT = 587; + + /** Default transport-layer-encrypted SMTP submission network port */ + public const uint16 SUBMISSION_TLS_PORT = 465; + +} + + +namespace Geary.Imap { + + /** Default clear-text IMAP network port */ + public const uint16 IMAP_PORT = 143; + + /** Default transport-layer-encrypted IMAP network port */ + public const uint16 IMAP_TLS_PORT = 993; + +} + + +/** The method used to negotiate a TLS session, if any. */ +public enum Geary.TlsNegotiationMethod { + /** No TLS session should be established. */ + NONE, + /** StartTLS should used to establish a session. */ + START_TLS, + /** A TLS session should be established at the transport layer. */ + TRANSPORT; + + + public static TlsNegotiationMethod for_value(string value) + throws EngineError { + return ObjectUtils.from_enum_nick( + typeof(TlsNegotiationMethod), value.ascii_down() + ); + } + + public string to_value() { + return ObjectUtils.to_enum_nick( + typeof(TlsNegotiationMethod), this + ); + } + +} + + +/** + * Encapsulates configuration information for a network service. + */ +public class Geary.ServiceInformation : GLib.Object { + + + /** Specifies the network protocol for this service. */ + public Protocol protocol { get; private set; } + + /** The server's address. */ + public string host { get; set; default = ""; } + + /** The server's port. */ + public uint16 port { get; set; default = 0; } + + /** The transport security method to use */ + public TlsNegotiationMethod transport_security { get; set; } + + /** + * Determines the source of auth credentials for SMTP services. + */ + public Credentials.Requirement credentials_requirement { get; set; } + + /** The credentials used for authenticating. */ + public Credentials? credentials { get; set; default = null; } + + /** + * Whether the password should be remembered. + * + * This only makes sense with providers that support saving the + * password. + */ + public bool remember_password { get; set; default = true; } + + /** + * Constructs a new configuration for a specific service. + */ + public ServiceInformation(Protocol proto, ServiceProvider provider) { + this.protocol = proto; + // Prefer TLS by RFC 8314, but use START_TLS for SMTP for the + // moment while its still more widely deployed. + this.transport_security = (proto == Protocol.SMTP) + ? TlsNegotiationMethod.START_TLS + : TlsNegotiationMethod.TRANSPORT; + this.credentials_requirement = (proto == Protocol.SMTP) + ? Credentials.Requirement.USE_INCOMING + : Credentials.Requirement.CUSTOM; + + provider.set_service_defaults(this); + } + + /** + * Constructs a copy of the given service configuration. + */ + public ServiceInformation.copy(ServiceInformation other) { + // Use OTHER here to get blank defaults + this(other.protocol, ServiceProvider.OTHER); + this.host = other.host; + this.port = other.port; + this.transport_security = other.transport_security; + this.credentials = ( + other.credentials != null ? other.credentials.copy() : null + ); + this.credentials_requirement = other.credentials_requirement; + this.remember_password = other.remember_password; + } + + + /** + * Returns the default port for this service type and settings. + */ + public uint16 get_default_port() { + uint16 port = 0; + + switch (this.protocol) { + case IMAP: + port = (this.transport_security == TlsNegotiationMethod.TRANSPORT) + ? Imap.IMAP_TLS_PORT + : Imap.IMAP_PORT; + break; + + case SMTP: + if (this.transport_security == TlsNegotiationMethod.TRANSPORT) { + port = Smtp.SUBMISSION_TLS_PORT; + } else if (this.credentials_requirement == + Credentials.Requirement.NONE) { + port = Smtp.SMTP_PORT; + } else { + port = Smtp.SUBMISSION_PORT; + } + break; + } + + return port; + } + + /** + * Returns true if another object is equal to this one. + */ + public bool equal_to(Geary.ServiceInformation other) { + return ( + this == other || + (this.host == other.host && + this.port == other.port && + this.transport_security == other.transport_security && + ((this.credentials == null && other.credentials == null) || + (this.credentials != null && other.credentials != null && + this.credentials.equal_to(other.credentials))) && + this.credentials_requirement == other.credentials_requirement && + this.remember_password == other.remember_password) + ); + } + +} diff -Nru geary-0.12.4/src/engine/api/geary-service-provider.vala geary-3.32.0/src/engine/api/geary-service-provider.vala --- geary-0.12.4/src/engine/api/geary-service-provider.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/api/geary-service-provider.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,4 +1,6 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. + * Copyright 2018 Michael Gratton * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. @@ -13,88 +15,47 @@ YAHOO, OUTLOOK, OTHER; - - public static ServiceProvider[] get_providers() { - return { GMAIL, YAHOO, OUTLOOK, OTHER }; + + public static ServiceProvider for_value(string value) + throws EngineError { + return ObjectUtils.from_enum_nick( + typeof(ServiceProvider), value.ascii_down() + ); } - - /** - * Returns the service provider in a serialized form. - * - * @see from_string - */ - public string to_string() { - switch (this) { - case GMAIL: - return "GMAIL"; - - case YAHOO: - return "YAHOO"; - - case OUTLOOK: - return "OUTLOOK"; - - case OTHER: - return "OTHER"; - - default: - assert_not_reached(); - } + + public string to_value() { + return ObjectUtils.to_enum_nick( + typeof(ServiceProvider), this + ); } - - /** - * Returns the service provider's name in a translated UTF-8 string suitable for display to the - * user. - */ - public string display_name() { + + + internal void set_account_defaults(AccountInformation service) { switch (this) { - case GMAIL: - return _("Gmail"); - - case YAHOO: - return _("Yahoo! Mail"); - - case OUTLOOK: - return _("Outlook.com"); - - case OTHER: - return _("Other"); - - default: - assert_not_reached(); + case GMAIL: + ImapEngine.GmailAccount.setup_account(service); + break; + case YAHOO: + ImapEngine.YahooAccount.setup_account(service); + break; + case OUTLOOK: + ImapEngine.OutlookAccount.setup_account(service); + break; } } - /** - * Converts a string form of the service provider (returned by - * {@link to_string} to a {@link ServiceProvider} value. - * - * Throws an error if the string is not valid. - * - * @see to_string - */ - public static ServiceProvider from_string(string str) throws Error { - switch (str.up()) { - case "GMAIL": - return GMAIL; - - case "YAHOO": - return YAHOO; - - case "OUTLOOK": - return OUTLOOK; - - case "OTHER": - return OTHER; - - default: - // Could use a better errordomain here, but for now - // this only gets used when parsing keyfiles in - // AccountInfo. - throw new KeyFileError.INVALID_VALUE( - "Unknown service provider type: %s", str - ); + internal void set_service_defaults(ServiceInformation service) { + switch (this) { + case GMAIL: + ImapEngine.GmailAccount.setup_service(service); + break; + case YAHOO: + ImapEngine.YahooAccount.setup_service(service); + break; + case OUTLOOK: + ImapEngine.OutlookAccount.setup_service(service); + break; } } -} +} diff -Nru geary-0.12.4/src/engine/api/geary-service.vala geary-3.32.0/src/engine/api/geary-service.vala --- geary-0.12.4/src/engine/api/geary-service.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/api/geary-service.vala 1970-01-01 00:00:00.000000000 +0000 @@ -1,62 +0,0 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. - * - * This software is licensed under the GNU Lesser General Public License - * (version 2.1 or later). See the COPYING file in this distribution. - */ - -/** - * The type of mail service provided by a particular destination. - */ -public enum Geary.Service { - IMAP, - SMTP; - - /** - * Returns a user-visible label for the {@link Service}. - */ - public string user_label() { - switch (this) { - case IMAP: - return _("IMAP"); - - case SMTP: - return _("SMTP"); - - default: - assert_not_reached(); - } - } - - /** - * Returns a short version of the enum key. - */ - public string name() { - switch (this) { - case IMAP: - return "IMAP"; - - case SMTP: - return "SMTP"; - - default: - assert_not_reached(); - } - } -} - -/** - * A bitfield of {@link Service}s. - */ -[Flags] -public enum Geary.ServiceFlag { - IMAP, - SMTP; - - public bool has_imap() { - return (this & IMAP) == IMAP; - } - - public bool has_smtp() { - return (this & SMTP) == SMTP; - } -} diff -Nru geary-0.12.4/src/engine/api/geary-special-folder-type.vala geary-3.32.0/src/engine/api/geary-special-folder-type.vala --- geary-0.12.4/src/engine/api/geary-special-folder-type.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/api/geary-special-folder-type.vala 2019-03-17 13:39:29.000000000 +0000 @@ -17,48 +17,48 @@ TRASH, OUTBOX, ARCHIVE; - + public unowned string get_display_name() { switch (this) { case INBOX: return _("Inbox"); - + case DRAFTS: return _("Drafts"); - + case SENT: return _("Sent Mail"); - + case FLAGGED: return _("Starred"); - + case IMPORTANT: return _("Important"); - + case ALL_MAIL: return _("All Mail"); - + case SPAM: return _("Spam"); - + case TRASH: return _("Trash"); - + case OUTBOX: return _("Outbox"); - + case SEARCH: return _("Search"); - + case ARCHIVE: return _("Archive"); - + case NONE: default: return _("None"); } } - + public bool is_outgoing() { return this == SENT || this == OUTBOX; } diff -Nru geary-0.12.4/src/engine/app/app-conversation-monitor.vala geary-3.32.0/src/engine/app/app-conversation-monitor.vala --- geary-0.12.4/src/engine/app/app-conversation-monitor.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/app/app-conversation-monitor.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,554 +1,748 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. + * Copyright 2018 Michael Gratton * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ +/** + * Loads conversation in a folder and monitors changes to them. + * + * The standard IMAP model does not provide a means of easily + * aggregating messages in a single conversation across mailboxes, + * hence this class provides applications with a means of loading + * complete conversations, one for each email message found in a + * specified base folder. This is an expensive operation since it may + * require opening several other folders to find all messages in the + * conversation, and so the monitor is lazy and will only load enough + * conversations to fill a minimal window size. Additional + * conversations can be loaded afterwards as needed. + * + * When monitoring starts via a call to {@link + * start_monitoring_async}, the folder will perform an initial + * //scan// of messages in the base folder and load conversation load + * based on that. Increasing {@link min_window_count} will cause + * additional scan operations to be executed as needed to fill the new + * window size. + * + * If the folder is backed by a remote mailbox, scans will be + * local-only if the remote is not open so as to not block. However + * this means any messages (and their conversations) that are not + * sufficiently complete to satisfy both the monitor's and the owner's + * email field requirements will not be found. If or when the folder + * does open a remote connection, the folder will be re-scanned to + * ensure any missing messages are picked up. + * + * The monitor will also keep track of messages being appended or + * removed account-wide, so that known conversations can be updated as + * needed. + */ public class Geary.App.ConversationMonitor : BaseObject { + /** - * These are the fields Conversations require to thread emails together. These fields will - * be retrieved regardless of the Field parameter passed to the constructor. + * The fields Conversations require to thread emails together. + * + * These fields will be retrieved regardless of the Field + * parameter passed to the constructor. */ public const Geary.Email.Field REQUIRED_FIELDS = Geary.Email.Field.REFERENCES | Geary.Email.Field.FLAGS | Geary.Email.Field.DATE; - - // # of messages to load at a time as we attempt to fill the min window. - private const int WINDOW_FILL_MESSAGE_COUNT = 5; - - private class ProcessJobContext : BaseObject { - public Gee.HashMap emails - = new Gee.HashMap(); - - public bool inside_scan; - - public ProcessJobContext(bool inside_scan) { - this.inside_scan = inside_scan; + + + private struct ProcessJobContext { + + public Gee.Map emails; + + + public ProcessJobContext() { + this.emails = new Gee.HashMap(); } + } - - public Geary.Folder folder { get; private set; } + + + /** + * A read-only view of loaded conversations. + * + * Note that since background tasks may asynchronously update the + * set at ant time, any asynchronous tasks carried out while + * holding an returned by this method may allow the iterator to + * become invalid. + */ + public Gee.Set read_only_view { + owned get { return this.conversations.read_only_view; } + } + + /** + * Number of conversations currently loaded by the monitor. + */ + public int size { get { return this.conversations.size; } } + + /** Folder from which the conversation is originating. */ + public Folder base_folder { get; private set; } + + /** Determines if this monitor is monitoring the base folder. */ public bool is_monitoring { get; private set; default = false; } - public int min_window_count { get { return _min_window_count; } + + /** Minimum number of emails large conversations should contain. */ + public int min_window_count { + get { return _min_window_count; } set { _min_window_count = value; - operation_queue.add(new FillWindowOperation(this, false)); + check_window_count(); } } - - public Geary.ProgressMonitor progress_monitor { get { return operation_queue.progress_monitor; } } - - private ConversationSet conversations = new ConversationSet(); - private Geary.Email.Field required_fields; - private Geary.Folder.OpenFlags open_flags; - private Cancellable? cancellable_monitor = null; - private bool reseed_notified = false; private int _min_window_count = 0; - private ConversationOperationQueue operation_queue = new ConversationOperationQueue(); - - /** - * "monitoring-started" is fired when the Conversations folder has been opened for monitoring. - */ - public virtual signal void monitoring_started() { - Logging.debug(Logging.Flag.CONVERSATIONS, "[%s] ConversationMonitor::monitoring_started", - folder.to_string()); + + /** Indicates progress conversations are being loaded. */ + public ProgressMonitor progress_monitor { + get; private set; default = new SimpleProgressMonitor(ProgressType.ACTIVITY); } - - /** - * "monitoring-stopped" is fired when the Geary.Folder object has closed (either due to error - * or user) and the Conversations object is therefore unable to continue monitoring. - */ - public virtual signal void monitoring_stopped() { - Logging.debug(Logging.Flag.CONVERSATIONS, "[%s] ConversationMonitor::monitoring_stopped", - folder.to_string()); + + /** The set of all conversations loaded by the monitor. */ + internal ConversationSet conversations { get; private set; } + + /** The oldest message from the base folder in the loaded window. */ + internal EmailIdentifier? window_lowest { + owned get { + return (this.window.is_empty) ? null : this.window.first(); + } } - + + private Geary.Email.Field required_fields; + private Geary.Folder.OpenFlags open_flags; + private ConversationOperationQueue queue = null; + private Cancellable? operation_cancellable = null; + + // Set of known, in-folder emails, explicitly loaded for the + // monitor's window. This exists purely to support the + // window_lowest property above, but we need to maintain a sorted + // set of all known messages since if the last known email is + // removed, we won't know what the next lowest is. Only email + // listed by one of the load_by_*_id methods are added here. Other + // in-folder messages pulled in for a conversation aren't added, + // since they may not be within the load window. + private Gee.SortedSet window = + new Gee.TreeSet((a,b) => { + return a.natural_sort_comparator(b); + }); + + /** - * "scan-started" is fired whenever beginning to load messages into the Conversations object. + * Fired when a message load has started. * - * Note that more than one load can be initiated, due to Conversations being completely - * asynchronous. "scan-started", "scan-error", and "scan-completed" will be fired (as - * appropriate) for each individual load request; that is, there is no internal counter to ensure - * only a single "scan-completed" is fired to indicate multiple loads have finished. + * Note that more than one load can be initiated, due to + * Conversations being completely asynchronous. Both this, and + * {@link scan_completed} will be fired for each individual load + * request; that is, there is no internal counter to ensure only a + * single completed signal is fired to indicate multiple loads + * have finished. */ public virtual signal void scan_started() { - Logging.debug(Logging.Flag.CONVERSATIONS, "[%s] ConversationMonitor::scan_started", - folder.to_string()); - } - - /** - * "scan-error" is fired when an Error is encounted while loading messages. It will be followed - * by a "scan-completed" signal. - */ - public virtual signal void scan_error(Error err) { - Logging.debug(Logging.Flag.CONVERSATIONS, "[%s] ConversationMonitor::scan_error %s", - folder.to_string(), err.message); + Logging.debug(Logging.Flag.CONVERSATIONS, + "[%s] ConversationMonitor::scan_started", + this.base_folder.to_string()); } - + /** - * "scan-completed" is fired when the scan of the email has finished. + * Fired when all extant message loads have completed. + * + * @see scan_started */ public virtual signal void scan_completed() { - Logging.debug(Logging.Flag.CONVERSATIONS, "[%s] ConversationMonitor::scan_completed", - folder.to_string()); + Logging.debug(Logging.Flag.CONVERSATIONS, + "[%s] ConversationMonitor::scan_completed", + this.base_folder.to_string()); } - + /** - * "seed-completed" is fired when the folder has opened and email has been populated. + * Fired when an error was encountered while loading messages. */ - public virtual signal void seed_completed() { - Logging.debug(Logging.Flag.CONVERSATIONS, "[%s] ConversationMonitor::seed_completed", - folder.to_string()); + public virtual signal void scan_error(Error err) { + Logging.debug(Logging.Flag.CONVERSATIONS, + "[%s] ConversationMonitor::scan_error %s", + this.base_folder.to_string(), err.message); } - + /** - * "conversations-added" indicates that one or more new Conversations have been detected while - * processing email, either due to a user-initiated load request or due to monitoring. + * Fired when one or more new conversations have been detected. + * + * This may be due to either a user-initiated load request or due + * to background monitoring. */ public virtual signal void conversations_added(Gee.Collection conversations) { - Logging.debug(Logging.Flag.CONVERSATIONS, "[%s] ConversationMonitor::conversations_added %d", - folder.to_string(), conversations.size); + Logging.debug(Logging.Flag.CONVERSATIONS, + "[%s] ConversationMonitor::conversations_added %d", + this.base_folder.to_string(), conversations.size); } - + /** - * "conversations-removed" is fired when all the email in a Conversation has been removed. - * It's possible this will be called without a signal alerting that it's emails have been - * removed, i.e. a "conversations-removed" signal may fire with no accompanying + * Fired when all email in a conversation has been removed. + * + * It's possible this will be called without a signal alerting + * that it's emails have been removed, i.e. a + * "conversations-removed" signal may fire with no accompanying * "conversation-trimmed". * - * Note that this can only occur when monitoring is enabled. There is (currently) no - * user call to manually remove email from Conversations. + * This may be due to either a user-initiated load request or due + * to background monitoring. */ public virtual signal void conversations_removed(Gee.Collection conversations) { - Logging.debug(Logging.Flag.CONVERSATIONS, "[%s] ConversationMonitor::conversations_removed %d", - folder.to_string(), conversations.size); + Logging.debug(Logging.Flag.CONVERSATIONS, + "[%s] ConversationMonitor::conversations_removed %d", + this.base_folder.to_string(), conversations.size); } - + /** - * "conversation-appended" is fired when one or more Email objects have been added to the - * specified Conversation. This can happen due to a user-initiated load or while monitoring - * the Folder. + * Fired when one or more email have been added to a conversation. + * + * This may be due to either a user-initiated load request or due + * to background monitoring. */ public virtual signal void conversation_appended(Conversation conversation, - Gee.Collection email) { - Logging.debug(Logging.Flag.CONVERSATIONS, "[%s] ConversationMonitor::conversation_appended", - folder.to_string()); - } - - /** - * "conversation-trimmed" is fired when one or more Emails have been removed from the Folder, - * and therefore from the specified Conversation. If the trimmed Email is the last usable - * Email in the Conversation, this signal will be followed by "conversation-removed". However, - * it's possible for "conversation-removed" to fire without "conversation-trimmed" preceding - * it, in the case of all emails being removed from a Conversation at once. + Gee.Collection email) { + Logging.debug(Logging.Flag.CONVERSATIONS, + "[%s] ConversationMonitor::conversation_appended", + this.base_folder.to_string()); + } + + /** + * Fired when one or more email have been removed from a conversation. + * + * If the trimmed email is the last usable email in the + * Conversation, this signal will be followed by + * "conversation-removed". However, it's possible for + * "conversation-removed" to fire without "conversation-trimmed" + * preceding it, in the case of all emails being removed from a + * conversation at once. * - * There is (currently) no user-specified call to manually remove Email from Conversations. - * This is only called when monitoring is enabled. + * This may be due to either a user-initiated load request or due + * to background monitoring. */ public virtual signal void conversation_trimmed(Conversation conversation, - Gee.Collection email) { - Logging.debug(Logging.Flag.CONVERSATIONS, "[%s] ConversationMonitor::conversation_trimmed", - folder.to_string()); + Gee.Collection email) { + Logging.debug(Logging.Flag.CONVERSATIONS, + "[%s] ConversationMonitor::conversation_trimmed", + this.base_folder.to_string()); } - + /** - * "email-flags-changed" is fired when the flags of an email in a conversation have changed, - * as reported by the monitored folder. The local copy of the Email is updated and this + * Fired when a conversation's email's flags have changed. + * + * The local copy of the email is first updated and then this * signal is fired. * - * Note that if the flags of an email not captured by the Conversations object change, no signal - * is fired. To know of all changes to all flags, subscribe to the Geary.Folder's + * Note that if the flags of an email not captured by the + * Conversations object change, no signal is fired. To know of + * all changes to all flags, subscribe to the base folder's * "email-flags-changed" signal. */ - public virtual signal void email_flags_changed(Conversation conversation, Geary.Email email) { - Logging.debug(Logging.Flag.CONVERSATIONS, "[%s] ConversationMonitor::email_flag_changed", - folder.to_string()); + public virtual signal void email_flags_changed(Conversation conversation, + Email email) { + Logging.debug(Logging.Flag.CONVERSATIONS, + "[%s] ConversationMonitor::email_flag_changed", + this.base_folder.to_string()); } - + /** * Creates a conversation monitor for the given folder. * - * @param folder Folder to monitor + * @param base_folder a Folder to monitor for conversations * @param open_flags See {@link Geary.Folder} * @param required_fields See {@link Geary.Folder} * @param min_window_count Minimum number of conversations that will be loaded */ - public ConversationMonitor(Geary.Folder folder, Geary.Folder.OpenFlags open_flags, - Geary.Email.Field required_fields, int min_window_count) { - this.folder = folder; + public ConversationMonitor(Folder base_folder, + Folder.OpenFlags open_flags, + Email.Field required_fields, + int min_window_count) { + this.base_folder = base_folder; this.open_flags = open_flags; this.required_fields = required_fields | REQUIRED_FIELDS; - _min_window_count = min_window_count; - } - - ~ConversationMonitor() { - if (is_monitoring) - debug("Warning: Conversations object destroyed without stopping monitoring"); - - // Manually detach all the weak refs in the Conversation objects - conversations.clear_owners(); - } - - protected virtual void notify_monitoring_started() { - monitoring_started(); - } - - protected virtual void notify_monitoring_stopped() { - monitoring_stopped(); - } - - protected virtual void notify_scan_started() { - scan_started(); - } - - protected virtual void notify_scan_error(Error err) { - scan_error(err); + this._min_window_count = min_window_count; + this.conversations = new ConversationSet(base_folder); } - - protected virtual void notify_scan_completed() { - scan_completed(); - } - - protected virtual void notify_seed_completed() { - seed_completed(); - } - - protected virtual void notify_conversations_added(Gee.Collection conversations) { - conversations_added(conversations); - } - - protected virtual void notify_conversations_removed(Gee.Collection conversations) { - conversations_removed(conversations); - } - - protected virtual void notify_conversation_appended(Conversation conversation, - Gee.Collection emails) { - conversation_appended(conversation, emails); - } - - protected virtual void notify_conversation_trimmed(Conversation conversation, - Gee.Collection emails) { - conversation_trimmed(conversation, emails); - } - - protected virtual void notify_email_flags_changed(Conversation conversation, Geary.Email email) { - email_flags_changed(conversation, email); - } - - public int get_conversation_count() { - return conversations.size; - } - - public Gee.Collection get_conversations() { - return conversations.conversations; - } - - public Geary.App.Conversation? get_conversation_for_email(Geary.EmailIdentifier email_id) { - return conversations.get_by_email_identifier(email_id); - } - + + /** + * Opens the base folder scans and starts monitoring conversations. + * + * This method will open the base folder, start a scan to load + * conversations from it, and starts monitoring the folder and + * account for messages being added or removed. + * + * The //cancellable// parameter will be used when opening the + * folder, but not subsequently when scanning for new messages. To + * cancel any such operations, simply close the monitor via {@link + * stop_monitoring_async}. + */ public async bool start_monitoring_async(Cancellable? cancellable = null) throws Error { - if (is_monitoring) + if (this.is_monitoring) return false; - - // set before yield to guard against reentrancy - is_monitoring = true; - - cancellable_monitor = cancellable; - - // Double check that the last run of the queue got stopped and that - // it's empty. - if (operation_queue.is_processing) - yield operation_queue.stop_processing_async(cancellable_monitor); - operation_queue.clear(); - - bool reseed_now = (folder.get_open_state() != Geary.Folder.OpenState.CLOSED); - - // Add the necessary initial operations ahead of anything the folder - // might add as it opens. - operation_queue.add(new LocalLoadOperation(this)); - // if already opened, go ahead and do a full load now from remote and local; otherwise, - // the reseed has to wait until the folder's remote is opened (handled in on_folder_opened) - if (reseed_now) - operation_queue.add(new ReseedOperation(this, "already opened")); - operation_queue.add(new FillWindowOperation(this, false)); - - folder.email_appended.connect(on_folder_email_appended); - folder.email_inserted.connect(on_folder_email_inserted); - folder.email_removed.connect(on_folder_email_removed); - folder.opened.connect(on_folder_opened); - folder.account.email_flags_changed.connect(on_account_email_flags_changed); - folder.account.email_locally_complete.connect(on_account_email_locally_complete); - // TODO: handle removed email - + + // Set early yield to guard against reentrancy + this.is_monitoring = true; + this.operation_cancellable = new Cancellable(); + + this.base_folder.email_appended.connect(on_folder_email_appended); + this.base_folder.email_inserted.connect(on_folder_email_inserted); + this.base_folder.email_locally_complete.connect(on_folder_email_complete); + this.base_folder.email_removed.connect(on_folder_email_removed); + this.base_folder.opened.connect(on_folder_opened); + this.base_folder.account.email_appended.connect(on_account_email_appended); + this.base_folder.account.email_inserted.connect(on_account_email_inserted); + this.base_folder.account.email_locally_complete.connect(on_account_email_complete); + this.base_folder.account.email_removed.connect(on_account_email_removed); + this.base_folder.account.email_flags_changed.connect(on_account_email_flags_changed); + + this.queue = new ConversationOperationQueue(this.progress_monitor); + this.queue.operation_error.connect(on_operation_error); + this.queue.add(new FillWindowOperation(this)); + try { - yield folder.open_async(open_flags, cancellable); + yield this.base_folder.open_async(open_flags, cancellable); } catch (Error err) { - is_monitoring = false; - - folder.email_appended.disconnect(on_folder_email_appended); - folder.email_inserted.disconnect(on_folder_email_inserted); - folder.email_removed.disconnect(on_folder_email_removed); - folder.opened.disconnect(on_folder_opened); - folder.account.email_flags_changed.disconnect(on_account_email_flags_changed); - folder.account.email_locally_complete.disconnect(on_account_email_locally_complete); - + try { + yield stop_monitoring_internal(false, null); + } catch (Error stop_error) { + warning( + "Error cleaning up after folder open error: %s", err.message + ); + } throw err; } - - notify_monitoring_started(); - reseed_notified = false; - - // Process operations in the background. - operation_queue.run_process_async.begin(); - + + // Now the folder is open, start the queue running + this.queue.run_process_async.begin(); + return true; } - - internal async void local_load_async() { - debug("ConversationMonitor seeding with local email for %s", folder.to_string()); - try { - yield load_by_id_async(null, min_window_count, Folder.ListFlags.LOCAL_ONLY, cancellable_monitor); - } catch (Error e) { - debug("Error loading local messages: %s", e.message); - } - debug("ConversationMonitor seeded for %s", folder.to_string()); - } - + /** - * Halt monitoring of the Folder and, if specified, close it. Note that the Cancellable - * supplied to start_monitoring_async() is used during monitoring but *not* for this method. - * If null is supplied as the Cancellable, no cancellable is used; pass the original Cancellable - * here to use that. + * Stops monitoring for new messages and closes the base folder. * - * Returns a result code that is semantically identical to the result of - * {@link Geary.Folder.close_async}. + * Returns a result code that is semantically identical to the + * result of {@link Geary.Folder.close_async}. + * + * The //cancellable// parameter will be used when waiting for + * internal monitor operations to complete, but will not prevent + * attempts to close the base folder. */ public async bool stop_monitoring_async(Cancellable? cancellable) throws Error { if (!is_monitoring) return false; - - yield operation_queue.stop_processing_async(cancellable); - - // set now to prevent reentrancy during yield or signal - is_monitoring = false; - - folder.email_appended.disconnect(on_folder_email_appended); - folder.email_inserted.disconnect(on_folder_email_inserted); - folder.email_removed.disconnect(on_folder_email_removed); - folder.opened.disconnect(on_folder_opened); - folder.account.email_flags_changed.disconnect(on_account_email_flags_changed); - folder.account.email_locally_complete.disconnect(on_account_email_locally_complete); - - bool closing = false; - Error? close_err = null; - try { - closing = yield folder.close_async(cancellable); - } catch (Error err) { - // throw, but only after cleaning up (which is to say, if close_async() fails, - // then the Folder is still treated as closed, which is the best that can be - // expected; it definitely shouldn't still be considered open). - debug("Unable to close monitored folder %s: %s", folder.to_string(), err.message); - - close_err = err; + + return yield stop_monitoring_internal(true, cancellable); + } + + /** + * Returns the conversation containing the given email, if any. + */ + public Conversation? get_by_email_identifier(Geary.EmailIdentifier email_id) { + return this.conversations.get_by_email_identifier(email_id); + } + + /** Ensures enough conversations are present, otherwise loads more. */ + internal void check_window_count() { + if (this.is_monitoring && this.conversations.size < this.min_window_count) { + this.queue.add(new FillWindowOperation(this)); } - - notify_monitoring_stopped(); - - if (close_err != null) - throw close_err; - - return closing; } - + + /** + * Returns the list of folders that disqualify emails from conversations. + */ + internal Gee.Collection get_search_folder_blacklist() { + Geary.SpecialFolderType[] blacklisted_folder_types = { + Geary.SpecialFolderType.SPAM, + Geary.SpecialFolderType.TRASH, + Geary.SpecialFolderType.DRAFTS, + }; + + Gee.ArrayList blacklist = new Gee.ArrayList(); + foreach (Geary.SpecialFolderType type in blacklisted_folder_types) { + try { + Geary.Folder? blacklist_folder = this.base_folder.account.get_special_folder(type); + if (blacklist_folder != null) + blacklist.add(blacklist_folder.path); + } catch (Error e) { + debug("Error finding special folder %s on account %s: %s", + type.to_string(), this.base_folder.account.to_string(), e.message); + } + } + + // Add "no folders" so we omit results that have been deleted permanently from the server. + blacklist.add(null); + + return blacklist; + } + /** - * See Geary.Folder.list_email_by_id_async() for details of how these parameters operate. Instead - * of returning emails, this method will load the Conversations object with them sorted into - * Conversation objects. + * Returns the list of flags that disqualify emails from conversations. */ - private async void load_by_id_async(Geary.EmailIdentifier? initial_id, int count, - Geary.Folder.ListFlags flags, Cancellable? cancellable) throws Error { + internal Geary.EmailFlags get_search_flag_blacklist() { + Geary.EmailFlags flags = new Geary.EmailFlags(); + flags.add(Geary.EmailFlags.DRAFT); + return flags; + } + + /** Loads messages from the base folder into the window. */ + internal async int load_by_id_async(EmailIdentifier? initial_id, + int count, + Folder.ListFlags flags = Folder.ListFlags.NONE) + throws Error { notify_scan_started(); + + if (this.base_folder.get_open_state() != Folder.OpenState.REMOTE) { + flags |= Folder.ListFlags.LOCAL_ONLY; + } + + int load_count = 0; + GLib.Error? scan_error = null; try { - yield process_email_async(yield folder.list_email_by_id_async(initial_id, - count, required_fields, flags, cancellable), new ProcessJobContext(true)); - } catch (Error err) { - list_error(err); - throw err; + Gee.Collection? messages = + yield this.base_folder.list_email_by_id_async( + initial_id, count, required_fields, flags, + this.operation_cancellable + ); + + if (messages != null && !messages.is_empty) { + load_count = messages.size; + + foreach (Email email in messages) { + this.window.add(email.id); + } + + yield process_email_async(messages, ProcessJobContext()); + } + } catch (GLib.Error err) { + scan_error = err; } + + notify_scan_completed(); + + if (scan_error != null) { + throw scan_error; + } + + return load_count; } - - private async void load_by_sparse_id(Gee.Collection ids, - Geary.Folder.ListFlags flags, Cancellable? cancellable) { + + /** Loads messages from the base folder into the window. */ + internal async void load_by_sparse_id(Gee.Collection ids, + Folder.ListFlags flags = Folder.ListFlags.NONE) + throws Error { notify_scan_started(); - + + if (this.base_folder.get_open_state() != Folder.OpenState.REMOTE) { + flags |= Folder.ListFlags.LOCAL_ONLY; + } + + GLib.Error? scan_error = null; try { - yield process_email_async(yield folder.list_email_by_sparse_id_async(ids, - required_fields, flags, cancellable), new ProcessJobContext(true)); - } catch (Error err) { - list_error(err); + Gee.Collection? messages = + yield this.base_folder.list_email_by_sparse_id_async( + ids, required_fields, flags, this.operation_cancellable + ); + + if (messages != null && !messages.is_empty) { + foreach (Email email in messages) { + this.window.add(email.id); + } + + yield process_email_async(messages, ProcessJobContext()); + } + } catch (GLib.Error err) { + scan_error = err; + } + + notify_scan_completed(); + + if (scan_error != null) { + throw scan_error; } } - - private async void external_load_by_sparse_id(Geary.Folder folder, - Gee.Collection ids, Geary.Folder.ListFlags flags, Cancellable? cancellable) { + + /** + * Loads messages from outside the monitor's base folder. + * + * Since this requires opening and closing the other folder, it is + * handled separately. + */ + internal async void external_load_by_sparse_id(Folder folder, + Gee.Collection ids, + Folder.ListFlags flags) + throws Error { bool opened = false; + + Gee.List? emails = null; try { - yield folder.open_async(Geary.Folder.OpenFlags.NONE, cancellable); + yield folder.open_async( + Geary.Folder.OpenFlags.NONE, this.operation_cancellable + ); opened = true; - - debug("Listing %d external emails", ids.size); - + + if (folder.get_open_state() != Folder.OpenState.REMOTE) { + flags |= Folder.ListFlags.LOCAL_ONLY; + } + // First just get the bare minimum we need to determine if we even // care about the messages. - Gee.List? emails = yield folder.list_email_by_sparse_id_async(ids, - Geary.Email.Field.REFERENCES, flags, cancellable); - - debug("List found %d emails", (emails == null ? 0 : emails.size)); - - Gee.HashSet relevant_ids = new Gee.HashSet(); - foreach (Geary.Email email in emails) { - Gee.Set? ancestors = email.get_ancestors(); - if (ancestors != null && - Geary.traverse(ancestors).any(id => conversations.has_message_id(id))) - relevant_ids.add(email.id); - } - - debug("%d external emails are relevant to current conversations", relevant_ids.size); - - // List the relevant messages again with the full set of fields, to - // make sure when we load them from the database we have all the - // data we need. - yield folder.list_email_by_sparse_id_async(relevant_ids, required_fields, flags, cancellable); - yield folder.close_async(cancellable); - opened = false; - - Gee.ArrayList search_emails = new Gee.ArrayList(); - foreach (Geary.EmailIdentifier id in relevant_ids) { - // TODO: parallelize this. - try { - Geary.Email email = yield folder.account.local_fetch_email_async(id, - required_fields, cancellable); - search_emails.add(email); - } catch (Error e) { - debug("Error fetching out of folder message: %s", e.message); + emails = yield folder.list_email_by_sparse_id_async( + ids, Geary.Email.Field.REFERENCES, flags, this.operation_cancellable + ); + if (emails != null) { + Gee.HashSet relevant_ids = + new Gee.HashSet(); + foreach (Geary.Email email in emails) { + Gee.Set? ancestors = email.get_ancestors(); + if (ancestors != null && + Geary.traverse(ancestors).any(id => conversations.has_message_id(id))) + relevant_ids.add(email.id); + } + + // List the relevant messages again with the full set of fields, to + // make sure when we load them from the database we have all the + // data we need. + if (!relevant_ids.is_empty) { + emails = yield folder.list_email_by_sparse_id_async( + relevant_ids, required_fields, flags, this.operation_cancellable + ); + } else { + emails = null; } } - - debug("Fetched %d relevant emails locally", search_emails.size); - - yield process_email_async(search_emails, new ProcessJobContext(false)); - } catch (Error e) { - debug("Error loading external emails: %s", e.message); + + yield folder.close_async(null); + opened = false; + } catch (Error err) { if (opened) { + // Always try to close the opened folder try { - yield folder.close_async(cancellable); - } catch (Error e) { - debug("Error closing folder %s: %s", folder.to_string(), e.message); + yield folder.close_async(null); + } catch (Error close_err) { + warning("Error closing folder %s: %s", + folder.to_string(), close_err.message); } } + throw err; + } + + if (emails != null && !emails.is_empty) { + Logging.debug( + Logging.Flag.CONVERSATIONS, + "Fetched %d relevant emails locally", emails.size + ); + yield process_email_async(emails, ProcessJobContext()); } } - - private void list_error(Error err) { - debug("Error while assembling conversations in %s: %s", folder.to_string(), err.message); - notify_scan_error(err); - notify_scan_completed(); + + /** Notifies of removed conversations and removes emails from the window. */ + internal void removed(Gee.Collection removed, + Gee.MultiMap trimmed, + Gee.Collection? base_folder_removed) { + foreach (Conversation conversation in trimmed.get_keys()) { + notify_conversation_trimmed(conversation, trimmed.get(conversation)); + } + + if (removed.size > 0) { + notify_conversations_removed(removed); + } + + if (base_folder_removed != null) { + this.window.remove_all(base_folder_removed); + } + } + + protected virtual void notify_scan_started() { + scan_started(); + } + + protected virtual void notify_scan_error(Error err) { + scan_error(err); + } + + protected virtual void notify_scan_completed() { + scan_completed(); + } + + protected virtual void notify_conversations_added(Gee.Collection conversations) { + conversations_added(conversations); + } + + protected virtual void notify_conversations_removed(Gee.Collection conversations) { + conversations_removed(conversations); + } + + protected virtual void notify_conversation_appended(Conversation conversation, + Gee.Collection emails) { + conversation_appended(conversation, emails); + } + + protected virtual void notify_conversation_trimmed(Conversation conversation, + Gee.Collection emails) { + conversation_trimmed(conversation, emails); + } + + protected virtual void notify_email_flags_changed(Conversation conversation, Geary.Email email) { + conversation.email_flags_changed(email); + email_flags_changed(conversation, email); + } + + private async bool stop_monitoring_internal(bool close_folder, + Cancellable? cancellable) + throws Error { + // set now to prevent reentrancy during yield or signal + is_monitoring = false; + + this.base_folder.email_appended.disconnect(on_folder_email_appended); + this.base_folder.email_inserted.disconnect(on_folder_email_inserted); + this.base_folder.email_locally_complete.disconnect(on_folder_email_complete); + this.base_folder.email_removed.disconnect(on_folder_email_removed); + this.base_folder.opened.disconnect(on_folder_opened); + this.base_folder.account.email_appended.disconnect(on_account_email_appended); + this.base_folder.account.email_inserted.disconnect(on_account_email_inserted); + this.base_folder.account.email_locally_complete.disconnect(on_account_email_complete); + this.base_folder.account.email_removed.disconnect(on_account_email_removed); + this.base_folder.account.email_flags_changed.disconnect(on_account_email_flags_changed); + + // Cancel outstanding ops so they don't block the queue closing + this.operation_cancellable.cancel(); + + // Keep track of errors stopping the queue but continue, since + // the cleanup below needs to occur. + Error? close_err = null; + try { + yield this.queue.stop_processing_async(cancellable); + } catch (Error err) { + close_err = err; + } + this.queue = null; + + this.operation_cancellable = null; + + bool closing = false; + if (close_folder) { + try { + // Always close the folder to prevent open leaks + closing = yield this.base_folder.close_async(null); + } catch (Error err) { + warning("Unable to close monitored folder %s: %s", + this.base_folder.to_string(), err.message); + close_err = err; + } + } + + if (close_err != null) + throw close_err; + + return closing; } - - private async void process_email_async(Gee.Collection? emails, ProcessJobContext job) { + + private async void process_email_async(Gee.Collection? emails, + ProcessJobContext job) + throws Error { if (emails == null || emails.size == 0) { yield process_email_complete_async(job); return; } - - Logging.debug(Logging.Flag.CONVERSATIONS, "[%s] ConversationMonitor::process_email: %d emails", - folder.to_string(), emails.size); - + + Logging.debug(Logging.Flag.CONVERSATIONS, + "[%s] ConversationMonitor::process_email: %d emails", + this.base_folder.to_string(), emails.size); + Gee.HashSet new_message_ids = new Gee.HashSet(); foreach (Geary.Email email in emails) { if (!job.emails.has_key(email.id)) { job.emails.set(email.id, email); - + + // Expand conversations whose messages have ancestors, and aren't marked + // for deletion. + Geary.EmailFlags? flags = email.email_flags; + bool marked_for_deletion = (flags != null) ? flags.is_deleted() : false; + Gee.Set? ancestors = email.get_ancestors(); - if (ancestors != null) { + if (ancestors != null && !marked_for_deletion) { Geary.traverse(ancestors) .filter(id => !new_message_ids.contains(id)) .add_all_to(new_message_ids); } } } - + // Expand the conversation to include any Message-IDs we know we need // and may have on disk, but aren't in the folder. yield expand_conversations_async(new_message_ids, job); - - Logging.debug(Logging.Flag.CONVERSATIONS, "[%s] ConversationMonitor::process_email completed: %d emails", - folder.to_string(), emails.size); + + Logging.debug(Logging.Flag.CONVERSATIONS, + "[%s] ConversationMonitor::process_email completed: %d emails", + this.base_folder.to_string(), emails.size); } - - private Gee.Collection get_search_blacklist() { - Geary.SpecialFolderType[] blacklisted_folder_types = { - Geary.SpecialFolderType.SPAM, - Geary.SpecialFolderType.TRASH, - Geary.SpecialFolderType.DRAFTS, - }; - - Gee.ArrayList blacklist = new Gee.ArrayList(); - foreach (Geary.SpecialFolderType type in blacklisted_folder_types) { - try { - Geary.Folder? blacklist_folder = folder.account.get_special_folder(type); - if (blacklist_folder != null) - blacklist.add(blacklist_folder.path); - } catch (Error e) { - debug("Error finding special folder %s on account %s: %s", - type.to_string(), folder.account.to_string(), e.message); + + private async void process_email_complete_async(ProcessJobContext job) { + Gee.Collection? added = null; + Gee.MultiMap? appended = null; + Gee.Collection? removed_due_to_merge = null; + try { + // Get known paths for all emails + Gee.MultiMap? email_paths = + yield this.base_folder.account.get_containing_folders_async( + job.emails.keys, + this.operation_cancellable + ); + + // Add them to the conversation set + if (email_paths != null) { + this.conversations.add_all_emails( + job.emails.values, email_paths, + out added, out appended, out removed_due_to_merge + ); } + } catch (GLib.IOError.CANCELLED err) { + // All good + } catch (GLib.Error err) { + warning("Unable to add emails to conversation: %s", err.message); + // Fall-through anyway + } + + if (removed_due_to_merge != null && removed_due_to_merge.size > 0) { + notify_conversations_removed(removed_due_to_merge); + } + + if (added != null && added.size > 0) + notify_conversations_added(added); + + if (appended != null) { + foreach (Conversation conversation in appended.get_keys()) + notify_conversation_appended(conversation, appended.get(conversation)); } - - // Add "no folders" so we omit results that have been deleted permanently from the server. - blacklist.add(null); - - return blacklist; - } - - private Geary.EmailFlags get_search_flag_blacklist() { - Geary.EmailFlags flags = new Geary.EmailFlags(); - flags.add(Geary.EmailFlags.DRAFT); - - return flags; } - + private async void expand_conversations_async(Gee.Set needed_message_ids, - ProcessJobContext job) { + ProcessJobContext job) + throws Error { if (needed_message_ids.size == 0) { yield process_email_complete_async(job); return; } - + Logging.debug(Logging.Flag.CONVERSATIONS, - "[%s] ConversationMonitor::expand_conversations: %d email ids", - folder.to_string(), needed_message_ids.size); - - Gee.Collection folder_blacklist = get_search_blacklist(); + "[%s] ConversationMonitor::expand_conversations: %d email ids", + this.base_folder.to_string(), needed_message_ids.size); + + Gee.Collection folder_blacklist = get_search_folder_blacklist(); Geary.EmailFlags flag_blacklist = get_search_flag_blacklist(); - + // execute all the local search operations at once Nonblocking.Batch batch = new Nonblocking.Batch(); foreach (RFC822.MessageID message_id in needed_message_ids) { - batch.add(new LocalSearchOperation(folder.account, message_id, required_fields, + batch.add(new LocalSearchOperation(this.base_folder.account, message_id, required_fields, folder_blacklist, flag_blacklist)); } - - try { - yield batch.execute_all_async(); - } catch (Error err) { - debug("Unable to search local mail for conversations: %s", err.message); - - yield process_email_complete_async(job); - return; - } - + + yield batch.execute_all_async(); + // collect their results into a single collection of addt'l emails Gee.HashMap needed_messages = new Gee.HashMap< Geary.EmailIdentifier, Geary.Email>(); @@ -560,224 +754,148 @@ .add_all_to_map(needed_messages, e => e.id); } } - + // process them as through they're been loaded from the folder; this, in turn, may // require more local searching of email yield process_email_async(needed_messages.values, job); - + Logging.debug(Logging.Flag.CONVERSATIONS, - "[%s] ConversationMonitor::expand_conversations completed: %d email ids (%d found)", - folder.to_string(), needed_message_ids.size, needed_messages.size); + "[%s] ConversationMonitor::expand_conversations completed: %d email ids (%d found)", + this.base_folder.to_string(), needed_message_ids.size, needed_messages.size); } - - private async void process_email_complete_async(ProcessJobContext job) { - Gee.Collection? added = null; - Gee.MultiMap? appended = null; - Gee.Collection? removed_due_to_merge = null; - try { - yield conversations.add_all_emails_async(job.emails.values, this, folder.path, out added, out appended, - out removed_due_to_merge, null); - } catch (Error err) { - debug("Unable to add emails to conversation: %s", err.message); - - // fall-through + + private void on_folder_opened(Geary.Folder.OpenState state, int count) { + if (state == Geary.Folder.OpenState.REMOTE) + this.queue.add(new ReseedOperation(this)); + } + + private void on_folder_email_appended(Gee.Collection appended) { + this.queue.add(new AppendOperation(this, appended)); + } + + private void on_folder_email_complete(Gee.Collection completed) { + // InsertOperation will add the emails only if they are after + // the earliest, which is what we want here. + this.queue.add(new InsertOperation(this, completed)); + } + + private void on_folder_email_inserted(Gee.Collection inserted) { + this.queue.add(new InsertOperation(this, inserted)); + } + + private void on_folder_email_removed(Gee.Collection removed) { + this.queue.add(new RemoveOperation(this, this.base_folder, removed)); + } + + private void on_account_email_appended(Folder folder, + Gee.Collection added) { + if (folder != this.base_folder) { + this.queue.add(new ExternalAppendOperation(this, folder, added)); } - - if (removed_due_to_merge != null && removed_due_to_merge.size > 0) { - notify_conversations_removed(removed_due_to_merge); + } + + private void on_account_email_complete(Folder folder, + Gee.Collection inserted) { + // ExternalAppendOperation will check to determine if the + // email is relevant for some existing conversation before + // adding it, which is what we want here. + if (folder != this.base_folder) { + this.queue.add(new ExternalAppendOperation(this, folder, inserted)); } - - if (added != null && added.size > 0) - notify_conversations_added(added); - - if (appended != null) { - foreach (Geary.App.Conversation conversation in appended.get_keys()) - notify_conversation_appended(conversation, appended.get(conversation)); + } + + private void on_account_email_inserted(Folder folder, + Gee.Collection inserted) { + // ExternalAppendOperation will check to determine if the + // email is relevant for some existing conversation before + // adding it, which is what we want here. + if (folder != this.base_folder) { + this.queue.add(new ExternalAppendOperation(this, folder, inserted)); + } + } + + private void on_account_email_removed(Folder folder, + Gee.Collection removed) { + if (folder != this.base_folder) { + this.queue.add(new RemoveOperation(this, folder, removed)); } - - if (job.inside_scan) - notify_scan_completed(); - } - - private void on_folder_email_appended(Gee.Collection appended_ids) { - operation_queue.add(new AppendOperation(this, appended_ids)); - } - - private void on_folder_email_inserted(Gee.Collection inserted_ids) { - operation_queue.add(new FillWindowOperation(this, true)); - } - - private void on_folder_email_removed(Gee.Collection removed_ids) { - operation_queue.add(new RemoveOperation(this, removed_ids)); - operation_queue.add(new FillWindowOperation(this, false)); - } - - private void on_account_email_locally_complete(Geary.Folder folder, - Gee.Collection complete_ids) { - operation_queue.add(new ExternalAppendOperation(this, folder, complete_ids)); - } - - internal async void append_emails_async(Gee.Collection appended_ids) { - debug("%d message(s) appended to %s, fetching to add to conversations...", appended_ids.size, - folder.to_string()); - - yield load_by_sparse_id(appended_ids, Geary.Folder.ListFlags.NONE, null); - } - - internal async void remove_emails_async(Gee.Collection removed_ids) { - debug("%d messages(s) removed from %s, trimming/removing conversations...", removed_ids.size, - folder.to_string()); - - Gee.Collection removed; - Gee.MultiMap trimmed; - yield conversations.remove_emails_and_check_in_folder_async(removed_ids, folder.account, - folder.path, out removed, out trimmed, null); - - foreach (Conversation conversation in trimmed.get_keys()) - notify_conversation_trimmed(conversation, trimmed.get(conversation)); - - if (removed.size > 0) - notify_conversations_removed(removed); - - // For any still-existing conversations that we've trimmed messages - // from, do a search for any messages that should still be there due to - // full conversations. This way, some removed messages are instead - // "demoted" to out-of-folder emails. This is kind of inefficient, but - // it doesn't seem like there's a way around it. - Gee.HashSet search_message_ids = new Gee.HashSet(); - foreach (Conversation conversation in trimmed.get_keys()) - search_message_ids.add_all(conversation.get_message_ids()); - yield expand_conversations_async(search_message_ids, new ProcessJobContext(false)); - } - - internal async void external_append_emails_async(Geary.Folder folder, - Gee.Collection appended_ids) { - if (get_search_blacklist().contains(folder.path)) - return; - - if (conversations.is_empty) - return; - - debug("%d out of folder message(s) appended to %s, fetching to add to conversations...", appended_ids.size, - folder.to_string()); - - yield external_load_by_sparse_id(folder, appended_ids, Geary.Folder.ListFlags.NONE, null); } - + private void on_account_email_flags_changed(Geary.Folder folder, - Gee.Map map) { - foreach (Geary.EmailIdentifier id in map.keys) { - Conversation? conversation = conversations.get_by_email_identifier(id); - if (conversation == null) + Gee.Map map) { + Gee.HashSet inserted_ids = new Gee.HashSet(); + Gee.HashSet removed_ids = new Gee.HashSet(); + Gee.HashSet removed_conversations = new Gee.HashSet(); + foreach (EmailIdentifier id in map.keys) { + Conversation? conversation = this.conversations.get_by_email_identifier(id); + if (conversation == null) { + if (folder == this.base_folder) { + // Check to see if the incoming message is sorted later than the last message in the + // window. If it is, don't resurrect it since it likely hasn't been loaded yet. + Geary.EmailIdentifier? lowest = this.window_lowest; + if (lowest != null) { + if (lowest.natural_sort_comparator(id) < 0) { + Logging.debug( + Logging.Flag.CONVERSATIONS, + "Unflagging email %s for deletion resurrects conversation", + id.to_string() + ); + inserted_ids.add(id); + } else { + Logging.debug( + Logging.Flag.CONVERSATIONS, + "Not resurrecting undeleted email %s outside of window", + id.to_string() + ); + } + } + } + continue; - + } + Email? email = conversation.get_email_by_id(id); if (email == null) continue; - + email.set_flags(map.get(id)); notify_email_flags_changed(conversation, email); + + // Remove conversation if get_emails yields an empty collection -- this probably means + // the conversation was deleted. + if (conversation.get_emails(Geary.App.Conversation.Ordering.NONE).size == 0) { + Logging.debug( + Logging.Flag.CONVERSATIONS, + "Flagging email %s for deletion evaporates conversation %s", + id.to_string(), conversation.to_string() + ); + this.conversations.remove_conversation(conversation); + removed_conversations.add(conversation); + removed_ids.add(id); + } } - } - - private async Geary.EmailIdentifier? get_lowest_email_id_async(Cancellable? cancellable) { - Geary.EmailIdentifier? earliest_id = null; - try { - yield folder.find_boundaries_async(conversations.get_email_identifiers(), - out earliest_id, null, cancellable); - } catch (Error e) { - debug("Error finding earliest email identifier: %s", e.message); - } - - return earliest_id; - } - - internal async void reseed_async(string why) { - Geary.EmailIdentifier? earliest_id = yield get_lowest_email_id_async(null); - try { - if (earliest_id != null) { - debug("ConversationMonitor (%s) reseeding starting from Email ID %s on opened %s", why, - earliest_id.to_string(), folder.to_string()); - yield load_by_id_async(earliest_id, int.MAX, - Geary.Folder.ListFlags.OLDEST_TO_NEWEST | Geary.Folder.ListFlags.INCLUDING_ID, - cancellable_monitor); - } else { - debug("ConversationMonitor (%s) reseeding latest %d emails on opened %s", why, - min_window_count, folder.to_string()); - yield load_by_id_async(null, min_window_count, Geary.Folder.ListFlags.NONE, cancellable_monitor); - } - } catch (Error e) { - debug("Reseed error: %s", e.message); - } - - if (!reseed_notified) { - reseed_notified = true; - notify_seed_completed(); + + // Notify about inserted messages + if (inserted_ids.size > 0) { + this.queue.add(new InsertOperation(this, inserted_ids)); } + + // Notify self about removed conversations + // NOTE: We are only notifying the conversation monitor about the removed conversations instead of + // enqueuing a RemoveOperation, because these messages haven't actually been removed. They're only + // hidden at the conversation-level for being marked as deleted. + removed( + removed_conversations, + new Gee.HashMultiMap(), + (folder == this.base_folder) ? removed_ids : null + ); } - - private void on_folder_opened(Geary.Folder.OpenState state, int count) { - // once remote is open, reseed with messages from the earliest ID to the latest - if (state == Geary.Folder.OpenState.BOTH || state == Geary.Folder.OpenState.REMOTE) - operation_queue.add(new ReseedOperation(this, state.to_string())); - } - - /** - * Attempts to load enough conversations to fill min_window_count. - */ - internal async void fill_window_async(bool is_insert) { - if (!is_monitoring) - return; - - if (!is_insert && min_window_count <= conversations.size) - return; - - int initial_message_count = conversations.get_email_count(); - - // only do local-load if the Folder isn't completely opened, otherwise this operation - // will block other (more important) operations while it waits for the folder to - // remote-open - Folder.ListFlags flags; - switch (folder.get_open_state()) { - case Folder.OpenState.CLOSED: - case Folder.OpenState.LOCAL: - case Folder.OpenState.OPENING: - flags = Folder.ListFlags.LOCAL_ONLY; - break; - - case Folder.OpenState.BOTH: - case Folder.OpenState.REMOTE: - flags = Folder.ListFlags.NONE; - break; - - default: - assert_not_reached(); - } - - Geary.EmailIdentifier? low_id = yield get_lowest_email_id_async(null); - if (low_id != null && !is_insert) { - // Load at least as many messages as remianing conversations. - int num_to_load = min_window_count - conversations.size; - if (num_to_load < WINDOW_FILL_MESSAGE_COUNT) - num_to_load = WINDOW_FILL_MESSAGE_COUNT; - - try { - yield load_by_id_async(low_id, num_to_load, flags, cancellable_monitor); - } catch(Error e) { - debug("Error filling conversation window: %s", e.message); - } - } else { - // No existing messages or an insert invalidated our existing list, - // need to start from scratch. - try { - yield load_by_id_async(null, min_window_count, flags, cancellable_monitor); - } catch(Error e) { - debug("Error filling conversation window: %s", e.message); - } + + private void on_operation_error(ConversationOperation op, Error err) { + if (!(err is GLib.IOError.CANCELLED)) { + warning("Error executing %s: %s", op.get_type().name(), err.message); } - - // Run again to make sure we're full unless we ran out of messages. - if (conversations.get_email_count() != initial_message_count) - operation_queue.add(new FillWindowOperation(this, is_insert)); + notify_scan_error(err); } + } diff -Nru geary-0.12.4/src/engine/app/app-conversation.vala geary-3.32.0/src/engine/app/app-conversation.vala --- geary-0.12.4/src/engine/app/app-conversation.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/app/app-conversation.vala 2019-03-17 13:39:29.000000000 +0000 @@ -18,9 +18,9 @@ RECV_DATE_ASCENDING, RECV_DATE_DESCENDING } - + /** - * Specify the location of the {@link Email} in relation to the {@link Folder} being monitored + * Specify the location of the {@link Email} in relation to the base folder being monitored * by the {@link Conversation}'s {@link ConversationMonitor}. * * IN_FOLDER represents Email that is found in the Folder the ConversationMonitor is @@ -37,15 +37,25 @@ OUT_OF_FOLDER_IN_FOLDER, ANYWHERE } - + private static int next_convnum = 0; - + + /** Folder from which the conversation originated. */ + public Folder base_folder { get; private set; } + + /** Cache of paths associated with each email */ + internal Gee.HashMultiMap path_map { + get; + private set; + default = new Gee.HashMultiMap< Geary.EmailIdentifier,Geary.FolderPath>(); + } + private Gee.HashMultiSet message_ids = new Gee.HashMultiSet(); - + private int convnum; - private weak Geary.App.ConversationMonitor? owner; + private Gee.HashMap emails = new Gee.HashMap(); - + // this isn't ideal but the cost of adding an email to multiple sorted sets once versus // the number of times they're accessed makes it worth it private Gee.SortedSet sent_date_ascending = new Gee.TreeSet( @@ -56,310 +66,386 @@ Geary.Email.compare_recv_date_ascending); private Gee.SortedSet recv_date_descending = new Gee.TreeSet( Geary.Email.compare_recv_date_descending); - - // by storing all paths for each EmailIdentifier, can lookup without blocking - private Gee.HashMultiMap path_map = new Gee.HashMultiMap< - Geary.EmailIdentifier, Geary.FolderPath>(); - + /** * Fired when email has been added to this conversation. */ public signal void appended(Geary.Email email); - + /** * Fired when email has been trimmed from this conversation. */ public signal void trimmed(Geary.Email email); - + /** * Fired when the flags of an email in this conversation have changed. */ public signal void email_flags_changed(Geary.Email email); - - public Conversation(Geary.App.ConversationMonitor owner) { - convnum = next_convnum++; - this.owner = owner; - - owner.email_flags_changed.connect(on_email_flags_changed); - owner.folder.account.email_discovered.connect(on_email_discovered); - owner.folder.account.email_removed.connect(on_email_removed); - } - - ~Conversation() { - clear_owner(); - } - - internal void clear_owner() { - if (owner != null) { - owner.email_flags_changed.disconnect(on_email_flags_changed); - owner.folder.account.email_discovered.disconnect(on_email_discovered); - owner.folder.account.email_removed.disconnect(on_email_removed); - } - - owner = null; + + + /** + * Constructs a conversation relative to the given base folder. + */ + internal Conversation(Geary.Folder base_folder) { + this.convnum = Conversation.next_convnum++; + this.base_folder = base_folder; } - + /** * Returns the number of emails in the conversation. */ public int get_count() { return emails.size; } - + /** * Returns the number of emails in the conversation in a particular folder. */ - public async int get_count_in_folder_async(Geary.Account account, Geary.FolderPath path, - Cancellable? cancellable) throws Error { - Gee.MultiMap? folder_map - = yield account.get_containing_folders_async(emails.keys, cancellable); - - int count = 0; - if (folder_map != null) { - foreach (Geary.EmailIdentifier id in folder_map.get_keys()) { - if (path in folder_map.get(id)) - ++count; + public uint get_count_in_folder(FolderPath path) { + uint count = 0; + foreach (Geary.EmailIdentifier id in this.path_map.get_keys()) { + if (path in this.path_map.get(id)) { + count++; } } - return count; } - + + /** + * Returns true if *any* message in the conversation is unread. + */ + public bool is_unread() { + return has_flag(Geary.EmailFlags.UNREAD); + } + + /** + * Returns true if any message in the conversation is not unread. + */ + public bool has_any_read_message() { + return is_missing_flag(Geary.EmailFlags.UNREAD); + } + + /** + * Returns true if *any* message in the conversation is flagged. + */ + public bool is_flagged() { + return has_flag(Geary.EmailFlags.FLAGGED); + } + + /** + * Returns the earliest (first sent) email in the Conversation. + * + * Note that here, sent denotes the value of the Date header, not + * being contained in the Sent folder. + */ + public Email? + get_earliest_sent_email(Location location, + Gee.Collection? blacklist = null) { + return get_single_email(Ordering.SENT_DATE_ASCENDING, location, blacklist); + } + + /** + * Returns the latest (most recently sent) email in the Conversation. + * + * Note that here, sent denotes the value of the Date header, not + * being contained in the Sent folder. + */ + public Email? + get_latest_sent_email(Location location, + Gee.Collection? blacklist = null) { + return get_single_email(Ordering.SENT_DATE_DESCENDING, location); + } + + /** + * Returns the earliest (first received) email in the Conversation. + */ + public Email? + get_earliest_recv_email(Location location, + Gee.Collection? blacklist = null) { + return get_single_email(Ordering.RECV_DATE_ASCENDING, location); + } + + /** + * Returns the latest (most recently received) email in the Conversation. + */ + public Email? + get_latest_recv_email(Location location, + Gee.Collection? blacklist = null) { + return get_single_email(Ordering.RECV_DATE_DESCENDING, location); + } + + public Gee.Collection + get_emails_flagged_for_deletion(Location location, + Gee.Collection? blacklist = null) { + Gee.Collection emails = get_emails(Ordering.NONE, location, blacklist, false); + Iterable filtered = traverse(emails); + return filtered.filter( + (e) => e.email_flags.is_deleted() + ).to_array_list(); + } + /** - * Returns all the email in the conversation sorted and filtered according to the specifiers. + * Returns the conversation's email, possibly sorted and filtered. * * {@link Location.IN_FOLDER} and {@link Location.OUT_OF_FOLDER} are the * only preferences honored; the others ({@link Location.IN_FOLDER_OUT_OF_FOLDER}, * {@link Location.IN_FOLDER_OUT_OF_FOLDER}, and {@link Location.ANYWHERE} * are all treated as ANYWHERE. */ - public Gee.Collection get_emails(Ordering ordering, Location location = Location.ANYWHERE) { - Gee.Collection email; + public Gee.List + get_emails(Ordering ordering, + Location location = Location.ANYWHERE, + Gee.Collection? blacklist = null, + bool filter_deleted = true) { + Gee.Collection email; switch (ordering) { case Ordering.SENT_DATE_ASCENDING: email = sent_date_ascending; break; - + case Ordering.SENT_DATE_DESCENDING: email = sent_date_descending; break; - + case Ordering.RECV_DATE_ASCENDING: email = recv_date_ascending; break; - + case Ordering.RECV_DATE_DESCENDING: email = recv_date_descending; break; - + case Ordering.NONE: email = emails.values; break; - + default: assert_not_reached(); } - + + Iterable filtered = traverse(email); switch (location) { - case Location.IN_FOLDER: - email = traverse(email) - .filter((e) => !is_in_current_folder(e.id)) - .to_array_list(); + case Location.IN_FOLDER: + filtered = filtered.filter((e) => is_in_base_folder(e.id)); break; - - case Location.OUT_OF_FOLDER: - email = traverse(email) - .filter((e) => is_in_current_folder(e.id)) - .to_array_list(); + + case Location.OUT_OF_FOLDER: + filtered = filtered.filter((e) => !is_in_base_folder(e.id)); break; - - case Location.IN_FOLDER_OUT_OF_FOLDER: - case Location.OUT_OF_FOLDER_IN_FOLDER: - case Location.ANYWHERE: - // make a modifiable copy - email = traverse(email).to_array_list(); + + default: + // Nothing to do break; - - default: - assert_not_reached(); } - - return email; - } - public bool is_in_current_folder(Geary.EmailIdentifier id) { - Gee.Collection? paths = path_map.get(id); + // Filter emails waiting to be expunged (\DELETED) + if (filter_deleted) { + filtered = filtered.filter( + (e) => (e.email_flags != null) ? !e.email_flags.is_deleted() : true + ); + } - return (paths != null && - owner != null && - paths.contains(owner.folder.path)); - } + if (blacklist != null && !blacklist.is_empty) { + if (blacklist.size == 1) { + FolderPath blacklist_path = + traverse(blacklist).first(); + filtered = filtered.filter( + (e) => !this.path_map.get(e.id).contains(blacklist_path) + ); + } else { + filtered = filtered.filter( + (e) => this.path_map.get(e.id).any_match( + (p) => !blacklist.contains(p) + ) + ); + } + } - /** - * Return all Message IDs associated with the conversation. - */ - public Gee.Collection get_message_ids() { - // Turn into a HashSet first, so we don't return duplicates. - Gee.HashSet ids = new Gee.HashSet(); - ids.add_all(message_ids); - return ids; + return filtered.to_array_list(); } - + /** - * Returns the email associated with the EmailIdentifier, if present in this conversation. + * Determines if the given id is in the conversation's base folder. */ - public Geary.Email? get_email_by_id(EmailIdentifier id) { - return emails.get(id); + public bool is_in_base_folder(Geary.EmailIdentifier id) { + Gee.Collection? paths = this.path_map.get(id); + return (paths != null && paths.contains(this.base_folder.path)); } - + /** - * Returns all EmailIdentifiers in the conversation, unsorted. + * Determines if the given id is in the conversation's base folder. */ - public Gee.Collection get_email_ids() { - return emails.keys; + public uint get_folder_count(Geary.EmailIdentifier id) { + Gee.Collection? paths = this.path_map.get(id); + uint count = 0; + if (paths != null) { + count = paths.size; + } + return count; } - + /** - * Add the email to the conversation if it wasn't already in there. Return - * whether it was added. - * - * known_paths should contain all the known FolderPaths this email is contained in. - * Conversation will monitor Account for additions and removals as they occur. + * Determines if an email with the give id exists in the conversation. */ - internal bool add(Email email, Gee.Collection known_paths) { - if (emails.has_key(email.id)) - return false; - - emails.set(email.id, email); - sent_date_ascending.add(email); - sent_date_descending.add(email); - recv_date_ascending.add(email); - recv_date_descending.add(email); - - Gee.Set? ancestors = email.get_ancestors(); - if (ancestors != null) - message_ids.add_all(ancestors); - - foreach (Geary.FolderPath path in known_paths) - path_map.set(email.id, path); - - appended(email); - - return true; - } - - // Returns the removed Message-IDs - internal Gee.Set? remove(Email email) { - emails.unset(email.id); - sent_date_ascending.remove(email); - sent_date_descending.remove(email); - recv_date_ascending.remove(email); - recv_date_descending.remove(email); - path_map.remove_all(email.id); - - Gee.Set removed_message_ids = new Gee.HashSet(); - - Gee.Set? ancestors = email.get_ancestors(); - if (ancestors != null) { - foreach (RFC822.MessageID ancestor_id in ancestors) { - // if remove() changes set (i.e. it was present) but no longer present, that - // means the ancestor_id was the last one and is formally removed - if (message_ids.remove(ancestor_id) && !message_ids.contains(ancestor_id)) - removed_message_ids.add(ancestor_id); - } - } - - trimmed(email); - - return (removed_message_ids.size > 0) ? removed_message_ids : null; + public bool contains_email_by_id(EmailIdentifier id) { + return emails.has_key(id); } - + /** - * Returns true if *any* message in the conversation is unread. + * Returns the email associated with the EmailIdentifier, if it exists. */ - public bool is_unread() { - return has_flag(Geary.EmailFlags.UNREAD); + public Geary.Email? get_email_by_id(EmailIdentifier id) { + return emails.get(id); } /** - * Returns true if any message in the conversation is not unread. + * Returns all EmailIdentifiers in the conversation, unsorted. */ - public bool has_any_read_message() { - return is_missing_flag(Geary.EmailFlags.UNREAD); + public Gee.Collection get_email_ids() { + return emails.keys; } /** - * Returns true if *any* message in the conversation is flagged. + * Return all Message IDs associated with the conversation. */ - public bool is_flagged() { - return has_flag(Geary.EmailFlags.FLAGGED); + public Gee.Collection get_message_ids() { + // Turn into a HashSet first, so we don't return duplicates. + Gee.HashSet ids = new Gee.HashSet(); + ids.add_all(message_ids); + return ids; } - + /** - * Returns the earliest (first sent) email in the Conversation. + * Returns a string representation for debugging. */ - public Geary.Email? get_earliest_sent_email(Location location) { - return get_single_email(Ordering.SENT_DATE_ASCENDING, location); + public string to_string() { + return "[#%d] (%d emails)".printf(convnum, emails.size); } - + /** - * Returns the latest (most recently sent) email in the Conversation. + * Add the email to the conversation if not already present. + * + * The value of `known_paths` should contain all the known {@link + * FolderPath} instances this email is contained within. + * + * Returns if the email was added, else false if already present + * and only `known_paths` were merged. */ - public Geary.Email? get_latest_sent_email(Location location) { - return get_single_email(Ordering.SENT_DATE_DESCENDING, location); + internal bool add(Email email, Gee.Collection known_paths) { + // Add the known paths to the path map regardless of whether + // the email is already in the conversation or not, so that it + // remains complete + foreach (Geary.FolderPath path in known_paths) + this.path_map.set(email.id, path); + + bool added = false; + if (!emails.has_key(email.id)) { + this.emails.set(email.id, email); + this.sent_date_ascending.add(email); + this.sent_date_descending.add(email); + this.recv_date_ascending.add(email); + this.recv_date_descending.add(email); + + Gee.Set? ancestors = email.get_ancestors(); + if (ancestors != null) + message_ids.add_all(ancestors); + + appended(email); + added = true; + } + return added; } - + /** - * Returns the earliest (first received) email in the Conversation. + * Unconditionally removes an email from the conversation. + * + * Returns all Message-IDs that should be removed as result of + * removing this message, or `null` if none were removed. */ - public Geary.Email? get_earliest_recv_email(Location location) { - return get_single_email(Ordering.RECV_DATE_ASCENDING, location); + internal Gee.Set? remove(Email email) { + Gee.Set? removed_ids = null; + + if (emails.unset(email.id)) { + this.sent_date_ascending.remove(email); + this.sent_date_descending.remove(email); + this.recv_date_ascending.remove(email); + this.recv_date_descending.remove(email); + this.path_map.remove_all(email.id); + + Gee.Set? ancestors = email.get_ancestors(); + if (ancestors != null) { + removed_ids = new Gee.HashSet(); + foreach (RFC822.MessageID ancestor_id in ancestors) { + // if remove() changes set (i.e. it was present) but no longer present, that + // means the ancestor_id was the last one and is formally removed + if (message_ids.remove(ancestor_id) && + !message_ids.contains(ancestor_id)) { + removed_ids.add(ancestor_id); + } + } + + if (removed_ids.size == 0) { + removed_ids = null; + } + } + + + trimmed(email); + } + + return removed_ids; } - + /** - * Returns the latest (most recently received) email in the Conversation. + * Removes the target path from the known set for the given id. */ - public Geary.Email? get_latest_recv_email(Location location) { - return get_single_email(Ordering.RECV_DATE_DESCENDING, location); + internal void remove_path(Geary.EmailIdentifier id, FolderPath path) { + this.path_map.remove(id, path); } - - private Geary.Email? get_single_email(Ordering ordering, Location location) { - // note that the location-ordering preferences are treated as ANYWHERE by get_emails() - Gee.Collection all = get_emails(ordering, location); - if (all.size == 0) + + private Geary.Email? + get_single_email(Ordering ordering, Location location, + Gee.Collection? blacklist = null) { + // note that the location-ordering preferences are treated as + // ANYWHERE by get_emails() + Gee.Collection all = get_emails( + ordering, location, blacklist + ); + if (all.size == 0) { return null; - - // Because IN_FOLDER_OUT_OF_FOLDER and OUT_OF_FOLDER_IN_FOLDER are treated as ANYWHERE, - // have to do our own filtering + } + + // Because IN_FOLDER_OUT_OF_FOLDER and OUT_OF_FOLDER_IN_FOLDER + // are treated as ANYWHERE, have to do our own filtering switch (location) { case Location.IN_FOLDER: case Location.OUT_OF_FOLDER: case Location.ANYWHERE: return traverse(all).first(); - + case Location.IN_FOLDER_OUT_OF_FOLDER: Geary.Email? found = traverse(all) - .first_matching((email) => is_in_current_folder(email.id)); - + .first_matching((email) => is_in_base_folder(email.id)); + return found ?? traverse(all).first(); - + case Location.OUT_OF_FOLDER_IN_FOLDER: Geary.Email? found = traverse(all) - .first_matching((email) => !is_in_current_folder(email.id)); - + .first_matching((email) => !is_in_base_folder(email.id)); + return found ?? traverse(all).first(); - + default: assert_not_reached(); } } - + private bool check_flag(Geary.NamedFlag flag, bool contains) { foreach (Geary.Email email in get_emails(Ordering.NONE)) { if (email.email_flags != null && email.email_flags.contains(flag) == contains) return true; } - + return false; } @@ -370,27 +456,5 @@ private bool is_missing_flag(Geary.NamedFlag flag) { return check_flag(flag, false); } - - private void on_email_flags_changed(Conversation conversation, Geary.Email email) { - if (conversation == this) - email_flags_changed(email); - } - - private void on_email_discovered(Geary.Folder folder, Gee.Collection ids) { - // only add to the internal map if a part of this Conversation - foreach (Geary.EmailIdentifier id in ids) { - if (emails.has_key(id)) - path_map.set(id, folder.path); - } - } - - private void on_email_removed(Geary.Folder folder, Gee.Collection ids) { - // To be forgiving, simply remove id without checking if it's a part of this Conversation - foreach (Geary.EmailIdentifier id in ids) - path_map.remove(id, folder.path); - } - - public string to_string() { - return "[#%d] (%d emails)".printf(convnum, emails.size); - } + } diff -Nru geary-0.12.4/src/engine/app/app-draft-manager.vala geary-3.32.0/src/engine/app/app-draft-manager.vala --- geary-0.12.4/src/engine/app/app-draft-manager.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/app/app-draft-manager.vala 2019-03-17 13:39:29.000000000 +0000 @@ -32,7 +32,7 @@ public const string PROP_VERSIONS_SAVED = "versions-saved"; public const string PROP_VERSIONS_DROPPED = "versions-dropped"; public const string PROP_DISCARD_ON_CLOSE = "discard-on-close"; - + /** * Current saved state of the draft. */ @@ -56,19 +56,19 @@ */ ERROR } - + private enum OperationType { PUSH, CLOSE } - + private class Operation : BaseObject { public OperationType op_type; public RFC822.Message? draft; public EmailFlags? flags; public DateTime? date_received; public Nonblocking.Semaphore? semaphore; - + public Operation(OperationType op_type, RFC822.Message? draft, EmailFlags? flags, DateTime? date_received, Nonblocking.Semaphore? semaphore) { this.op_type = op_type; @@ -78,7 +78,7 @@ this.semaphore = semaphore; } } - + /** * Indicates the {@link DraftManager} is open and ready for service. * @@ -86,17 +86,17 @@ * {@link open_async} completes, not when this property changes to true. */ public bool is_open { get; private set; default = false; } - + /** * The current saved state of the draft. */ public DraftState draft_state { get; private set; default = DraftState.NOT_STORED; } - + /** * The {@link Geary.EmailIdentifier} of the last saved draft. */ public Geary.EmailIdentifier? current_draft_id { get; private set; default = null; } - + /** * The version number of the most recently saved draft. * @@ -106,39 +106,40 @@ * A {@link discard} operation will reset this counter to zero. */ public int versions_saved { get; private set; default = 0; } - + /** * The number of drafts dropped as new ones are added to the queue. * * @see dropped */ public int versions_dropped { get; private set; default = 0; } - + /** * When set, the draft will be discarded when {@link close_async} is called. * * In addition, when set all future {@link update}s will result in the draft being dropped. */ public bool discard_on_close { get; set; default = false; } - + private Account account; private Folder? drafts_folder = null; private FolderSupport.Create? create_support = null; private FolderSupport.Remove? remove_support = null; - private Nonblocking.Mailbox mailbox = new Nonblocking.Mailbox(); + private Nonblocking.Queue mailbox = + new Nonblocking.Queue.fifo(); private bool was_opened = false; private Error? fatal_err = null; - + /** * Fired when a draft is successfully saved. */ public signal void stored(Geary.RFC822.Message draft); - + /** * Fired when a draft is discarded. */ public signal void discarded(); - + /** * Fired when a draft is dropped. * @@ -146,7 +147,7 @@ * to be pushed to the server. The queued draft is dropped in favor of the new one. */ public signal void dropped(Geary.RFC822.Message draft); - + /** * Fired when unable to save a draft but the {@link DraftManager} remains open. * @@ -157,7 +158,7 @@ public virtual signal void draft_failed(Geary.RFC822.Message draft, Error err) { debug("%s: Unable to create draft: %s", to_string(), err.message); } - + /** * Fired if an unrecoverable error occurs while processing drafts. * @@ -165,24 +166,24 @@ */ public virtual signal void fatal(Error err) { fatal_err = err; - + debug("%s: Irrecoverable failure: %s", to_string(), err.message); } - + public DraftManager(Geary.Account account) { this.account = account; } - + protected virtual void notify_stored(Geary.RFC822.Message draft) { versions_saved++; stored(draft); } - + protected virtual void notify_discarded() { versions_saved = 0; discarded(); } - + /** * Open the {@link DraftManager} and prepare it for handling composed messages. * @@ -204,17 +205,17 @@ throw new EngineError.ALREADY_OPEN("%s is already open", to_string()); else if (was_opened) throw new EngineError.UNSUPPORTED("%s cannot be re-opened", to_string()); - + was_opened = true; - + current_draft_id = initial_draft_id; if (current_draft_id != null) draft_state = DraftState.STORED; - + drafts_folder = account.get_special_folder(SpecialFolderType.DRAFTS); if (drafts_folder == null) throw new EngineError.NOT_FOUND("%s: No drafts folder found", to_string()); - + // if drafts folder doesn't support create and remove, call it quits create_support = drafts_folder as Geary.FolderSupport.Create; remove_support = drafts_folder as Geary.FolderSupport.Remove; @@ -222,11 +223,11 @@ throw new EngineError.UNSUPPORTED("%s: Drafts folder %s does not support create and remove", to_string(), drafts_folder.to_string()); } - + drafts_folder.closed.connect(on_folder_closed); - - yield drafts_folder.open_async(Folder.OpenFlags.NONE, cancellable); - + + yield drafts_folder.open_async(Folder.OpenFlags.NO_DELAY, cancellable); + // if drafts folder doesn't return the identifier of newly created emails, then this object // can't do it's work ... wait until open to check for this, to be absolutely sure if (drafts_folder.properties.create_never_returns_id) { @@ -235,25 +236,25 @@ } catch (Error err) { // ignore } - + throw new EngineError.UNSUPPORTED("%s: Drafts folder %s does not return created mail ID", to_string(), drafts_folder.to_string()); } - + // start the operation message loop, which ensures commands are handled in orderly fashion operation_loop_async.begin(); - + // done is_open = true; } - + private void on_folder_closed(Folder.CloseReason reason) { if (reason == Folder.CloseReason.FOLDER_CLOSED) { fatal(new EngineError.SERVER_UNAVAILABLE("%s: Unexpected drafts folder closed (%s)", to_string(), reason.to_string())); } } - + /** * Flush pending operations and close the {@link DraftManager}. * @@ -265,10 +266,10 @@ public async void close_async(Cancellable? cancellable = null) throws Error { if (!is_open || drafts_folder == null) return; - + // prevent further operations is_open = false; - + // don't flush a CLOSE down the pipe if failed, the operation loop is closed for business if (fatal_err == null) { // if discarding on close, do so now @@ -277,25 +278,25 @@ // which doesn't submit_push(null, null, null); } - + // flush pending I/O Nonblocking.Semaphore semaphore = new Nonblocking.Semaphore(cancellable); mailbox.send(new Operation(OperationType.CLOSE, null, null, null, semaphore)); - + // wait for close to complete try { yield semaphore.wait_async(cancellable); } catch (Error err) { if (err is IOError.CANCELLED) throw err; - + // fall through } } - + // Disconnect before closing, as signal handler is for unexpected closes drafts_folder.closed.disconnect(on_folder_closed); - + try { yield drafts_folder.close_async(cancellable); } finally { @@ -304,12 +305,12 @@ remove_support = null; } } - + private void check_open() throws EngineError { if (!is_open) throw new EngineError.OPEN_REQUIRED("%s is not open", to_string()); } - + /** * Save draft on the server, potentially replacing (deleting) an already-existing version. * @@ -322,10 +323,10 @@ public Geary.Nonblocking.Semaphore? update(Geary.RFC822.Message draft, Geary.EmailFlags? flags, DateTime? date_received) throws Error { check_open(); - + return submit_push(draft, flags, date_received); } - + /** * Delete all versions of the composed email from the server. * @@ -340,10 +341,10 @@ */ public Geary.Nonblocking.Semaphore? discard() throws Error { check_open(); - + return submit_push(null, null, null); } - + // Note that this call doesn't check_open(), important when used within close_async() private Nonblocking.Semaphore? submit_push(RFC822.Message? draft, EmailFlags? flags, DateTime? date_received) { @@ -351,10 +352,10 @@ if (draft != null && discard_on_close) { versions_dropped++; dropped(draft); - + return null; } - + // clear out pending pushes (which can be updates or discards) mailbox.revoke_matching((op) => { // count and notify of dropped drafts @@ -362,63 +363,62 @@ versions_dropped++; dropped(op.draft); } - + return op.op_type == OperationType.PUSH; }); - + Nonblocking.Semaphore semaphore = new Nonblocking.Semaphore(); - + // schedule this draft for update (if null, it's a discard) mailbox.send(new Operation(OperationType.PUSH, draft, flags, date_received, semaphore)); - + return semaphore; } - + private async void operation_loop_async() { for (;;) { // if a fatal error occurred (it can happen outside the loop), shutdown without // reporting it again if (fatal_err != null) break; - + Operation op; try { - op = yield mailbox.recv_async(null); + op = yield mailbox.receive(null); } catch (Error err) { fatal(err); - break; } - + bool continue_loop = yield operation_loop_iteration_async(op); - + // fire semaphore, if present if (op.semaphore != null) op.semaphore.blind_notify(); - + if (!continue_loop) break; } } - + // Returns false if time to exit. private async bool operation_loop_iteration_async(Operation op) { // watch for CLOSE if (op.op_type == OperationType.CLOSE) return false; - + // make sure there's a folder to work with if (drafts_folder == null || drafts_folder.get_open_state() == Folder.OpenState.CLOSED) { fatal(new EngineError.SERVER_UNAVAILABLE("%s: premature drafts folder close", to_string())); - + return false; } - + // at this point, only operation left is PUSH assert(op.op_type == OperationType.PUSH); - + draft_state = DraftState.STORING; - + // delete old draft for all PUSHes: best effort ... since create_email_async() will handle // replacement in a transactional-kinda-way, only outright delete if not using create if (current_draft_id != null && op.draft == null) { @@ -431,35 +431,35 @@ debug("%s: Unable to remove existing draft %s: %s", to_string(), current_draft_id.to_string(), err.message); } - + // always clear draft id (assuming that retrying a failed remove is unnecessary), but // only signal the discard if it actually was removed current_draft_id = null; if (success) notify_discarded(); } - + // if draft supplied, save it if (op.draft != null) { try { current_draft_id = yield create_support.create_email_async(op.draft, op.flags, op.date_received, current_draft_id, null); - + draft_state = DraftState.STORED; notify_stored(op.draft); } catch (Error err) { draft_state = DraftState.ERROR; - + // notify subscribers draft_failed(op.draft, err); } } else { draft_state = DraftState.NOT_STORED; } - + return true; } - + public string to_string() { return "%s DraftManager".printf(account.to_string()); } diff -Nru geary-0.12.4/src/engine/app/app-email-store.vala geary-3.32.0/src/engine/app/app-email-store.vala --- geary-0.12.4/src/engine/app/app-email-store.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/app/app-email-store.vala 2019-03-17 13:39:29.000000000 +0000 @@ -6,11 +6,11 @@ public class Geary.App.EmailStore : BaseObject { public weak Geary.Account account { get; private set; } - + public EmailStore(Geary.Account account) { this.account = account; } - + /** * Return a map of EmailIdentifiers to the special Geary.FolderSupport * interfaces each one supports. For example, if an EmailIdentifier comes @@ -24,7 +24,7 @@ = yield account.get_containing_folders_async(emails, cancellable); if (folders == null) return null; - + Gee.HashSet all_support = new Gee.HashSet(); all_support.add(typeof(Geary.FolderSupport.Archive)); all_support.add(typeof(Geary.FolderSupport.Copy)); @@ -32,12 +32,12 @@ all_support.add(typeof(Geary.FolderSupport.Mark)); all_support.add(typeof(Geary.FolderSupport.Move)); all_support.add(typeof(Geary.FolderSupport.Remove)); - + Gee.HashMultiMap map = new Gee.HashMultiMap(); foreach (Geary.EmailIdentifier email in folders.get_keys()) { Gee.HashSet support = new Gee.HashSet(); - + foreach (Geary.FolderPath path in folders.get(email)) { Geary.Folder folder; try { @@ -46,7 +46,7 @@ debug("Error getting a folder from path %s: %s", path.to_string(), e.message); continue; } - + foreach (Type type in all_support) { if (folder.get_type().is_a(type)) support.add(type); @@ -54,13 +54,13 @@ if (support.contains_all(all_support)) break; } - + Geary.Collection.multi_map_set_all(map, email, support); } - + return (map.size > 0 ? map : null); } - + /** * Lists any set of EmailIdentifiers as if they were all in one folder. */ @@ -71,7 +71,7 @@ yield do_folder_operation_async(op, emails, cancellable); return (op.results.size > 0 ? op.results : null); } - + /** * Fetches any EmailIdentifier regardless of what folder it's in. */ @@ -81,12 +81,12 @@ FetchOperation op = new Geary.App.FetchOperation(required_fields, flags); yield do_folder_operation_async(op, Geary.iterate(email_id).to_array_list(), cancellable); - + if (op.result == null) throw new EngineError.NOT_FOUND("Couldn't fetch email ID %s", email_id.to_string()); return op.result; } - + /** * Marks any set of EmailIdentifiers as if they were all in one * Geary.FolderSupport.Mark folder. @@ -97,7 +97,7 @@ yield do_folder_operation_async(new Geary.App.MarkOperation(flags_to_add, flags_to_remove), emails, cancellable); } - + /** * Copies any set of EmailIdentifiers as if they were all in one * Geary.FolderSupport.Copy folder. @@ -107,7 +107,7 @@ yield do_folder_operation_async(new Geary.App.CopyOperation(destination), emails, cancellable); } - + private async Gee.HashMap get_folder_instances_async( Gee.Collection paths, Cancellable? cancellable) throws Error { Gee.HashMap folders @@ -118,7 +118,7 @@ } return folders; } - + private Geary.FolderPath? next_folder_for_operation(AsyncFolderOperation operation, Gee.MultiMap folders_to_ids, Gee.Map folders) throws Error { @@ -129,12 +129,12 @@ assert(folders.has_key(path)); if (!folders.get(path).get_type().is_a(operation.folder_type)) continue; - + int count = folders_to_ids.get(path).size; if (count == 0) continue; - - if (folders.get(path).get_open_state() == Geary.Folder.OpenState.BOTH) { + + if (folders.get(path).get_open_state() == Geary.Folder.OpenState.REMOTE) { if (!best_is_open) { best_is_open = true; best_count = 0; @@ -142,69 +142,66 @@ } else if (best_is_open) { continue; } - + if (count > best_count) { best_count = count; best = path; } } - + return best; } - + private async void do_folder_operation_async(AsyncFolderOperation operation, Gee.Collection emails, Cancellable? cancellable) throws Error { if (emails.size == 0) return; - + debug("EmailStore %s running %s on %d emails", account.to_string(), operation.get_type().name(), emails.size); - + Gee.MultiMap? ids_to_folders = yield account.get_containing_folders_async(emails, cancellable); if (ids_to_folders == null) return; - + Gee.MultiMap folders_to_ids = Geary.Collection.reverse_multi_map(ids_to_folders); Gee.HashMap folders = yield get_folder_instances_async(folders_to_ids.get_keys(), cancellable); - + Geary.FolderPath? path; while ((path = next_folder_for_operation(operation, folders_to_ids, folders)) != null) { Geary.Folder folder = folders.get(path); Gee.Collection ids = folders_to_ids.get(path); assert(ids.size > 0); - + bool open = false; Gee.Collection? used_ids = null; + GLib.Error? op_error = null; try { - debug("EmailStore opening %s for %s on %d emails", folder.to_string(), - operation.get_type().name(), ids.size); - - yield folder.open_async(Geary.Folder.OpenFlags.FAST_OPEN, cancellable); + yield folder.open_async(Folder.OpenFlags.NONE, cancellable); open = true; - used_ids = yield operation.execute_async(folder, ids, cancellable); - - yield folder.close_async(cancellable); - open = false; - - debug("EmailStore closed %s after %s on %d emails", folder.to_string(), - operation.get_type().name(), ids.size); - } catch (Error e) { - debug("Error performing an operation on messages in %s: %s", folder.to_string(), e.message); - - if (open) { - try { - yield folder.close_async(cancellable); - open = false; - } catch (Error e) { - debug("Error closing folder %s: %s", folder.to_string(), e.message); - } + } catch (GLib.Error err) { + op_error = err; + } + + if (open) { + try { + // Don't use the cancellable here, if it's been opened + // we need to try to close it. + yield folder.close_async(null); + } catch (Error e) { + warning("Error closing folder %s: %s", + folder.to_string(), e.message); } } - + + if (op_error != null) { + throw op_error; + } + // We don't want to operate on any mails twice. if (used_ids != null) { foreach (Geary.EmailIdentifier id in used_ids.to_array()) { @@ -215,10 +212,7 @@ // And we don't want to operate on the same folder twice. folders_to_ids.remove_all(path); } - - debug("EmailStore %s done running %s on %d emails", account.to_string(), - operation.get_type().name(), emails.size); - + if (folders_to_ids.size > 0) { debug("Couldn't perform %s on some messages in %s", operation.get_type().name(), account.to_string()); diff -Nru geary-0.12.4/src/engine/app/conversation-monitor/app-append-operation.vala geary-3.32.0/src/engine/app/conversation-monitor/app-append-operation.vala --- geary-0.12.4/src/engine/app/conversation-monitor/app-append-operation.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/app/conversation-monitor/app-append-operation.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,18 +1,27 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ private class Geary.App.AppendOperation : ConversationOperation { + + private Gee.Collection appended_ids; - - public AppendOperation(ConversationMonitor monitor, Gee.Collection appended_ids) { + + + public AppendOperation(ConversationMonitor monitor, + Gee.Collection appended_ids) { base(monitor); this.appended_ids = appended_ids; } - - public override async void execute_async() { - yield monitor.append_emails_async(appended_ids); + + public override async void execute_async() throws Error { + debug("%d message(s) appended to %s, fetching to add to conversations...", + this.appended_ids.size, this.monitor.base_folder.to_string()); + + yield this.monitor.load_by_sparse_id(this.appended_ids); } + } diff -Nru geary-0.12.4/src/engine/app/conversation-monitor/app-conversation-operation-queue.vala geary-3.32.0/src/engine/app/conversation-monitor/app-conversation-operation-queue.vala --- geary-0.12.4/src/engine/app/conversation-monitor/app-conversation-operation-queue.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/app/conversation-monitor/app-conversation-operation-queue.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,4 +1,5 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. @@ -6,78 +7,80 @@ private class Geary.App.ConversationOperationQueue : BaseObject { public bool is_processing { get; private set; default = false; } - public Geary.SimpleProgressMonitor progress_monitor { get; private set; default = - new Geary.SimpleProgressMonitor(Geary.ProgressType.ACTIVITY); } - - private Geary.Nonblocking.Mailbox mailbox - = new Geary.Nonblocking.Mailbox(); + + /** Tracks progress running operations in this queue. */ + public Geary.ProgressMonitor progress_monitor { get; private set; } + + private Geary.Nonblocking.Queue mailbox + = new Geary.Nonblocking.Queue.fifo(); private Geary.Nonblocking.Spinlock processing_done_spinlock = new Geary.Nonblocking.Spinlock(); - + + /** Fired when an error occurs executing an operation. */ + public signal void operation_error(ConversationOperation op, Error err); + + public ConversationOperationQueue(ProgressMonitor progress) { + this.progress_monitor = progress; + } + public void clear() { mailbox.clear(); } - + public void add(ConversationOperation op) { - // There should only ever be one FillWindowOperation at a time. - FillWindowOperation? fill_op = op as FillWindowOperation; - if (fill_op != null) { - Gee.Collection removed - = mailbox.revoke_matching(o => o is FillWindowOperation); - - // If there were any "insert" fill window ops, preserve that flag, - // as otherwise we might miss some data. - if (!fill_op.is_insert) { - foreach (ConversationOperation removed_op in removed) { - FillWindowOperation? removed_fill = removed_op as FillWindowOperation; - assert(removed_fill != null); - - if (removed_fill.is_insert) { - fill_op.is_insert = true; - break; - } + bool add_op = true; + + if (!op.allow_duplicates) { + Type op_type = op.get_type(); + foreach (ConversationOperation other in this.mailbox.get_all()) { + if (other.get_type() == op_type) { + add_op = false; + break; } } } - - mailbox.send(op); + + if (add_op) { + this.mailbox.send(op); + } } - - public async void stop_processing_async(Cancellable? cancellable) { - clear(); - add(new TerminateOperation()); - - try { + + public async void stop_processing_async(Cancellable? cancellable) + throws Error { + if (this.is_processing) { + clear(); + add(new TerminateOperation()); yield processing_done_spinlock.wait_async(cancellable); - } catch (Error e) { - debug("Error waiting for conversation operation queue to finish processing: %s", - e.message); } } - + public async void run_process_async() { is_processing = true; - + for (;;) { ConversationOperation op; try { - op = yield mailbox.recv_async(); + op = yield mailbox.receive(); } catch (Error e) { debug("Error processing in conversation operation mailbox: %s", e.message); break; } if (op is TerminateOperation) break; - + if (!progress_monitor.is_in_progress) progress_monitor.notify_start(); - - yield op.execute_async(); - + + try { + yield op.execute_async(); + } catch (Error err) { + operation_error(op, err); + } + if (mailbox.size == 0) progress_monitor.notify_finish(); } - + is_processing = false; processing_done_spinlock.blind_notify(); } diff -Nru geary-0.12.4/src/engine/app/conversation-monitor/app-conversation-operation.vala geary-3.32.0/src/engine/app/conversation-monitor/app-conversation-operation.vala --- geary-0.12.4/src/engine/app/conversation-monitor/app-conversation-operation.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/app/conversation-monitor/app-conversation-operation.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,15 +1,34 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. + * Copyright 2018 Michael Gratton * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ -private abstract class Geary.App.ConversationOperation : BaseObject { +/** + * An internal operation used to keep conversations up to date. + * + * Classes implementing this interface are used by {@link + * ConversationMonitor} to asynchronously keep conversations up to + * date as messages are added to, updated, and removed from folders. + */ +internal abstract class Geary.App.ConversationOperation : BaseObject { + + + /** Determines if multiple instances of this operation can be queued. */ + public bool allow_duplicates { get; private set; } + + /** The monitor this operation will be applied to. */ protected weak ConversationMonitor? monitor = null; - - public ConversationOperation(ConversationMonitor? monitor) { + + + protected ConversationOperation(ConversationMonitor? monitor, + bool allow_duplicates = true) { this.monitor = monitor; + this.allow_duplicates = allow_duplicates; } - - public abstract async void execute_async(); + + public abstract async void execute_async() throws Error; + } diff -Nru geary-0.12.4/src/engine/app/conversation-monitor/app-conversation-set.vala geary-3.32.0/src/engine/app/conversation-monitor/app-conversation-set.vala --- geary-0.12.4/src/engine/app/conversation-monitor/app-conversation-set.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/app/conversation-monitor/app-conversation-set.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,46 +1,58 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. + * Copyright 2017-2019 Michael Gratton * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ +/** + * Creates and maintains set of conversations by adding and removing email. + */ private class Geary.App.ConversationSet : BaseObject { + + + /** The base folder for this set of conversations. */ + public Folder base_folder { get; private set; } + + /** Determines the number of conversations in the set. */ + public int size { get { return _conversations.size; } } + + /** Determines the set contains no conversations. */ + public bool is_empty { get { return _conversations.is_empty; } } + + /** Returns a read-only view of conversations in the set. */ + public Gee.Set read_only_view { + owned get { return _conversations.read_only_view; } + } + + private Gee.Set _conversations = new Gee.HashSet(); - + // Maps email ids to conversations. private Gee.HashMap email_id_map = new Gee.HashMap(); - + // Contains the full set of Message IDs theoretically in each conversation, // as determined by the ancestors of all messages in the conversation. private Gee.HashMap logical_message_id_map = new Gee.HashMap(); - - public int size { get { return _conversations.size; } } - public bool is_empty { get { return _conversations.is_empty; } } - public Gee.Collection conversations { - owned get { return _conversations.read_only_view; } - } - - public ConversationSet() { + + + /** + * Constructs a new conversation set. + * + * The `base_folder` argument is the base folder for the + * conversation monitor that owns this set. + */ + public ConversationSet(Folder base_folder) { + this.base_folder = base_folder; } - + public int get_email_count() { return email_id_map.size; } - - public Gee.Collection get_email_identifiers() { - return email_id_map.keys; - } - - public bool contains(Conversation conversation) { - return _conversations.contains(conversation); - } - - public bool has_email_identifier(Geary.EmailIdentifier id) { - return email_id_map.has_key(id); - } - + /** * Return whether the set has the given Message ID. If logical_set, * there's no requirement that any conversation actually contain a message @@ -50,106 +62,37 @@ public bool has_message_id(Geary.RFC822.MessageID message_id) { return logical_message_id_map.has_key(message_id); } - + public Conversation? get_by_email_identifier(Geary.EmailIdentifier id) { return email_id_map.get(id); } - - public void clear_owners() { - foreach (Conversation conversation in _conversations) - conversation.clear_owner(); - } - - // Returns a Collection of zero or more Conversations that have Message-IDs associated with - // the ancestors of the supplied Email ... if more than one, then add_email() should not be - // called - private Gee.Set get_associated_conversations(Geary.Email email) { - Gee.Set? ancestors = email.get_ancestors(); - if (ancestors != null) { - return Geary.traverse(ancestors) - .map_nonnull(a => logical_message_id_map.get(a)) - .to_hash_set(); - } - - return Gee.Set.empty(); - } - + /** - * Add the email (requires Field.REFERENCES) to the mix, potentially - * replacing an existing email with the same id, or creating a new - * conversation if necessary. In the event of a duplicate (as detected by - * Message-ID), the email whose EmailIdentifier has the - * preferred_folder_path will be kept, and the other discarded (note that - * we always prefer an identifier with a non-null folder path over a null - * folder path, regardless of what the non-null path is). Return null if - * we didn't add the email (e.g. it was a dupe and we preferred the - * existing email), or the conversation it was added to. Return in - * added_conversation whether a new conversation was created. + * Adds a collection of emails to conversations in this set. + * + * This method will create and/or merge conversations as + * needed. The collection `emails` contains the messages to be + * added, and for each email in the collection, there should be an + * entry in `id_to_paths` that indicates the folders each message + * is known to belong to. * - * NOTE: Do not call this method if get_associated_conversations() returns a Collection with - * a size greater than one. That indicates the Conversations *must* be merged before adding. + * The three collections returned include any conversation that + * were created, any that had email appended to them (and the + * messages that were appended), and any that were removed due to + * being merged into another. */ - private Conversation? add_email(Geary.Email email, ConversationMonitor monitor, - Geary.FolderPath? preferred_folder_path, Gee.Collection? known_paths, - out bool added_conversation) { - added_conversation = false; - - if (email_id_map.has_key(email.id)) - return null; - - Gee.Set associated = get_associated_conversations(email); - assert(associated.size <= 1); - - Conversation? conversation = null; - if (associated.size == 1) - conversation = Collection.get_first(associated); - - if (conversation == null) { - conversation = new Conversation(monitor); - _conversations.add(conversation); - - added_conversation = true; - } - - add_email_to_conversation(conversation, email, known_paths); - - return conversation; - } - - private void add_email_to_conversation(Conversation conversation, Geary.Email email, - Gee.Collection? known_paths) { - if (!conversation.add(email, known_paths)) { - error("Couldn't add duplicate email %s to conversation %s", - email.id.to_string(), conversation.to_string()); - } - - email_id_map.set(email.id, conversation); - - Gee.Set? ancestors = email.get_ancestors(); - if (ancestors != null) { - foreach (Geary.RFC822.MessageID ancestor in ancestors) - logical_message_id_map.set(ancestor, conversation); - } - } - - public async void add_all_emails_async(Gee.Collection emails, - ConversationMonitor monitor, Geary.FolderPath? preferred_folder_path, - out Gee.Collection added, - out Gee.MultiMap appended, - out Gee.Collection removed_due_to_merge, - Cancellable? cancellable) throws Error { - // Get known paths for all emails - Gee.Map? id_map = Email.emails_to_map(emails); - Gee.MultiMap? id_to_paths = null; - if (id_map != null) { - id_to_paths = yield monitor.folder.account.get_containing_folders_async(id_map.keys, - cancellable); - } - - Gee.HashSet _added = new Gee.HashSet(); - Gee.HashMultiMap _appended - = new Gee.HashMultiMap(); - Gee.HashSet _removed_due_to_merge = new Gee.HashSet(); + public void add_all_emails(Gee.Collection emails, + Gee.MultiMap id_to_paths, + out Gee.Collection added, + out Gee.MultiMap appended, + out Gee.Collection removed_due_to_merge) { + Gee.HashSet _added = + new Gee.HashSet(); + Gee.HashMultiMap _appended = + new Gee.HashMultiMap(); + Gee.HashSet _removed_due_to_merge = + new Gee.HashSet(); + foreach (Geary.Email email in emails) { Gee.Set associated = get_associated_conversations(email); if (associated.size > 1) { @@ -162,257 +105,297 @@ // By doing this first, it prevents ConversationSet getting itself into a bad state // where more than one Conversation thinks it "owns" a Message-ID debug("Merging %d conversations due new email associating with all...", associated.size); - + // Note that this call will modify the List so it only holds the to-be-axed // Conversations Gee.Set moved_email = new Gee.HashSet(); - Conversation dest = yield merge_conversations_async(monitor, associated, moved_email, - cancellable); + Conversation dest = merge_conversations( + associated, moved_email + ); assert(!associated.contains(dest)); - + // remove the remaining conversations from the added/appended Collections _added.remove_all(associated); foreach (Conversation removed_conversation in associated) _appended.remove_all(removed_conversation); - + // but notify caller they were merged away _removed_due_to_merge.add_all(associated); - + // the dest was always appended to, never created if (!_added.contains(dest)) { foreach (Geary.Email moved in moved_email) _appended.set(dest, moved); } - - // Nasty ol' Email won't cause problems now -- but let's check anyway! - assert(get_associated_conversations(email).size <= 1); } - - bool added_conversation; - Conversation? conversation = add_email( - email, monitor, preferred_folder_path, - (id_to_paths != null) ? id_to_paths.get(email.id) : null, - out added_conversation); - - if (conversation == null) - continue; - - if (added_conversation) { - _added.add(conversation); - } else { - if (!_added.contains(conversation)) - _appended.set(conversation, email); + + Conversation? conversation = null; + bool added_conversation = false; + Gee.Collection? known_paths = id_to_paths.get(email.id); + if (known_paths != null) { + // Don't add an email with no known paths - it may + // have been removed after being listed for adding. + conversation = add_email( + email, known_paths, out added_conversation + ); + } + + if (conversation != null) { + if (added_conversation) { + _added.add(conversation); + } else { + if (!_added.contains(conversation)) + _appended.set(conversation, email); + } } } - + added = _added; appended = _appended; removed_due_to_merge = _removed_due_to_merge; } - + + /** + * Removes a number of emails from conversations in this set. + * + * This method will remove and/or trim conversations as + * needed. The collection `emails_ids` contains the identifiers + * of emails to be removed. + * + * The returned collections include any conversations that were + * removed (if all of their emails were removed), and any that + * were trimmed and the emails that were trimmed from it, + * respectively. + */ + public void remove_all_emails_by_identifier(FolderPath source_path, + Gee.Collection ids, + Gee.Collection removed, + Gee.MultiMap trimmed) { + Gee.Set remaining = new Gee.HashSet(); + + foreach (Geary.EmailIdentifier id in ids) { + Conversation? conversation = email_id_map.get(id); + // The conversation could be null if the conversation + // monitor only goes back a few emails, but something old + // gets removed. It's especially likely when changing + // search terms in the search folder. + if (conversation != null) { + // Conditionally remove email from its conversation + Geary.Email? email = conversation.get_email_by_id(id); + if (email != null) { + switch (conversation.get_folder_count(id)) { + case 0: + warning("Email %s conversation %s not in any folders", + id.to_string(), conversation.to_string()); + break; + + case 1: + remove_email_from_conversation(conversation, email); + trimmed.set(conversation, email); + break; + + default: + conversation.remove_path(id, source_path); + break; + } + } + + if (conversation.get_count() == 0) { + Logging.debug( + Logging.Flag.CONVERSATIONS, + "Conversation %s evaporated: No messages remains", + conversation.to_string() + ); + removed.add(conversation); + remaining.remove(conversation); + trimmed.remove_all(conversation); + remove_conversation(conversation); + } else { + remaining.add(conversation); + } + } + } + + if (source_path.equal_to(this.base_folder.path)) { + // Now that all email have been processed, check reach + // remaining conversation to ensure it has at least one + // email in the base folder. It might not if remaining + // email in the conversation also exists in another + // folder, and so is especially likely for servers that + // have an All Email folder, since email will likely be in + // two different folders. + foreach (Conversation conversation in remaining) { + if (conversation.get_count_in_folder(source_path) == 0) { + Logging.debug( + Logging.Flag.CONVERSATIONS, + "Conversation %s dropped: No messages in base folder remain", + conversation.to_string() + ); + removed.add(conversation); + trimmed.remove_all(conversation); + remove_conversation(conversation); + } + } + } + } + + /** + * Removes a conversation from the set. + */ + public void remove_conversation(Conversation conversation) { + Gee.Collection conversation_emails = conversation.get_emails( + Conversation.Ordering.NONE, // ordering + Conversation.Location.ANYWHERE, // location + null, // blacklist + false // filter deleted (false, so we remove emails that are flagged for deletion too) + ); + + foreach (Geary.Email conversation_email in conversation_emails) + remove_email_from_conversation(conversation, conversation_email); + + if (!_conversations.remove(conversation)) + error("Conversation %s already removed from set", conversation.to_string()); + } + + // Returns a Collection of zero or more Conversations that have Message-IDs associated with + // the ancestors of the supplied Email ... if more than one, then add_email() should not be + // called + private Gee.Set get_associated_conversations(Geary.Email email) { + Gee.Set? ancestors = email.get_ancestors(); + if (ancestors != null) { + return Geary.traverse(ancestors) + .map_nonnull(a => logical_message_id_map.get(a)) + .to_hash_set(); + } + + return Gee.Set.empty(); + } + + /** + * Conditionally adds an email to a conversation. + * + * The given email will be added to new conversation if there are + * not any associated conversations, added to an existing + * conversation if it does not exist in an associated + * conversation, otherwise if in an existing conversation, + * `known_paths` will be merged with the email's paths in that + * conversation. + * + * Returns the conversation the email was strictly added to, else + * `null` if the conversation was simply merged. The parameter + * `added_conversation` is set `true` if the returned conversation + * was created, else it is set to `false`. + */ + private Conversation? add_email(Geary.Email email, + Gee.Collection? known_paths, + out bool added_conversation) { + added_conversation = false; + + Conversation? conversation = email_id_map.get(email.id); + if (conversation != null) { + // Exists in a conversation, so re-add it directly to + // merge its paths + conversation.add(email, known_paths); + // Don't give the caller the idea that the email was + // added + conversation = null; + } else { + Gee.Set associated = + get_associated_conversations(email); + conversation = Collection.get_first(associated); + if (conversation == null) { + // Not in or related to any existing conversations, so + // create one + conversation = new Conversation(this.base_folder); + _conversations.add(conversation); + added_conversation = true; + } + + // Add it and update the set + add_email_to_conversation(conversation, email, known_paths); + } + + return conversation; + } + + /** + * Unconditionally adds an email to a conversation. + */ + private void add_email_to_conversation(Conversation conversation, Geary.Email email, + Gee.Collection? known_paths) { + if (!conversation.add(email, known_paths)) { + error("Couldn't add duplicate email %s to conversation %s", + email.id.to_string(), conversation.to_string()); + } + + email_id_map.set(email.id, conversation); + + Gee.Set? ancestors = email.get_ancestors(); + if (ancestors != null) { + foreach (Geary.RFC822.MessageID ancestor in ancestors) + logical_message_id_map.set(ancestor, conversation); + } + } + // This method will remove the destination (merged) Conversation from the List and return it // as the result, along with a Collection of email that must be merged into it - private async Conversation merge_conversations_async(ConversationMonitor monitor, - Gee.Set conversations, Gee.Set moved_email, - Cancellable? cancellable) throws Error { + private Conversation merge_conversations(Gee.Set conversations, + Gee.Set moved_email) { assert(conversations.size > 0); - + // find the largest conversation and merge the others into it Conversation? dest = null; foreach (Conversation conversation in conversations) { if (dest == null || conversation.get_count() > dest.get_count()) dest = conversation; } - + // remove the largest from the list so it's not included in the Collection of source // conversations merged into it bool removed = conversations.remove(dest); assert(removed); - - foreach (Conversation conversation in conversations) - moved_email.add_all(conversation.get_emails(Conversation.Ordering.NONE)); - - // convert total sum of Emails to move into map of ID -> Email - Gee.Map? id_map = Geary.Email.emails_to_map(moved_email); - // there better be some Email here, otherwise things are really hosed - assert(id_map != null && id_map.size > 0); - - // remove using the standard call, to ensure all state is updated - Gee.MultiMap trimmed_conversations; - Gee.Collection removed_conversations; - remove_all_emails_by_identifier(id_map.keys, out removed_conversations, out trimmed_conversations); - - // Conversations should have been removed, not trimmed, and it better have only been the - // conversations we're merging - assert(trimmed_conversations.size == 0); - assert(removed_conversations.size == conversations.size); - foreach (Conversation conversation in conversations) - assert(removed_conversations.contains(conversation)); - - // Get known paths for all emails being moved - Gee.MultiMap? id_to_paths = - yield monitor.folder.account.get_containing_folders_async(id_map.keys, cancellable); - - // now add all that email back to the destination Conversation - foreach (Geary.Email moved in moved_email) - add_email_to_conversation(dest, moved, (id_to_paths != null) ? id_to_paths.get(moved.id) : null); - - return dest; - } - - private void remove_email_from_conversation(Conversation conversation, Geary.Email email) { - // Be very strict about our internal state getting out of whack, since - // it would indicate a nasty error in our logic that we need to fix. - if (!email_id_map.unset(email.id)) - error("Email %s already removed from conversation set", email.id.to_string()); - - Gee.Set? removed_message_ids = conversation.remove(email); - if (removed_message_ids != null) { - foreach (Geary.RFC822.MessageID removed_message_id in removed_message_ids) { - if (!logical_message_id_map.unset(removed_message_id)) { - error("Message ID %s already removed from conversation set logical map", - removed_message_id.to_string()); + + // Collect all emails and their paths from all conversations + // to be merged, then remove those conversations + Gee.MultiMap? id_to_paths = + new Gee.HashMultiMap(); + foreach (Conversation conversation in conversations) { + foreach (EmailIdentifier id in conversation.path_map.get_keys()) { + moved_email.add(conversation.get_email_by_id(id)); + foreach (FolderPath path in conversation.path_map.get(id)) { + id_to_paths.set(id, path); } } - } - } - - private void remove_conversation(Conversation conversation) { - foreach (Geary.Email conversation_email in conversation.get_emails(Conversation.Ordering.NONE)) - remove_email_from_conversation(conversation, conversation_email); - - if (!_conversations.remove(conversation)) - error("Conversation %s already removed from set", conversation.to_string()); - - conversation.clear_owner(); - } - - private Conversation? remove_email_by_identifier(Geary.EmailIdentifier id, - out Geary.Email? removed_email, out bool removed_conversation) { - removed_email = null; - removed_conversation = false; - - Conversation? conversation = email_id_map.get(id); - // This can happen when the conversation monitor only goes back a few - // emails, but something old gets removed. It's especially likely when - // changing search terms in the search folder. - if (conversation == null) - return null; - - Geary.Email? email = conversation.get_email_by_id(id); - if (email == null) - error("Unable to locate email %s in conversation %s", id.to_string(), conversation.to_string()); - removed_email = email; - - remove_email_from_conversation(conversation, email); - - // Evaporate conversations with no more messages. - if (conversation.get_count() == 0) { - debug("Removing email %s evaporates conversation %s", id.to_string(), conversation.to_string()); remove_conversation(conversation); - - removed_conversation = true; } - - return conversation; - } - - public void remove_all_emails_by_identifier(Gee.Collection ids, - out Gee.Collection removed, - out Gee.MultiMap trimmed) { - Gee.HashSet _removed = new Gee.HashSet(); - Gee.HashMultiMap _trimmed - = new Gee.HashMultiMap(); - - foreach (Geary.EmailIdentifier id in ids) { - Geary.Email email; - bool removed_conversation; - Conversation? conversation = remove_email_by_identifier( - id, out email, out removed_conversation); - - if (conversation == null) - continue; - - if (removed_conversation) { - if (_trimmed.contains(conversation)) - _trimmed.remove_all(conversation); - _removed.add(conversation); - } else { - _trimmed.set(conversation, email); - } + + // Now add all that email back to the destination Conversation + foreach (Geary.Email moved in moved_email) { + add_email_to_conversation(dest, moved, id_to_paths.get(moved.id)); } - - removed = _removed; - trimmed = _trimmed; + + return dest; } - + /** - * Make sure that the conversation has some emails in the given folder, and - * remove the conversation if not. Return true if there were emails in the - * folder, or false if the conversation was removed. + * Unconditionally removes an email from a conversation. */ - public async bool check_conversation_in_folder_async(Conversation conversation, Geary.Account account, - Geary.FolderPath required_folder_path, Cancellable? cancellable) throws Error { - if ((yield conversation.get_count_in_folder_async(account, required_folder_path, cancellable)) == 0) { - debug("Evaporating conversation %s because it has no emails in %s", - conversation.to_string(), required_folder_path.to_string()); - remove_conversation(conversation); - - return false; + private void remove_email_from_conversation(Conversation conversation, Geary.Email email) { + if (!this.email_id_map.unset(email.id)) { + warning("Email %s already removed from conversation set", + email.id.to_string()); } - - return true; - } - - /** - * Check a set of emails using check_conversation_in_folder_async(), return - * the set of emails that were removed due to not being in the folder. - */ - public async Gee.Collection check_conversations_in_folder_async( - Gee.Collection conversations, Geary.Account account, - Geary.FolderPath required_folder_path, Cancellable? cancellable) { - Gee.ArrayList evaporated = new Gee.ArrayList(); - foreach (Geary.App.Conversation conversation in conversations) { - try { - if (!(yield check_conversation_in_folder_async( - conversation, account, required_folder_path, cancellable))) { - evaporated.add(conversation); + + Gee.Set? removed_message_ids = conversation.remove(email); + debug("Removed %d messages from conversation", removed_message_ids != null ? removed_message_ids.size : 0); + if (removed_message_ids != null) { + foreach (Geary.RFC822.MessageID removed_message_id in removed_message_ids) { + if (!logical_message_id_map.unset(removed_message_id)) { + error("Message ID %s already removed from conversation set logical map", + removed_message_id.to_string()); } - } catch (Error e) { - debug("Unable to check conversation %s for messages in %s: %s", - conversation.to_string(), required_folder_path.to_string(), e.message); } } - - return evaporated; - } - - public async void remove_emails_and_check_in_folder_async( - Gee.Collection ids, Geary.Account account, - Geary.FolderPath required_folder_path, out Gee.Collection removed, - out Gee.MultiMap trimmed, Cancellable? cancellable) { - Gee.HashSet _removed = new Gee.HashSet(); - Gee.HashMultiMap _trimmed - = new Gee.HashMultiMap(); - - Gee.Collection initial_removed; - Gee.MultiMap initial_trimmed; - remove_all_emails_by_identifier(ids, out initial_removed, out initial_trimmed); - - Gee.Collection evaporated = yield check_conversations_in_folder_async( - initial_trimmed.get_keys(), account, required_folder_path, cancellable); - - _removed.add_all(initial_removed); - _removed.add_all(evaporated); - - foreach (Conversation conversation in initial_trimmed.get_keys()) { - if (!(conversation in _removed)) { - Geary.Collection.multi_map_set_all( - _trimmed, conversation, initial_trimmed.get(conversation)); - } - } - - removed = _removed; - trimmed = _trimmed; } + } diff -Nru geary-0.12.4/src/engine/app/conversation-monitor/app-external-append-operation.vala geary-3.32.0/src/engine/app/conversation-monitor/app-external-append-operation.vala --- geary-0.12.4/src/engine/app/conversation-monitor/app-external-append-operation.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/app/conversation-monitor/app-external-append-operation.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,21 +1,34 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ private class Geary.App.ExternalAppendOperation : ConversationOperation { + private Geary.Folder folder; private Gee.Collection appended_ids; - - public ExternalAppendOperation(ConversationMonitor monitor, Geary.Folder folder, - Gee.Collection appended_ids) { + + public ExternalAppendOperation(ConversationMonitor monitor, + Geary.Folder folder, + Gee.Collection appended_ids) { base(monitor); this.folder = folder; this.appended_ids = appended_ids; } - - public override async void execute_async() { - yield monitor.external_append_emails_async(folder, appended_ids); + + public override async void execute_async() throws Error { + if (!this.monitor.get_search_folder_blacklist().contains(folder.path) && + !this.monitor.conversations.is_empty) { + debug("%d out of folder message(s) appended to %s, fetching to add to conversations...", + this.appended_ids.size, + this.folder.to_string()); + + yield this.monitor.external_load_by_sparse_id( + this.folder, this.appended_ids, Geary.Folder.ListFlags.NONE + ); + } } + } diff -Nru geary-0.12.4/src/engine/app/conversation-monitor/app-fill-window-operation.vala geary-3.32.0/src/engine/app/conversation-monitor/app-fill-window-operation.vala --- geary-0.12.4/src/engine/app/conversation-monitor/app-fill-window-operation.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/app/conversation-monitor/app-fill-window-operation.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,18 +1,56 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. + * Copyright 2018 Michael Gratton * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ private class Geary.App.FillWindowOperation : ConversationOperation { - public bool is_insert { get; internal set; } - - public FillWindowOperation(ConversationMonitor monitor, bool is_insert) { - base(monitor); - this.is_insert = is_insert; + + + // Maximum and minimum number of messages to load in one fill + // operation. The maximum exists to retain some degree of + // responsiveness when loading conversations, given we must load + // the conversation closure for each message loaded here: Loading + // a single email might cause a conversation with tens of messages + // to also have to be pulled in, so the max provides some kind up + // upper bound to mitigate huge loads and start delivering + // conversations sooner rather than later. The minimum ensures + // that enough new messages are found in one operation to justify + // the expense. + private const int MAX_FILL_COUNT = 20; + private const int MIN_FILL_COUNT = 5; + + + public FillWindowOperation(ConversationMonitor monitor) { + base(monitor, false); } - - public override async void execute_async() { - yield monitor.fill_window_async(is_insert); + + public override async void execute_async() throws Error { + int num_to_load = (int) ( + (this.monitor.min_window_count - this.monitor.conversations.size) + ); + if (num_to_load < MIN_FILL_COUNT) { + num_to_load = MIN_FILL_COUNT; + } else if (num_to_load > MAX_FILL_COUNT) { + num_to_load = MAX_FILL_COUNT; + } + + debug( + "Filling %d messages in %s...", + num_to_load, this.monitor.base_folder.to_string() + ); + + int loaded = yield this.monitor.load_by_id_async( + this.monitor.window_lowest, num_to_load + ); + + // Check to see if we need any more, but only if we actually + // loaded some, so we don't keep loop loading when we have + // already loaded all in the folder. + if (loaded == num_to_load) { + this.monitor.check_window_count(); + } } } diff -Nru geary-0.12.4/src/engine/app/conversation-monitor/app-insert-operation.vala geary-3.32.0/src/engine/app/conversation-monitor/app-insert-operation.vala --- geary-0.12.4/src/engine/app/conversation-monitor/app-insert-operation.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/src/engine/app/conversation-monitor/app-insert-operation.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,45 @@ +/* + * Copyright 2016 Software Freedom Conservancy Inc. + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +/** + * Handles an insertion of messages from a monitor's base folder. + */ +private class Geary.App.InsertOperation : ConversationOperation { + + + private Gee.Collection inserted_ids; + + public InsertOperation(ConversationMonitor monitor, + Gee.Collection inserted_ids) { + base(monitor); + this.inserted_ids = inserted_ids; + } + + public override async void execute_async() throws Error { + Geary.EmailIdentifier? lowest = this.monitor.window_lowest; + Gee.Collection? to_insert = null; + if (lowest != null) { + to_insert = new Gee.LinkedList(); + foreach (EmailIdentifier inserted in this.inserted_ids) { + if (lowest.natural_sort_comparator(inserted) < 0) { + to_insert.add(inserted); + } + } + } else { + to_insert = this.inserted_ids; + } + + debug("Inserting %d messages in %s after %d inserted...", + to_insert.size, + this.monitor.base_folder.to_string(), + this.inserted_ids.size); + yield this.monitor.load_by_sparse_id(to_insert); + + // Check to see if we need any more + this.monitor.check_window_count(); + } +} diff -Nru geary-0.12.4/src/engine/app/conversation-monitor/app-local-load-operation.vala geary-3.32.0/src/engine/app/conversation-monitor/app-local-load-operation.vala --- geary-0.12.4/src/engine/app/conversation-monitor/app-local-load-operation.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/app/conversation-monitor/app-local-load-operation.vala 1970-01-01 00:00:00.000000000 +0000 @@ -1,15 +0,0 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. - * - * This software is licensed under the GNU Lesser General Public License - * (version 2.1 or later). See the COPYING file in this distribution. - */ - -private class Geary.App.LocalLoadOperation : ConversationOperation { - public LocalLoadOperation(ConversationMonitor monitor) { - base(monitor); - } - - public override async void execute_async() { - yield monitor.local_load_async(); - } -} diff -Nru geary-0.12.4/src/engine/app/conversation-monitor/app-local-search-operation.vala geary-3.32.0/src/engine/app/conversation-monitor/app-local-search-operation.vala --- geary-0.12.4/src/engine/app/conversation-monitor/app-local-search-operation.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/app/conversation-monitor/app-local-search-operation.vala 2019-03-17 13:39:29.000000000 +0000 @@ -11,10 +11,10 @@ public Geary.Email.Field required_fields; public Gee.Collection? blacklist; public Geary.EmailFlags? flag_blacklist; - + // OUT public Gee.MultiMap? emails = null; - + public LocalSearchOperation(Geary.Account account, RFC822.MessageID message_id, Geary.Email.Field required_fields, Gee.Collection blacklist, Geary.EmailFlags? flag_blacklist) { @@ -24,11 +24,11 @@ this.blacklist = blacklist; this.flag_blacklist = flag_blacklist; } - + public override async Object? execute_async(Cancellable? cancellable) throws Error { emails = yield account.local_search_message_id_async(message_id, required_fields, false, blacklist, flag_blacklist); - + return null; } } diff -Nru geary-0.12.4/src/engine/app/conversation-monitor/app-remove-operation.vala geary-3.32.0/src/engine/app/conversation-monitor/app-remove-operation.vala --- geary-0.12.4/src/engine/app/conversation-monitor/app-remove-operation.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/app/conversation-monitor/app-remove-operation.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,18 +1,51 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ private class Geary.App.RemoveOperation : ConversationOperation { + + private Geary.Folder source_folder; private Gee.Collection removed_ids; - - public RemoveOperation(ConversationMonitor monitor, Gee.Collection removed_ids) { + + public RemoveOperation(ConversationMonitor monitor, + Geary.Folder source_folder, + Gee.Collection removed_ids) { base(monitor); + this.source_folder = source_folder; this.removed_ids = removed_ids; } - - public override async void execute_async() { - yield monitor.remove_emails_async(removed_ids); + + public override async void execute_async() throws Error { + debug("%d messages(s) removed from %s, trimming/removing conversations...", + this.removed_ids.size, this.source_folder.to_string() + ); + + Gee.Set removed = new Gee.HashSet(); + Gee.MultiMap trimmed = + new Gee.HashMultiMap(); + this.monitor.conversations.remove_all_emails_by_identifier( + source_folder.path, + removed_ids, + removed, + trimmed + ); + + + // Fire signals, clean up + this.monitor.removed( + removed, + trimmed, + (this.source_folder == this.monitor.base_folder) ? this.removed_ids : null + ); + + // Check we still have enough conversations if any were + // removed + if (!removed.is_empty) { + this.monitor.check_window_count(); + } } + } diff -Nru geary-0.12.4/src/engine/app/conversation-monitor/app-reseed-operation.vala geary-3.32.0/src/engine/app/conversation-monitor/app-reseed-operation.vala --- geary-0.12.4/src/engine/app/conversation-monitor/app-reseed-operation.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/app/conversation-monitor/app-reseed-operation.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,18 +1,43 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ +/** + * Re-scans the base folder for messages after its remote has opened. + * + * The reseed in effect checks for any existing message that did not + * satisfy the email field requirements for the conversation monitor + * or the required fields passed to its constructor, causing these + * fields to be downloaded from the remote. + */ private class Geary.App.ReseedOperation : ConversationOperation { - private string why; - - public ReseedOperation(ConversationMonitor monitor, string why) { - base(monitor); - this.why = why; + + + public ReseedOperation(ConversationMonitor monitor) { + base(monitor, false); } - - public override async void execute_async() { - yield monitor.reseed_async(why); + + public override async void execute_async() throws Error { + EmailIdentifier? earliest_id = this.monitor.window_lowest; + if (earliest_id != null) { + debug("Reseeding starting from Email ID %s on opened %s", + earliest_id.to_string(), this.monitor.base_folder.to_string()); + // Some conversations have already been loaded, so check + // from the earliest known right through to the end of the + // vector for updated mesages + yield this.monitor.load_by_id_async( + earliest_id, + int.MAX, + Folder.ListFlags.OLDEST_TO_NEWEST | Folder.ListFlags.INCLUDING_ID + ); + } else { + // No conversations are present, so do a check to get the + // side effect of queuing a fill operation. + this.monitor.check_window_count(); + } } + } diff -Nru geary-0.12.4/src/engine/app/conversation-monitor/app-terminate-operation.vala geary-3.32.0/src/engine/app/conversation-monitor/app-terminate-operation.vala --- geary-0.12.4/src/engine/app/conversation-monitor/app-terminate-operation.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/app/conversation-monitor/app-terminate-operation.vala 2019-03-17 13:39:29.000000000 +0000 @@ -8,7 +8,7 @@ public TerminateOperation() { base(null); } - + public override async void execute_async() { } } diff -Nru geary-0.12.4/src/engine/app/email-store/app-async-folder-operation.vala geary-3.32.0/src/engine/app/email-store/app-async-folder-operation.vala --- geary-0.12.4/src/engine/app/email-store/app-async-folder-operation.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/app/email-store/app-async-folder-operation.vala 2019-03-17 13:39:29.000000000 +0000 @@ -6,7 +6,7 @@ private abstract class Geary.App.AsyncFolderOperation : BaseObject { public abstract Type folder_type { get; } - + public abstract async Gee.Collection execute_async( Geary.Folder folder, Gee.Collection ids, Cancellable? cancellable) throws Error; diff -Nru geary-0.12.4/src/engine/app/email-store/app-copy-operation.vala geary-3.32.0/src/engine/app/email-store/app-copy-operation.vala --- geary-0.12.4/src/engine/app/email-store/app-copy-operation.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/app/email-store/app-copy-operation.vala 2019-03-17 13:39:29.000000000 +0000 @@ -6,19 +6,19 @@ private class Geary.App.CopyOperation : Geary.App.AsyncFolderOperation { public override Type folder_type { get { return typeof(Geary.FolderSupport.Copy); } } - + public Geary.FolderPath destination; - + public CopyOperation(Geary.FolderPath destination) { this.destination = destination; } - + public override async Gee.Collection execute_async( Geary.Folder folder, Gee.Collection ids, Cancellable? cancellable) throws Error { Geary.FolderSupport.Copy? copy = folder as Geary.FolderSupport.Copy; assert(copy != null); - + Gee.List list = Geary.Collection.to_array_list(ids); yield copy.copy_email_async(list, destination, cancellable); diff -Nru geary-0.12.4/src/engine/app/email-store/app-fetch-operation.vala geary-3.32.0/src/engine/app/email-store/app-fetch-operation.vala --- geary-0.12.4/src/engine/app/email-store/app-fetch-operation.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/app/email-store/app-fetch-operation.vala 2019-03-17 13:39:29.000000000 +0000 @@ -6,23 +6,23 @@ private class Geary.App.FetchOperation : Geary.App.AsyncFolderOperation { public override Type folder_type { get { return typeof(Geary.Folder); } } - + public Geary.Email? result = null; public Geary.Email.Field required_fields; public Geary.Folder.ListFlags flags; - + public FetchOperation(Geary.Email.Field required_fields, Geary.Folder.ListFlags flags) { this.required_fields = required_fields; this.flags = flags; } - + public override async Gee.Collection execute_async( Geary.Folder folder, Gee.Collection ids, Cancellable? cancellable) throws Error { assert(result == null); Geary.EmailIdentifier? id = Geary.Collection.get_first(ids); assert(id != null); - + result = yield folder.fetch_email_async( id, required_fields, flags, cancellable); return Geary.iterate(id).to_array_list(); diff -Nru geary-0.12.4/src/engine/app/email-store/app-list-operation.vala geary-3.32.0/src/engine/app/email-store/app-list-operation.vala --- geary-0.12.4/src/engine/app/email-store/app-list-operation.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/app/email-store/app-list-operation.vala 2019-03-17 13:39:29.000000000 +0000 @@ -6,17 +6,17 @@ private class Geary.App.ListOperation : Geary.App.AsyncFolderOperation { public override Type folder_type { get { return typeof(Geary.Folder); } } - + public Gee.HashSet results; public Geary.Email.Field required_fields; public Geary.Folder.ListFlags flags; - + public ListOperation(Geary.Email.Field required_fields, Geary.Folder.ListFlags flags) { results = new Gee.HashSet(); this.required_fields = required_fields; this.flags = flags; } - + public override async Gee.Collection execute_async( Geary.Folder folder, Gee.Collection ids, Cancellable? cancellable) throws Error { diff -Nru geary-0.12.4/src/engine/app/email-store/app-mark-operation.vala geary-3.32.0/src/engine/app/email-store/app-mark-operation.vala --- geary-0.12.4/src/engine/app/email-store/app-mark-operation.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/app/email-store/app-mark-operation.vala 2019-03-17 13:39:29.000000000 +0000 @@ -6,21 +6,21 @@ private class Geary.App.MarkOperation : Geary.App.AsyncFolderOperation { public override Type folder_type { get { return typeof(Geary.FolderSupport.Mark); } } - + public Geary.EmailFlags? flags_to_add; public Geary.EmailFlags? flags_to_remove; - + public MarkOperation(Geary.EmailFlags? flags_to_add, Geary.EmailFlags? flags_to_remove) { this.flags_to_add = flags_to_add; this.flags_to_remove = flags_to_remove; } - + public override async Gee.Collection execute_async( Geary.Folder folder, Gee.Collection ids, Cancellable? cancellable) throws Error { Geary.FolderSupport.Mark? mark = folder as Geary.FolderSupport.Mark; assert(mark != null); - + Gee.List list = Geary.Collection.to_array_list(ids); yield mark.mark_email_async(list, flags_to_add, flags_to_remove, cancellable); diff -Nru geary-0.12.4/src/engine/common/common-message-data.vala geary-3.32.0/src/engine/common/common-message-data.vala --- geary-0.12.4/src/engine/common/common-message-data.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/common/common-message-data.vala 2019-03-17 13:39:29.000000000 +0000 @@ -33,30 +33,30 @@ public abstract class Geary.MessageData.StringMessageData : AbstractMessageData, Gee.Hashable { public string value { get; private set; } - + private uint stored_hash = uint.MAX; - - public StringMessageData(string value) { + + protected StringMessageData(string value) { this.value = value; } - + /** * Default definition of equals is case-sensitive comparison. */ public virtual bool equal_to(StringMessageData other) { if (this == other) return true; - + if (hash() != other.hash()) return false; - + return (value == other.value); } - + public virtual uint hash() { return (stored_hash != uint.MAX) ? stored_hash : (stored_hash = str_hash(value)); } - + public override string to_string() { return value; } @@ -65,19 +65,19 @@ public abstract class Geary.MessageData.IntMessageData : AbstractMessageData, Gee.Hashable { public int value { get; private set; } - - public IntMessageData(int value) { + + protected IntMessageData(int value) { this.value = value; } - + public virtual bool equal_to(IntMessageData other) { return (value == other.value); } - + public virtual uint hash() { return value; } - + public override string to_string() { return value.to_string(); } @@ -86,24 +86,24 @@ public abstract class Geary.MessageData.Int64MessageData : AbstractMessageData, Gee.Hashable { public int64 value { get; private set; } - + private uint stored_hash = uint.MAX; - - public Int64MessageData(int64 value) { + + protected Int64MessageData(int64 value) { this.value = value; } - + public virtual bool equal_to(Int64MessageData other) { if (this == other) return true; - + return (value == other.value); } - + public virtual uint hash() { return (stored_hash != uint.MAX) ? stored_hash : (stored_hash = int64_hash(value)); } - + public override string to_string() { return value.to_string(); } @@ -112,12 +112,12 @@ public abstract class Geary.MessageData.BlockMessageData : AbstractMessageData { public string data_name { get; private set; } public Geary.Memory.Buffer buffer { get; private set; } - - public BlockMessageData(string data_name, Geary.Memory.Buffer buffer) { + + protected BlockMessageData(string data_name, Geary.Memory.Buffer buffer) { this.data_name = data_name; this.buffer = buffer; } - + public override string to_string() { return "%s (%lub)".printf(data_name, buffer.size); } diff -Nru geary-0.12.4/src/engine/db/db-connection.vala geary-3.32.0/src/engine/db/db-connection.vala --- geary-0.12.4/src/engine/db/db-connection.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/db/db-connection.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,16 +1,19 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ /** - * A Connection represents a connection to an open database. Because SQLite uses a - * synchronous interface, all calls are blocking. Db.Database offers asynchronous queries by - * pooling connections and invoking queries from background threads. + * A Connection represents a connection to an open database. * - * Connections are associated with a Database. Use Database.open_connection() to create - * one. + * Because SQLite uses a synchronous interface, all calls are + * blocking. Db.Database offers asynchronous queries by pooling + * connections and invoking queries from background threads. + * + * Connections are associated with a Database. Use + * Database.open_connection() to create one. * * A Connection will close when its last reference is dropped. */ @@ -20,13 +23,13 @@ * Default value is for *no* timeout, that is, the Sqlite will not retry BUSY results. */ public const int DEFAULT_BUSY_TIMEOUT_MSEC = 0; - + /** * This value gives a generous amount of time for SQLite to finish a big write operation and * relinquish the lock to other waiting transactions. */ public const int RECOMMENDED_BUSY_TIMEOUT_MSEC = 60 * 1000; - + private const string PRAGMA_FOREIGN_KEYS = "foreign_keys"; private const string PRAGMA_RECURSIVE_TRIGGERS = "recursive_triggers"; private const string PRAGMA_USER_VERSION = "user_version"; @@ -36,50 +39,52 @@ private const string PRAGMA_FREELIST_COUNT = "freelist_count"; private const string PRAGMA_PAGE_COUNT = "page_count"; private const string PRAGMA_PAGE_SIZE = "page_size"; - + // this is used for logging purposes only; connection numbers mean nothing to SQLite private static int next_cx_number = 0; - + /** * See [[http://www.sqlite.org/c3ref/last_insert_rowid.html]] */ public int64 last_insert_rowid { get { return db.last_insert_rowid(); } } - + /** * See [[http://www.sqlite.org/c3ref/changes.html]] */ public int last_modified_rows { get { return db.changes(); } } - + /** * See [[http://www.sqlite.org/c3ref/total_changes.html]] */ public int total_modified_rows { get { return db.total_changes(); } } - + public weak Database database { get; private set; } - + internal Sqlite.Database db; - + private int cx_number; private int busy_timeout_msec = DEFAULT_BUSY_TIMEOUT_MSEC; - + internal Connection(Database database, int sqlite_flags, Cancellable? cancellable) throws Error { this.database = database; - + lock (next_cx_number) { cx_number = next_cx_number++; } - + check_cancelled("Connection.ctor", cancellable); - + try { - throw_on_error("Connection.ctor", Sqlite.Database.open_v2(database.db_file.get_path(), - out db, sqlite_flags, null)); + throw_on_error( + "Connection.ctor", + Sqlite.Database.open_v2(database.path, out db, sqlite_flags, null) + ); } catch (DatabaseError derr) { // don't throw BUSY error for open unless no db object was returned, as it's possible for // open_v2() to return an error *and* a valid Database object, see: @@ -88,44 +93,44 @@ throw derr; } } - + /** * Execute a plain text SQL statement. More than one SQL statement may be in the string. See * [[http://www.sqlite.org/lang.html]] for more information on SQLite's SQL syntax. * * There is no way to retrieve a result iterator from this call. * - * This may be called from a TransactionMethod called within exec_transaction() or - * Db.Database.exec_transaction_async(). + * This may be called from a TransactionMethod called within + * {@link exec_transaction} or {@link exec_transaction_async}. * * See [[http://www.sqlite.org/c3ref/exec.html]] */ public void exec(string sql, Cancellable? cancellable = null) throws Error { check_cancelled("Connection.exec", cancellable); - + throw_on_error("Connection.exec", db.exec(sql), sql); - + // Don't use Context.log(), which is designed for logging Results and Statements Logging.debug(Logging.Flag.SQL, "exec:\n\t%s", sql); } - + /** * Loads a text file of SQL commands into memory and executes them at once with exec(). * * There is no way to retrieve a result iterator from this call. * - * This can be called from a TransactionMethod called within exec_transaction() or - * Db.Database.exec_transaction_async(). + * This may be called from a TransactionMethod called within + * {@link exec_transaction} or {@link exec_transaction_async}. */ public void exec_file(File file, Cancellable? cancellable = null) throws Error { check_cancelled("Connection.exec_file", cancellable); - + string sql; FileUtils.get_contents(file.get_path(), out sql); - + exec(sql, cancellable); } - + /** * Executes a plain text SQL statement and returns a Result object directly. * This call creates an intermediate Statement object which may be fetched from Result.statement. @@ -133,7 +138,7 @@ public Result query(string sql, Cancellable? cancellable = null) throws Error { return (new Statement(this, sql)).exec(cancellable); } - + /** * Prepares a Statement which may have values bound to it and executed. See * [[http://www.sqlite.org/c3ref/prepare.html]] @@ -141,31 +146,35 @@ public Statement prepare(string sql) throws DatabaseError { return new Statement(this, sql); } - + /** * See set_busy_timeout_msec(). */ public int get_busy_timeout_msec() { return busy_timeout_msec; } - + /** - * Sets busy timeout time in milliseconds. Zero or a negative value indicates that all - * operations that SQLite returns BUSY will be retried until they complete with success or error. - * Otherwise, after said amount of time has transpired, DatabaseError.BUSY will be thrown. + * Sets busy timeout time in milliseconds. * - * This is imperative for exec_transaction() and Db.Database.exec_transaction_async(), because - * those calls will throw a DatabaseError.BUSY call immediately if another transaction has + * Zero or a negative value indicates that all operations that + * SQLite returns BUSY will be retried until they complete with + * success or error. Otherwise, after said amount of time has + * transpired, DatabaseError.BUSY will be thrown. + * + * This is imperative for {@link exec_transaction} {@link + * exec_transaction_async}, because those calls will throw a + * DatabaseError.BUSY call immediately if another transaction has * acquired the reserved or exclusive locks. */ public void set_busy_timeout_msec(int busy_timeout_msec) throws Error { if (this.busy_timeout_msec == busy_timeout_msec) return; - + throw_on_error("Database.set_busy_timeout", db.busy_timeout(busy_timeout_msec)); this.busy_timeout_msec = busy_timeout_msec; } - + /** * Returns the result of a PRAGMA as a boolean. See [[http://www.sqlite.org/pragma.html]] * @@ -180,28 +189,28 @@ case "true": case "on": return true; - + case "0": case "no": case "false": case "off": return false; - + default: debug("Db.Connection.get_pragma_bool: unknown PRAGMA boolean response \"%s\"", response); - + return false; } } - + /** * Sets a boolean PRAGMA value to either "true" or "false". */ public void set_pragma_bool(string name, bool b) throws Error { exec("PRAGMA %s=%s".printf(name, b ? "true" : "false")); } - + /** * Returns the result of a PRAGMA as an integer. See [[http://www.sqlite.org/pragma.html]] * @@ -212,14 +221,14 @@ public int get_pragma_int(string name) throws Error { return query("PRAGMA %s".printf(name)).int_at(0); } - + /** * Sets an integer PRAGMA value. */ public void set_pragma_int(string name, int d) throws Error { exec("PRAGMA %s=%d".printf(name, d)); } - + /** * Returns the result of a PRAGMA as a 64-bit integer. See [[http://www.sqlite.org/pragma.html]] * @@ -230,28 +239,28 @@ public int64 get_pragma_int64(string name) throws Error { return query("PRAGMA %s".printf(name)).int64_at(0); } - + /** * Sets a 64-bit integer PRAGMA value. */ public void set_pragma_int64(string name, int64 ld) throws Error { exec("PRAGMA %s=%s".printf(name, ld.to_string())); } - + /** * Returns the result of a PRAGMA as a string. See [[http://www.sqlite.org/pragma.html]] */ public string get_pragma_string(string name) throws Error { return query("PRAGMA %s".printf(name)).nonnull_string_at(0); } - + /** * Sets a string PRAGMA value. */ public void set_pragma_string(string name, string str) throws Error { exec("PRAGMA %s=%s".printf(name, str)); } - + /** * Returns the user_version number maintained by SQLite. * @@ -262,7 +271,7 @@ public int get_user_version_number() throws Error { return get_pragma_int(PRAGMA_USER_VERSION); } - + /** * Sets the user version number, which is a private number maintained by the user. * VersionedDatabase uses this to maintain the version number of the database. @@ -272,7 +281,7 @@ public void set_user_version_number(int version) throws Error { set_pragma_int(PRAGMA_USER_VERSION, version); } - + /** * Gets the schema version number, which is maintained by SQLite. See * [[http://www.sqlite.org/pragma.html#pragma_schema_version]] @@ -282,84 +291,84 @@ public int get_schema_version_number() throws Error { return get_pragma_int(PRAGMA_SCHEMA_VERSION); } - + /** * See [[http://www.sqlite.org/pragma.html#pragma_foreign_keys]] */ public void set_foreign_keys(bool enabled) throws Error { set_pragma_bool(PRAGMA_FOREIGN_KEYS, enabled); } - + /** * See [[http://www.sqlite.org/pragma.html#pragma_foreign_keys]] */ public bool get_foreign_keys() throws Error { return get_pragma_bool(PRAGMA_FOREIGN_KEYS); } - + /** * See [[http://www.sqlite.org/pragma.html#pragma_recursive_triggers]] */ public void set_recursive_triggers(bool enabled) throws Error { set_pragma_bool(PRAGMA_RECURSIVE_TRIGGERS, enabled); } - + /** * See [[http://www.sqlite.org/pragma.html#pragma_recursive_triggers]] */ public bool get_recursive_triggers() throws Error { return get_pragma_bool(PRAGMA_RECURSIVE_TRIGGERS); } - + /** * See [[http://www.sqlite.org/pragma.html#pragma_secure_delete]] */ public void set_secure_delete(bool enabled) throws Error { set_pragma_bool(PRAGMA_SECURE_DELETE, enabled); } - + /** * See [[http://www.sqlite.org/pragma.html#pragma_secure_delete]] */ public bool get_secure_delete() throws Error { return get_pragma_bool(PRAGMA_SECURE_DELETE); } - + /** * See [[http://www.sqlite.org/pragma.html#pragma_synchronous]] */ public void set_synchronous(SynchronousMode mode) throws Error { set_pragma_string(PRAGMA_SYNCHRONOUS, mode.sql()); } - + /** * See [[http://www.sqlite.org/pragma.html#pragma_synchronous]] */ public SynchronousMode get_synchronous() throws Error { return SynchronousMode.parse(get_pragma_string(PRAGMA_SYNCHRONOUS)); } - + /** * See [[https://www.sqlite.org/pragma.html#pragma_freelist_count]] */ public int64 get_free_page_count() throws Error { return get_pragma_int64(PRAGMA_FREELIST_COUNT); } - + /** * See [[https://www.sqlite.org/pragma.html#pragma_page_count]] */ public int64 get_total_page_count() throws Error { return get_pragma_int64(PRAGMA_PAGE_COUNT); } - + /** * See [[https://www.sqlite.org/pragma.html#pragma_page_size]] */ public int get_page_size() throws Error { return get_pragma_int(PRAGMA_PAGE_SIZE); } - + /** * Executes one or more queries inside an SQLite transaction. This call will initiate a * transaction according to the TransactionType specified (although this is merely an @@ -382,10 +391,10 @@ } catch (Error err) { if (!(err is IOError.CANCELLED)) debug("Connection.exec_transaction: unable to %s: %s", type.sql(), err.message); - + throw err; } - + // If transaction throws an Error, must rollback, always TransactionOutcome outcome = TransactionOutcome.ROLLBACK; Error? caught_err = null; @@ -395,10 +404,10 @@ } catch (Error err) { if (!(err is IOError.CANCELLED)) debug("Connection.exec_transaction: transaction threw error: %s", err.message); - + caught_err = err; } - + // commit/rollback ... don't use Cancellable for TransactionOutcome because it's SQL *must* // execute in order to unlock the database try { @@ -407,19 +416,42 @@ debug("Connection.exec_transaction: Unable to %s transaction: %s", outcome.to_string(), err.message); } - + if (caught_err != null) throw caught_err; - + return outcome; } - + + /** + * Starts a new asynchronous transaction for this connection. + * + * Asynchronous transactions are handled via background + * threads. The background thread calls {@link exec_transaction}; + * see that method for more information about coding a + * transaction. The only caveat is that the {@link + * TransactionMethod} passed to it must be thread-safe. + * + * Throws {@link DatabaseError.OPEN_REQUIRED} if not open. + */ + public async TransactionOutcome exec_transaction_async(TransactionType type, + TransactionMethod cb, + Cancellable? cancellable) + throws Error { + // create job to execute in background thread + TransactionAsyncJob job = new TransactionAsyncJob( + this, type, cb, cancellable + ); + + this.database.add_async_job(job); + return yield job.wait_for_completion_async(); + } + public override Connection? get_connection() { return this; } - + public string to_string() { - return "[%d] %s".printf(cx_number, database.db_file.get_basename()); + return "[%d] %s".printf(cx_number, database.path); } } - diff -Nru geary-0.12.4/src/engine/db/db-context.vala geary-3.32.0/src/engine/db/db-context.vala --- geary-0.12.4/src/engine/db/db-context.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/db/db-context.vala 2019-03-17 13:39:29.000000000 +0000 @@ -16,31 +16,31 @@ public virtual Database? get_database() { return get_connection() != null ? get_connection().database : null; } - + public virtual Connection? get_connection() { return get_statement() != null ? get_statement().connection : null; } - + public virtual Statement? get_statement() { return get_result() != null ? get_result().statement : null; } - + public virtual Result? get_result() { return null; } - + protected inline int throw_on_error(string? method, int result, string? raw = null) throws DatabaseError { return Db.throw_on_error(this, method, result, raw); } - + [PrintfFormat] protected void log(string fmt, ...) { if (!Logging.are_all_flags_set(Logging.Flag.SQL)) return; - + Connection? cx = get_connection(); Statement? stmt = get_statement(); - + if (stmt != null) { Logging.debug(Logging.Flag.SQL, "%s %s\n\t<%s>", (cx != null) ? cx.to_string() : "[no cx]", diff -Nru geary-0.12.4/src/engine/db/db-database.vala geary-3.32.0/src/engine/db/db-database.vala --- geary-0.12.4/src/engine/db/db-database.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/db/db-database.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,27 +1,52 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. + * Copyright 2018 Michael Gratton * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ /** - * Database represents an SQLite file. Multiple Connections may be opened to against the - * Database. + * Represents a single SQLite database. * - * Since it's often just more bookkeeping to maintain a single global connection, Database also - * offers a master connection which may be used to perform queries and transactions. + * Each database supports multiple {@link Connection}s that allow SQL + * queries to be executed, however if a single connection is required + * by an app, this class also provides convenience methods to execute + * queries against a common ''master'' connection. * - * Database also offers asynchronous transactions which work via connection and thread pools. - * - * NOTE: In-memory databases are currently unsupported. + * This class offers a number of asynchronous methods, however since + * SQLite only supports a synchronous API, these are implemented using + * a pool of background threads. Asynchronous transactions are + * available via {@link exec_transaction_async}. */ public class Geary.Db.Database : Geary.Db.Context { + + + /** The path passed to SQLite to open a transient database. */ + public const string MEMORY_PATH = "file::memory:?cache=shared"; + + /** The default number of threaded connections opened. */ public const int DEFAULT_MAX_CONCURRENCY = 4; - - public File db_file { get; private set; } + + /** + * The database's location on the filesystem. + * + * If null, this is a transient, in-memory database. + */ + public File? file { get; private set; } + + /** + * The path passed to Sqlite when opening the database. + * + * This will be the path to the database file on disk for + * persistent databases, else {@link MEMORY_PATH} for transient + * databases. + */ + public string path { get; private set; } + public DatabaseFlags flags { get; private set; } - + private bool _is_open = false; public bool is_open { get { @@ -29,52 +54,69 @@ return _is_open; } } - + private set { lock (_is_open) { _is_open = value; } } } - + private Connection? master_connection = null; private int outstanding_async_jobs = 0; private ThreadPool? thread_pool = null; private unowned PrepareConnection? prepare_cb = null; - - public Database(File db_file) { - this.db_file = db_file; + + /** + * Constructs a new database that is persisted on disk. + */ + public Database.persistent(File db_file) { + this.file = db_file; + this.path = db_file.get_path(); + } + + /** + * Constructs a new database that is stored in memory only. + */ + public Database.transient() { + this.file = null; + this.path = MEMORY_PATH; } - + ~Database() { - // Not thrilled about using lock in a dtor - lock (outstanding_async_jobs) { - assert(outstanding_async_jobs == 0); + // Not thrilled about long-running tasks in a dtor + if (this.thread_pool != null) { + GLib.ThreadPool.free((owned) this.thread_pool, true, true); } } - + /** - * Opens the Database, creating any files and directories it may need in the process depending - * on the DatabaseFlags. + * Prepares the database for use. + * + * This will create any needed files and directories, check the + * database's integrity, and so on, depending on the flags passed + * to this method. * - * NOTE: A Database may be closed, but the Connections it creates will always be valid as - * they hold a reference to their source Database. To release a Database's resources, drop all - * references to it and its associated Connections, Statements, and Results. + * NOTE: A Database may be closed, but the Connections it creates + * will always be valid as they hold a reference to their source + * Database. To release a Database's resources, drop all + * references to it and its associated Connections, Statements, + * and Results. */ - public virtual void open(DatabaseFlags flags, PrepareConnection? prepare_cb, - Cancellable? cancellable = null) throws Error { + public virtual async void open(DatabaseFlags flags, + PrepareConnection? prepare_cb, + Cancellable? cancellable = null) + throws Error { if (is_open) return; - + this.flags = flags; this.prepare_cb = prepare_cb; - - if ((flags & DatabaseFlags.CREATE_DIRECTORY) != 0) { - File db_dir = db_file.get_parent(); - if (!db_dir.query_exists(cancellable)) - db_dir.make_directory_with_parents(cancellable); + + if (this.file != null && (flags & DatabaseFlags.CREATE_DIRECTORY) != 0) { + yield Geary.Files.make_directory_with_parents(this.file.get_parent()); } - + if (threadsafe()) { if (thread_pool == null) { thread_pool = new ThreadPool.with_owned_data(on_async_job, @@ -83,15 +125,20 @@ } else { warning("SQLite not thread-safe: asynchronous queries will not be available"); } - - if ((flags & DatabaseFlags.CHECK_CORRUPTION) != 0) - check_for_corruption(flags, cancellable); - + + if ((flags & DatabaseFlags.CHECK_CORRUPTION) != 0 && + this.file != null && + yield Geary.Files.query_exists_async(this.file, cancellable)) { + yield Nonblocking.Concurrent.global.schedule_async(() => { + check_for_corruption(flags, cancellable); + }, cancellable); + } + is_open = true; } - + private void check_for_corruption(DatabaseFlags flags, Cancellable? cancellable) throws Error { - // if the file exists, open a connection and test for corruption by creating a dummy table, + // Open a connection and test for corruption by creating a dummy table, // adding a row, selecting the row, then dropping the table ... can only do this for // read-write databases, however // @@ -100,37 +147,34 @@ // // TODO: Allow the caller to specify the name of the test table, so we're not clobbering // theirs (however improbable it is to name a table "CorruptionCheckTable") - bool exists = db_file.query_exists(cancellable); - if (exists && (flags & DatabaseFlags.READ_ONLY) == 0) { + if ((flags & DatabaseFlags.READ_ONLY) == 0) { Connection cx = new Connection(this, Sqlite.OPEN_READWRITE, cancellable); - + try { // drop existing test table (in case created in prior failed open) cx.exec("DROP TABLE IF EXISTS CorruptionCheckTable"); - + // create dummy table with a "subtantial" column cx.exec("CREATE TABLE CorruptionCheckTable (text_col TEXT)"); - + // insert row cx.exec("INSERT INTO CorruptionCheckTable (text_col) VALUES ('xyzzy')"); - + // select row cx.exec("SELECT * FROM CorruptionCheckTable"); - + // drop table cx.exec("DROP TABLE CorruptionCheckTable"); } catch (Error err) { - throw new DatabaseError.CORRUPT("Possible integrity problem discovered in %s: %s", - db_file.get_path(), err.message); + throw new DatabaseError.CORRUPT( + "Possible integrity problem discovered in %s: %s", + this.path, + err.message + ); } - } else if (!exists && (flags & DatabaseFlags.CREATE_FILE) == 0) { - // file doesn't exist and no flag to create it ... that's bad too, might as well - // let them know now - throw new DatabaseError.CORRUPT("Database file %s not found and no CREATE_FILE flag", - db_file.get_path()); } } - + /** * Closes the Database, releasing any resources it may hold, including the master connection. * @@ -142,43 +186,57 @@ public virtual void close(Cancellable? cancellable = null) throws Error { if (!is_open) return; - + // drop the master connection, which holds a ref back to this object master_connection = null; - + // As per the contract above, can't simply drop the thread and connection pools; that would // be bad. - + is_open = false; } - + private void check_open() throws Error { - if (!is_open) - throw new DatabaseError.OPEN_REQUIRED("Database %s not open", db_file.get_path()); + if (!is_open) { + throw new DatabaseError.OPEN_REQUIRED( + "Database %s not open", this.path + ); + } } - + /** * Throws DatabaseError.OPEN_REQUIRED if not open. */ - public Connection open_connection(Cancellable? cancellable = null) throws Error { - return internal_open_connection(false, cancellable); + public async Connection open_connection(Cancellable? cancellable = null) + throws Error { + Connection? cx = null; + yield Nonblocking.Concurrent.global.schedule_async(() => { + cx = internal_open_connection(false, cancellable); + }, cancellable); + return cx; } - + private Connection internal_open_connection(bool master, Cancellable? cancellable) throws Error { check_open(); - - int sqlite_flags = (flags & DatabaseFlags.READ_ONLY) != 0 ? Sqlite.OPEN_READONLY + + int sqlite_flags = (flags & DatabaseFlags.READ_ONLY) != 0 + ? Sqlite.OPEN_READONLY : Sqlite.OPEN_READWRITE; + if ((flags & DatabaseFlags.CREATE_FILE) != 0) sqlite_flags |= Sqlite.OPEN_CREATE; - + + if (this.file == null) { + sqlite_flags |= SQLITE_OPEN_URI; + } + Connection cx = new Connection(this, sqlite_flags, cancellable); if (prepare_cb != null) prepare_cb(cx, master); - + return cx; } - + /** * The master connection is a general-use connection many of the calls in Database (including * exec(), exec_file(), query(), prepare(), and exec_trnasaction()) use to perform their work. @@ -189,10 +247,10 @@ public Connection get_master_connection() throws Error { if (master_connection == null) master_connection = internal_open_connection(true, null); - + return master_connection; } - + /** * Calls Connection.exec() on the master connection. * @@ -201,7 +259,7 @@ public void exec(string sql, Cancellable? cancellable = null) throws Error { get_master_connection().exec(sql, cancellable); } - + /** * Calls Connection.exec_file() on the master connection. * @@ -210,7 +268,7 @@ public void exec_file(File file, Cancellable? cancellable = null) throws Error { get_master_connection().exec_file(file, cancellable); } - + /** * Calls Connection.prepare() on the master connection. * @@ -219,7 +277,7 @@ public Statement prepare(string sql) throws Error { return get_master_connection().prepare(sql); } - + /** * Calls Connection.query() on the master connection. * @@ -228,7 +286,7 @@ public Result query(string sql, Cancellable? cancellable = null) throws Error { return get_master_connection().query(sql, cancellable); } - + /** * Calls Connection.exec_transaction() on the master connection. * @@ -238,58 +296,73 @@ Cancellable? cancellable = null) throws Error { return get_master_connection().exec_transaction(type, cb, cancellable); } - + /** - * Asynchronous transactions are handled via background threads using a pool of Connections. - * The background thread calls Connection.exec_transaction(); see that method for more - * information about coding a transaction. The only caveat is that the TransactionMethod - * must be thread-safe. + * Starts a new asynchronous transaction using a new connection. * - * Throws DatabaseError.OPEN_REQUIRED if not open. + * Asynchronous transactions are handled via background + * threads. The background thread opens a new connection, and + * calls {@link Connection.exec_transaction}; see that method for + * more information about coding a transaction. The only caveat is + * that the {@link TransactionMethod} passed to it must be + * thread-safe. + * + * Throws {@link DatabaseError.OPEN_REQUIRED} if not open. */ - public async TransactionOutcome exec_transaction_async(TransactionType type, TransactionMethod cb, - Cancellable? cancellable) throws Error { + public async TransactionOutcome exec_transaction_async(TransactionType type, + TransactionMethod cb, + Cancellable? cancellable) + throws Error { + TransactionAsyncJob job = new TransactionAsyncJob( + null, type, cb, cancellable + ); + add_async_job(job); + return yield job.wait_for_completion_async(); + } + + /** Adds the given job to the thread pool. */ + internal void add_async_job(TransactionAsyncJob new_job) throws Error { check_open(); - - if (thread_pool == null) - throw new DatabaseError.GENERAL("SQLite thread safety disabled, async operations unallowed"); - - // create job to execute in background thread - TransactionAsyncJob job = new TransactionAsyncJob(type, cb, cancellable); - - lock (outstanding_async_jobs) { - outstanding_async_jobs++; + + if (this.thread_pool == null) { + throw new DatabaseError.GENERAL( + "SQLite thread safety disabled, async operations unallowed" + ); } - - thread_pool.add(job); - - return yield job.wait_for_completion_async(); + + lock (this.outstanding_async_jobs) { + this.outstanding_async_jobs++; + } + + this.thread_pool.add(new_job); } - + // This method must be thread-safe. private void on_async_job(owned TransactionAsyncJob job) { // *never* use master connection for threaded operations - Connection? cx = null; + Connection? cx = job.cx; Error? open_err = null; - try { - cx = open_connection(); - } catch (Error err) { - open_err = err; - debug("Warning: unable to open database connection to %s, cancelling AsyncJob: %s", - db_file.get_path(), err.message); + if (cx == null) { + try { + cx = internal_open_connection(false, job.cancellable); + } catch (Error err) { + open_err = err; + debug("Warning: unable to open database connection to %s, cancelling AsyncJob: %s", + this.path, err.message); + } } - + if (cx != null) job.execute(cx); else job.failed(open_err); - + lock (outstanding_async_jobs) { assert(outstanding_async_jobs > 0); --outstanding_async_jobs; } } - + public override Database? get_database() { return this; } diff -Nru geary-0.12.4/src/engine/db/db-result.vala geary-3.32.0/src/engine/db/db-result.vala --- geary-0.12.4/src/engine/db/db-result.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/db/db-result.vala 2019-03-17 13:39:29.000000000 +0000 @@ -6,108 +6,108 @@ public class Geary.Db.Result : Geary.Db.Context { public bool finished { get; private set; default = false; } - + public Statement statement { get; private set; } - + // This results in an automatic first next(). internal Result(Statement statement, Cancellable? cancellable) throws Error { this.statement = statement; - + statement.resetted.connect(on_query_finished); statement.bindings_cleared.connect(on_query_finished); - + next(cancellable); } - + ~Result() { statement.resetted.disconnect(on_query_finished); statement.bindings_cleared.disconnect(on_query_finished); } - + private void on_query_finished() { finished = true; } - + /** * Returns true if results are waiting, false if finished, or throws a DatabaseError. */ public bool next(Cancellable? cancellable = null) throws Error { check_cancelled("Result.next", cancellable); - + if (!finished) { Timer timer = new Timer(); finished = throw_on_error("Result.next", statement.stmt.step(), statement.sql) != Sqlite.ROW; if (timer.elapsed() > 1.0) debug("\n\nDB QUERY STEP \"%s\"\nelapsed=%lf\n\n", statement.sql, timer.elapsed()); - + log(finished ? "NO ROW" : "ROW"); } - + return !finished; } - + /** * column is zero-based. */ public bool is_null_at(int column) throws DatabaseError { verify_at(column); - + bool is_null = statement.stmt.column_type(column) == Sqlite.NULL; log("is_null_at(%d) -> %s", column, is_null.to_string()); - + return is_null; } - + /** * column is zero-based. */ public double double_at(int column) throws DatabaseError { verify_at(column); - + double d = statement.stmt.column_double(column); log("double_at(%d) -> %lf", column, d); - + return d; } - + /** * column is zero-based. */ public int int_at(int column) throws DatabaseError { verify_at(column); - + int i = statement.stmt.column_int(column); log("int_at(%d) -> %d", column, i); - + return i; } - + /** * column is zero-based. */ public uint uint_at(int column) throws DatabaseError { return (uint) int64_at(column); } - + /** * column is zero-based. */ public long long_at(int column) throws DatabaseError { return (long) int64_at(column); } - + /** * column is zero-based. */ public int64 int64_at(int column) throws DatabaseError { verify_at(column); - + int64 i64 = statement.stmt.column_int64(column); log("int64_at(%d) -> %s", column, i64.to_string()); - + return i64; } - + /** * Returns the column value as a bool. The value is treated as an int and converted into a * bool: false == 0, true == !0. @@ -117,7 +117,7 @@ public bool bool_at(int column) throws DatabaseError { return int_at(column) != 0; } - + /** * column is zero-based. * @@ -126,7 +126,7 @@ public int64 rowid_at(int column) throws DatabaseError { return int64_at(column); } - + /** * column is zero-based. * @@ -136,13 +136,13 @@ */ public unowned string? string_at(int column) throws DatabaseError { verify_at(column); - + unowned string? s = statement.stmt.column_text(column); log("string_at(%d) -> %s", column, (s != null) ? s : "(null)"); - + return s; } - + /** * column is zero-based. * @@ -152,10 +152,10 @@ */ public unowned string nonnull_string_at(int column) throws DatabaseError { unowned string? s = string_at(column); - + return (s != null) ? s : ""; } - + /** * column is zero-based. */ @@ -164,22 +164,22 @@ // internally ... GrowableBuffer is better for large blocks Memory.GrowableBuffer buffer = new Memory.GrowableBuffer(); buffer.append(nonnull_string_at(column).data); - + return buffer; } - + private void verify_at(int column) throws DatabaseError { if (finished) throw new DatabaseError.FINISHED("Query finished"); - + if (column < 0) throw new DatabaseError.LIMITS("column %d < 0", column); - + int count = statement.get_column_count(); if (column >= count) throw new DatabaseError.LIMITS("column %d >= %d", column, count); } - + /** * name is the name of the column in the result set. See Statement.get_column_index() for name * matching rules. @@ -187,7 +187,7 @@ public bool is_null_for(string name) throws DatabaseError { return is_null_at(convert_for(name)); } - + /** * name is the name of the column in the result set. See Statement.get_column_index() for name * matching rules. @@ -195,7 +195,7 @@ public double double_for(string name) throws DatabaseError { return double_at(convert_for(name)); } - + /** * name is the name of the column in the result set. See Statement.get_column_index() for name * matching rules. @@ -203,7 +203,7 @@ public int int_for(string name) throws DatabaseError { return int_at(convert_for(name)); } - + /** * name is the name of the column in the result set. See Statement.get_column_index() for name * matching rules. @@ -211,7 +211,7 @@ public uint uint_for(string name) throws DatabaseError { return (uint) int64_for(name); } - + /** * name is the name of the column in the result set. See Statement.get_column_index() for name * matching rules. @@ -219,7 +219,7 @@ public long long_for(string name) throws DatabaseError { return long_at(convert_for(name)); } - + /** * name is the name of the column in the result set. See Statement.get_column_index() for name * matching rules. @@ -227,7 +227,7 @@ public int64 int64_for(string name) throws DatabaseError { return int64_at(convert_for(name)); } - + /** * name is the name of the column in the result set. See Statement.get_column_index() for name * matching rules. @@ -237,7 +237,7 @@ public bool bool_for(string name) throws DatabaseError { return bool_at(convert_for(name)); } - + /** * name is the name of the column in the result set. See Statement.get_column_index() for name * matching rules. @@ -247,7 +247,7 @@ public int64 rowid_for(string name) throws DatabaseError { return int64_for(name); } - + /** * name is the name of the column in the result set. See Statement.get_column_index() for name * matching rules. @@ -259,7 +259,7 @@ public unowned string? string_for(string name) throws DatabaseError { return string_at(convert_for(name)); } - + /** * name is the name of the column in the result set. See Statement.get_column_index() for name * matching rules. @@ -271,7 +271,7 @@ public unowned string nonnull_string_for(string name) throws DatabaseError { return nonnull_string_at(convert_for(name)); } - + /** * name is the name of the column in the result set. See Statement.get_column_index() for name * matching rules. @@ -279,18 +279,18 @@ public Memory.Buffer string_buffer_for(string name) throws DatabaseError { return string_buffer_at(convert_for(name)); } - + private int convert_for(string name) throws DatabaseError { if (finished) throw new DatabaseError.FINISHED("Query finished"); - + int column = statement.get_column_index(name); if (column < 0) throw new DatabaseError.LIMITS("column \"%s\" not in result set", name); - + return column; } - + public override Result? get_result() { return this; } diff -Nru geary-0.12.4/src/engine/db/db-statement.vala geary-3.32.0/src/engine/db/db-statement.vala --- geary-0.12.4/src/engine/db/db-statement.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/db/db-statement.vala 2019-03-17 13:39:29.000000000 +0000 @@ -9,41 +9,41 @@ public unowned string sql { get { return !String.is_empty(raw) ? raw : stmt.sql(); } } - + public Connection connection { get; private set; } - + internal Sqlite.Statement stmt; - + private Gee.HashMap? column_map = null; - + /** * Fired when the Statement is executed the first time (after creation or after a reset). */ public signal void executed(); - + /** * Fired when the Statement is reset. */ public signal void resetted(); - + /** * Fired when the Statement's bindings are cleared. */ public signal void bindings_cleared(); - + private Gee.HashSet held_buffers = new Gee.HashSet(); - + internal Statement(Connection connection, string sql) throws DatabaseError { this.connection = connection; // save for logging in case prepare_v2() fails raw = sql; - + throw_on_error("Statement.ctor", connection.db.prepare_v2(sql, -1, out stmt, null), sql); - + // not needed any longer raw = null; } - + /** * Reset the Statement for reuse, optionally clearing all bindings as well. If bindings are * not cleared, valued bound previously will be maintained. @@ -53,26 +53,26 @@ public Statement reset(ResetScope reset_scope) throws DatabaseError { if (reset_scope == ResetScope.CLEAR_BINDINGS) throw_on_error("Statement.clear_bindings", stmt.clear_bindings()); - + throw_on_error("Statement.reset", stmt.reset()); - + // fire signals after Statement has been altered -- this prevents reentrancy while the // Statement is in a halfway state if (reset_scope == ResetScope.CLEAR_BINDINGS) bindings_cleared(); - + resetted(); - + return this; } - + /** * Returns the number of columns the Statement will return in a Result. */ public int get_column_count() { return stmt.column_count(); } - + /** * Returns the column name for column at the zero-based index. * @@ -81,7 +81,7 @@ public unowned string? get_column_name(int index) { return stmt.column_name(index); } - + /** * Returns the zero-based column index matching the column name. Column names are * case-insensitive. @@ -92,7 +92,7 @@ // prepare column map only if names requested if (column_map == null) { column_map = new Gee.HashMap(Geary.String.stri_hash, Geary.String.stri_equal); - + int cols = stmt.column_count(); for (int ctr = 0; ctr < cols; ctr++) { string? column_name = stmt.column_name(ctr); @@ -100,22 +100,22 @@ column_map.set(column_name, ctr); } } - + return column_map.has_key(name) ? column_map.get(name) : -1; } - + /** * Executes the Statement and returns a Result object. The Result starts pointing at the first * row in the result set. If empty, Result.finished will be true. */ public Result exec(Cancellable? cancellable = null) throws Error { Result results = new Result(this, cancellable); - + executed(); - + return results; } - + /** * Executes the Statement and returns the last inserted rowid. If this Statement is not * an INSERT, it will return the rowid of the last prior INSERT. @@ -125,13 +125,13 @@ public int64 exec_insert(Cancellable? cancellable = null) throws Error { new Result(this, cancellable); int64 rowid = connection.last_insert_rowid; - + // fire signal after safely retrieving the rowid executed(); - + return rowid; } - + /** * Executes the Statement and returns the number of rows modified by the operation. This * Statement should be an INSERT, UPDATE, or DELETE, otherwise this will return the number @@ -142,54 +142,54 @@ public int exec_get_modified(Cancellable? cancellable = null) throws Error { new Result(this, cancellable); int modified = connection.last_modified_rows; - + // fire signal after safely retrieving the count executed(); - + return modified; } - + /** * index is zero-based. */ public Statement bind_double(int index, double d) throws DatabaseError { throw_on_error("Statement.bind_double", stmt.bind_double(index + 1, d)); - + return this; } - + /** * index is zero-based. */ public Statement bind_int(int index, int i) throws DatabaseError { throw_on_error("Statement.bind_int", stmt.bind_int(index + 1, i)); - + return this; } - + /** * index is zero-based. */ public Statement bind_uint(int index, uint u) throws DatabaseError { return bind_int64(index, (int64) u); } - + /** * index is zero-based. */ public Statement bind_long(int index, long l) throws DatabaseError { return bind_int64(index, (int64) l); } - + /** * index is zero-based. */ public Statement bind_int64(int index, int64 i64) throws DatabaseError { throw_on_error("Statement.bind_int64", stmt.bind_int64(index + 1, i64)); - + return this; } - + /** * Binds a bool to the column. A bool is stored as an integer, false == 0, true == 1. Note * that fetching a bool via Result is more lenient; see Result.bool_at() and Result.bool_from(). @@ -199,7 +199,7 @@ public Statement bind_bool(int index, bool b) throws DatabaseError { return bind_int(index, b ? 1 : 0); } - + /** * index is zero-based. * @@ -210,7 +210,7 @@ public Statement bind_rowid(int index, int64 rowid) throws DatabaseError { return (rowid != Db.INVALID_ROWID) ? bind_int64(index, rowid) : bind_null(index); } - + /** * index is zero-based. * @@ -218,19 +218,19 @@ */ public Statement bind_null(int index) throws DatabaseError { throw_on_error("Statement.bind_null", stmt.bind_null(index + 1)); - + return this; } - + /** * index is zero-based. */ public Statement bind_string(int index, string? s) throws DatabaseError { throw_on_error("Statement.bind_string", stmt.bind_text(index + 1, s)); - + return this; } - + /** * Binds the string representation of a {@link Memory.Buffer} to the replacement value * in the {@link Statement}. @@ -244,25 +244,25 @@ public Statement bind_string_buffer(int index, Memory.Buffer? buffer) throws DatabaseError { if (buffer == null) return bind_string(index, null); - + Memory.UnownedStringBuffer? unowned_buffer = buffer as Memory.UnownedStringBuffer; if (unowned_buffer == null) { throw_on_error("Statement.bind_string_buffer", stmt.bind_text(index + 1, buffer.to_string())); - + return this; } - + // hold on to buffer for lifetime of Statement, SQLite's callback isn't enough for us to // selectively unref each Buffer as it's done with it held_buffers.add(unowned_buffer); - + // note use of _bind_text, which is for static and other strings with their own memory // management stmt._bind_text(index + 1, unowned_buffer.to_unowned_string()); - + return this; } - + public override Statement? get_statement() { return this; } diff -Nru geary-0.12.4/src/engine/db/db-synchronous-mode.vala geary-3.32.0/src/engine/db/db-synchronous-mode.vala --- geary-0.12.4/src/engine/db/db-synchronous-mode.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/db/db-synchronous-mode.vala 2019-03-17 13:39:29.000000000 +0000 @@ -8,29 +8,29 @@ OFF = 0, NORMAL = 1, FULL = 2; - + public unowned string sql() { switch (this) { case OFF: return "off"; - + case NORMAL: return "normal"; - + case FULL: default: return "full"; } } - + public static SynchronousMode parse(string str) { switch (str.down()) { case "off": return OFF; - + case "normal": return NORMAL; - + case "full": default: return FULL; diff -Nru geary-0.12.4/src/engine/db/db-transaction-async-job.vala geary-3.32.0/src/engine/db/db-transaction-async-job.vala --- geary-0.12.4/src/engine/db/db-transaction-async-job.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/db/db-transaction-async-job.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,33 +1,38 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ private class Geary.Db.TransactionAsyncJob : BaseObject { + + internal Connection? cx { get; private set; default = null; } + internal Cancellable cancellable { get; private set; } + private TransactionType type; private unowned TransactionMethod cb; - private Cancellable cancellable; private Nonblocking.Event completed; private TransactionOutcome outcome = TransactionOutcome.ROLLBACK; private Error? caught_err = null; - - public TransactionAsyncJob(TransactionType type, TransactionMethod cb, Cancellable? cancellable) { + + + public TransactionAsyncJob(Connection? cx, + TransactionType type, + TransactionMethod cb, + Cancellable? cancellable) { + this.cx = cx; this.type = type; this.cb = cb; this.cancellable = cancellable ?? new Cancellable(); - - completed = new Nonblocking.Event(); - } - - public void cancel() { - cancellable.cancel(); + + this.completed = new Nonblocking.Event(); } - + public bool is_cancelled() { return cancellable.is_cancelled(); } - + // Called in background thread context internal void execute(Connection cx) { // execute transaction @@ -35,36 +40,36 @@ // possible was cancelled during interim of scheduling and execution if (is_cancelled()) throw new IOError.CANCELLED("Async transaction cancelled"); - + outcome = cx.exec_transaction(type, cb, cancellable); } catch (Error err) { if (!(err is IOError.CANCELLED)) debug("AsyncJob: transaction completed with error: %s", err.message); - + caught_err = err; } - + schedule_completion(); } - + // Called in background thread context internal void failed(Error err) { // store as a caught thread to report to original caller caught_err = err; - + schedule_completion(); } - + private void schedule_completion() { // notify foreground thread of completion // because Idle doesn't hold a ref, manually keep this object alive ref(); - + // NonblockingSemaphore and its brethren are not thread-safe, so need to signal notification // of completion in the main thread Idle.add(on_notify_completed); } - + private bool on_notify_completed() { try { completed.notify(); @@ -76,23 +81,22 @@ debug("Unable to notify AsyncTransaction has completed w/o err: %s", err.message); } } - + // manually unref; do NOT touch "this" once unref() returns, as this object may be freed unref(); - + return false; } - + // No way to cancel this because the callback thread *must* finish before // we move on here. Any I/O the thread is doing can still be cancelled - // using our cancel() above. + // using the job's cancellable. public async TransactionOutcome wait_for_completion_async() throws Error { - yield completed.wait_async(); - if (caught_err != null) - throw caught_err; - - return outcome; + yield this.completed.wait_async(); + if (this.caught_err != null) + throw this.caught_err; + + return this.outcome; } } - diff -Nru geary-0.12.4/src/engine/db/db-transaction-outcome.vala geary-3.32.0/src/engine/db/db-transaction-outcome.vala --- geary-0.12.4/src/engine/db/db-transaction-outcome.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/db/db-transaction-outcome.vala 2019-03-17 13:39:29.000000000 +0000 @@ -7,31 +7,31 @@ public enum Geary.Db.TransactionOutcome { ROLLBACK = 0, COMMIT = 1, - + // coarse synonyms SUCCESS = COMMIT, FAILURE = ROLLBACK, DONE = COMMIT; - + public unowned string sql() { switch (this) { case COMMIT: return "COMMIT TRANSACTION"; - + case ROLLBACK: default: return "ROLLBACK TRANSACTION"; } } - + public string to_string() { switch (this) { case ROLLBACK: return "rollback"; - + case COMMIT: return "commit"; - + default: return "(unknown: %d)".printf(this); } diff -Nru geary-0.12.4/src/engine/db/db-transaction-type.vala geary-3.32.0/src/engine/db/db-transaction-type.vala --- geary-0.12.4/src/engine/db/db-transaction-type.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/db/db-transaction-type.vala 2019-03-17 13:39:29.000000000 +0000 @@ -8,38 +8,38 @@ DEFERRED, IMMEDIATE, EXCLUSIVE, - + // coarse synonyms RO = DEFERRED, RW = IMMEDIATE, WR = EXCLUSIVE, WO = EXCLUSIVE; - + public unowned string sql() { switch (this) { case IMMEDIATE: return "BEGIN IMMEDIATE"; - + case EXCLUSIVE: return "BEGIN EXCLUSIVE"; - + case DEFERRED: default: return "BEGIN DEFERRED"; } } - + public string to_string() { switch (this) { case DEFERRED: return "DEFERRED"; - + case IMMEDIATE: return "IMMEDIATE"; - + case EXCLUSIVE: return "EXCLUSIVE"; - + default: return "(unknown: %d)".printf(this); } diff -Nru geary-0.12.4/src/engine/db/db.vala geary-3.32.0/src/engine/db/db.vala --- geary-0.12.4/src/engine/db/db.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/db/db.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,21 +1,30 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ /** - * Geary.Db is a simple wrapper around SQLite to make it more GObject-ish and easier to code in - * Vala. It also uses threads and some concurrency features of SQLite to allow for asynchronous + * A simple database access layer. + * + * Geary.Db is a simple wrapper around SQLite to make it more + * GObject-ish and easier to code in Vala. It also uses threads and + * some concurrency features of SQLite to allow for asynchronous * access to the database. * - * There is no attempt here to hide or genericize the backing database library; this is designed with - * SQLite in mind. As such, many of the calls are merely direct front-ends to the underlying - * SQLite call. + * There is no attempt here to hide or genericize the backing database + * library; this is designed with SQLite in mind. As such, many of + * the calls are merely direct front-ends to the underlying SQLite + * call. * - * The design of the classes and interfaces owes a debt to SQLHeavy (http://code.google.com/p/sqlheavy/). + * The design of the classes and interfaces owes a debt to + * [[http://code.google.com/p/sqlheavy/|SQLHeavy]]. */ +// Work around missing const in sqlite3.vapi. See Bug 795627. +extern const int SQLITE_OPEN_URI; + extern int sqlite3_enable_shared_cache(int enabled); namespace Geary.Db { @@ -99,10 +108,10 @@ case Sqlite.ROW: return result; } - + string location = !String.is_empty(method) - ? "(%s %s) ".printf(method, ctx.get_database().db_file.get_path()) - : "(%s) ".printf(ctx.get_database().db_file.get_path()); + ? "(%s %s) ".printf(method, ctx.get_database().path) + : "(%s) ".printf(ctx.get_database().path); string errmsg = (ctx.get_connection() != null) ? " - %s".printf(ctx.get_connection().db.errmsg()) : ""; string sql; if (ctx.get_statement() != null) @@ -111,14 +120,14 @@ sql = " (%s)".printf(raw); else sql = ""; - + string msg = "%s[err=%d]%s%s".printf(location, result, errmsg, sql); - + switch (result) { case Sqlite.BUSY: case Sqlite.LOCKED: throw new DatabaseError.BUSY(msg); - + case Sqlite.IOERR: case Sqlite.PERM: case Sqlite.READONLY: @@ -126,32 +135,32 @@ case Sqlite.NOLFS: case Sqlite.AUTH: throw new DatabaseError.ACCESS(msg); - + case Sqlite.CORRUPT: case Sqlite.FORMAT: case Sqlite.NOTADB: throw new DatabaseError.CORRUPT(msg); - + case Sqlite.NOMEM: throw new DatabaseError.MEMORY(msg); - + case Sqlite.ABORT: throw new DatabaseError.ABORT(msg); - + case Sqlite.INTERRUPT: throw new DatabaseError.INTERRUPT(msg); - + case Sqlite.FULL: case Sqlite.EMPTY: case Sqlite.TOOBIG: case Sqlite.CONSTRAINT: case Sqlite.RANGE: throw new DatabaseError.LIMITS(msg); - + case Sqlite.SCHEMA: case Sqlite.MISMATCH: throw new DatabaseError.TYPESPEC(msg); - + case Sqlite.ERROR: case Sqlite.INTERNAL: case Sqlite.MISUSE: diff -Nru geary-0.12.4/src/engine/db/db-versioned-database.vala geary-3.32.0/src/engine/db/db-versioned-database.vala --- geary-0.12.4/src/engine/db/db-versioned-database.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/db/db-versioned-database.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,200 +1,241 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. + * Copyright 2018 Michael Gratton * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ +/** + * A SQLite database with a versioned, upgradeable schema. + * + * This class uses the SQLite user version pragma to track the current + * version of a database, and a set of SQL scripts (one per version) + * to manage updating from one version to another. When the database + * is first opened by a call to {@link open}, its current version is + * checked against the set of available scripts, and each available + * version script above the current version is applied in + * order. Derived classes may override the {@link pre_upgrade} and + * {@link post_upgrade} methods to perform additional work before and + * after an upgrade script is executed, and {@link starting_upgrade} + * and {@link completed_upgrade} to be notified of the upgrade process + * starting and finishing. + */ public class Geary.Db.VersionedDatabase : Geary.Db.Database { - public delegate void WorkCallback(); - - private static Mutex upgrade_mutex = new Mutex(); - + + + private static Geary.Nonblocking.Mutex upgrade_mutex = + new Geary.Nonblocking.Mutex(); + + public File schema_dir { get; private set; } - - public VersionedDatabase(File db_file, File schema_dir) { - base (db_file); - + + /** {@inheritDoc} */ + public VersionedDatabase.persistent(File db_file, File schema_dir) { + base.persistent(db_file); + this.schema_dir = schema_dir; + } + + /** {@inheritDoc} */ + public VersionedDatabase.transient(File schema_dir) { + base.transient(); this.schema_dir = schema_dir; } - + + /** Returns the current schema version number of this database. */ + public int get_schema_version() + throws GLib.Error { + return get_master_connection().get_user_version_number(); + } + /** * Called by {@link open} if a schema upgrade is required and beginning. * - * If called by {@link open_background}, this will be called in the context of a background - * thread. - * * If new_db is set to true, the database is being created from scratch. */ protected virtual void starting_upgrade(int current_version, bool new_db) { } - + /** * Called by {@link open} just before performing a schema upgrade step. - * - * If called by {@link open_background}, this will be called in the context of a background - * thread. */ - protected virtual void pre_upgrade(int version) { + protected virtual async void pre_upgrade(int version, Cancellable? cancellable) + throws Error { } - + /** * Called by {@link open} just after performing a schema upgrade step. - * - * If called by {@link open_background}, this will be called in the context of a background - * thread. */ - protected virtual void post_upgrade(int version) { + protected virtual async void post_upgrade(int version, Cancellable? cancellable) + throws Error { } - + /** * Called by {@link open} if a schema upgrade was required and has now completed. - * - * If called by {@link open_background}, this will be called in the context of a background - * thread. */ protected virtual void completed_upgrade(int final_version) { } - - private File get_schema_file(int db_version) { - return schema_dir.get_child("version-%03d.sql".printf(db_version)); - } - + /** - * Creates or opens the database, initializing and upgrading the schema. + * Prepares the database for use, initializing and upgrading the schema. * - * If it's detected that the database has a schema version that's unavailable in the schema - * directory, throws {@link DatabaseError.SCHEMA_VERSION}. Generally this indicates the - * user attempted to load the database with an older version of the application. - */ - public override void open(DatabaseFlags flags, PrepareConnection? prepare_cb, - Cancellable? cancellable = null) throws Error { - base.open(flags, prepare_cb, cancellable); - + * If it's detected that the database has a schema version that's + * unavailable in the schema directory, throws {@link + * DatabaseError.SCHEMA_VERSION}. Generally this indicates the + * user attempted to load the database with an older version of + * the application. + */ + public override async void open(DatabaseFlags flags, + PrepareConnection? prepare_cb, + Cancellable? cancellable = null) + throws Error { + yield base.open(flags, prepare_cb, cancellable); + // get Connection for upgrade activity - Connection cx = open_connection(cancellable); - + Connection cx = yield open_connection(cancellable); + int db_version = cx.get_user_version_number(); - debug("VersionedDatabase.upgrade: current database schema for %s: %d", db_file.get_path(), - db_version); - + debug("VersionedDatabase.upgrade: current database schema for %s: %d", + this.path, db_version); + // If the DB doesn't exist yet, the version number will be zero, but also treat negative // values as new bool new_db = db_version <= 0; - + // Initialize new database to version 1 (note the preincrement in the loop below) if (db_version < 0) db_version = 0; - - // Check for database schemas newer than what's available in the schema directory; this - // happens some times in development or if a user attempts to roll back their version - // of the app without restoring a backup of the database ... since schema is so important - // to database coherency, need to protect against both - // - // Note that this is checking for a schema file for the current version of the database - // (assuming it's version 1 or better); the next check autoincrements to look for the - // *next* version of the database - if (db_version > 0 && !get_schema_file(db_version).query_exists(cancellable)) { - throw new DatabaseError.SCHEMA_VERSION("%s schema %d unknown to current schema plan", - db_file.get_path(), db_version); + + if (db_version > 0) { + // Check for database schemas newer than what's available + // in the schema directory; this happens some times in + // development or if a user attempts to roll back their + // version of the app without restoring a backup of the + // database ... since schema is so important to database + // coherency, need to protect against both + // + // Note that this is checking for a schema file for the + // current version of the database (assuming it's version + // 1 or better); the next check autoincrements to look for + // the *next* version of the database + if (!yield exists(get_schema_file(db_version), cancellable)) { + throw new DatabaseError.SCHEMA_VERSION( + "%s schema %d unknown to current schema plan", + this.path, db_version + ); + } } - + // Go through all the version scripts in the schema directory and apply each of them. bool started = false; for (;;) { File upgrade_script = get_schema_file(++db_version); - if (!upgrade_script.query_exists(cancellable)) + if (!yield exists(upgrade_script, cancellable)) { break; - + } + if (!started) { starting_upgrade(db_version, new_db); started = true; } - - // Since these upgrades run in a background thread, there's a possibility they - // can run in parallel. That leads to Geary absolutely taking over the machine, - // with potentially several threads all doing heavy database manipulation at - // once. So, we wrap this bit in a mutex lock so that only one database is - // updating at once. It means overall it might take a bit longer, but it keeps - // things usable in the meantime. See . - upgrade_mutex.@lock(); - - pre_upgrade(db_version); - - check_cancelled("VersionedDatabase.open", cancellable); - + + // Since these upgrades run in a background thread, + // there's a possibility they can run in parallel. That + // leads to Geary absolutely taking over the machine, with + // potentially several threads all doing heavy database + // manipulation at once. So, we wrap this bit in a mutex + // lock so that only one database is updating at once. It + // means overall it might take a bit longer, but it keeps + // things usable in the meantime. See + // . + int token = yield VersionedDatabase.upgrade_mutex.claim_async( + cancellable + ); + + Error? locked_err = null; try { - debug("Upgrading database to version %d with %s", db_version, upgrade_script.get_path()); - cx.exec_transaction(TransactionType.EXCLUSIVE, (cx) => { - cx.exec_file(upgrade_script, cancellable); - cx.set_user_version_number(db_version); - - return TransactionOutcome.COMMIT; - }, cancellable); + yield execute_upgrade( + cx, db_version, upgrade_script, cancellable + ); } catch (Error err) { - warning("Error upgrading database to version %d: %s", db_version, err.message); - upgrade_mutex.unlock(); - - throw err; - } - - post_upgrade(db_version); - - upgrade_mutex.unlock(); + locked_err = err; + } + + VersionedDatabase.upgrade_mutex.release(ref token); + + if (locked_err != null) { + throw locked_err; + } } - + if (started) completed_upgrade(db_version); } - - /** - * Opens the database in a background thread so foreground work can be performed while updating. - * - * Since {@link open} may take a considerable amount of time for a {@link VersionedDatabase}, - * background_open() can be used to perform that work in a thread while the calling thread - * "pumps" a {@link WorkCallback} every work_cb_msec milliseconds. In general, this is - * designed for allowing an event queue to execute tasks or update a progress monitor of some - * kind. - * - * Note that the database is not opened while the callback is executing and so it should not - * call into the database (unless it's a call safe to use prior to open). - * - * If work_cb_sec is zero or less, WorkCallback is called continuously, which may or may not be - * desired. - * - * @see open - */ - public void open_background(DatabaseFlags flags, PrepareConnection? prepare_cb, - WorkCallback work_cb, int work_cb_msec, Cancellable? cancellable = null) throws Error { - // use a SpinWaiter to safely wait for the thread to exit while occassionally calling the - // WorkCallback (which can not abort in current impl.) to do foreground work. - Synchronization.SpinWaiter waiter = new Synchronization.SpinWaiter(work_cb_msec, () => { - work_cb(); - - // continue (never abort) - return true; - }); - - // do the open in a background thread - Error? thread_err = null; - Thread thread = new Thread.try("Geary.Db.VersionedDatabase.open()", () => { - try { - open(flags, prepare_cb, cancellable); - } catch (Error err) { - thread_err = err; + + private async void execute_upgrade(Connection cx, + int db_version, + GLib.File upgrade_script, + Cancellable? cancellable) + throws Error { + debug("Upgrading database to version %d with %s", + db_version, upgrade_script.get_path()); + + check_cancelled("VersionedDatabase.open", cancellable); + try { + yield pre_upgrade(db_version, cancellable); + } catch (Error err) { + if (!(err is IOError.CANCELLED)) { + warning("Error executing pre-upgrade for version %d: %s", + db_version, err.message); + } + throw err; + } + + check_cancelled("VersionedDatabase.open", cancellable); + try { + yield cx.exec_transaction_async(TransactionType.EXCLUSIVE, (cx) => { + cx.exec_file(upgrade_script, cancellable); + cx.set_user_version_number(db_version); + + return TransactionOutcome.COMMIT; + }, cancellable); + } catch (Error err) { + if (!(err is IOError.CANCELLED)) { + warning("Error upgrading database to version %d: %s", + db_version, err.message); } - - // notify the foreground waiter we're done - waiter.notify(); - - return true; - }); - - // wait until thread is completed and then dispose of it - waiter.wait(); - thread = null; - - if (thread_err != null) - throw thread_err; + throw err; + } + + check_cancelled("VersionedDatabase.open", cancellable); + try { + yield post_upgrade(db_version, cancellable); + } catch (Error err) { + if (!(err is IOError.CANCELLED)) { + warning("Error executing post-upgrade for version %d: %s", + db_version, err.message); + } + throw err; + } + } + + private File get_schema_file(int db_version) { + return schema_dir.get_child("version-%03d.sql".printf(db_version)); } -} + private async bool exists(GLib.File target, Cancellable? cancellable) { + bool ret = true; + try { + yield target.query_info_async( + GLib.FileAttribute.STANDARD_TYPE, + GLib.FileQueryInfoFlags.NONE, + GLib.Priority.DEFAULT, + cancellable + ); + } catch (Error err) { + ret = false; + } + return ret; + } + +} diff -Nru geary-0.12.4/src/engine/imap/api/imap-account-session.vala geary-3.32.0/src/engine/imap/api/imap-account-session.vala --- geary-0.12.4/src/engine/imap/api/imap-account-session.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/src/engine/imap/api/imap-account-session.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,459 @@ +/* + * Copyright 2016 Software Freedom Conservancy Inc. + * Copyright 2018 Michael Gratton . + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +/** + * An interface between the high-level engine API and the IMAP stack. + * + * Because of the complexities of the IMAP protocol, class takes + * common operations that a Geary.Account implementation would need + * (in particular, {@link Geary.ImapEngine.GenericAccount}) and makes + * them into simple async calls. + * + * Geary.Imap.Account manages the {@link Imap.Folder} objects it + * returns, but only in the sense that it will not create new + * instances repeatedly. Otherwise, it does not refresh or update the + * Imap.Folders themselves (such as update their {@link + * Imap.StatusData} periodically). That's the responsibility of the + * higher layers of the stack. + */ +internal class Geary.Imap.AccountSession : Geary.Imap.SessionObject { + + private FolderRoot root; + private Gee.HashMap folders = + new Gee.HashMap(); + + private Nonblocking.Mutex cmd_mutex = new Nonblocking.Mutex(); + private Gee.List? list_collector = null; + private Gee.List? status_collector = null; + + + internal AccountSession(string account_id, + FolderRoot root, + ClientSession session) { + base("%s:account".printf(account_id), session); + this.root = root; + + session.list.connect(on_list_data); + session.status.connect(on_status_data); + } + + /** + * Returns the root path for the default personal namespace. + */ + public async FolderPath get_default_personal_namespace(Cancellable? cancellable) + throws Error { + ClientSession session = claim_session(); + if (session.personal_namespaces.is_empty) { + throw new ImapError.INVALID("No personal namespace found"); + } + + Namespace ns = session.personal_namespaces[0]; + string prefix = ns.prefix; + string? delim = ns.delim; + if (delim != null && prefix.has_suffix(delim)) { + prefix = prefix.substring(0, prefix.length - delim.length); + } + + return Geary.String.is_empty(prefix) + ? this.root + : this.root.get_child(prefix); + } + + /** + * Determines if the given folder path appears to a valid mailbox. + */ + public bool is_folder_path_valid(FolderPath? path) throws GLib.Error { + bool is_valid = false; + if (path != null) { + ClientSession session = claim_session(); + try { + session.get_mailbox_for_path(path); + is_valid = true; + } catch (GLib.Error err) { + // still not valid + } + } + return is_valid; + } + + /** + * Creates a new special folder on the remote server. + * + * The given path must be a fully-qualified path, including + * namespace prefix. + * + * If the optional special folder type is specified, and + * CREATE-SPECIAL-USE is supported by the connection, that will be + * used to specify the type of the new folder. + */ + public async void create_folder_async(FolderPath path, + Geary.SpecialFolderType? type, + Cancellable? cancellable) + throws Error { + ClientSession session = claim_session(); + MailboxSpecifier mailbox = session.get_mailbox_for_path(path); + bool can_create_special = session.capabilities.has_capability(Capabilities.CREATE_SPECIAL_USE); + CreateCommand cmd = (type != null && can_create_special) + ? new CreateCommand.special_use(mailbox, type) + : new CreateCommand(mailbox); + + StatusResponse response = yield send_command_async( + session, cmd, null, null, cancellable + ); + + if (response.status != Status.OK) { + throw new ImapError.SERVER_ERROR( + "Server reports error creating folder %s: %s", + mailbox.to_string(), response.to_string() + ); + } + } + + /** + * Returns a single folder, from the account's cache or fetched fresh. + * + * If the folder has previously been retrieved, that is returned + * instead of fetching it again. If not, it is fetched from the + * server and cached for future use. + */ + public async Imap.Folder fetch_folder_async(FolderPath path, + Cancellable? cancellable) + throws Error { + ClientSession session = claim_session(); + Imap.Folder? folder = this.folders.get(path); + if (folder == null) { + Gee.List? mailboxes = yield send_list_async( + session, path, false, cancellable + ); + if (mailboxes.is_empty) { + throw_not_found(path); + } + + MailboxInformation mailbox_info = mailboxes.get(0); + Imap.FolderProperties? props = null; + if (!mailbox_info.attrs.is_no_select) { + StatusData status = yield send_status_async( + session, + mailbox_info.mailbox, + StatusDataType.all(), + cancellable + ); + props = new Imap.FolderProperties.selectable( + mailbox_info.attrs, + status, + session.capabilities + ); + } else { + props = new Imap.FolderProperties.not_selectable(mailbox_info.attrs); + } + + folder = new Imap.Folder(path, props); + this.folders.set(path, folder); + } + return folder; + } + + /** + * Returns a list of children of the given folder. + * + * This method will perform a pipe-lined IMAP SELECT for all + * folders found, and hence should be used with care. + */ + public async Gee.List + fetch_child_folders_async(FolderPath parent, + GLib.Cancellable? cancellable) + throws GLib.Error { + ClientSession session = claim_session(); + Gee.List children = new Gee.ArrayList(); + Gee.List mailboxes = yield send_list_async( + session, parent, true, cancellable + ); + if (mailboxes.size == 0) { + return children; + } + + // Work out which folders need a STATUS and send them all + // pipe-lined to minimise network and server latency. + Gee.Map info_map = new Gee.HashMap< + MailboxSpecifier, MailboxInformation>(); + Gee.Map cmd_map = new Gee.HashMap< + StatusCommand, MailboxSpecifier>(); + foreach (MailboxInformation mailbox_info in mailboxes) { + if (!mailbox_info.attrs.is_no_select) { + // Mailbox needs a SELECT + info_map.set(mailbox_info.mailbox, mailbox_info); + cmd_map.set( + new StatusCommand(mailbox_info.mailbox, StatusDataType.all()), + mailbox_info.mailbox + ); + } else { + // Mailbox is unselectable, so doesn't need a STATUS, + // so we can create it now if it does not already + // exist + FolderPath path = session.get_path_for_mailbox( + this.root, mailbox_info.mailbox + ); + Folder? child = this.folders.get(path); + if (child == null) { + child = new Imap.Folder( + path, + new Imap.FolderProperties.not_selectable(mailbox_info.attrs) + ); + this.folders.set(path, child); + } + children.add(child); + } + } + + if (!cmd_map.is_empty) { + Gee.List status_results = new Gee.ArrayList(); + Gee.Map responses = yield send_multiple_async( + session, + cmd_map.keys, + null, + status_results, + cancellable + ); + + foreach (Command cmd in responses.keys) { + StatusCommand status_cmd = (StatusCommand) cmd; + StatusResponse response = responses.get(cmd); + + MailboxSpecifier mailbox = cmd_map.get(status_cmd); + MailboxInformation mailbox_info = info_map.get(mailbox); + + if (response.status != Status.OK) { + message("Unable to get STATUS of %s: %s", mailbox.to_string(), response.to_string()); + message("STATUS command: %s", cmd.to_string()); + continue; + } + + // Server might return results in any order, so need + // to find it + StatusData? status = null; + foreach (StatusData status_data in status_results) { + if (status_data.mailbox.equal_to(mailbox)) { + status = status_data; + break; + } + } + if (status == null) { + message("Unable to get STATUS of %s: not returned from server", mailbox.to_string()); + continue; + } + status_results.remove(status); + + FolderPath child_path = session.get_path_for_mailbox( + this.root, mailbox_info.mailbox + ); + Imap.Folder? child = this.folders.get(child_path); + + if (child != null) { + child.properties.update_status(status); + } else { + child = new Imap.Folder( + child_path, + new Imap.FolderProperties.selectable( + mailbox_info.attrs, + status, + session.capabilities + ) + ); + this.folders.set(child_path, child); + } + + children.add(child); + } + + if (status_results.size > 0) + debug("%d STATUS results leftover", status_results.size); + } + + return children; + } + + internal void folders_removed(Gee.Collection paths) { + foreach (FolderPath path in paths) { + if (folders.has_key(path)) + folders.unset(path); + } + } + + /** {@inheritDoc} */ + public override ClientSession? close() { + ClientSession old_session = base.close(); + if (old_session != null) { + old_session.list.disconnect(on_list_data); + old_session.status.disconnect(on_status_data); + } + return old_session; + } + + // Performs a LIST against the server, returning the results + private async Gee.List send_list_async(ClientSession session, + FolderPath folder, + bool list_children, + Cancellable? cancellable) + throws Error { + bool can_xlist = session.capabilities.has_capability(Capabilities.XLIST); + + // Request SPECIAL-USE if available and not using XLIST + ListReturnParameter? return_param = null; + if (session.capabilities.supports_special_use() && !can_xlist) { + return_param = new ListReturnParameter(); + return_param.add_special_use(); + } + + ListCommand cmd; + if (folder.is_root) { + // List the server root + cmd = new ListCommand.wildcarded( + "", new MailboxSpecifier("%"), can_xlist, return_param + ); + } else { + // List either the given folder or its children + string specifier = session.get_mailbox_for_path(folder).name; + if (list_children) { + string? delim = session.get_delimiter_for_path(folder); + if (delim == null) { + throw new ImapError.INVALID("Cannot list children of namespace with no delimiter"); + } + specifier = specifier + delim + "%"; + } + cmd = new ListCommand(new MailboxSpecifier(specifier), can_xlist, return_param); + } + + Gee.List list_results = new Gee.ArrayList(); + StatusResponse response = yield send_command_async(session, cmd, list_results, null, cancellable); + if (response.status != Status.OK) { + throw new ImapError.SERVER_ERROR("Unable to list children of %s: %s", + (folder != null) ? folder.to_string() : "root", response.to_string()); + } + + // See note at ListCommand about some servers returning the + // parent's name alongside their children ... this filters + // this out + if (folder != null && list_children) { + Gee.Iterator iter = list_results.iterator(); + while (iter.next()) { + FolderPath list_path = session.get_path_for_mailbox( + this.root, iter.get().mailbox + ); + if (list_path.equal_to(folder)) { + debug("Removing parent from LIST results: %s", list_path.to_string()); + iter.remove(); + } + } + } + + return list_results; + } + + private async StatusData send_status_async(ClientSession session, + MailboxSpecifier mailbox, + StatusDataType[] status_types, + Cancellable? cancellable) + throws Error { + Gee.List status_results = new Gee.ArrayList(); + StatusResponse response = yield send_command_async( + session, + new StatusCommand(mailbox, status_types), + null, + status_results, + cancellable + ); + + if (response.status != Status.OK) { + throw new ImapError.SERVER_ERROR("Error fetching \"%s\" STATUS: %s", + mailbox.to_string(), + response.to_string()); + } + + if (status_results.size != 1) { + throw new ImapError.INVALID("Invalid result count (%d) \"%s\" STATUS: %s", + status_results.size, + mailbox.to_string(), + response.to_string()); + } + + return status_results[0]; + } + + private async StatusResponse send_command_async(ClientSession session, + Command cmd, + Gee.List? list_results, + Gee.List? status_results, + Cancellable? cancellable) throws Error { + Gee.Map responses = yield send_multiple_async( + session, + Geary.iterate(cmd).to_array_list(), + list_results, + status_results, + cancellable + ); + + assert(responses.size == 1); + + return Geary.Collection.get_first(responses.values); + } + + private async Gee.Map + send_multiple_async(ClientSession session, + Gee.Collection cmds, + Gee.List? list_results, + Gee.List? status_results, + Cancellable? cancellable) + throws Error { + Gee.Map? responses = null; + int token = yield this.cmd_mutex.claim_async(cancellable); + + // set up collectors + this.list_collector = list_results; + this.status_collector = status_results; + + Error? cmd_err = null; + try { + responses = yield session.send_multiple_commands_async( + cmds, cancellable + ); + } catch (Error err) { + cmd_err = err; + } + + // tear down collectors + this.list_collector = null; + this.status_collector = null; + + this.cmd_mutex.release(ref token); + + if (cmd_err != null) { + throw cmd_err; + } + + return responses; + } + + [NoReturn] + private void throw_not_found(Geary.FolderPath? path) throws EngineError { + throw new EngineError.NOT_FOUND( + "Folder not found: %s", + (path != null) ? path.to_string() : "[root]" + ); + } + + private void on_list_data(MailboxInformation mailbox_info) { + if (list_collector != null) + list_collector.add(mailbox_info); + } + + private void on_status_data(StatusData status_data) { + if (status_collector != null) + status_collector.add(status_data); + } + +} diff -Nru geary-0.12.4/src/engine/imap/api/imap-account.vala geary-3.32.0/src/engine/imap/api/imap-account.vala --- geary-0.12.4/src/engine/imap/api/imap-account.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/api/imap-account.vala 1970-01-01 00:00:00.000000000 +0000 @@ -1,615 +0,0 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. - * - * This software is licensed under the GNU Lesser General Public License - * (version 2.1 or later). See the COPYING file in this distribution. - */ - -/** - * Provides an interface into the IMAP stack that provides a simpler interface for a - * Geary.Account implementation. - * - * Because of the complexities of the IMAP protocol, this private class takes common operations - * that a Geary.Account implementation would need (in particular, {@link Geary.ImapEngine.Account} - * and makes them into simple async calls. - * - * Geary.Imap.Account manages the {@link Imap.Folder} objects it returns, but only in the sense - * that it will not create new instances repeatedly. Otherwise, it does not refresh or update the - * Imap.Folders themselves (such as update their {@link Imap.StatusData} periodically). - * That's the responsibility of the higher layers of the stack. - */ - -private class Geary.Imap.Account : BaseObject { - public bool is_open { get; private set; default = false; } - - private string name; - private AccountInformation account_information; - private ClientSessionManager session_mgr; - private ClientSession? account_session = null; - private Nonblocking.Mutex account_session_mutex = new Nonblocking.Mutex(); - private Nonblocking.Mutex cmd_mutex = new Nonblocking.Mutex(); - private Gee.HashMap path_to_mailbox = new Gee.HashMap< - FolderPath, MailboxInformation>(); - private Gee.HashMap folders = new Gee.HashMap(); - private Gee.List? list_collector = null; - private Gee.List? status_collector = null; - private Gee.List? server_data_collector = null; - private Imap.MailboxSpecifier? inbox_specifier = null; - private string hierarchy_delimiter = null; - - - public signal void login_failed(Geary.Credentials? cred, StatusResponse? response); - - public Account(Geary.AccountInformation account_information) { - name = "IMAP Account for %s".printf(account_information.imap_credentials.to_string()); - this.account_information = account_information; - this.session_mgr = new ClientSessionManager(account_information); - - session_mgr.login_failed.connect(on_login_failed); - } - - private void check_open() throws Error { - if (!is_open) - throw new EngineError.OPEN_REQUIRED("Imap.Account not open"); - } - - public async void open_async(Cancellable? cancellable = null) throws Error { - if (is_open) - throw new EngineError.ALREADY_OPEN("Imap.Account already open"); - - yield session_mgr.open_async(cancellable); - - is_open = true; - } - - public async void close_async(Cancellable? cancellable = null) throws Error { - if (!is_open) - return; - - yield drop_session_async(cancellable); - - try { - yield session_mgr.close_async(cancellable); - } catch (Error err) { - // ignored - } - - is_open = false; - } - - // Claiming session in open_async() would delay opening, which make take too long ... rather, - // this is used by the various calls to put off claiming a session until needed (which - // possibly is long enough for ClientSessionManager to get a few ready). - private async ClientSession claim_session_async(Cancellable? cancellable) throws Error { - // check if available session is in good state - if (account_session != null - && account_session.get_protocol_state(null) != ClientSession.ProtocolState.AUTHORIZED) { - yield drop_session_async(cancellable); - } - - int token = yield account_session_mutex.claim_async(cancellable); - - Error? err = null; - if (account_session == null) { - try { - account_session = yield session_mgr.claim_authorized_session_async(cancellable); - - account_session.list.connect(on_list_data); - account_session.status.connect(on_status_data); - account_session.server_data_received.connect(on_server_data_received); - account_session.disconnected.connect(on_disconnected); - - // Determine INBOX, hierarchy delimiter, special use flags, etc - yield list_inbox(account_session, cancellable); - } catch (Error claim_err) { - err = claim_err; - } - } - - account_session_mutex.release(ref token); - - if (err != null) { - if (account_session != null) - yield drop_session_async(null); - - throw err; - } - - return account_session; - } - - private async void list_inbox(ClientSession session, Cancellable? cancellable) - throws Error { - // Can't use send_command_async() directly because this is - // called by claim_session_async(), which is called by - // send_command_async(). - int token = yield this.cmd_mutex.claim_async(cancellable); - Error? release_error = null; - try { - // collect server data directly for direct decoding - this.server_data_collector = new Gee.ArrayList(); - - MailboxInformation? info = null; - Imap.StatusResponse response = yield session.send_command_async( - new ListCommand(MailboxSpecifier.inbox, false, null), cancellable); - if (response.status == Imap.Status.OK && !this.server_data_collector.is_empty) { - info = MailboxInformation.decode(this.server_data_collector[0], false); - } - - if (info != null) { - this.inbox_specifier = info.mailbox; - this.hierarchy_delimiter = info.delim; - } - - if (this.hierarchy_delimiter == null) { - // LIST response didn't include a delim for the - // INBOX. Ideally we'd just use NAMESPACE instead (Bug - // 726866) but for now, just list folders alongside the - // INBOX. - this.server_data_collector = new Gee.ArrayList(); - response = yield session.send_command_async( - new ListCommand.wildcarded( - "", new MailboxSpecifier("%"), false, null - ), - cancellable - ); - if (response.status == Imap.Status.OK) { - foreach (ServerData data in this.server_data_collector) { - info = MailboxInformation.decode(data, false); - this.hierarchy_delimiter = info.delim; - if (this.hierarchy_delimiter != null) { - break; - } - } - } - } - - if (this.inbox_specifier == null) { - throw new ImapError.INVALID("Unable to find INBOX"); - } - if (this.hierarchy_delimiter == null) { - throw new ImapError.INVALID("Unable to determine hierarchy delimiter"); - } - debug("[%s] INBOX specifier: %s", to_string(), - this.inbox_specifier.to_string()); - debug("[%s] Hierarchy delimiter: %s", to_string(), - this.hierarchy_delimiter); - } finally { - this.server_data_collector = new Gee.ArrayList(); - try { - this.cmd_mutex.release(ref token); - } catch (Error e) { - // Vala 0.32.1 won't let you throw an exception from a - // finally block :( - release_error = e; - } - } - if (release_error != null) { - throw release_error; - } - } - - private async void drop_session_async(Cancellable? cancellable) { - debug("[%s] Dropping account session...", to_string()); - - int token; - try { - token = yield account_session_mutex.claim_async(cancellable); - } catch (Error err) { - debug("Unable to claim Imap.Account session mutex: %s", err.message); - - return; - } - - string desc = account_session != null ? account_session.to_string() : "(none)"; - - if (account_session != null) { - // disconnect signals before releasing (in particular, "disconnected" will in turn - // reenter this method, so avoid that) - account_session.list.disconnect(on_list_data); - account_session.status.disconnect(on_status_data); - account_session.server_data_received.disconnect(on_server_data_received); - account_session.disconnected.disconnect(on_disconnected); - - debug("[%s] Releasing account session %s", to_string(), desc); - - try { - yield session_mgr.release_session_async(account_session, cancellable); - } catch (Error err) { - // ignored - } - - debug("[%s] Released account session %s", to_string(), desc); - - account_session = null; - } - - try { - account_session_mutex.release(ref token); - } catch (Error err) { - // ignored - } - - debug("[%s] Dropped account session (%s)", to_string(), desc); - } - - private void on_list_data(MailboxInformation mailbox_info) { - if (list_collector != null) - list_collector.add(mailbox_info); - } - - private void on_status_data(StatusData status_data) { - if (status_collector != null) - status_collector.add(status_data); - } - - private void on_server_data_received(ServerData server_data) { - if (server_data_collector != null) - server_data_collector.add(server_data); - } - - private void on_disconnected() { - drop_session_async.begin(null); - } - - public async bool folder_exists_async(FolderPath path, Cancellable? cancellable) throws Error { - return path_to_mailbox.has_key(path); - } - - public async void create_folder_async(FolderPath path, Cancellable? cancellable) throws Error { - check_open(); - yield claim_session_async(cancellable); - - StatusResponse response = yield send_command_async(new CreateCommand( - new MailboxSpecifier.from_folder_path(path, this.hierarchy_delimiter)), null, null, cancellable); - - if (response.status != Status.OK) { - throw new ImapError.SERVER_ERROR("Server reports error creating path %s: %s", path.to_string(), - response.to_string()); - } - } - - // By supplying fallback STATUS, the Folder may be fetched if a network error occurs; if null, - // the network error is thrown - public async Imap.Folder fetch_folder_async(FolderPath path, out bool created, - StatusData? fallback_status_data, Cancellable? cancellable) throws Error { - check_open(); - - created = false; - - if (folders.has_key(path)) - return folders.get(path); - - created = true; - - // if not in map, use list_children_async to add it (if it exists) - if (!path_to_mailbox.has_key(path)) { - debug("Listing children to find %s", path.to_string()); - yield list_children_async(path.get_parent(), cancellable); - } - - MailboxInformation? mailbox_info = path_to_mailbox.get(path); - if (mailbox_info == null) - throw_not_found(path); - - // construct folder path for new folder, converting XLIST Inbox name to canonical INBOX - FolderPath folder_path = mailbox_info.get_path(inbox_specifier); - - Imap.Folder folder; - if (!mailbox_info.attrs.is_no_select) { - StatusData status; - try { - status = yield fetch_status_async(folder_path, StatusDataType.all(), cancellable); - } catch (Error err) { - if (fallback_status_data == null) - throw err; - - debug("Unable to fetch STATUS for %s, using fallback from local: %s", folder_path.to_string(), - err.message); - - status = fallback_status_data; - } - - folder = new Imap.Folder(folder_path, session_mgr, status, mailbox_info, this.hierarchy_delimiter); - } else { - folder = new Imap.Folder.unselectable(folder_path, session_mgr, mailbox_info, this.hierarchy_delimiter); - } - - folders.set(path, folder); - - return folder; - } - - /** - * Returns an Imap.Folder that is not stored long-term in the Imap.Account object. - * - * This means the Imap.Folder is not re-used or used by multiple users or containers. This is - * useful for one-shot operations on the server. - */ - public async Imap.Folder fetch_unrecycled_folder_async(FolderPath path, Cancellable? cancellable) - throws Error { - check_open(); - - MailboxInformation? mailbox_info = path_to_mailbox.get(path); - if (mailbox_info == null) - throw_not_found(path); - - // construct canonical folder path - FolderPath folder_path = mailbox_info.get_path(inbox_specifier); - - Imap.Folder folder; - if (!mailbox_info.attrs.is_no_select) { - StatusData status = yield fetch_status_async(folder_path, StatusDataType.all(), cancellable); - - folder = new Imap.Folder(folder_path, session_mgr, status, mailbox_info, this.hierarchy_delimiter); - } else { - folder = new Imap.Folder.unselectable(folder_path, session_mgr, mailbox_info, this.hierarchy_delimiter); - } - - return folder; - } - - internal void folders_removed(Gee.Collection paths) { - foreach (FolderPath path in paths) { - if (path_to_mailbox.has_key(path)) - path_to_mailbox.unset(path); - if (folders.has_key(path)) - folders.unset(path); - } - } - - public async void fetch_counts_async(FolderPath path, out int unseen, out int total, - Cancellable? cancellable) throws Error { - check_open(); - - MailboxInformation? mailbox_info = path_to_mailbox.get(path); - if (mailbox_info == null) - throw_not_found(path); - if (mailbox_info.attrs.is_no_select) { - throw new EngineError.UNSUPPORTED("Can't fetch unseen count for unselectable folder %s", - path.to_string()); - } - - StatusData data = yield fetch_status_async(path, { StatusDataType.UNSEEN, StatusDataType.MESSAGES }, - cancellable); - unseen = data.unseen; - total = data.messages; - } - - private async StatusData fetch_status_async(FolderPath path, StatusDataType[] status_types, - Cancellable? cancellable) throws Error { - check_open(); - - Gee.List status_results = new Gee.ArrayList(); - StatusResponse response = yield send_command_async( - new StatusCommand(new MailboxSpecifier.from_folder_path(path, this.hierarchy_delimiter), status_types), - null, status_results, cancellable); - - throw_fetch_error(response, path, status_results.size); - - return status_results[0]; - } - - private void throw_fetch_error(StatusResponse response, FolderPath path, int result_count) - throws Error { - assert(response.is_completion); - - if (response.status != Status.OK) { - throw new ImapError.SERVER_ERROR("Server reports error for path %s: %s", path.to_string(), - response.to_string()); - } - - if (result_count != 1) { - throw new ImapError.INVALID("Server reports %d results for fetch of path %s: %s", - result_count, path.to_string(), response.to_string()); - } - } - - public async Gee.List? list_child_folders_async(FolderPath? parent, Cancellable? cancellable) - throws Error { - check_open(); - - Gee.List? child_info = yield list_children_async(parent, cancellable); - if (child_info == null || child_info.size == 0) { - debug("No children found listing %s", (parent != null) ? parent.to_string() : "root"); - - return null; - } - - Gee.List child_folders = new Gee.ArrayList(); - - Gee.Map info_map = new Gee.HashMap< - MailboxSpecifier, MailboxInformation>(); - Gee.Map cmd_map = new Gee.HashMap< - StatusCommand, MailboxSpecifier>(); - foreach (MailboxInformation mailbox_info in child_info) { - // if new mailbox is unselectable, don't bother doing a STATUS command - if (mailbox_info.attrs.is_no_select) { - Imap.Folder folder = new Imap.Folder.unselectable(mailbox_info.get_path(inbox_specifier), - session_mgr, mailbox_info, this.hierarchy_delimiter); - folders.set(folder.path, folder); - child_folders.add(folder); - - continue; - } - - info_map.set(mailbox_info.mailbox, mailbox_info); - cmd_map.set(new StatusCommand(mailbox_info.mailbox, StatusDataType.all()), - mailbox_info.mailbox); - } - - // if no STATUS results are needed, bail out with what's been collected - if (cmd_map.size == 0) - return child_folders; - - Gee.List status_results = new Gee.ArrayList(); - Gee.Map responses = yield send_multiple_async(cmd_map.keys, - null, status_results, cancellable); - - foreach (Command cmd in responses.keys) { - StatusCommand status_cmd = (StatusCommand) cmd; - StatusResponse response = responses.get(cmd); - - MailboxSpecifier mailbox = cmd_map.get(status_cmd); - MailboxInformation mailbox_info = info_map.get(mailbox); - - if (response.status != Status.OK) { - message("Unable to get STATUS of %s: %s", mailbox.to_string(), response.to_string()); - message("STATUS command: %s", cmd.to_string()); - - continue; - } - - StatusData? found_status = null; - foreach (StatusData status_data in status_results) { - if (status_data.mailbox.equal_to(mailbox)) { - found_status = status_data; - - break; - } - } - - if (found_status == null) { - message("Unable to get STATUS of %s: not returned from server", mailbox.to_string()); - - continue; - } - - status_results.remove(found_status); - - FolderPath folder_path = mailbox_info.get_path(inbox_specifier); - - // if already have an Imap.Folder for this mailbox, use that - Imap.Folder? folder = folders.get(folder_path); - if (folder != null) { - folder.properties.update_status(found_status); - } else { - folder = new Imap.Folder(folder_path, session_mgr, found_status, mailbox_info, this.hierarchy_delimiter); - folders.set(folder.path, folder); - } - - child_folders.add(folder); - } - - if (status_results.size > 0) - debug("%d STATUS results leftover", status_results.size); - - return child_folders; - } - - private async Gee.List? list_children_async(FolderPath? parent, Cancellable? cancellable) - throws Error { - ClientSession session = yield claim_session_async(cancellable); - bool can_xlist = session.capabilities.has_capability(Capabilities.XLIST); - - // Request SPECIAL-USE if available and not using XLIST - ListReturnParameter? return_param = null; - if (session.capabilities.supports_special_use() && !can_xlist) { - return_param = new ListReturnParameter(); - return_param.add_special_use(); - } - - ListCommand cmd; - if (parent == null) { - cmd = new ListCommand.wildcarded("", new MailboxSpecifier("%"), can_xlist, return_param); - } else { - if (hierarchy_delimiter == null) { - throw new ImapError.INVALID("Unable to list children of %s: no delimiter specified", - parent.to_string()); - } - - string? specifier = parent.get_fullpath(hierarchy_delimiter); - specifier += specifier.has_suffix(hierarchy_delimiter) ? "%" : (hierarchy_delimiter + "%"); - - cmd = new ListCommand(new MailboxSpecifier(specifier), can_xlist, return_param); - } - - Gee.List list_results = new Gee.ArrayList(); - StatusResponse response = yield send_command_async(cmd, list_results, null, cancellable); - - if (response.status != Status.OK) { - throw new ImapError.SERVER_ERROR("Unable to list children of %s: %s", - (parent != null) ? parent.to_string() : "root", response.to_string()); - } - - // See note at ListCommand about some servers returning the parent's name alongside their - // children ... this filters this out - if (parent != null) { - Gee.Iterator iter = list_results.iterator(); - while (iter.next()) { - FolderPath list_path = iter.get().mailbox.to_folder_path(hierarchy_delimiter, - inbox_specifier); - if (list_path.equal_to(parent)) { - debug("Removing parent from LIST results: %s", list_path.to_string()); - iter.remove(); - } - } - } - - // stash all MailboxInformation by path - // TODO: remove any MailboxInformation for this parent that is not found (i.e. has been - // removed on the server) - foreach (MailboxInformation mailbox_info in list_results) - path_to_mailbox.set(mailbox_info.get_path(inbox_specifier), mailbox_info); - - return (list_results.size > 0) ? list_results : null; - } - - private async StatusResponse send_command_async(Command cmd, - Gee.List? list_results, Gee.List? status_results, - Cancellable? cancellable) throws Error { - Gee.Map responses = yield send_multiple_async( - Geary.iterate(cmd).to_array_list(), list_results, status_results, - cancellable); - - assert(responses.size == 1); - - return Geary.Collection.get_first(responses.values); - } - - private async Gee.Map send_multiple_async( - Gee.Collection cmds, Gee.List? list_results, - Gee.List? status_results, Cancellable? cancellable) throws Error { - int token = yield cmd_mutex.claim_async(cancellable); - - // set up collectors - list_collector = list_results; - status_collector = status_results; - - Gee.Map? responses = null; - Error? err = null; - try { - ClientSession session = yield claim_session_async(cancellable); - responses = yield session.send_multiple_commands_async(cmds, cancellable); - } catch (Error send_err) { - err = send_err; - } - - // disconnect collectors - list_collector = null; - status_collector = null; - - cmd_mutex.release(ref token); - - if (err != null) - throw err; - - assert(responses != null); - - return responses; - } - - [NoReturn] - private void throw_not_found(Geary.FolderPath? path) throws EngineError { - throw new EngineError.NOT_FOUND("Folder %s not found on %s", - (path != null) ? path.to_string() : "root", session_mgr.to_string()); - } - - private void on_login_failed(StatusResponse? response) { - login_failed(account_information.imap_credentials, response); - } - - public string to_string() { - return name; - } -} - diff -Nru geary-0.12.4/src/engine/imap/api/imap-client-service.vala geary-3.32.0/src/engine/imap/api/imap-client-service.vala --- geary-0.12.4/src/engine/imap/api/imap-client-service.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/src/engine/imap/api/imap-client-service.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,509 @@ +/* + * Copyright 2016 Software Freedom Conservancy Inc. + * Copyright 2017-2018 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +/** + * Manages a pool of IMAP client sessions. + * + * When started and when the remote host is reachable, the manager + * will establish a pool of {@link ClientSession} instances that are + * connected to the service's endpoint, ensuring there are at least + * {@link min_pool_size} available. A connected, authorised client + * session can be obtained from the connection pool by calling {@link + * claim_authorized_session_async}, and when finished with returned by + * calling {@link release_session_async}. + * + * This class is not thread-safe. + */ +internal class Geary.Imap.ClientService : Geary.ClientService { + + + private const int DEFAULT_MIN_POOL_SIZE = 1; + private const int DEFAULT_MAX_FREE_SIZE = 1; + private const int CHECK_NOOP_THRESHOLD_SEC = 5; + + /** + * Set to zero or negative value if keepalives should be disabled when a connection has not + * selected a mailbox. (This is not recommended.) + * + * This only affects newly created sessions or sessions leaving the selected/examined state + * and returning to an authorized state. + */ + public uint unselected_keepalive_sec { get; set; default = ClientSession.DEFAULT_UNSELECTED_KEEPALIVE_SEC; } + + /** + * Set to zero or negative value if keepalives should be disabled when a mailbox is selected + * or examined. (This is not recommended.) + * + * This only affects newly selected/examined sessions. + */ + public uint selected_keepalive_sec { get; set; default = ClientSession.DEFAULT_SELECTED_KEEPALIVE_SEC; } + + /** + * Set to zero or negative value if keepalives should be disabled when a mailbox is selected + * or examined and IDLE is supported. (This is not recommended.) + * + * This only affects newly selected/examined sessions. + */ + public uint selected_with_idle_keepalive_sec { get; set; default = ClientSession.DEFAULT_SELECTED_WITH_IDLE_KEEPALIVE_SEC; } + + /** + * Specifies the minimum number of sessions to keep open. + * + * The manager will attempt to keep at least this number of + * connections open at all times. + * + * Setting this does not immediately adjust the pool size in + * either direction. Adjustment will happen as connections are + * needed or closed. + */ + public int min_pool_size { get; set; default = DEFAULT_MIN_POOL_SIZE; } + + /** + * Specifies the maximum number of free sessions to keep open. + * + * If there are already this number of free sessions available, + * the manager will close any additional sessions that are + * released, instead of keeping them for re-use. However it will + * not close sessions if doing so would reduce the size of the + * pool below {@link min_pool_size}. + * + * Setting this does not immediately adjust the pool size in + * either direction. Adjustment will happen as connections are + * needed or closed. + */ + public int max_free_size { get; set; default = DEFAULT_MAX_FREE_SIZE; } + + /** + * Determines if returned sessions should be kept or discarded. + */ + public bool discard_returned_sessions = false; + + private Nonblocking.Mutex sessions_mutex = new Nonblocking.Mutex(); + private Gee.Set all_sessions = + new Gee.HashSet(); + private Nonblocking.Queue free_queue = + new Nonblocking.Queue.fifo(); + + private Cancellable? pool_cancellable = null; + + + public ClientService(AccountInformation account, + ServiceInformation service, + Endpoint remote) { + base(account, service, remote); + } + + /** + * Starts the manager opening IMAP client sessions. + */ + public override async void start(GLib.Cancellable? cancellable = null) + throws GLib.Error { + if (this.is_running) { + throw new EngineError.ALREADY_OPEN( + "IMAP client service already open" + ); + } + + this.pool_cancellable = new Cancellable(); + notify_started(); + } + + /** + * Stops the manager running, closing any existing sessions. + */ + public override async void stop(GLib.Cancellable? cancellable = null) + throws GLib.Error { + if (!this.is_running) { + return; + } + + notify_stopped(); + + this.pool_cancellable.cancel(); + yield close_pool(); + + // TODO: This isn't the best (deterministic) way to deal with + // this, but it's easy and works for now + int attempts = 0; + while (this.all_sessions.size > 0) { + debug("[%s] Waiting for client sessions to disconnect...", + this.account.id); + Timeout.add(250, this.stop.callback); + yield; + + // give up after three seconds + if (++attempts > 12) + break; + } + } + + /** + * Claims a free session, blocking until one becomes available. + * + * This call will fail fast if the pool is known to not in the + * right state (bad authorisation credentials, host not ready, + * etc), but then will block while attempting to obtain a + * connection if the free queue is empty. If an error occurs when + * this connection is in progress, then the call will block until + * another becomes available (host becomes reachable again, user + * enters password, etc). If this is undesirable, then the caller + * may cancel the call. + * + * @throws ImapError.UNAUTHENTICATED if the stored credentials are + * invalid. + * @throws ImapError.UNAVAILABLE if the IMAP endpoint is not + * trusted or is not reachable. + */ + public async ClientSession + claim_authorized_session_async(GLib.Cancellable? cancellable) + throws GLib.Error { + if (!this.is_running) { + throw new EngineError.OPEN_REQUIRED( + "IMAP client service is not running" + ); + } + + debug("Claiming session with %d of %d free", + this.free_queue.size, this.all_sessions.size); + + if (this.current_status == AUTHENTICATION_FAILED) { + throw new ImapError.UNAUTHENTICATED("Invalid credentials"); + } + + if (this.current_status == TLS_VALIDATION_FAILED) { + throw new ImapError.UNAVAILABLE( + "Untrusted host %s", this.remote.to_string() + ); + } + + ClientSession? claimed = null; + while (claimed == null) { + // This isn't racy since this is class is not accessed by + // multiple threads. Don't wait for it to return though + // because we only want to kick off establishing the + // connection, and wait for it via the queue. + if (this.free_queue.size == 0) { + this.check_pool.begin(true); + } + + claimed = yield this.free_queue.receive(cancellable); + + // Connection may have gone bad sitting in the queue, so + // check it before using it + if (!(yield check_session(claimed, true))) { + claimed = null; + } + } + + return claimed; + } + + public async void release_session_async(ClientSession session) + throws Error { + // Don't check_open(), it's valid for this to be called when + // is_running is false, that happens during mop-up + + debug("[%s] Returning session with %d of %d free", + this.account.id, this.free_queue.size, this.all_sessions.size); + + bool too_many_free = ( + this.free_queue.size >= this.max_free_size && + this.all_sessions.size > this.min_pool_size + ); + + if (!this.is_running || this.discard_returned_sessions || too_many_free) { + yield force_disconnect(session); + } else if (yield check_session(session, false)) { + bool free = true; + MailboxSpecifier? mailbox = null; + ClientSession.ProtocolState proto = session.get_protocol_state(out mailbox); + // If the session has a mailbox selected, close it before + // adding it back to the pool + if (proto == ClientSession.ProtocolState.SELECTED || + proto == ClientSession.ProtocolState.SELECTING) { + // always close mailbox to return to authorized state + try { + yield session.close_mailbox_async(pool_cancellable); + } catch (ImapError imap_error) { + debug("[%s] Error attempting to close released session %s: %s", + this.account.id, session.to_string(), imap_error.message); + free = false; + } + + if (session.get_protocol_state(null) != + ClientSession.ProtocolState.AUTHORIZED) { + // Closing it didn't work, so drop it + yield force_disconnect(session); + free = false; + } + } + + if (free) { + debug("[%s] Unreserving session %s", + this.account.id, session.to_string()); + this.free_queue.send(session); + } + } + } + + /** Restarts the client session pool. */ + protected override void became_reachable() { + this.check_pool.begin(false); + } + + /** Closes the client session pool. */ + protected override void became_unreachable() { + this.close_pool.begin(); + } + + private async void check_pool(bool is_claiming) { + debug("Checking session pool with %d of %d free", + this.free_queue.size, this.all_sessions.size); + + if (!is_claiming) { + // To prevent spurious connection failures, ensure tokens + // are up-to-date before attempting a connection, but + // after we know we should be able to connect to it + try { + bool loaded = yield this.account.load_incoming_credentials( + this.pool_cancellable + ); + if (!loaded) { + notify_authentication_failed(); + return; + } + } catch (GLib.Error err) { + notify_connection_failed(new ErrorContext(err)); + return; + } + } + + int needed = this.min_pool_size - this.all_sessions.size; + if (needed <= 0 && is_claiming) { + needed = 1; + } + + // Open as many as needed in parallel + while (needed > 0) { + add_pool_session.begin(); + needed--; + } + } + + private async void add_pool_session() { + ClientSession? new_session = null; + try { + new_session = yield this.create_new_authorized_session( + this.pool_cancellable + ); + } catch (ImapError.UNAUTHENTICATED err) { + debug("[%s] Auth error adding new session to the pool: %s", + this.account.id, err.message); + notify_authentication_failed(); + } catch (GLib.TlsError.BAD_CERTIFICATE err) { + // Don't notify of an error here, since the untrusted host + // handler will be dealing with it already. + debug("[%s] TLS validation error adding new session to the pool: %s", + this.account.id, err.message); + } catch (GLib.IOError.CANCELLED err) { + // Nothing to do here + } catch (GLib.Error err) { + Geary.ErrorContext context = new Geary.ErrorContext(err); + debug("[%s] Error creating new session for the pool: %s", + this.account.id, context.format_full_error()); + notify_connection_failed(context); + } + + if (new_session == null) { + // An error was thrown, so close the pool + this.close_pool.begin(); + } else { + try { + yield this.sessions_mutex.execute_locked(() => { + this.all_sessions.add(new_session); + }); + this.free_queue.send(new_session); + notify_connected(); + } catch (GLib.Error err) { + Geary.ErrorContext context = new Geary.ErrorContext(err); + debug("[%s] Error adding new session to the pool: %s", + this.account.id, context.format_full_error()); + notify_connection_failed(context); + new_session.disconnect_async.begin(null); + this.close_pool.begin(); + } + } + } + + /** Determines if a session is valid, disposing of it if not. */ + private async bool check_session(ClientSession target, bool claiming) { + bool valid = false; + switch (target.get_protocol_state(null)) { + case ClientSession.ProtocolState.AUTHORIZED: + case ClientSession.ProtocolState.CLOSING_MAILBOX: + valid = true; + break; + + case ClientSession.ProtocolState.SELECTED: + case ClientSession.ProtocolState.SELECTING: + if (claiming) { + yield force_disconnect(target); + } else { + valid = true; + } + break; + + case ClientSession.ProtocolState.UNCONNECTED: + // Already disconnected, so drop it on the floor + try { + yield remove_session_async(target); + } catch (Error err) { + debug("[%s] Error removing unconnected session: %s", + this.account.id, err.message); + } + break; + + default: + yield force_disconnect(target); + break; + } + + // We now know if the session /thinks/ it is in a reasonable + // state, but if we're claiming a new session it *needs* to be + // good, and in particular we want to ensure the connection + // hasn't timed out or otherwise been dropped. So send a NOOP + // and wait for it to find out. Only do this if we haven't + // seen a response from the server in a little while, however. + // + // XXX This is problematic since the server may send untagged + // responses to the NOOP, but at least since it won't be in + // the Selected state, we won't lose notifications of new + // messages, etc, only folder status, which should eventually + // get picked up by UpdateRemoteFolders. :/ + if (claiming && + target.last_seen + (CHECK_NOOP_THRESHOLD_SEC * 1000000) < GLib.get_real_time()) { + try { + debug("Sending NOOP when claiming a session"); + yield target.send_command_async( + new NoopCommand(), this.pool_cancellable + ); + } catch (Error err) { + debug("Error sending NOOP: %s", err.message); + valid = false; + } + } + + return valid; + } + + private async ClientSession create_new_authorized_session(Cancellable? cancellable) throws Error { + debug("[%s] Opening new session", this.account.id); + Credentials? login = this.configuration.credentials; + if (login != null && !login.is_complete()) { + throw new ImapError.UNAUTHENTICATED("Token not loaded"); + } + + ClientSession new_session = new ClientSession(remote); + yield new_session.connect_async(cancellable); + + try { + yield new_session.initiate_session_async(login, cancellable); + } catch (Error err) { + // need to disconnect before throwing error ... don't + // honor Cancellable here, it's important to disconnect + // the client before dropping the ref + try { + yield new_session.disconnect_async(null); + } catch (Error disconnect_err) { + debug("[%s] Error disconnecting due to session initiation failure, ignored: %s", + new_session.to_string(), disconnect_err.message); + } + + throw err; + } + + // Only bother tracking disconnects and enabling keeping alive + // now the session is properly established. + new_session.disconnected.connect(on_disconnected); + new_session.enable_keepalives(selected_keepalive_sec, + unselected_keepalive_sec, + selected_with_idle_keepalive_sec); + + return new_session; + } + + private async void close_pool() { + debug("Closing the pool, disconnecting %d sessions", + this.all_sessions.size); + + // Take a copy and work off that while scheduling disconnects, + // since as they disconnect they'll remove themselves from the + // sessions list and cause the loop below to explode. + ClientSession[]? to_close = null; + try { + yield this.sessions_mutex.execute_locked(() => { + to_close = this.all_sessions.to_array(); + }); + } catch (Error err) { + debug("Error occurred copying sessions: %s", err.message); + } + + // Disconnect all existing sessions at once. Don't block + // waiting for any since we don't want to delay closing the + // others. + foreach (ClientSession session in to_close) { + session.disconnect_async.begin(null); + } + } + + private async void force_disconnect(ClientSession session) { + debug("[%s] Dropping session %s", this.account.id, session.to_string()); + + try { + yield remove_session_async(session); + } catch (Error err) { + debug("[%s] Error removing session: %s", + this.account.id, err.message); + } + + // Don't wait for this to finish because we don't want to + // block claiming a new session, shutdown, etc. + session.disconnect_async.begin(null); + } + + private async bool remove_session_async(ClientSession session) throws Error { + // Ensure the session isn't held on to, anywhere + + this.free_queue.revoke(session); + + bool removed = false; + yield this.sessions_mutex.execute_locked(() => { + removed = this.all_sessions.remove(session); + }); + + if (removed) { + session.disconnected.disconnect(on_disconnected); + } + return removed; + } + + private void on_disconnected(ClientSession session, ClientSession.DisconnectReason reason) { + this.remove_session_async.begin( + session, + (obj, res) => { + try { + this.remove_session_async.end(res); + } catch (Error err) { + debug("[%s] Error removing disconnected session: %s", + this.account.id, err.message); + } + } + ); + } + +} diff -Nru geary-0.12.4/src/engine/imap/api/imap-email-flags.vala geary-3.32.0/src/engine/imap/api/imap-email-flags.vala --- geary-0.12.4/src/engine/imap/api/imap-email-flags.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/api/imap-email-flags.vala 2019-03-17 13:39:29.000000000 +0000 @@ -6,23 +6,26 @@ public class Geary.Imap.EmailFlags : Geary.EmailFlags { public MessageFlags message_flags { get; private set; } - + public EmailFlags(MessageFlags flags) { message_flags = flags; - + if (!flags.contains(MessageFlag.SEEN)) add(UNREAD); - + if (flags.contains(MessageFlag.FLAGGED)) add(FLAGGED); - + if (flags.contains(MessageFlag.LOAD_REMOTE_IMAGES)) add(LOAD_REMOTE_IMAGES); - + if (flags.contains(MessageFlag.DRAFT)) add(DRAFT); + + if (flags.contains(MessageFlag.DELETED)) + add(DELETED); } - + /** * Converts a generic {@link Geary.EmailFlags} to IMAP's internal representation of them. * @@ -32,60 +35,66 @@ Imap.EmailFlags? imap_flags = api_flags as Imap.EmailFlags; if (imap_flags != null) return imap_flags; - + Gee.List msg_flags_add; Gee.List msg_flags_remove; Geary.Imap.MessageFlag.from_email_flags(api_flags, null, out msg_flags_add, out msg_flags_remove); - + Gee.ArrayList msg_flags = new Gee.ArrayList(); - + foreach(MessageFlag mf in msg_flags_add) msg_flags.add(mf); - + // This is a special case, since it's read and seen are opposites. if (!api_flags.is_unread()) msg_flags.add(MessageFlag.SEEN); - + foreach(MessageFlag mf in msg_flags_remove) msg_flags.remove(mf); - + return new Imap.EmailFlags(new MessageFlags(msg_flags)); } - + protected override void notify_added(Gee.Collection added) { foreach (NamedFlag flag in added) { if (flag.equal_to(UNREAD)) message_flags.remove(MessageFlag.SEEN); - + if (flag.equal_to(FLAGGED)) message_flags.add(MessageFlag.FLAGGED); - + if (flag.equal_to(LOAD_REMOTE_IMAGES)) message_flags.add(MessageFlag.LOAD_REMOTE_IMAGES); - + if (flag.equal_to(DRAFT)) message_flags.add(MessageFlag.DRAFT); + + if (flag.equal_to(DELETED)) + message_flags.add(MessageFlag.DELETED); } - + base.notify_added(added); } - + protected override void notify_removed(Gee.Collection removed) { foreach (NamedFlag flag in removed) { if (flag.equal_to(UNREAD)) message_flags.add(MessageFlag.SEEN); - + if (flag.equal_to(FLAGGED)) message_flags.remove(MessageFlag.FLAGGED); - + if (flag.equal_to(LOAD_REMOTE_IMAGES)) message_flags.remove(MessageFlag.LOAD_REMOTE_IMAGES); - + if (flag.equal_to(DRAFT)) message_flags.remove(MessageFlag.DRAFT); + + if (flag.equal_to(DELETED)) + message_flags.remove(MessageFlag.DELETED); } - + base.notify_removed(removed); } } diff -Nru geary-0.12.4/src/engine/imap/api/imap-email-properties.vala geary-3.32.0/src/engine/imap/api/imap-email-properties.vala --- geary-0.12.4/src/engine/imap/api/imap-email-properties.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/api/imap-email-properties.vala 2019-03-17 13:39:29.000000000 +0000 @@ -7,33 +7,33 @@ public class Geary.Imap.EmailProperties : Geary.EmailProperties, Gee.Hashable { public InternalDate? internaldate { get; private set; } public RFC822.Size? rfc822_size { get; private set; } - + public EmailProperties(InternalDate? internaldate, RFC822.Size? rfc822_size) { base (internaldate.value, rfc822_size.value); - + this.internaldate = internaldate; this.rfc822_size = rfc822_size; } - + public bool equal_to(Geary.Imap.EmailProperties other) { if (this == other) return true; - + // for simplicity and robustness, internaldate and rfc822_size must be present in both // to be considered equal if (internaldate == null || other.internaldate == null) return false; - + if (rfc822_size == null || other.rfc822_size == null) return false; - + return true; } - + public uint hash() { return to_string().hash(); } - + public override string to_string() { return "internaldate:%s/size:%s".printf((internaldate != null) ? internaldate.to_string() : "(none)", (rfc822_size != null) ? rfc822_size.to_string() : "(none)"); diff -Nru geary-0.12.4/src/engine/imap/api/imap-folder-properties.vala geary-3.32.0/src/engine/imap/api/imap-folder-properties.vala --- geary-0.12.4/src/engine/imap/api/imap-folder-properties.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/api/imap-folder-properties.vala 2019-03-17 13:39:29.000000000 +0000 @@ -28,7 +28,7 @@ * When new STATUS information comes in, this object's status_messages, unseen, recent, and attrs * fields are updated. * - * When a SELECT/EXAMINE occurs on this folder, this object's select_examine_messages, unseen, + * When a SELECT/EXAMINE occurs on this folder, this object's select_examine_messages, * recent, uid_validity, and uid_next are updated. * * Over time, this object accumulates information depending on what operation was last @@ -37,7 +37,7 @@ * The base class' email_total is updated when either *_messages is updated; however, SELECT/EXAMINE * is considered more authoritative than STATUS. */ - + public class Geary.Imap.FolderProperties : Geary.FolderProperties { /** * -1 if the Folder was not opened via SELECT or EXAMINE. Updated as EXISTS server data @@ -56,45 +56,93 @@ public UIDValidity? uid_validity { get; internal set; } public UID? uid_next { get; internal set; } public MailboxAttributes attrs { get; internal set; } - + + /** - * Note that unseen from SELECT/EXAMINE is the *position* of the first unseen message, - * not the total unseen count, so it's not be passed in here, but rather only from the unseen - * count from a STATUS command - */ - public FolderProperties(int messages, int email_unread, int recent, UIDValidity? uid_validity, - UID? uid_next, MailboxAttributes attrs) { - // give the base class a zero email_unread, as the notion of "unknown" doesn't exist in - // its contract - base (messages, email_unread, Trillian.UNKNOWN, Trillian.UNKNOWN, Trillian.UNKNOWN, false, - false, false); - - select_examine_messages = messages; - status_messages = -1; - this.recent = recent; + * Constructs properties for an IMAP folder that can be selected. + */ + public FolderProperties.selectable(MailboxAttributes attrs, + StatusData status, + Capabilities capabilities) { + this( + attrs, + status.messages, + status.unseen, + capabilities.supports_uidplus() + ); + + this.select_examine_messages = -1; + this.status_messages = status.messages; + this.recent = status.recent; + this.unseen = status.unseen; + this.uid_validity = status.uid_validity; + this.uid_next = status.uid_next; + } + + /** + * Constructs properties for an IMAP folder that can not be selected. + */ + public FolderProperties.not_selectable(MailboxAttributes attrs) { + this(attrs, 0, 0, false); + + this.select_examine_messages = 0; + this.status_messages = -1; + this.recent = 0; + this.unseen = -1; + this.uid_validity = null; + this.uid_next = null; + } + + /** + * Reconstitutes properties for an IMAP folder from the database + */ + internal FolderProperties.from_imapdb(MailboxAttributes attrs, + int email_total, + int email_unread, + UIDValidity? uid_validity, + UID? uid_next) { + this(attrs, email_total, email_unread, false); + + this.select_examine_messages = email_total; + this.status_messages = -1; + this.recent = 0; this.unseen = -1; this.uid_validity = uid_validity; this.uid_next = uid_next; - this.attrs = attrs; - - init_flags(); } - - public FolderProperties.status(StatusData status, MailboxAttributes attrs) { - base (status.messages, status.unseen, Trillian.UNKNOWN, Trillian.UNKNOWN, Trillian.UNKNOWN, - false, false, false); - - select_examine_messages = -1; - status_messages = status.messages; - recent = status.recent; - unseen = status.unseen; - uid_validity = status.uid_validity; - uid_next = status.uid_next; + + protected FolderProperties(MailboxAttributes attrs, + int email_total, + int email_unread, + bool supports_uid) { + Trillian has_children = Trillian.UNKNOWN; + if (attrs.contains(MailboxAttribute.HAS_NO_CHILDREN)) + has_children = Trillian.FALSE; + else if (attrs.contains(MailboxAttribute.HAS_CHILDREN)) + has_children = Trillian.TRUE; + + Trillian supports_children = Trillian.UNKNOWN; + // has_children implies supports_children + if (has_children != Trillian.UNKNOWN) { + supports_children = has_children; + } else { + // !supports_children implies !has_children + supports_children = Trillian.from_boolean(!attrs.contains(MailboxAttribute.NO_INFERIORS)); + if (supports_children.is_impossible()) + has_children = Trillian.FALSE; + } + + Trillian is_openable = Trillian.from_boolean(!attrs.is_no_select); + + base(email_total, email_unread, + has_children, supports_children, is_openable, + false, // not local + false, // not virtual + !supports_uid); + this.attrs = attrs; - - init_flags(); } - + /** * Use with {@link FolderProperties} of the *same folder* seen at different times (i.e. after * SELECTing versus data stored locally). Only compares fields that suggest the contents of @@ -107,19 +155,19 @@ if (uid_next != null && other.uid_next != null && !uid_next.equal_to(other.uid_next)) { debug("%s FolderProperties changed: UIDNEXT=%s other.UIDNEXT=%s", name, uid_next.to_string(), other.uid_next.to_string()); - + return true; } - + // UIDVALIDITY changes indicate the entire folder's contents have potentially altered and // the client needs to reset its local vector if (uid_validity != null && other.uid_validity != null && !uid_validity.equal_to(other.uid_validity)) { debug("%s FolderProperties changed: UIDVALIDITY=%s other.UIDVALIDITY=%s", name, uid_validity.to_string(), other.uid_validity.to_string()); - + return true; } - + // Gmail includes Chat messages in STATUS results but not in SELECT/EXAMINE // results, so message count comparison has to be from the same origin ... use SELECT/EXAMINE // first, as it's more authoritative in many ways @@ -128,47 +176,24 @@ if (diff != 0) { debug("%s FolderProperties changed: SELECT/EXAMINE=%d other.SELECT/EXAMINE=%d diff=%d", name, select_examine_messages, other.select_examine_messages, diff); - + return true; } } - + if (status_messages >= 0 && other.status_messages >= 0) { int diff = status_messages - other.status_messages; if (diff != 0) { debug("%s FolderProperties changed: STATUS=%d other.STATUS=%d diff=%d", name, status_messages, other.status_messages, diff); - + return true; } } - + return false; } - - private void init_flags() { - // \HasNoChildren & \HasChildren are optional attributes (could check for CHILDREN extension, - // but unnecessary here) - if (attrs.contains(MailboxAttribute.HAS_NO_CHILDREN)) - has_children = Trillian.FALSE; - else if (attrs.contains(MailboxAttribute.HAS_CHILDREN)) - has_children = Trillian.TRUE; - else - has_children = Trillian.UNKNOWN; - - // has_children implies supports_children - if (has_children != Trillian.UNKNOWN) { - supports_children = has_children; - } else { - // !supports_children implies !has_children - supports_children = Trillian.from_boolean(!attrs.contains(MailboxAttribute.NO_INFERIORS)); - if (supports_children.is_impossible()) - has_children = Trillian.FALSE; - } - - is_openable = Trillian.from_boolean(!attrs.is_no_select); - } - + /** * Update an existing {@link FolderProperties} with fresh {@link StatusData}. * @@ -182,39 +207,39 @@ uid_validity = status.uid_validity; uid_next = status.uid_next; } - + public void set_status_message_count(int messages, bool force) { if (messages < 0) return; - + status_messages = messages; - + // select/examine more authoritative than status, unless the caller knows otherwise if (force || (select_examine_messages < 0)) email_total = messages; } - + public void set_select_examine_message_count(int messages) { if (messages < 0) return; - + select_examine_messages = messages; - + // select/examine more authoritative than status email_total = messages; } - + public void set_status_unseen(int count) { // drop unknown counts, especially if known is held here if (count < 0) return; - + unseen = count; - + // update base class value (which clients see) email_unread = count; } - + public void set_from_session_capabilities(Capabilities capabilities) { create_never_returns_id = !capabilities.supports_uidplus(); } diff -Nru geary-0.12.4/src/engine/imap/api/imap-folder-root.vala geary-3.32.0/src/engine/imap/api/imap-folder-root.vala --- geary-0.12.4/src/engine/imap/api/imap-folder-root.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/api/imap-folder-root.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,40 +1,55 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. * * This software is licensed under the GNU Lesser General Public License - * (version 2.1 or later). See the COPYING file in this distribution. + * (version 2.1 or later). See the COPYING file in this distribution. */ /** * The root of all IMAP mailbox paths. * - * Because IMAP has peculiar requirements about its mailbox paths (in particular, Inbox is - * guaranteed at the root and is named case-insensitive, and that delimiters are particular to - * each path), this class ensure certain requirements are held throughout the library. + * Because IMAP has peculiar requirements about its mailbox paths (in + * particular, Inbox is guaranteed at the root and is named + * case-insensitive, and that delimiters are particular to each path), + * this class ensure certain requirements are held throughout the + * library. */ +public class Geary.Imap.FolderRoot : Geary.FolderRoot { -private class Geary.Imap.FolderRoot : Geary.FolderRoot { - public bool is_inbox { get; private set; } - - public FolderRoot(string basename) { - bool init_is_inbox; - string normalized_basename = init(basename, out init_is_inbox); - - base (normalized_basename, !init_is_inbox, true); - - is_inbox = init_is_inbox; + + /** + * The canonical path for the IMAP inbox. + * + * This specific path object will always be returned when a child + * with some case-insensitive version of the IMAP inbox mailbox is + * obtained via {@link get_child} from this root folder. However + * since multiple folder roots may be constructed, in general + * {@link FolderPath.equal_to} or {@link FolderPath.compare_to} + * should still be used for testing equality with this path. + */ + public FolderPath inbox { get; private set; } + + + public FolderRoot() { + base(false); + this.inbox = base.get_child( + MailboxSpecifier.CANONICAL_INBOX_NAME, + Trillian.FALSE + ); } - - // This is the magic that ensures the canonical IMAP Inbox name is used throughout the engine - private static string init(string basename, out bool is_inbox) { - if (MailboxSpecifier.is_inbox_name(basename)) { - is_inbox = true; - - return MailboxSpecifier.CANONICAL_INBOX_NAME; - } - - is_inbox = false; - - return basename; + + /** + * Creates a path that is a child of this folder. + * + * If the given basename is that of the IMAP inbox, then {@link + * inbox} will be returned. + */ + public override + FolderPath get_child(string basename, + Trillian is_case_sensitive = Trillian.UNKNOWN) { + return (MailboxSpecifier.is_inbox_name(basename)) + ? this.inbox + : base.get_child(basename, is_case_sensitive); } -} +} diff -Nru geary-0.12.4/src/engine/imap/api/imap-folder-session.vala geary-3.32.0/src/engine/imap/api/imap-folder-session.vala --- geary-0.12.4/src/engine/imap/api/imap-folder-session.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/src/engine/imap/api/imap-folder-session.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,1100 @@ +/* + * Copyright 2016 Software Freedom Conservancy Inc. + * Copyright 2018 Michael Gratton . + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +// this is used internally to indicate a recoverable failure +private errordomain Geary.Imap.FolderError { + RETRY +} + +/** + * An interface between the high-level engine API and an IMAP mailbox. + * + * Because of the complexities of the IMAP protocol, class takes + * common operations that a Geary.Folder implementation would need + * (in particular, {@link Geary.ImapEngine.MinimalFolder}) and makes + * them into simple async calls. + * + * When constructed, this class will issue an IMAP SELECT command for + * the mailbox represented by this folder, placing the session in the + * Selected state. + */ +private class Geary.Imap.FolderSession : Geary.Imap.SessionObject { + + private const Geary.Email.Field BASIC_FETCH_FIELDS = Email.Field.ENVELOPE | Email.Field.DATE + | Email.Field.ORIGINATORS | Email.Field.RECEIVERS | Email.Field.REFERENCES + | Email.Field.SUBJECT | Email.Field.HEADER; + + + /** The folder this session operates on. */ + public Imap.Folder folder { get; private set; } + + /** Determines if this folder immutable. */ + public Trillian readonly { get; private set; default = Trillian.UNKNOWN; } + + /** Determines if this folder accepts custom IMAP flags. */ + public Trillian accepts_user_flags { get; private set; default = Trillian.UNKNOWN; } + + /** Determines if this folder accepts custom IMAP flags. */ + public MessageFlags? permanent_flags { get; private set; default = null; } + + /** + * Set to true when it's detected that the server doesn't allow a + * space between "header.fields" and the list of email headers to + * be requested via FETCH; see: + * [[https://bugzilla.gnome.org/show_bug.cgi?id=714902|Bug * 714902]] + */ + public bool imap_header_fields_hack { get; private set; default = false; } + + private Nonblocking.Mutex cmd_mutex = new Nonblocking.Mutex(); + private Gee.HashMap? fetch_accumulator = null; + private Gee.Set? search_accumulator = null; + + /** + * A (potentially unsolicited) response from the server. + * + * See [[http://tools.ietf.org/html/rfc3501#section-7.3.1]] + */ + public signal void exists(int total); + + /** + * A (potentially unsolicited) response from the server. + * + * See [[http://tools.ietf.org/html/rfc3501#section-7.3.2]] + */ + public signal void recent(int total); + + /** + * A (potentially unsolicited) response from the server. + * + * See [[http://tools.ietf.org/html/rfc3501#section-7.4.1]] + */ + public signal void expunge(SequenceNumber position); + + /** + * Fabricated from the IMAP signals and state obtained at open_async(). + */ + public signal void appended(int count); + + /** + * Fabricated from the IMAP signals and state obtained at open_async(). + */ + public signal void updated(SequenceNumber pos, FetchedData data); + + /** + * Fabricated from the IMAP signals and state obtained at open_async(). + */ + public signal void removed(SequenceNumber pos); + + + public async FolderSession(string account_id, + ClientSession session, + Imap.Folder folder, + Cancellable cancellable) + throws Error { + base("%s:%s".printf(account_id, folder.path.to_string()), session); + this.folder = folder; + + if (folder.properties.attrs.is_no_select) { + throw new ImapError.NOT_SUPPORTED( + "Folder cannot be selected: %s", + folder.path.to_string() + ); + } + + // Update based on our current session + folder.properties.set_from_session_capabilities(session.capabilities); + + // connect to interesting signals *before* selecting + session.exists.connect(on_exists); + session.expunge.connect(on_expunge); + session.fetch.connect(on_fetch); + session.recent.connect(on_recent); + session.search.connect(on_search); + session.status_response_received.connect(on_status_response); + + MailboxSpecifier mailbox = session.get_mailbox_for_path(folder.path); + StatusResponse? response = yield session.select_async( + mailbox, cancellable + ); + + switch (response.status) { + case Status.OK: + // all good + break; + + case Status.BAD: + case Status.NO: + throw new ImapError.NOT_SUPPORTED( + "Server disallowed SELECT %s: %s", + this.folder.path.to_string(), + response.to_string() + ); + + default: + throw new ImapError.SERVER_ERROR( + "Unable to SELECT %s: %s", + this.folder.path.to_string(), + response.to_string() + ); + } + + // if at end of SELECT command accepts_user_flags is still + // UNKKNOWN, treat as TRUE because, according to IMAP spec, if + // PERMANENTFLAGS are not returned, then assume OK + if (this.accepts_user_flags == Trillian.UNKNOWN) + this.accepts_user_flags = Trillian.TRUE; + } + + /** + * Enables IMAP IDLE for the session, if supported. + */ + public async void enable_idle(Cancellable? cancellable) + throws Error { + ClientSession session = claim_session(); + int token = yield this.cmd_mutex.claim_async(cancellable); + Error? cmd_err = null; + try { + yield session.enable_idle(cancellable); + } catch (Error err) { + cmd_err = err; + } + + this.cmd_mutex.release(ref token); + + if (cmd_err != null) { + throw cmd_err; + } + } + + /** {@inheritDoc} */ + public override ClientSession? close() { + ClientSession? old_session = base.close(); + if (old_session != null) { + old_session.exists.disconnect(on_exists); + old_session.expunge.disconnect(on_expunge); + old_session.fetch.disconnect(on_fetch); + old_session.recent.disconnect(on_recent); + old_session.search.disconnect(on_search); + old_session.status_response_received.disconnect(on_status_response); + } + return old_session; + } + + private void on_exists(int total) { + debug("%s EXISTS %d", to_string(), total); + + int old_total = this.folder.properties.select_examine_messages; + this.folder.properties.set_select_examine_message_count(total); + + exists(total); + if (old_total >= 0 && old_total < total) { + appended(total - old_total); + } + } + + private void on_expunge(SequenceNumber pos) { + debug("%s EXPUNGE %s", to_string(), pos.to_string()); + + int old_total = this.folder.properties.select_examine_messages; + if (old_total > 0) { + this.folder.properties.set_select_examine_message_count( + old_total - 1 + ); + } + + expunge(pos); + removed(pos); + } + + private void on_fetch(FetchedData data) { + // add if not found, merge if already received data for this email + if (this.fetch_accumulator != null) { + FetchedData? existing = this.fetch_accumulator.get(data.seq_num); + this.fetch_accumulator.set( + data.seq_num, (existing != null) ? data.combine(existing) : data + ); + } else { + debug("%s: FETCH (unsolicited): %s:", + to_string(), + data.to_string()); + updated(data.seq_num, data); + } + } + + private void on_recent(int total) { + debug("%s RECENT %d", to_string(), total); + this.folder.properties.recent = total; + recent(total); + } + + private void on_search(int64[] seq_or_uid) { + // All SEARCH from this class are UID SEARCH, so can reliably convert and add to + // accumulator + if (this.search_accumulator != null) { + foreach (int64 uid in seq_or_uid) { + try { + this.search_accumulator.add(new UID.checked(uid)); + } catch (ImapError imaperr) { + debug("%s Unable to process SEARCH UID result: %s", to_string(), imaperr.message); + } + } + } else { + debug("%s Not handling unsolicited SEARCH response", to_string()); + } + } + + private void on_status_response(StatusResponse status_response) { + // only interested in ResponseCodes here + ResponseCode? response_code = status_response.response_code; + if (response_code == null) + return; + + try { + // Have to take a copy of the string property before evaluation due to this bug: + // https://bugzilla.gnome.org/show_bug.cgi?id=703818 + string value = response_code.get_response_code_type().value; + switch (value) { + case ResponseCodeType.READONLY: + this.readonly = Trillian.TRUE; + break; + + case ResponseCodeType.READWRITE: + this.readonly = Trillian.FALSE; + break; + + case ResponseCodeType.UIDNEXT: + this.folder.properties.uid_next = response_code.get_uid_next(); + break; + + case ResponseCodeType.UIDVALIDITY: + this.folder.properties.uid_validity = response_code.get_uid_validity(); + break; + + case ResponseCodeType.UNSEEN: + // do NOT update properties.unseen, as the UNSEEN response code (here) means + // the sequence number of the first unseen message, not the total count of + // unseen messages + break; + + case ResponseCodeType.PERMANENT_FLAGS: + this.permanent_flags = response_code.get_permanent_flags(); + this.accepts_user_flags = Trillian.from_boolean( + this.permanent_flags.contains(MessageFlag.ALLOWS_NEW) + ); + break; + + default: + // ignored + break; + } + } catch (ImapError ierr) { + debug("Unable to parse ResponseCode %s: %s", response_code.to_string(), + ierr.message); + } + } + + // All commands must executed inside the cmd_mutex; returns FETCH or STORE results + // + // FETCH commands can generate a FolderError.RETRY. State will be updated to accomodate retry, + // but all Commands must be regenerated to ensure new state is reflected in requests. + private async Gee.Map? exec_commands_async(Gee.Collection cmds, + Gee.HashMap? fetch_results, + Gee.Set? search_results, + Cancellable? cancellable) + throws Error { + ClientSession session = claim_session(); + Gee.Map? responses = null; + int token = yield this.cmd_mutex.claim_async(cancellable); + + this.fetch_accumulator = fetch_results; + this.search_accumulator = search_results; + + Error? cmd_err = null; + try { + responses = yield session.send_multiple_commands_async( + cmds, cancellable + ); + } catch (Error err) { + cmd_err = err; + } + + this.fetch_accumulator = null; + this.search_accumulator = null; + + this.cmd_mutex.release(ref token); + + if (cmd_err != null) { + throw cmd_err; + } + + foreach (Command cmd in responses.keys) { + throw_on_failed_status(responses.get(cmd), cmd); + } + + return responses; + } + + // HACK: See https://bugzilla.gnome.org/show_bug.cgi?id=714902 + // + // Detect when a server has returned a BAD response to FETCH BODY[HEADER.FIELDS (HEADER-LIST)] + // due to space between HEADER.FIELDS and (HEADER-LIST) + private bool retry_bad_header_fields_response(Command cmd, StatusResponse response) { + if (response.status != Status.BAD) + return false; + + FetchCommand? fetch = cmd as FetchCommand; + if (fetch == null) + return false; + + foreach (FetchBodyDataSpecifier body_specifier in fetch.for_body_data_specifiers) { + switch (body_specifier.section_part) { + case FetchBodyDataSpecifier.SectionPart.HEADER_FIELDS: + case FetchBodyDataSpecifier.SectionPart.HEADER_FIELDS_NOT: + // use value stored in specifier, not this folder's setting, as it's possible + // the folder's setting was enabled after sending command but before response + // returned + if (body_specifier.request_header_fields_space) + return true; + break; + } + } + + return false; + } + + private void throw_on_failed_status(StatusResponse response, Command cmd) throws Error { + assert(response.is_completion); + + switch (response.status) { + case Status.OK: + return; + + case Status.NO: + throw new ImapError.SERVER_ERROR("Request %s failed on %s: %s", cmd.to_string(), + to_string(), response.to_string()); + + case Status.BAD: { + // if a FetchBodyDataSpecifier is used to request for a header field BAD is returned, + // could be a specific formatting mistake some servers make of not allowing a space + // between the "header.fields" and list of email header names, i.e. + // + // "body[header.fields (references)]" + // + // If so, then enable a hack to work around this and retry the FETCH + if (retry_bad_header_fields_response(cmd, response)) { + imap_header_fields_hack = true; + + throw new FolderError.RETRY("BAD response to header.fields FETCH BODY, retry with hack"); + } + + throw new ImapError.INVALID("Bad request %s on %s: %s", cmd.to_string(), + to_string(), response.to_string()); + } + + default: + throw new ImapError.NOT_SUPPORTED("Unknown response status to %s on %s: %s", + cmd.to_string(), to_string(), response.to_string()); + } + } + + // Utility method for listing UIDs on the remote within the supplied range + public async Gee.Set? list_uids_async(MessageSet msg_set, Cancellable? cancellable) + throws Error { + // Although FETCH could be used, SEARCH is more efficient in returning pure UID results, + // which is all we're interested in here + SearchCriteria criteria = new SearchCriteria(SearchCriterion.message_set(msg_set)); + SearchCommand cmd = new SearchCommand.uid(criteria); + + Gee.Set search_results = new Gee.HashSet(); + yield exec_commands_async( + Geary.iterate(cmd).to_array_list(), + null, + search_results, + cancellable + ); + + return (search_results.size > 0) ? search_results : null; + } + + private Gee.Collection assemble_list_commands(Imap.MessageSet msg_set, + Geary.Email.Field fields, out FetchBodyDataSpecifier? header_specifier, + out FetchBodyDataSpecifier? body_specifier, out FetchBodyDataSpecifier? preview_specifier, + out FetchBodyDataSpecifier? preview_charset_specifier) { + // getting all the fields can require multiple FETCH commands (some servers don't handle + // well putting every required data item into single command), so aggregate FetchCommands + Gee.Collection cmds = new Gee.ArrayList(); + + // if not a UID FETCH, request UIDs for all messages so their EmailIdentifier can be + // created without going back to the database (assuming the messages have already been + // pulled down, not a guarantee); if request is for NONE, that guarantees that the + // EmailIdentifier will be set, and so fetch UIDs (which looks funny but works when + // listing a range for contents: UID FETCH x:y UID) + if (!msg_set.is_uid || fields == Geary.Email.Field.NONE) + cmds.add(new FetchCommand.data_type(msg_set, FetchDataSpecifier.UID)); + + // convert bulk of the "basic" fields into a one or two FETCH commands (some servers have + // exhibited bugs or return NO when too many FETCH data types are combined on a single + // command) + if (fields.requires_any(BASIC_FETCH_FIELDS)) { + Gee.List data_types = new Gee.ArrayList(); + fields_to_fetch_data_types(fields, data_types, out header_specifier); + + // Add all simple data types as one FETCH command + if (data_types.size > 0) + cmds.add(new FetchCommand(msg_set, data_types, null)); + + // Add all body data types as separate FETCH command + if (header_specifier != null) + cmds.add(new FetchCommand.body_data_type(msg_set, header_specifier)); + } else { + header_specifier = null; + } + + // RFC822 BODY is a separate command + if (fields.require(Email.Field.BODY)) { + body_specifier = new FetchBodyDataSpecifier.peek(FetchBodyDataSpecifier.SectionPart.TEXT, + null, -1, -1, null); + + cmds.add(new FetchCommand.body_data_type(msg_set, body_specifier)); + } else { + body_specifier = null; + } + + // PREVIEW obtains the content type and a truncated version of + // the first part of the message, which often leads to poor + // results. It can also be also be synthesised from the + // email's RFC822 message in fetched_data_to_email, if the + // fields needed for reconstructing the RFC822 message are + // present. If so, rely on that and don't also request any + // additional data for the preview here. + if (fields.require(Email.Field.PREVIEW) && + !fields.require(Email.REQUIRED_FOR_MESSAGE)) { + // Get the preview text (the initial MAX_PREVIEW_BYTES of + // the first MIME section + + preview_specifier = new FetchBodyDataSpecifier.peek(FetchBodyDataSpecifier.SectionPart.NONE, + { 1 }, 0, Geary.Email.MAX_PREVIEW_BYTES, null); + cmds.add(new FetchCommand.body_data_type(msg_set, preview_specifier)); + + // Also get the character set to properly decode it + preview_charset_specifier = new FetchBodyDataSpecifier.peek( + FetchBodyDataSpecifier.SectionPart.MIME, { 1 }, -1, -1, null); + cmds.add(new FetchCommand.body_data_type(msg_set, preview_charset_specifier)); + } else { + preview_specifier = null; + preview_charset_specifier = null; + } + + // PROPERTIES and FLAGS are a separate command + if (fields.requires_any(Email.Field.PROPERTIES | Email.Field.FLAGS)) { + Gee.List data_types = new Gee.ArrayList(); + + if (fields.require(Geary.Email.Field.PROPERTIES)) { + data_types.add(FetchDataSpecifier.INTERNALDATE); + data_types.add(FetchDataSpecifier.RFC822_SIZE); + } + + if (fields.require(Geary.Email.Field.FLAGS)) + data_types.add(FetchDataSpecifier.FLAGS); + + cmds.add(new FetchCommand(msg_set, data_types, null)); + } + + return cmds; + } + + // Returns a no-message-id ImapDB.EmailIdentifier with the UID stored in it. + public async Gee.List? list_email_async(MessageSet msg_set, + Geary.Email.Field fields, + Cancellable? cancellable) + throws Error { + Gee.HashMap fetched = + new Gee.HashMap(); + FetchBodyDataSpecifier? header_specifier = null; + FetchBodyDataSpecifier? body_specifier = null; + FetchBodyDataSpecifier? preview_specifier = null; + FetchBodyDataSpecifier? preview_charset_specifier = null; + for (;;) { + Gee.Collection cmds = assemble_list_commands(msg_set, fields, + out header_specifier, out body_specifier, out preview_specifier, + out preview_charset_specifier); + if (cmds.size == 0) { + throw new ImapError.INVALID("No FETCH commands generate for list request %s %s", + msg_set.to_string(), fields.to_list_string()); + } + + // Commands prepped, do the fetch and accumulate all the responses + try { + yield exec_commands_async(cmds, fetched, null, cancellable); + } catch (Error err) { + if (err is FolderError.RETRY) { + debug("Retryable server failure detected for %s: %s", to_string(), err.message); + + continue; + } + + throw err; + } + + break; + } + + if (fetched.size == 0) + return null; + + // Convert fetched data into Geary.Email objects + // because this could be for a lot of email, do in a background thread + Gee.List email_list = new Gee.ArrayList(); + yield Nonblocking.Concurrent.global.schedule_async(() => { + foreach (SequenceNumber seq_num in fetched.keys) { + FetchedData fetched_data = fetched.get(seq_num); + + // the UID should either have been fetched (if using positional addressing) or should + // have come back with the response (if using UID addressing) + UID? uid = fetched_data.data_map.get(FetchDataSpecifier.UID) as UID; + if (uid == null) { + message("Unable to list message #%s on %s: No UID returned from server", + seq_num.to_string(), to_string()); + + continue; + } + + try { + Geary.Email email = fetched_data_to_email(to_string(), uid, fetched_data, fields, + header_specifier, body_specifier, preview_specifier, preview_charset_specifier); + if (!email.fields.fulfills(fields)) { + message("%s: %s missing=%s fetched=%s", to_string(), email.id.to_string(), + fields.clear(email.fields).to_list_string(), fetched_data.to_string()); + + continue; + } + + email_list.add(email); + } catch (Error err) { + debug("%s: Unable to convert email for %s %s: %s", to_string(), uid.to_string(), + fetched_data.to_string(), err.message); + } + } + }, cancellable); + + return (email_list.size > 0) ? email_list : null; + } + + /** + * Returns the sequence numbers for a set of UIDs. + * + * The `msg_set` parameter must be a set containing UIDs. An error + * is thrown if the sequence numbers cannot be determined. + */ + public async Gee.Map uid_to_position_async(MessageSet msg_set, + Cancellable? cancellable) + throws Error { + if (!msg_set.is_uid) { + throw new ImapError.NOT_SUPPORTED("Message set must contain UIDs"); + } + + Gee.List cmds = new Gee.ArrayList(); + cmds.add(new FetchCommand.data_type(msg_set, FetchDataSpecifier.UID)); + + Gee.HashMap fetched = + new Gee.HashMap(); + yield exec_commands_async(cmds, fetched, null, cancellable); + + if (fetched.is_empty) { + throw new ImapError.INVALID("Server returned no sequence numbers"); + } + + Gee.Map map = new Gee.HashMap(); + foreach (SequenceNumber seq_num in fetched.keys) { + map.set( + (UID) fetched.get(seq_num).data_map.get(FetchDataSpecifier.UID), + seq_num + ); + } + return map; + } + + public async void remove_email_async(Gee.List msg_sets, Cancellable? cancellable) + throws Error { + ClientSession session = claim_session(); + Gee.List flags = new Gee.ArrayList(); + flags.add(MessageFlag.DELETED); + + Gee.List cmds = new Gee.ArrayList(); + + // Build STORE command for all MessageSets, see if all are UIDs so we can use UID EXPUNGE + bool all_uid = true; + foreach (MessageSet msg_set in msg_sets) { + if (!msg_set.is_uid) + all_uid = false; + + cmds.add(new StoreCommand(msg_set, flags, StoreCommand.Option.ADD_FLAGS)); + } + + // TODO: Only use old-school EXPUNGE when closing folder (or rely on CLOSE to do that work + // for us). See: + // http://redmine.yorba.org/issues/7532 + // + // However, current client implementation doesn't properly close INBOX when application + // shuts down, which means deleted messages return at application start. See: + // http://redmine.yorba.org/issues/6865 + if (all_uid && session.capabilities.supports_uidplus()) { + foreach (MessageSet msg_set in msg_sets) + cmds.add(new ExpungeCommand.uid(msg_set)); + } else { + cmds.add(new ExpungeCommand()); + } + + yield exec_commands_async(cmds, null, null, cancellable); + } + + public async void mark_email_async(Gee.List msg_sets, Geary.EmailFlags? flags_to_add, + Geary.EmailFlags? flags_to_remove, Cancellable? cancellable) throws Error { + Gee.List msg_flags_add = new Gee.ArrayList(); + Gee.List msg_flags_remove = new Gee.ArrayList(); + MessageFlag.from_email_flags(flags_to_add, flags_to_remove, out msg_flags_add, + out msg_flags_remove); + + if (msg_flags_add.size == 0 && msg_flags_remove.size == 0) + return; + + Gee.Collection cmds = new Gee.ArrayList(); + foreach (MessageSet msg_set in msg_sets) { + if (msg_flags_add.size > 0) + cmds.add(new StoreCommand(msg_set, msg_flags_add, StoreCommand.Option.ADD_FLAGS)); + + if (msg_flags_remove.size > 0) + cmds.add(new StoreCommand(msg_set, msg_flags_remove, StoreCommand.Option.REMOVE_FLAGS)); + } + + yield exec_commands_async(cmds, null, null, cancellable); + } + + // Returns a mapping of the source UID to the destination UID. If the MessageSet is not for + // UIDs, then null is returned. If the server doesn't support COPYUID, null is returned. + public async Gee.Map? copy_email_async(MessageSet msg_set, FolderPath destination, + Cancellable? cancellable) throws Error { + ClientSession session = claim_session(); + + MailboxSpecifier mailbox = session.get_mailbox_for_path(destination); + CopyCommand cmd = new CopyCommand(msg_set, mailbox); + + Gee.Map? responses = yield exec_commands_async( + Geary.iterate(cmd).to_array_list(), null, null, cancellable); + + if (!responses.has_key(cmd)) + return null; + + StatusResponse response = responses.get(cmd); + if (response.response_code != null && msg_set.is_uid) { + Gee.List? src_uids = null; + Gee.List? dst_uids = null; + try { + response.response_code.get_copyuid(null, out src_uids, out dst_uids); + } catch (ImapError ierr) { + debug("Unable to retrieve COPYUID UIDs: %s", ierr.message); + } + + if (!Collection.is_empty(src_uids) && !Collection.is_empty(dst_uids)) { + Gee.Map copyuids = new Gee.HashMap(); + int ctr = 0; + for (;;) { + UID? src_uid = (ctr < src_uids.size) ? src_uids[ctr] : null; + UID? dst_uid = (ctr < dst_uids.size) ? dst_uids[ctr] : null; + + if (src_uid != null && dst_uid != null) + copyuids.set(src_uid, dst_uid); + else + break; + + ctr++; + } + + if (copyuids.size > 0) + return copyuids; + } + } + + return null; + } + + public async Gee.SortedSet? search_async(SearchCriteria criteria, Cancellable? cancellable) + throws Error { + // always perform a UID SEARCH + Gee.Collection cmds = new Gee.ArrayList(); + cmds.add(new SearchCommand.uid(criteria)); + + Gee.Set search_results = new Gee.HashSet(); + yield exec_commands_async(cmds, null, search_results, cancellable); + + Gee.SortedSet tree = null; + if (search_results.size > 0) { + tree = new Gee.TreeSet(); + tree.add_all(search_results); + } + return tree; + } + + // NOTE: If fields are added or removed from this method, BASIC_FETCH_FIELDS *must* be updated + // as well + private void fields_to_fetch_data_types(Geary.Email.Field fields, + Gee.List data_types_list, out FetchBodyDataSpecifier? header_specifier) { + // pack all the needed headers into a single FetchBodyDataType + string[] field_names = new string[0]; + + // The assumption here is that because ENVELOPE is such a common fetch command, the + // server will have optimizations for it, whereas if we called for each header in the + // envelope separately, the server has to chunk harder parsing the RFC822 header ... have + // to add References because IMAP ENVELOPE doesn't return them for some reason (but does + // return Message-ID and In-Reply-To) + if (fields.is_all_set(Geary.Email.Field.ENVELOPE)) { + data_types_list.add(FetchDataSpecifier.ENVELOPE); + field_names += "References"; + + // remove those flags and process any remaining + fields = fields.clear(Geary.Email.Field.ENVELOPE); + } + + foreach (Geary.Email.Field field in Geary.Email.Field.all()) { + switch (fields & field) { + case Geary.Email.Field.DATE: + field_names += "Date"; + break; + + case Geary.Email.Field.ORIGINATORS: + field_names += "From"; + field_names += "Sender"; + field_names += "Reply-To"; + break; + + case Geary.Email.Field.RECEIVERS: + field_names += "To"; + field_names += "Cc"; + field_names += "Bcc"; + break; + + case Geary.Email.Field.REFERENCES: + field_names += "References"; + field_names += "Message-ID"; + field_names += "In-Reply-To"; + break; + + case Geary.Email.Field.SUBJECT: + field_names += "Subject"; + break; + + case Geary.Email.Field.HEADER: + // TODO: If the entire header is being pulled, then no need to pull down partial + // headers; simply get them all and decode what is needed directly + data_types_list.add(FetchDataSpecifier.RFC822_HEADER); + break; + + case Geary.Email.Field.NONE: + case Geary.Email.Field.BODY: + case Geary.Email.Field.PROPERTIES: + case Geary.Email.Field.FLAGS: + case Geary.Email.Field.PREVIEW: + // not set or fetched separately + break; + + default: + assert_not_reached(); + } + } + + // convert field names into single FetchBodyDataType object + if (field_names.length > 0) { + header_specifier = new FetchBodyDataSpecifier.peek( + FetchBodyDataSpecifier.SectionPart.HEADER_FIELDS, null, -1, -1, field_names); + if (imap_header_fields_hack) + header_specifier.omit_request_header_fields_space(); + } else { + header_specifier = null; + } + } + + private static Geary.Email fetched_data_to_email(string folder_name, UID uid, + FetchedData fetched_data, Geary.Email.Field required_fields, + FetchBodyDataSpecifier? header_specifier, FetchBodyDataSpecifier? body_specifier, + FetchBodyDataSpecifier? preview_specifier, FetchBodyDataSpecifier? preview_charset_specifier) throws Error { + // note the use of INVALID_ROWID, as the rowid for this email (if one is present in the + // database) is unknown at this time; this means ImapDB *must* create a new EmailIdentifier + // for this email after create/merge is completed + Geary.Email email = new Geary.Email(new ImapDB.EmailIdentifier.no_message_id(uid)); + + // accumulate these to submit Imap.EmailProperties all at once + InternalDate? internaldate = null; + RFC822.Size? rfc822_size = null; + + // accumulate these to submit References all at once + RFC822.MessageID? message_id = null; + RFC822.MessageIDList? in_reply_to = null; + RFC822.MessageIDList? references = null; + + // loop through all available FetchDataTypes and gather converted data + foreach (FetchDataSpecifier data_type in fetched_data.data_map.keys) { + MessageData? data = fetched_data.data_map.get(data_type); + if (data == null) + continue; + + switch (data_type) { + case FetchDataSpecifier.ENVELOPE: + Envelope envelope = (Envelope) data; + + email.set_send_date(envelope.sent); + email.set_message_subject(envelope.subject); + email.set_originators( + envelope.from, + envelope.sender.equal_to(envelope.from) || envelope.sender.size == 0 ? null : envelope.sender[0], + envelope.reply_to.equal_to(envelope.from) ? null : envelope.reply_to + ); + email.set_receivers(envelope.to, envelope.cc, envelope.bcc); + + // store these to add to References all at once + message_id = envelope.message_id; + in_reply_to = envelope.in_reply_to; + break; + + case FetchDataSpecifier.RFC822_HEADER: + email.set_message_header((RFC822.Header) data); + break; + + case FetchDataSpecifier.RFC822_TEXT: + email.set_message_body((RFC822.Text) data); + break; + + case FetchDataSpecifier.RFC822_SIZE: + rfc822_size = (RFC822.Size) data; + break; + + case FetchDataSpecifier.FLAGS: + email.set_flags(new Imap.EmailFlags((MessageFlags) data)); + break; + + case FetchDataSpecifier.INTERNALDATE: + internaldate = (InternalDate) data; + break; + + default: + // everything else dropped on the floor (not applicable to Geary.Email) + break; + } + } + + // Only set PROPERTIES if all have been found + if (internaldate != null && rfc822_size != null) + email.set_email_properties(new Geary.Imap.EmailProperties(internaldate, rfc822_size)); + + // if the header was requested, convert its fields now + bool has_header_specifier = fetched_data.body_data_map.has_key(header_specifier); + if (header_specifier != null && !has_header_specifier) { + message("[%s] No header specifier \"%s\" found:", folder_name, + header_specifier.to_string()); + foreach (FetchBodyDataSpecifier specifier in fetched_data.body_data_map.keys) + message("[%s] has %s", folder_name, specifier.to_string()); + } else if (header_specifier != null && has_header_specifier) { + RFC822.Header headers = new RFC822.Header( + fetched_data.body_data_map.get(header_specifier)); + + // DATE + if (required_but_not_set(Geary.Email.Field.DATE, required_fields, email)) { + string? value = headers.get_header("Date"); + RFC822.Date? date = null; + if (!String.is_empty(value)) { + try { + date = new RFC822.Date(value); + } catch (GLib.Error err) { + debug( + "Error parsing date from FETCH response: %s", + err.message + ); + } + } + email.set_send_date(date); + } + + // ORIGINATORS + if (required_but_not_set(Geary.Email.Field.ORIGINATORS, required_fields, email)) { + RFC822.MailboxAddresses? from = null; + string? value = headers.get_header("From"); + if (!String.is_empty(value)) + from = new RFC822.MailboxAddresses.from_rfc822_string(value); + + RFC822.MailboxAddress? sender = null; + value = headers.get_header("Sender"); + if (!String.is_empty(value)) + sender = new RFC822.MailboxAddress.from_rfc822_string(value); + + RFC822.MailboxAddresses? reply_to = null; + value = headers.get_header("Reply-To"); + if (!String.is_empty(value)) + reply_to = new RFC822.MailboxAddresses.from_rfc822_string(value); + + email.set_originators(from, sender, reply_to); + } + + // RECEIVERS + if (required_but_not_set(Geary.Email.Field.RECEIVERS, required_fields, email)) { + RFC822.MailboxAddresses? to = null; + string? value = headers.get_header("To"); + if (!String.is_empty(value)) + to = new RFC822.MailboxAddresses.from_rfc822_string(value); + + RFC822.MailboxAddresses? cc = null; + value = headers.get_header("Cc"); + if (!String.is_empty(value)) + cc = new RFC822.MailboxAddresses.from_rfc822_string(value); + + RFC822.MailboxAddresses? bcc = null; + value = headers.get_header("Bcc"); + if (!String.is_empty(value)) + bcc = new RFC822.MailboxAddresses.from_rfc822_string(value); + + email.set_receivers(to, cc, bcc); + } + + // REFERENCES + // (Note that it's possible the request used an IMAP ENVELOPE, in which case only the + // References header will be present if REFERENCES were required, which is why + // REFERENCES is set at the bottom of the method, when all information has been gathered + if (message_id == null) { + string? value = headers.get_header("Message-ID"); + if (!String.is_empty(value)) + message_id = new RFC822.MessageID(value); + } + + if (in_reply_to == null) { + string? value = headers.get_header("In-Reply-To"); + if (!String.is_empty(value)) + in_reply_to = new RFC822.MessageIDList.from_rfc822_string(value); + } + + if (references == null) { + string? value = headers.get_header("References"); + if (!String.is_empty(value)) + references = new RFC822.MessageIDList.from_rfc822_string(value); + } + + // SUBJECT + // Unlike DATE, allow for empty subjects + if (required_but_not_set(Geary.Email.Field.SUBJECT, required_fields, email)) { + string? value = headers.get_header("Subject"); + if (value != null) + email.set_message_subject(new RFC822.Subject.decode(value)); + else + email.set_message_subject(null); + } + } + + // It's possible for all these fields to be null even though they were requested from + // the server, so use requested fields for determination + if (required_but_not_set(Geary.Email.Field.REFERENCES, required_fields, email)) + email.set_full_references(message_id, in_reply_to, references); + + // if preview was requested, get it now ... both identifiers + // must be supplied if one is + if (preview_specifier != null || preview_charset_specifier != null) { + assert(preview_specifier != null && preview_charset_specifier != null); + + if (fetched_data.body_data_map.has_key(preview_specifier) + && fetched_data.body_data_map.has_key(preview_charset_specifier)) { + email.set_message_preview(new RFC822.PreviewText.with_header( + fetched_data.body_data_map.get(preview_charset_specifier), + fetched_data.body_data_map.get(preview_specifier))); + } else { + message("[%s] No preview specifiers \"%s\" and \"%s\" found", folder_name, + preview_specifier.to_string(), preview_charset_specifier.to_string()); + foreach (FetchBodyDataSpecifier specifier in fetched_data.body_data_map.keys) + message("[%s] has %s", folder_name, specifier.to_string()); + } + } + + // If body was requested, get it now. We also set the preview + // here from the body if possible since for HTML messages at + // least there's a lot of boilerplate HTML to wade through to + // get some actual preview text, which usually requires more + // than Geary.Email.MAX_PREVIEW_BYTES will allow for + if (body_specifier != null) { + if (fetched_data.body_data_map.has_key(body_specifier)) { + email.set_message_body(new Geary.RFC822.Text( + fetched_data.body_data_map.get(body_specifier))); + + // Try to set the preview + Geary.RFC822.Message? message = null; + try { + message = email.get_message(); + } catch (Error e) { + // Not enough fields to construct the message + } + if (message != null) { + string preview = message.get_preview(); + if (preview.length > Geary.Email.MAX_PREVIEW_BYTES) { + preview = Geary.String.safe_byte_substring( + preview, Geary.Email.MAX_PREVIEW_BYTES + ); + } + email.set_message_preview( + new RFC822.PreviewText.from_string(preview) + ); + } + } else { + message("[%s] No body specifier \"%s\" found", folder_name, + body_specifier.to_string()); + foreach (FetchBodyDataSpecifier specifier in fetched_data.body_data_map.keys) + message("[%s] has %s", folder_name, specifier.to_string()); + } + } + + return email; + } + + // Returns a no-message-id ImapDB.EmailIdentifier with the UID stored in it. + // This method does not take a cancellable; there is currently no way to tell if an email was + // created or not if exec_commands_async() is cancelled during the append. For atomicity's sake, + // callers need to remove the returned email ID if a cancel occurred. + public async Geary.EmailIdentifier? create_email_async(RFC822.Message message, Geary.EmailFlags? flags, + DateTime? date_received) throws Error { + ClientSession session = claim_session(); + + MessageFlags? msg_flags = null; + if (flags != null) { + Imap.EmailFlags imap_flags = Imap.EmailFlags.from_api_email_flags(flags); + msg_flags = imap_flags.message_flags; + } else { + msg_flags = new MessageFlags(Geary.iterate(MessageFlag.SEEN).to_array_list()); + } + + InternalDate? internaldate = null; + if (date_received != null) + internaldate = new InternalDate.from_date_time(date_received); + + MailboxSpecifier mailbox = session.get_mailbox_for_path(this.folder.path); + AppendCommand cmd = new AppendCommand( + mailbox, msg_flags, internaldate, message.get_network_buffer(false) + ); + + Gee.Map responses = yield exec_commands_async( + Geary.iterate(cmd).to_array_list(), null, null, null); + + // Grab the response and parse out the UID, if available. + StatusResponse response = responses.get(cmd); + if (response.status == Status.OK && response.response_code != null && + response.response_code.get_response_code_type().is_value("appenduid")) { + UID new_id = new UID.checked(response.response_code.get_as_string(2).as_int64()); + + return new ImapDB.EmailIdentifier.no_message_id(new_id); + } + + // We didn't get a UID back from the server. + return null; + } + + private static bool required_but_not_set(Geary.Email.Field check, Geary.Email.Field users_fields, Geary.Email email) { + return users_fields.require(check) ? !email.fields.is_all_set(check) : false; + } +} diff -Nru geary-0.12.4/src/engine/imap/api/imap-folder.vala geary-3.32.0/src/engine/imap/api/imap-folder.vala --- geary-0.12.4/src/engine/imap/api/imap-folder.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/api/imap-folder.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,1107 +1,34 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. + * Copyright 2018 Michael Gratton . * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ -// this is used internally to indicate a recoverable failure -private errordomain Geary.Imap.FolderError { - RETRY -} - -private class Geary.Imap.Folder : BaseObject { - private const Geary.Email.Field BASIC_FETCH_FIELDS = Email.Field.ENVELOPE | Email.Field.DATE - | Email.Field.ORIGINATORS | Email.Field.RECEIVERS | Email.Field.REFERENCES - | Email.Field.SUBJECT | Email.Field.HEADER; +/** + * Represents a mailbox on an IMAP server. + * + * Everything we can glean from an IMAP LIST for a specific folder is + * encapsulated here. Any information requires the folder to be + * selected, and hence there is no other information about + * non-selectable folders that can be obtained. + * + * Note the mailbox name is not represented since that may differ + * based on the client session being used to connect to the server. + */ +internal class Geary.Imap.Folder : Geary.BaseObject { - public bool is_open { get; private set; default = false; } + /** The full path to this folder. */ public FolderPath path { get; private set; } - public Imap.FolderProperties properties { get; private set; } - public MailboxInformation info { get; private set; } - public string delim { get; private set; } - public MessageFlags? permanent_flags { get; private set; default = null; } - public Trillian readonly { get; private set; default = Trillian.UNKNOWN; } - public Trillian accepts_user_flags { get; private set; default = Trillian.UNKNOWN; } - /** - * Set to true when it's detected that the server doesn't allow a space between "header.fields" - * and the list of email headers to be requested via FETCH; see - * https://bugzilla.gnome.org/show_bug.cgi?id=714902 - */ - public bool imap_header_fields_hack { get; private set; default = false; } - - private ClientSessionManager session_mgr; - private ClientSession? session = null; - private Nonblocking.Mutex cmd_mutex = new Nonblocking.Mutex(); - private Gee.HashMap fetch_accumulator = new Gee.HashMap< - SequenceNumber, FetchedData>(); - private Gee.Set search_accumulator = new Gee.HashSet(); - - /** - * A (potentially unsolicited) response from the server. - * - * See [[http://tools.ietf.org/html/rfc3501#section-7.3.1]] - */ - public signal void exists(int total); - - /** - * A (potentially unsolicited) response from the server. - * - * See [[http://tools.ietf.org/html/rfc3501#section-7.4.1]] - */ - public signal void expunge(SequenceNumber position); - - /** - * A (potentially unsolicited) response from the server. - * - * See [[http://tools.ietf.org/html/rfc3501#section-7.3.2]] - */ - public signal void recent(int total); - - /** - * Fabricated from the IMAP signals and state obtained at open_async(). - */ - public signal void appended(int total); - - /** - * Fabricated from the IMAP signals and state obtained at open_async(). - */ - public signal void removed(SequenceNumber pos, int total); - - /** - * Note that close_async() still needs to be called after this signal is fired. - */ - public signal void disconnected(ClientSession.DisconnectReason reason); - - internal Folder(FolderPath path, ClientSessionManager session_mgr, StatusData status, MailboxInformation info, string delim) { - // Used to assert() here, but that meant that any issue with internationalization/encoding - // made Geary unusable for a subset of servers accessed/configured in a non-English language... - // this is not the end of the world, but it does suggest an I18N issue, potentially with - // how XLIST returns folder names on different servers. - if (!status.mailbox.equal_to(info.mailbox)) { - message("%s: IMAP folder created with differing mailbox names (STATUS=%s LIST=%s)", - path.to_string(), status.to_string(), info.to_string()); - } - this.session_mgr = session_mgr; - this.info = info; - this.delim = delim; - this.path = path; + /** IMAP properties reported by the server. */ + public Imap.FolderProperties properties { get; private set; } - properties = new Imap.FolderProperties.status(status, info.attrs); - } - internal Folder.unselectable(FolderPath path, ClientSessionManager session_mgr, MailboxInformation info, string delim) { - this.session_mgr = session_mgr; - this.info = info; - this.delim = delim; + internal Folder(FolderPath path, Imap.FolderProperties properties) { this.path = path; - - properties = new Imap.FolderProperties(0, 0, 0, null, null, info.attrs); - } - - public async void open_async(Cancellable? cancellable) throws Error { - if (is_open) - throw new EngineError.ALREADY_OPEN("%s already open", to_string()); - - fetch_accumulator.clear(); - - session = yield session_mgr.claim_authorized_session_async(cancellable); - - // connect to interesting signals *before* selecting - session.exists.connect(on_exists); - session.expunge.connect(on_expunge); - session.fetch.connect(on_fetch); - session.recent.connect(on_recent); - session.search.connect(on_search); - session.status_response_received.connect(on_status_response); - session.disconnected.connect(on_disconnected); - - properties.set_from_session_capabilities(session.capabilities); - - StatusResponse? response = null; - Error? select_err = null; - try { - response = yield session.select_async( - new MailboxSpecifier.from_folder_path(path, this.delim), cancellable); - } catch (Error err) { - select_err = err; - } - - // if select_err is null, then response can not be null - if (select_err != null || response.status != Status.OK) { - // don't use user-supplied cancellable; it may be cancelled, and even if not, do not want - // to cancel this operation - yield release_session_async(null); - - if (select_err != null) - throw select_err; - - switch (response.status) { - case Status.BAD: - case Status.NO: - throw new ImapError.NOT_SUPPORTED("Server disallowed SELECT %s: %s", path.to_string(), - response.to_string()); - - default: - throw new ImapError.SERVER_ERROR("Unable to SELECT %s: %s", path.to_string(), - response.to_string()); - } - } - - // if at end of SELECT command accepts_user_flags is still UNKKNOWN, treat as TRUE because, - // according to IMAP spec, if PERMANENTFLAGS are not returned, then assume OK - if (accepts_user_flags == Trillian.UNKNOWN) - accepts_user_flags = Trillian.TRUE; - - is_open = true; - } - - public async void close_async(Cancellable? cancellable) throws Error { - if (!is_open) - return; - - yield release_session_async(cancellable); - - fetch_accumulator.clear(); - - readonly = Trillian.UNKNOWN; - accepts_user_flags = Trillian.UNKNOWN; - - is_open = false; - } - - private async void release_session_async(Cancellable? cancellable) { - if (session == null) - return; - - // set this.session to null before yielding to ClientSessionManager - ClientSession release_session = session; - session = null; - - release_session.exists.disconnect(on_exists); - release_session.expunge.disconnect(on_expunge); - release_session.fetch.disconnect(on_fetch); - release_session.recent.disconnect(on_recent); - release_session.search.disconnect(on_search); - release_session.status_response_received.disconnect(on_status_response); - release_session.disconnected.disconnect(on_disconnected); - - try { - yield session_mgr.release_session_async(release_session, cancellable); - } catch (Error err) { - debug("Unable to release session %s: %s", release_session.to_string(), err.message); - } - } - - private void on_exists(int total) { - debug("%s EXISTS %d", to_string(), total); - - int old_total = properties.select_examine_messages; - properties.set_select_examine_message_count(total); - - // don't fire signals until opened - if (!is_open) - return; - - exists(total); - if (old_total < total) - appended(total); - } - - private void on_expunge(SequenceNumber pos) { - debug("%s EXPUNGE %s", to_string(), pos.to_string()); - - properties.set_select_examine_message_count(properties.select_examine_messages - 1); - - // don't fire signals until opened - if (!is_open) - return; - - expunge(pos); - removed(pos, properties.select_examine_messages); - } - - private void on_fetch(FetchedData fetched_data) { - // add if not found, merge if already received data for this email - FetchedData? already_present = fetch_accumulator.get(fetched_data.seq_num); - fetch_accumulator.set(fetched_data.seq_num, - (already_present != null) ? fetched_data.combine(already_present) : fetched_data); - } - - private void on_recent(int total) { - debug("%s RECENT %d", to_string(), total); - - properties.recent = total; - - // don't fire signal until opened - if (is_open) - recent(total); - } - - private void on_search(int64[] seq_or_uid) { - // All SEARCH from this class are UID SEARCH, so can reliably convert and add to - // accumulator - foreach (int64 uid in seq_or_uid) { - try { - search_accumulator.add(new UID.checked(uid)); - } catch (ImapError imaperr) { - debug("%s Unable to process SEARCH UID result: %s", to_string(), imaperr.message); - } - } - } - - private void on_status_response(StatusResponse status_response) { - // only interested in ResponseCodes here - ResponseCode? response_code = status_response.response_code; - if (response_code == null) - return; - - try { - // Have to take a copy of the string property before evaluation due to this bug: - // https://bugzilla.gnome.org/show_bug.cgi?id=703818 - string value = response_code.get_response_code_type().value; - switch (value) { - case ResponseCodeType.READONLY: - readonly = Trillian.TRUE; - break; - - case ResponseCodeType.READWRITE: - readonly = Trillian.FALSE; - break; - - case ResponseCodeType.UIDNEXT: - properties.uid_next = response_code.get_uid_next(); - break; - - case ResponseCodeType.UIDVALIDITY: - properties.uid_validity = response_code.get_uid_validity(); - break; - - case ResponseCodeType.UNSEEN: - // do NOT update properties.unseen, as the UNSEEN response code (here) means - // the sequence number of the first unseen message, not the total count of - // unseen messages - break; - - case ResponseCodeType.PERMANENT_FLAGS: - permanent_flags = response_code.get_permanent_flags(); - accepts_user_flags = Trillian.from_boolean( - permanent_flags.contains(MessageFlag.ALLOWS_NEW)); - break; - - default: - // ignored - break; - } - } catch (ImapError ierr) { - debug("Unable to parse ResponseCode %s: %s", response_code.to_string(), - ierr.message); - } - } - - private void on_disconnected(ClientSession.DisconnectReason reason) { - debug("%s DISCONNECTED %s", to_string(), reason.to_string()); - - disconnected(reason); - } - - private void check_open() throws Error { - if (!is_open || session == null) - throw new EngineError.OPEN_REQUIRED("Imap.Folder %s not open", to_string()); - } - - // All commands must executed inside the cmd_mutex; returns FETCH or STORE results - // - // FETCH commands can generate a FolderError.RETRY. State will be updated to accomodate retry, - // but all Commands must be regenerated to ensure new state is reflected in requests. - private async Gee.Map? exec_commands_async(Gee.Collection cmds, - out Gee.HashMap? fetched, out Gee.Set? search_results, - Cancellable? cancellable) throws Error { - int token = yield cmd_mutex.claim_async(cancellable); - Gee.Map? responses = null; - // execute commands with mutex locked - Error? err = null; - try { - // check open after acquiring mutex, so that if an error is thrown it's caught and - // mutex can be closed - check_open(); - - responses = yield session.send_multiple_commands_async(cmds, cancellable); - } catch (Error store_fetch_err) { - err = store_fetch_err; - } - - // swap out results and clear accumulators - if (fetch_accumulator.size > 0) { - fetched = fetch_accumulator; - fetch_accumulator = new Gee.HashMap(); - } else { - fetched = null; - } - - if (search_accumulator.size > 0) { - search_results = search_accumulator; - search_accumulator = new Gee.HashSet(); - } else { - search_results = null; - } - - // unlock after clearing accumulators - cmd_mutex.release(ref token); - - if (err != null) - throw err; - - // process response stati after unlocking and clearing accumulators - assert(responses != null); - foreach (Command cmd in responses.keys) - throw_on_failed_status(responses.get(cmd), cmd); - - return responses; - } - - // HACK: See https://bugzilla.gnome.org/show_bug.cgi?id=714902 - // - // Detect when a server has returned a BAD response to FETCH BODY[HEADER.FIELDS (HEADER-LIST)] - // due to space between HEADER.FIELDS and (HEADER-LIST) - private bool retry_bad_header_fields_response(Command cmd, StatusResponse response) { - if (response.status != Status.BAD) - return false; - - FetchCommand? fetch = cmd as FetchCommand; - if (fetch == null) - return false; - - foreach (FetchBodyDataSpecifier body_specifier in fetch.for_body_data_specifiers) { - switch (body_specifier.section_part) { - case FetchBodyDataSpecifier.SectionPart.HEADER_FIELDS: - case FetchBodyDataSpecifier.SectionPart.HEADER_FIELDS_NOT: - // use value stored in specifier, not this folder's setting, as it's possible - // the folder's setting was enabled after sending command but before response - // returned - if (body_specifier.request_header_fields_space) - return true; - break; - } - } - - return false; - } - - private void throw_on_failed_status(StatusResponse response, Command cmd) throws Error { - assert(response.is_completion); - - switch (response.status) { - case Status.OK: - return; - - case Status.NO: - throw new ImapError.SERVER_ERROR("Request %s failed on %s: %s", cmd.to_string(), - to_string(), response.to_string()); - - case Status.BAD: { - // if a FetchBodyDataSpecifier is used to request for a header field BAD is returned, - // could be a specific formatting mistake some servers make of not allowing a space - // between the "header.fields" and list of email header names, i.e. - // - // "body[header.fields (references)]" - // - // If so, then enable a hack to work around this and retry the FETCH - if (retry_bad_header_fields_response(cmd, response)) { - imap_header_fields_hack = true; - - throw new FolderError.RETRY("BAD response to header.fields FETCH BODY, retry with hack"); - } - - throw new ImapError.INVALID("Bad request %s on %s: %s", cmd.to_string(), - to_string(), response.to_string()); - } - - default: - throw new ImapError.NOT_SUPPORTED("Unknown response status to %s on %s: %s", - cmd.to_string(), to_string(), response.to_string()); - } - } - - // Utility method for listing UIDs on the remote within the supplied range - public async Gee.Set? list_uids_async(MessageSet msg_set, Cancellable? cancellable) - throws Error { - check_open(); - - // Although FETCH could be used, SEARCH is more efficient in returning pure UID results, - // which is all we're interested in here - SearchCriteria criteria = new SearchCriteria(SearchCriterion.message_set(msg_set)); - SearchCommand cmd = new SearchCommand.uid(criteria); - - Gee.Set? search_results; - yield exec_commands_async(Geary.iterate(cmd).to_array_list(), null, out search_results, - cancellable); - - return (search_results != null && search_results.size > 0) ? search_results : null; - } - - private Gee.Collection assemble_list_commands(Imap.MessageSet msg_set, - Geary.Email.Field fields, out FetchBodyDataSpecifier? header_specifier, - out FetchBodyDataSpecifier? body_specifier, out FetchBodyDataSpecifier? preview_specifier, - out FetchBodyDataSpecifier? preview_charset_specifier) { - // getting all the fields can require multiple FETCH commands (some servers don't handle - // well putting every required data item into single command), so aggregate FetchCommands - Gee.Collection cmds = new Gee.ArrayList(); - - // if not a UID FETCH, request UIDs for all messages so their EmailIdentifier can be - // created without going back to the database (assuming the messages have already been - // pulled down, not a guarantee); if request is for NONE, that guarantees that the - // EmailIdentifier will be set, and so fetch UIDs (which looks funny but works when - // listing a range for contents: UID FETCH x:y UID) - if (!msg_set.is_uid || fields == Geary.Email.Field.NONE) - cmds.add(new FetchCommand.data_type(msg_set, FetchDataSpecifier.UID)); - - // convert bulk of the "basic" fields into a one or two FETCH commands (some servers have - // exhibited bugs or return NO when too many FETCH data types are combined on a single - // command) - if (fields.requires_any(BASIC_FETCH_FIELDS)) { - Gee.List data_types = new Gee.ArrayList(); - fields_to_fetch_data_types(fields, data_types, out header_specifier); - - // Add all simple data types as one FETCH command - if (data_types.size > 0) - cmds.add(new FetchCommand(msg_set, data_types, null)); - - // Add all body data types as separate FETCH command - if (header_specifier != null) - cmds.add(new FetchCommand.body_data_type(msg_set, header_specifier)); - } else { - header_specifier = null; - } - - // RFC822 BODY is a separate command - if (fields.require(Email.Field.BODY)) { - body_specifier = new FetchBodyDataSpecifier.peek(FetchBodyDataSpecifier.SectionPart.TEXT, - null, -1, -1, null); - - cmds.add(new FetchCommand.body_data_type(msg_set, body_specifier)); - } else { - body_specifier = null; - } - - // PREVIEW obtains the content type and a truncated version of - // the first part of the message, which often leads to poor - // results. It can also be also be synthesised from the - // email's RFC822 message in fetched_data_to_email, if the - // fields needed for reconstructing the RFC822 message are - // present. If so, rely on that and don't also request any - // additional data for the preview here. - if (fields.require(Email.Field.PREVIEW) && - !fields.require(Email.REQUIRED_FOR_MESSAGE)) { - // Get the preview text (the initial MAX_PREVIEW_BYTES of - // the first MIME section - - preview_specifier = new FetchBodyDataSpecifier.peek(FetchBodyDataSpecifier.SectionPart.NONE, - { 1 }, 0, Geary.Email.MAX_PREVIEW_BYTES, null); - cmds.add(new FetchCommand.body_data_type(msg_set, preview_specifier)); - - // Also get the character set to properly decode it - preview_charset_specifier = new FetchBodyDataSpecifier.peek( - FetchBodyDataSpecifier.SectionPart.MIME, { 1 }, -1, -1, null); - cmds.add(new FetchCommand.body_data_type(msg_set, preview_charset_specifier)); - } else { - preview_specifier = null; - preview_charset_specifier = null; - } - - // PROPERTIES and FLAGS are a separate command - if (fields.requires_any(Email.Field.PROPERTIES | Email.Field.FLAGS)) { - Gee.List data_types = new Gee.ArrayList(); - - if (fields.require(Geary.Email.Field.PROPERTIES)) { - data_types.add(FetchDataSpecifier.INTERNALDATE); - data_types.add(FetchDataSpecifier.RFC822_SIZE); - } - - if (fields.require(Geary.Email.Field.FLAGS)) - data_types.add(FetchDataSpecifier.FLAGS); - - cmds.add(new FetchCommand(msg_set, data_types, null)); - } - - return cmds; - } - - // Returns a no-message-id ImapDB.EmailIdentifier with the UID stored in it. - public async Gee.List? list_email_async(MessageSet msg_set, Geary.Email.Field fields, - Cancellable? cancellable) throws Error { - check_open(); - - Gee.HashMap? fetched = null; - FetchBodyDataSpecifier? header_specifier = null; - FetchBodyDataSpecifier? body_specifier = null; - FetchBodyDataSpecifier? preview_specifier = null; - FetchBodyDataSpecifier? preview_charset_specifier = null; - for (;;) { - Gee.Collection cmds = assemble_list_commands(msg_set, fields, - out header_specifier, out body_specifier, out preview_specifier, - out preview_charset_specifier); - if (cmds.size == 0) { - throw new ImapError.INVALID("No FETCH commands generate for list request %s %s", - msg_set.to_string(), fields.to_list_string()); - } - - // Commands prepped, do the fetch and accumulate all the responses - try { - yield exec_commands_async(cmds, out fetched, null, cancellable); - } catch (Error err) { - if (err is FolderError.RETRY) { - debug("Retryable server failure detected for %s: %s", to_string(), err.message); - - continue; - } - - throw err; - } - - break; - } - - if (fetched == null || fetched.size == 0) - return null; - - // Convert fetched data into Geary.Email objects - // because this could be for a lot of email, do in a background thread - Gee.List email_list = new Gee.ArrayList(); - yield Nonblocking.Concurrent.global.schedule_async(() => { - foreach (SequenceNumber seq_num in fetched.keys) { - FetchedData fetched_data = fetched.get(seq_num); - - // the UID should either have been fetched (if using positional addressing) or should - // have come back with the response (if using UID addressing) - UID? uid = fetched_data.data_map.get(FetchDataSpecifier.UID) as UID; - if (uid == null) { - message("Unable to list message #%s on %s: No UID returned from server", - seq_num.to_string(), to_string()); - - continue; - } - - try { - Geary.Email email = fetched_data_to_email(to_string(), uid, fetched_data, fields, - header_specifier, body_specifier, preview_specifier, preview_charset_specifier); - if (!email.fields.fulfills(fields)) { - message("%s: %s missing=%s fetched=%s", to_string(), email.id.to_string(), - fields.clear(email.fields).to_list_string(), fetched_data.to_string()); - - continue; - } - - email_list.add(email); - } catch (Error err) { - debug("%s: Unable to convert email for %s %s: %s", to_string(), uid.to_string(), - fetched_data.to_string(), err.message); - } - } - }, cancellable); - - return (email_list.size > 0) ? email_list : null; - } - - public async Gee.Map? uid_to_position_async(MessageSet msg_set, - Cancellable? cancellable) throws Error { - check_open(); - - // MessageSet better be UID addressing - assert(msg_set.is_uid); - - Gee.List cmds = new Gee.ArrayList(); - cmds.add(new FetchCommand.data_type(msg_set, FetchDataSpecifier.UID)); - - Gee.HashMap? fetched; - yield exec_commands_async(cmds, out fetched, null, cancellable); - - if (fetched == null || fetched.size == 0) - return null; - - Gee.Map map = new Gee.HashMap(); - foreach (SequenceNumber seq_num in fetched.keys) - map.set((UID) fetched.get(seq_num).data_map.get(FetchDataSpecifier.UID), seq_num); - - return map; - } - - public async void remove_email_async(Gee.List msg_sets, Cancellable? cancellable) - throws Error { - check_open(); - - Gee.List flags = new Gee.ArrayList(); - flags.add(MessageFlag.DELETED); - - Gee.List cmds = new Gee.ArrayList(); - - // Build STORE command for all MessageSets, see if all are UIDs so we can use UID EXPUNGE - bool all_uid = true; - foreach (MessageSet msg_set in msg_sets) { - if (!msg_set.is_uid) - all_uid = false; - - cmds.add(new StoreCommand(msg_set, flags, StoreCommand.Option.ADD_FLAGS)); - } - - // TODO: Only use old-school EXPUNGE when closing folder (or rely on CLOSE to do that work - // for us). See: - // http://redmine.yorba.org/issues/7532 - // - // However, current client implementation doesn't properly close INBOX when application - // shuts down, which means deleted messages return at application start. See: - // http://redmine.yorba.org/issues/6865 - if (all_uid && session.capabilities.supports_uidplus()) { - foreach (MessageSet msg_set in msg_sets) - cmds.add(new ExpungeCommand.uid(msg_set)); - } else { - cmds.add(new ExpungeCommand()); - } - - yield exec_commands_async(cmds, null, null, cancellable); - } - - public async void mark_email_async(Gee.List msg_sets, Geary.EmailFlags? flags_to_add, - Geary.EmailFlags? flags_to_remove, Cancellable? cancellable) throws Error { - check_open(); - - Gee.List msg_flags_add = new Gee.ArrayList(); - Gee.List msg_flags_remove = new Gee.ArrayList(); - MessageFlag.from_email_flags(flags_to_add, flags_to_remove, out msg_flags_add, - out msg_flags_remove); - - if (msg_flags_add.size == 0 && msg_flags_remove.size == 0) - return; - - Gee.Collection cmds = new Gee.ArrayList(); - foreach (MessageSet msg_set in msg_sets) { - if (msg_flags_add.size > 0) - cmds.add(new StoreCommand(msg_set, msg_flags_add, StoreCommand.Option.ADD_FLAGS)); - - if (msg_flags_remove.size > 0) - cmds.add(new StoreCommand(msg_set, msg_flags_remove, StoreCommand.Option.REMOVE_FLAGS)); - } - - yield exec_commands_async(cmds, null, null, cancellable); - } - - // Returns a mapping of the source UID to the destination UID. If the MessageSet is not for - // UIDs, then null is returned. If the server doesn't support COPYUID, null is returned. - public async Gee.Map? copy_email_async(MessageSet msg_set, FolderPath destination, - Cancellable? cancellable) throws Error { - check_open(); - - CopyCommand cmd = new CopyCommand(msg_set, - new MailboxSpecifier.from_folder_path(destination, this.delim)); - - Gee.Map? responses = yield exec_commands_async( - Geary.iterate(cmd).to_array_list(), null, null, cancellable); - - if (!responses.has_key(cmd)) - return null; - - StatusResponse response = responses.get(cmd); - if (response.response_code != null && msg_set.is_uid) { - Gee.List? src_uids = null; - Gee.List? dst_uids = null; - try { - response.response_code.get_copyuid(null, out src_uids, out dst_uids); - } catch (ImapError ierr) { - debug("Unable to retrieve COPYUID UIDs: %s", ierr.message); - } - - if (!Collection.is_empty(src_uids) && !Collection.is_empty(dst_uids)) { - Gee.Map copyuids = new Gee.HashMap(); - int ctr = 0; - for (;;) { - UID? src_uid = (ctr < src_uids.size) ? src_uids[ctr] : null; - UID? dst_uid = (ctr < dst_uids.size) ? dst_uids[ctr] : null; - - if (src_uid != null && dst_uid != null) - copyuids.set(src_uid, dst_uid); - else - break; - - ctr++; - } - - if (copyuids.size > 0) - return copyuids; - } - } - - return null; - } - - public async Gee.SortedSet? search_async(SearchCriteria criteria, Cancellable? cancellable) - throws Error { - check_open(); - - // always perform a UID SEARCH - Gee.Collection cmds = new Gee.ArrayList(); - cmds.add(new SearchCommand.uid(criteria)); - - Gee.Set? search_results; - yield exec_commands_async(cmds, null, out search_results, cancellable); - if (search_results == null || search_results.size == 0) - return null; - - Gee.SortedSet tree = new Gee.TreeSet(); - tree.add_all(search_results); - - return tree; + this.properties = properties; } - - // NOTE: If fields are added or removed from this method, BASIC_FETCH_FIELDS *must* be updated - // as well - private void fields_to_fetch_data_types(Geary.Email.Field fields, - Gee.List data_types_list, out FetchBodyDataSpecifier? header_specifier) { - // pack all the needed headers into a single FetchBodyDataType - string[] field_names = new string[0]; - - // The assumption here is that because ENVELOPE is such a common fetch command, the - // server will have optimizations for it, whereas if we called for each header in the - // envelope separately, the server has to chunk harder parsing the RFC822 header ... have - // to add References because IMAP ENVELOPE doesn't return them for some reason (but does - // return Message-ID and In-Reply-To) - if (fields.is_all_set(Geary.Email.Field.ENVELOPE)) { - data_types_list.add(FetchDataSpecifier.ENVELOPE); - field_names += "References"; - - // remove those flags and process any remaining - fields = fields.clear(Geary.Email.Field.ENVELOPE); - } - - foreach (Geary.Email.Field field in Geary.Email.Field.all()) { - switch (fields & field) { - case Geary.Email.Field.DATE: - field_names += "Date"; - break; - - case Geary.Email.Field.ORIGINATORS: - field_names += "From"; - field_names += "Sender"; - field_names += "Reply-To"; - break; - - case Geary.Email.Field.RECEIVERS: - field_names += "To"; - field_names += "Cc"; - field_names += "Bcc"; - break; - - case Geary.Email.Field.REFERENCES: - field_names += "References"; - field_names += "Message-ID"; - field_names += "In-Reply-To"; - break; - - case Geary.Email.Field.SUBJECT: - field_names += "Subject"; - break; - - case Geary.Email.Field.HEADER: - // TODO: If the entire header is being pulled, then no need to pull down partial - // headers; simply get them all and decode what is needed directly - data_types_list.add(FetchDataSpecifier.RFC822_HEADER); - break; - - case Geary.Email.Field.NONE: - case Geary.Email.Field.BODY: - case Geary.Email.Field.PROPERTIES: - case Geary.Email.Field.FLAGS: - case Geary.Email.Field.PREVIEW: - // not set or fetched separately - break; - - default: - assert_not_reached(); - } - } - - // convert field names into single FetchBodyDataType object - if (field_names.length > 0) { - header_specifier = new FetchBodyDataSpecifier.peek( - FetchBodyDataSpecifier.SectionPart.HEADER_FIELDS, null, -1, -1, field_names); - if (imap_header_fields_hack) - header_specifier.omit_request_header_fields_space(); - } else { - header_specifier = null; - } - } - - private static Geary.Email fetched_data_to_email(string folder_name, UID uid, - FetchedData fetched_data, Geary.Email.Field required_fields, - FetchBodyDataSpecifier? header_specifier, FetchBodyDataSpecifier? body_specifier, - FetchBodyDataSpecifier? preview_specifier, FetchBodyDataSpecifier? preview_charset_specifier) throws Error { - // note the use of INVALID_ROWID, as the rowid for this email (if one is present in the - // database) is unknown at this time; this means ImapDB *must* create a new EmailIdentifier - // for this email after create/merge is completed - Geary.Email email = new Geary.Email(new ImapDB.EmailIdentifier.no_message_id(uid)); - - // accumulate these to submit Imap.EmailProperties all at once - InternalDate? internaldate = null; - RFC822.Size? rfc822_size = null; - - // accumulate these to submit References all at once - RFC822.MessageID? message_id = null; - RFC822.MessageIDList? in_reply_to = null; - RFC822.MessageIDList? references = null; - - // loop through all available FetchDataTypes and gather converted data - foreach (FetchDataSpecifier data_type in fetched_data.data_map.keys) { - MessageData? data = fetched_data.data_map.get(data_type); - if (data == null) - continue; - - switch (data_type) { - case FetchDataSpecifier.ENVELOPE: - Envelope envelope = (Envelope) data; - - email.set_send_date(envelope.sent); - email.set_message_subject(envelope.subject); - email.set_originators( - envelope.from, - envelope.sender.equal_to(envelope.from) || envelope.sender.size == 0 ? null : envelope.sender[0], - envelope.reply_to.equal_to(envelope.from) ? null : envelope.reply_to - ); - email.set_receivers(envelope.to, envelope.cc, envelope.bcc); - - // store these to add to References all at once - message_id = envelope.message_id; - in_reply_to = envelope.in_reply_to; - break; - - case FetchDataSpecifier.RFC822_HEADER: - email.set_message_header((RFC822.Header) data); - break; - - case FetchDataSpecifier.RFC822_TEXT: - email.set_message_body((RFC822.Text) data); - break; - - case FetchDataSpecifier.RFC822_SIZE: - rfc822_size = (RFC822.Size) data; - break; - - case FetchDataSpecifier.FLAGS: - email.set_flags(new Imap.EmailFlags((MessageFlags) data)); - break; - - case FetchDataSpecifier.INTERNALDATE: - internaldate = (InternalDate) data; - break; - - default: - // everything else dropped on the floor (not applicable to Geary.Email) - break; - } - } - - // Only set PROPERTIES if all have been found - if (internaldate != null && rfc822_size != null) - email.set_email_properties(new Geary.Imap.EmailProperties(internaldate, rfc822_size)); - - // if the header was requested, convert its fields now - bool has_header_specifier = fetched_data.body_data_map.has_key(header_specifier); - if (header_specifier != null && !has_header_specifier) { - message("[%s] No header specifier \"%s\" found:", folder_name, - header_specifier.to_string()); - foreach (FetchBodyDataSpecifier specifier in fetched_data.body_data_map.keys) - message("[%s] has %s", folder_name, specifier.to_string()); - } else if (header_specifier != null && has_header_specifier) { - RFC822.Header headers = new RFC822.Header( - fetched_data.body_data_map.get(header_specifier)); - - // DATE - if (required_but_not_set(Geary.Email.Field.DATE, required_fields, email)) { - string? value = headers.get_header("Date"); - if (!String.is_empty(value)) - email.set_send_date(new RFC822.Date(value)); - else - email.set_send_date(null); - } - - // ORIGINATORS - if (required_but_not_set(Geary.Email.Field.ORIGINATORS, required_fields, email)) { - RFC822.MailboxAddresses? from = null; - string? value = headers.get_header("From"); - if (!String.is_empty(value)) - from = new RFC822.MailboxAddresses.from_rfc822_string(value); - - RFC822.MailboxAddress? sender = null; - value = headers.get_header("Sender"); - if (!String.is_empty(value)) - sender = new RFC822.MailboxAddress.from_rfc822_string(value); - - RFC822.MailboxAddresses? reply_to = null; - value = headers.get_header("Reply-To"); - if (!String.is_empty(value)) - reply_to = new RFC822.MailboxAddresses.from_rfc822_string(value); - - email.set_originators(from, sender, reply_to); - } - // RECEIVERS - if (required_but_not_set(Geary.Email.Field.RECEIVERS, required_fields, email)) { - RFC822.MailboxAddresses? to = null; - string? value = headers.get_header("To"); - if (!String.is_empty(value)) - to = new RFC822.MailboxAddresses.from_rfc822_string(value); - - RFC822.MailboxAddresses? cc = null; - value = headers.get_header("Cc"); - if (!String.is_empty(value)) - cc = new RFC822.MailboxAddresses.from_rfc822_string(value); - - RFC822.MailboxAddresses? bcc = null; - value = headers.get_header("Bcc"); - if (!String.is_empty(value)) - bcc = new RFC822.MailboxAddresses.from_rfc822_string(value); - - email.set_receivers(to, cc, bcc); - } - - // REFERENCES - // (Note that it's possible the request used an IMAP ENVELOPE, in which case only the - // References header will be present if REFERENCES were required, which is why - // REFERENCES is set at the bottom of the method, when all information has been gathered - if (message_id == null) { - string? value = headers.get_header("Message-ID"); - if (!String.is_empty(value)) - message_id = new RFC822.MessageID(value); - } - - if (in_reply_to == null) { - string? value = headers.get_header("In-Reply-To"); - if (!String.is_empty(value)) - in_reply_to = new RFC822.MessageIDList.from_rfc822_string(value); - } - - if (references == null) { - string? value = headers.get_header("References"); - if (!String.is_empty(value)) - references = new RFC822.MessageIDList.from_rfc822_string(value); - } - - // SUBJECT - // Unlike DATE, allow for empty subjects - if (required_but_not_set(Geary.Email.Field.SUBJECT, required_fields, email)) { - string? value = headers.get_header("Subject"); - if (value != null) - email.set_message_subject(new RFC822.Subject.decode(value)); - else - email.set_message_subject(null); - } - } - - // It's possible for all these fields to be null even though they were requested from - // the server, so use requested fields for determination - if (required_but_not_set(Geary.Email.Field.REFERENCES, required_fields, email)) - email.set_full_references(message_id, in_reply_to, references); - - // if preview was requested, get it now ... both identifiers - // must be supplied if one is - if (preview_specifier != null || preview_charset_specifier != null) { - assert(preview_specifier != null && preview_charset_specifier != null); - - if (fetched_data.body_data_map.has_key(preview_specifier) - && fetched_data.body_data_map.has_key(preview_charset_specifier)) { - email.set_message_preview(new RFC822.PreviewText.with_header( - fetched_data.body_data_map.get(preview_specifier), - fetched_data.body_data_map.get(preview_charset_specifier))); - } else { - message("[%s] No preview specifiers \"%s\" and \"%s\" found", folder_name, - preview_specifier.to_string(), preview_charset_specifier.to_string()); - foreach (FetchBodyDataSpecifier specifier in fetched_data.body_data_map.keys) - message("[%s] has %s", folder_name, specifier.to_string()); - } - } - - // If body was requested, get it now. We also set the preview - // here from the body if possible since for HTML messages at - // least there's a lot of boilerplate HTML to wade through to - // get some actual preview text, which usually requires more - // than Geary.Email.MAX_PREVIEW_BYTES will allow for - if (body_specifier != null) { - if (fetched_data.body_data_map.has_key(body_specifier)) { - email.set_message_body(new Geary.RFC822.Text( - fetched_data.body_data_map.get(body_specifier))); - - // Try to set the preview - Geary.RFC822.Message? message = null; - try { - message = email.get_message(); - } catch (Error e) { - // Not enough fields to construct the message - } - if (message != null) { - string preview = message.get_preview(); - if (preview.length > Geary.Email.MAX_PREVIEW_BYTES) { - preview = Geary.String.safe_byte_substring( - preview, Geary.Email.MAX_PREVIEW_BYTES - ); - } - email.set_message_preview( - new RFC822.PreviewText.from_string(preview) - ); - } - } else { - message("[%s] No body specifier \"%s\" found", folder_name, - body_specifier.to_string()); - foreach (FetchBodyDataSpecifier specifier in fetched_data.body_data_map.keys) - message("[%s] has %s", folder_name, specifier.to_string()); - } - } - - return email; - } - - // Returns a no-message-id ImapDB.EmailIdentifier with the UID stored in it. - // This method does not take a cancellable; there is currently no way to tell if an email was - // created or not if exec_commands_async() is cancelled during the append. For atomicity's sake, - // callers need to remove the returned email ID if a cancel occurred. - public async Geary.EmailIdentifier? create_email_async(RFC822.Message message, Geary.EmailFlags? flags, - DateTime? date_received) throws Error { - check_open(); - - MessageFlags? msg_flags = null; - if (flags != null) { - Imap.EmailFlags imap_flags = Imap.EmailFlags.from_api_email_flags(flags); - msg_flags = imap_flags.message_flags; - } else { - msg_flags = new MessageFlags(Geary.iterate(MessageFlag.SEEN).to_array_list()); - } - - InternalDate? internaldate = null; - if (date_received != null) - internaldate = new InternalDate.from_date_time(date_received); - - AppendCommand cmd = new AppendCommand(new MailboxSpecifier.from_folder_path(path, this.delim), - msg_flags, internaldate, message.get_network_buffer(false)); - - Gee.Map responses = yield exec_commands_async( - Geary.iterate(cmd).to_array_list(), null, null, null); - - // Grab the response and parse out the UID, if available. - StatusResponse response = responses.get(cmd); - if (response.status == Status.OK && response.response_code != null && - response.response_code.get_response_code_type().is_value("appenduid")) { - UID new_id = new UID.checked(response.response_code.get_as_string(2).as_int64()); - - return new ImapDB.EmailIdentifier.no_message_id(new_id); - } - - // We didn't get a UID back from the server. - return null; - } - - private static bool required_but_not_set(Geary.Email.Field check, Geary.Email.Field users_fields, Geary.Email email) { - return users_fields.require(check) ? !email.fields.is_all_set(check) : false; - } - - public string to_string() { - return path.to_string(); - } } - diff -Nru geary-0.12.4/src/engine/imap/api/imap-session-object.vala geary-3.32.0/src/engine/imap/api/imap-session-object.vala --- geary-0.12.4/src/engine/imap/api/imap-session-object.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/src/engine/imap/api/imap-session-object.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,102 @@ +/* + * Copyright 2018 Michael Gratton . + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +/** + * Base class for IMAP client session objects. + * + * Since a client session can come and go as the server and network + * changes, IMAP client objects need to be sensitive to the state of + * the connection. This abstract class manages access to an IMAP + * client session for objects that use connections to an IMAP server, + * ensuring it is no longer available if the client session is + * disconnected. + * + * This class is ''not'' thread safe. + */ +public abstract class Geary.Imap.SessionObject : BaseObject { + + + /** Determines if this object has a valid session or not. */ + public bool is_valid { get { return this.session != null; } } + + private string id; + private ClientSession? session; + + + /** Fired if the object's connection to the server is lost. */ + public signal void disconnected(ClientSession.DisconnectReason reason); + + + /** + * Constructs a new IMAP object with the given session. + */ + protected SessionObject(string id, ClientSession session) { + this.id = id; + this.session = session; + this.session.disconnected.connect(on_disconnected); + } + + ~SessionObject() { + if (close() != null) { + debug("%s: destroyed without releasing its session".printf(this.id)); + } + } + + /** + * Drops this object's association with its client session. + * + * Calling this method unhooks the object from its session, and + * makes it unavailable for further use. This does //not// + * disconnect the client session from its server. + * + * @return the old IMAP client session, for returning to the pool, + * etc, if any. + */ + public virtual ClientSession? close() { + ClientSession? old_session = this.session; + this.session = null; + + if (old_session != null) { + old_session.disconnected.disconnect(on_disconnected); + } + + return old_session; + } + + /** + * Returns a string representation of this object for debugging. + */ + public virtual string to_string() { + return "%s:%s".printf( + this.id, + this.session != null ? this.session.to_string() : "(session dropped)" + ); + } + + /** + * Obtains IMAP session the server for use by this object. + * + * @throws ImapError.NOT_CONNECTED if the session with the server + * server has been dropped via {@link close}, or because + * the connection was lost. + */ + protected ClientSession claim_session() + throws ImapError { + if (this.session == null) { + throw new ImapError.NOT_CONNECTED("IMAP object has no session"); + } + return this.session; + } + + private void on_disconnected(ClientSession.DisconnectReason reason) { + debug("%s: DISCONNECTED %s", to_string(), reason.to_string()); + + close(); + disconnected(reason); + } + +} diff -Nru geary-0.12.4/src/engine/imap/command/imap-append-command.vala geary-3.32.0/src/engine/imap/command/imap-append-command.vala --- geary-0.12.4/src/engine/imap/command/imap-append-command.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/command/imap-append-command.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,4 +1,5 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. @@ -9,23 +10,25 @@ * * See [[http://tools.ietf.org/html/rfc3501#section-6.3.11]] */ - public class Geary.Imap.AppendCommand : Command { + public const string NAME = "append"; - + public AppendCommand(MailboxSpecifier mailbox, MessageFlags? flags, InternalDate? internal_date, Memory.Buffer message) { base (NAME); - - add(mailbox.to_parameter()); - - if (flags != null && flags.size > 0) - add(flags.to_parameter()); - - if (internal_date != null) - add(internal_date.to_parameter()); - - add(new LiteralParameter(message)); + + this.args.add(mailbox.to_parameter()); + + if (flags != null && flags.size > 0) { + this.args.add(flags.to_parameter()); + } + + if (internal_date != null) { + this.args.add(internal_date.to_parameter()); + } + + this.args.add(new LiteralParameter(message)); } -} +} diff -Nru geary-0.12.4/src/engine/imap/command/imap-authenticate-command.vala geary-3.32.0/src/engine/imap/command/imap-authenticate-command.vala --- geary-0.12.4/src/engine/imap/command/imap-authenticate-command.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/src/engine/imap/command/imap-authenticate-command.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,114 @@ +/* + * Copyright 2018 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +/** + * The IMAP AUTHENTICATE command. + * + * See [[http://tools.ietf.org/html/rfc3501#section-6.2.2]] + */ +public class Geary.Imap.AuthenticateCommand : Command { + + + public const string NAME = "authenticate"; + + private const string OAUTH2_METHOD = "xoauth2"; + private const string OAUTH2_RESP = "user=%s\001auth=Bearer %s\001\001"; + + + public string method { get; private set; } + + private LiteralParameter? response_literal = null; + private bool serialised = false; + private Geary.Nonblocking.Spinlock error_lock; + private GLib.Cancellable error_cancellable = new GLib.Cancellable(); + + + private AuthenticateCommand(string method, string data) { + base(NAME, { method, data }); + this.method = method; + this.error_lock = new Geary.Nonblocking.Spinlock(this.error_cancellable); + } + + public AuthenticateCommand.oauth2(string user, string token) { + string encoded_token = Base64.encode( + OAUTH2_RESP.printf(user, token).data + ); + this(OAUTH2_METHOD, encoded_token); + } + + internal override async void send(Serializer ser, + GLib.Cancellable cancellable) + throws GLib.Error { + yield base.send(ser, cancellable); + this.serialised = true; + + // Need to manually flush here since the connection will be + // waiting for all pending commands to complete before + // flushing it itself + yield ser.flush_stream(cancellable); + } + + public override string to_string() { + return "%s %s %s ".printf( + tag.to_string(), this.name, this.method + ); + } + + internal override async void send_wait(Serializer ser, + GLib.Cancellable cancellable) + throws GLib.Error { + // Wait to either get a response or a continuation request + yield this.error_lock.wait_async(cancellable); + if (this.response_literal != null) { + yield this.response_literal.serialize_data(ser, cancellable); + ser.push_eol(cancellable); + yield ser.flush_stream(cancellable); + } + + yield wait_until_complete(cancellable); + } + + internal override void completed(StatusResponse new_status) + throws ImapError { + this.error_lock.blind_notify(); + base.completed(new_status); + } + + internal override void continuation_requested(ContinuationResponse response) + throws ImapError { + if (!this.serialised) { + // Allow any args sent as literals to be processed + // normally + base.continuation_requested(response); + } else { + if (this.method != AuthenticateCommand.OAUTH2_METHOD || + this.response_literal != null) { + cancel_send(); + throw new ImapError.INVALID( + "Unexpected AUTHENTICATE continuation request" + ); + } + + // Continuation will be a Base64 encoded JSON blob and which + // indicates a login failure. We don't really care about that + // (do we?) though since once we acknowledge it with a + // zero-length response the server will respond with an IMAP + // error. + this.response_literal = new LiteralParameter( + Geary.Memory.EmptyBuffer.instance + ); + // Notify serialisation to continue + this.error_lock.blind_notify(); + } + } + + protected override void cancel_send() { + base.cancel_send(); + this.error_cancellable.cancel(); + } + +} diff -Nru geary-0.12.4/src/engine/imap/command/imap-capability-command.vala geary-3.32.0/src/engine/imap/command/imap-capability-command.vala --- geary-0.12.4/src/engine/imap/command/imap-capability-command.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/command/imap-capability-command.vala 2019-03-17 13:39:29.000000000 +0000 @@ -12,7 +12,7 @@ public class Geary.Imap.CapabilityCommand : Command { public const string NAME = "capability"; - + public CapabilityCommand() { base (NAME); } diff -Nru geary-0.12.4/src/engine/imap/command/imap-close-command.vala geary-3.32.0/src/engine/imap/command/imap-close-command.vala --- geary-0.12.4/src/engine/imap/command/imap-close-command.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/command/imap-close-command.vala 2019-03-17 13:39:29.000000000 +0000 @@ -10,7 +10,7 @@ public class Geary.Imap.CloseCommand : Command { public const string NAME = "close"; - + public CloseCommand() { base (NAME); } diff -Nru geary-0.12.4/src/engine/imap/command/imap-command.vala geary-3.32.0/src/engine/imap/command/imap-command.vala --- geary-0.12.4/src/engine/imap/command/imap-command.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/command/imap-command.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,4 +1,6 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. + * Copyright 2018 Michael Gratton * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. @@ -15,8 +17,14 @@ * * See [[http://tools.ietf.org/html/rfc3501#section-6]] */ +public class Geary.Imap.Command : BaseObject { + + /** + * Default timeout to wait for a server response for a command. + */ + public const uint DEFAULT_RESPONSE_TIMEOUT_SEC = 30; + -public class Geary.Imap.Command : RootParameters { /** * All IMAP commands are tagged with an identifier assigned by the client. * @@ -29,87 +37,344 @@ * @see assign_tag */ public Tag tag { get; private set; } - + /** - * The name (or "verb") of the {@link Command}. + * The name (or "verb") of this command. */ public string name { get; private set; } - + + /** + * Number of seconds to wait for a server response to this command. + */ + public uint response_timeout { + get { + return this._response_timeout; + } + set { + this._response_timeout = value; + this.response_timer.interval = value; + } + } + private uint _response_timeout = DEFAULT_RESPONSE_TIMEOUT_SEC; + + /** The status response for the command, once it has been received. */ + public StatusResponse? status { get; private set; default = null; } + /** - * Zero or more arguments for the {@link Command}. + * The command's arguments as parameters. * - * Note that some Commands have require args and others are optional. The format of the - * arguments ({@link StringParameter}, {@link ListParameter}, etc.) is sometimes crucial. + * Subclassess may append arguments to this before {@link send} is + * called, ideally from their constructors. */ - public string[]? args { get; private set; } - + protected ListParameter args { + get; private set; default = new RootParameters(); + } + /** - * Create a Command with an unassigned Tag. + * Timer used to check for a response within {@link response_timeout}. + */ + protected TimeoutManager response_timer { get; private set; } + + private Geary.Nonblocking.Semaphore complete_lock = + new Geary.Nonblocking.Semaphore(); + + private bool timed_out = false; + private bool cancelled = false; + + private Geary.Nonblocking.Spinlock? literal_spinlock = null; + private GLib.Cancellable? literal_cancellable = null; + + + /** + * Fired when the response timeout for this command has been reached. + */ + public signal void response_timed_out(); + + /** + * Constructs a new command with an unassigned tag. + * + * Any arguments provided here will be converted to appropriate + * string arguments * - * @see tag + * @see Tag */ public Command(string name, string[]? args = null) { - tag = Tag.get_unassigned(); + this.tag = Tag.get_unassigned(); this.name = name; - this.args = args; - - stock_params(); + if (args != null) { + foreach (string arg in args) { + this.args.add(Parameter.get_for_string(arg)); + } + } + + this.response_timer = new TimeoutManager.seconds( + this._response_timeout, on_response_timeout + ); } - + + public bool has_name(string name) { + return Ascii.stri_equal(this.name, name); + } + /** - * Create a Command with an assigned Tag. + * Assign a Tag to this command, if currently unassigned. * - * @see tag + * Can only be called on a Command that holds an unassigned tag, + * and hence this can only be called once at most. Throws an error + * if already assigned or if the supplied tag is unassigned. */ - public Command.assigned(Tag tag, string name, string[]? args = null) - requires (tag.is_tagged() && tag.is_assigned()) { - this.tag = tag; - this.name = name; - this.args = args; - - stock_params(); - } - - private void stock_params() { - add(tag); - add(new AtomParameter(name)); - if (args != null) { - foreach (string arg in args) - add(Parameter.get_for_string(arg)); + internal void assign_tag(Tag new_tag) throws ImapError { + if (this.tag.is_assigned()) { + throw new ImapError.SERVER_ERROR( + "%s: Command tag is already assigned", to_brief_string() + ); } + if (!new_tag.is_assigned()) { + throw new ImapError.SERVER_ERROR( + "%s: New tag is not assigned", to_brief_string() + ); + } + + this.tag = new_tag; } - + /** - * Assign a {@link Tag} to a {@link Command} with an unassigned placeholder Tag. + * Serialises this command for transmission to the server. * - * Can only be called on a Command that holds an unassigned Tag. Thus, this can only be called - * once at most, and zero times if Command.assigned() was used to generate the Command. - * Fires an assertion if either of these cases is true, or if the supplied Tag is unassigned. + * This will serialise its tag, name and arguments (if + * any). Arguments are treated as strings and escaped as needed, + * including being encoded as a literal. If any literals are + * required, this method will yield until a command continuation + * has been received, when it will resume the same process. */ - public void assign_tag(Tag tag) { - assert(!this.tag.is_assigned()); - assert(tag.is_assigned()); - - this.tag = tag; - - // Tag is always at index zero. - try { - Parameter param = replace(0, tag); - assert(param is Tag); - } catch (ImapError err) { - error("Unable to assign Tag for command %s: %s", to_string(), err.message); + internal virtual async void send(Serializer ser, + GLib.Cancellable cancellable) + throws GLib.Error { + this.response_timer.start(); + this.tag.serialize(ser, cancellable); + ser.push_space(cancellable); + ser.push_unquoted_string(this.name, cancellable); + + if (this.args != null) { + foreach (Parameter arg in this.args.get_all()) { + ser.push_space(cancellable); + arg.serialize(ser, cancellable); + + LiteralParameter literal = arg as LiteralParameter; + if (literal != null) { + // Need to manually flush after serialising the + // literal param, so it actually gets to the + // server + yield ser.flush_stream(cancellable); + + if (this.literal_spinlock == null) { + // Lazily create these since they usually + // won't be needed + this.literal_cancellable = new GLib.Cancellable(); + this.literal_spinlock = new Geary.Nonblocking.Spinlock( + this.literal_cancellable + ); + } + + // Will get notified via continuation_requested + // when server indicated the literal can be sent. + yield this.literal_spinlock.wait_async(cancellable); + yield literal.serialize_data(ser, cancellable); + } + } } + + ser.push_eol(cancellable); } - - public bool has_name(string name) { - return Ascii.stri_equal(this.name, name); + + /** + * Check for command-specific server responses after sending. + * + * This method is called after {@link send} and after {@link + * ClientSession} has signalled the command has been sent, but + * before the next command is processed. It allows command + * implementations (e.g. {@link IdleCommand}) to asynchronously + * wait for some kind of response from the server before allowing + * additional commands to be sent. + * + * Most commands will not need to override this, and it by default + * does nothing. + */ + internal virtual async void send_wait(Serializer ser, + GLib.Cancellable cancellable) + throws GLib.Error { + // Nothing to do by default + } + + /** + * Cancels this command's execution. + * + * When this method is called, all locks will be released, + * including {@link wait_until_complete}, which will then throw a + * `GLib.IOError.CANCELLED` error. + */ + internal virtual void cancel_command() { + cancel_send(); + this.cancelled = true; + this.response_timer.reset(); + this.complete_lock.blind_notify(); + } + + /** + * Yields until the command has been completed or cancelled. + * + * Throws an error if the command or the cancellable argument is + * cancelled, if the command timed out, or if the command's + * response was bad. + */ + public async void wait_until_complete(GLib.Cancellable cancellable) + throws GLib.Error { + yield this.complete_lock.wait_async(cancellable); + + if (this.cancelled) { + throw new GLib.IOError.CANCELLED( + "%s: Command was cancelled", to_brief_string() + ); + } + + if (this.timed_out) { + throw new ImapError.TIMED_OUT( + "%s: Command timed out", to_brief_string() + ); + } + + // Since this is part of the public API, perform a strict + // check on the status code. + check_status(true); } - - public override void serialize(Serializer ser, Tag tag) throws Error { - assert(tag.is_assigned()); - - base.serialize(ser, tag); - ser.push_end_of_message(); + + public virtual string to_string() { + string args = this.args.to_string(); + return (Geary.String.is_empty(args)) + ? "%s %s".printf(this.tag.to_string(), this.name) + : "%s %s %s".printf(this.tag.to_string(), this.name, args); + } + + /** + * Called when a tagged status response is received for this command. + * + * This will update the command's {@link status} property, then + * throw an error if it does not indicate a successful completion. + */ + internal virtual void completed(StatusResponse new_status) + throws ImapError { + if (this.status != null) { + cancel_send(); + throw new ImapError.SERVER_ERROR( + "%s: Duplicate status response received: %s", + to_brief_string(), + status.to_string() + ); + } + + this.status = new_status; + this.response_timer.reset(); + this.complete_lock.blind_notify(); + cancel_send(); + // Since this gets called by the client connection only check + // for an expected server response, good or bad + check_status(false); + } + + /** + * Called when tagged server data is received for this command. + */ + internal virtual void data_received(ServerData data) + throws ImapError { + if (this.status != null) { + cancel_send(); + throw new ImapError.SERVER_ERROR( + "%s: Server data received when command already complete: %s", + to_brief_string(), + data.to_string() + ); + } + + this.response_timer.start(); } -} + /** + * Called when a continuation was requested by the server. + * + * This will notify the command's literal spinlock so that if + * {@link send} is waiting to send a literal, it will do so + * now. + */ + internal virtual void + continuation_requested(ContinuationResponse continuation) + throws ImapError { + if (this.status != null) { + cancel_send(); + throw new ImapError.SERVER_ERROR( + "%s: Continuation requested when command already complete", + to_brief_string() + ); + } + + if (this.literal_spinlock == null) { + cancel_send(); + throw new ImapError.SERVER_ERROR( + "%s: Continuation requested but no literals available", + to_brief_string() + ); + } + + this.response_timer.start(); + this.literal_spinlock.blind_notify(); + } + + /** + * Cancels any existing serialisation in progress. + * + * When this method is called, any non I/O related process + * blocking the blocking {@link send} must be cancelled. + */ + protected virtual void cancel_send() { + if (this.literal_cancellable != null) { + this.literal_cancellable.cancel(); + } + } + + private void check_status(bool require_okay) throws ImapError { + if (this.status == null) { + throw new ImapError.SERVER_ERROR( + "%s: No command response was received", + to_brief_string() + ); + } + + if (!this.status.is_completion) { + throw new ImapError.SERVER_ERROR( + "%s: Command status response is not a completion: %s", + to_brief_string(), + this.status.to_string() + ); + } + + // XXX should we be distinguishing between NO and BAD + // responses here? + if (require_okay && this.status.status != Status.OK) { + throw new ImapError.SERVER_ERROR( + "%s: Command failed: %s", + to_brief_string(), + this.status.to_string() + ); + } + } + + private string to_brief_string() { + return "%s %s".printf(this.tag.to_string(), this.name); + } + + private void on_response_timeout() { + this.timed_out = true; + cancel_command(); + response_timed_out(); + } + +} diff -Nru geary-0.12.4/src/engine/imap/command/imap-compress-command.vala geary-3.32.0/src/engine/imap/command/imap-compress-command.vala --- geary-0.12.4/src/engine/imap/command/imap-compress-command.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/command/imap-compress-command.vala 2019-03-17 13:39:29.000000000 +0000 @@ -10,9 +10,9 @@ public class Geary.Imap.CompressCommand : Command { public const string NAME = "compress"; - + public const string ALGORITHM_DEFLATE = "deflate"; - + public CompressCommand(string algorithm) { base (NAME, { algorithm }); } diff -Nru geary-0.12.4/src/engine/imap/command/imap-copy-command.vala geary-3.32.0/src/engine/imap/command/imap-copy-command.vala --- geary-0.12.4/src/engine/imap/command/imap-copy-command.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/command/imap-copy-command.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,4 +1,5 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. @@ -7,16 +8,15 @@ /** * See [[http://tools.ietf.org/html/rfc3501#section-6.4.7]] */ - public class Geary.Imap.CopyCommand : Command { + public const string NAME = "copy"; public const string UID_NAME = "uid copy"; public CopyCommand(MessageSet message_set, MailboxSpecifier destination) { - base (message_set.is_uid ? UID_NAME : NAME); + base(message_set.is_uid ? UID_NAME : NAME); - add(message_set.to_parameter()); - add(destination.to_parameter()); + this.args.add(message_set.to_parameter()); + this.args.add(destination.to_parameter()); } } - diff -Nru geary-0.12.4/src/engine/imap/command/imap-create-command.vala geary-3.32.0/src/engine/imap/command/imap-create-command.vala --- geary-0.12.4/src/engine/imap/command/imap-create-command.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/command/imap-create-command.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,23 +1,82 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2017 Michael Gratton + * Copyright 2016 Software Freedom Conservancy Inc. * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ /** - * See [[http://tools.ietf.org/html/rfc3501#section-6.3.3]] + * The IMAP CREATE command. + * + * This command also supports the RFC 6154 Special-Use CREATE + * extension. + * + * See [[http://tools.ietf.org/html/rfc3501#section-6.3.3]] and + * [[https://tools.ietf.org/html/rfc6154#section-3]] */ - public class Geary.Imap.CreateCommand : Command { - public const string NAME = "create"; - + + public const string NAME_ATOM = "create"; + public const string USE_ATOM = "use"; + public MailboxSpecifier mailbox { get; private set; } - + + public Geary.SpecialFolderType use { + get; private set; default = Geary.SpecialFolderType.NONE; + } + + + private static MailboxAttribute? get_special_folder_type(Geary.SpecialFolderType type) { + switch (type) { + case Geary.SpecialFolderType.TRASH: + return MailboxAttribute.SPECIAL_FOLDER_TRASH; + + case Geary.SpecialFolderType.DRAFTS: + return MailboxAttribute.SPECIAL_FOLDER_DRAFTS; + + case Geary.SpecialFolderType.SENT: + return MailboxAttribute.SPECIAL_FOLDER_SENT; + + case Geary.SpecialFolderType.ARCHIVE: + return MailboxAttribute.SPECIAL_FOLDER_ARCHIVE; + + case Geary.SpecialFolderType.SPAM: + return MailboxAttribute.SPECIAL_FOLDER_JUNK; + + case Geary.SpecialFolderType.FLAGGED: + return MailboxAttribute.SPECIAL_FOLDER_STARRED; + + case Geary.SpecialFolderType.ALL_MAIL: + return MailboxAttribute.SPECIAL_FOLDER_ALL; + + default: + return null; + } + } + public CreateCommand(MailboxSpecifier mailbox) { - base (NAME); - + base(NAME_ATOM); this.mailbox = mailbox; - - add(mailbox.to_parameter()); + this.args.add(mailbox.to_parameter()); } + + public CreateCommand.special_use(MailboxSpecifier mailbox, + Geary.SpecialFolderType use) { + this(mailbox); + this.use = use; + + MailboxAttribute? attr = get_special_folder_type(use); + if (attr != null) { + ListParameter use_types = new ListParameter(); + use_types.add(new AtomParameter(attr.to_string())); + + ListParameter use_param = new ListParameter(); + use_param.add(new AtomParameter(USE_ATOM)); + use_param.add(use_types); + + this.args.add(use_param); + } + } + } diff -Nru geary-0.12.4/src/engine/imap/command/imap-delete-command.vala geary-3.32.0/src/engine/imap/command/imap-delete-command.vala --- geary-0.12.4/src/engine/imap/command/imap-delete-command.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/src/engine/imap/command/imap-delete-command.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,26 @@ +/* + * Copyright 2018 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +/** + * The RFC 3501 DELETE command. + * + * Deletes the given mailbox. Per the RFC, this must not be used to + * delete mailboxes with child (inferior) mailboxes and that also are + * marked \Noselect. + * + * See [[http://tools.ietf.org/html/rfc3501#section-6.3.4]] + */ +public class Geary.Imap.DeleteCommand : Command { + + public const string NAME = "DELETE"; + + public DeleteCommand(MailboxSpecifier mailbox) { + base(NAME); + this.args.add(mailbox.to_parameter()); + } + +} diff -Nru geary-0.12.4/src/engine/imap/command/imap-examine-command.vala geary-3.32.0/src/engine/imap/command/imap-examine-command.vala --- geary-0.12.4/src/engine/imap/command/imap-examine-command.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/command/imap-examine-command.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,4 +1,5 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. @@ -9,18 +10,16 @@ * * @see SelectCommand */ - public class Geary.Imap.ExamineCommand : Command { + public const string NAME = "examine"; - + public MailboxSpecifier mailbox { get; private set; } - + public ExamineCommand(MailboxSpecifier mailbox) { - base (NAME); - + base(NAME); this.mailbox = mailbox; - - add(mailbox.to_parameter()); + this.args.add(mailbox.to_parameter()); } -} +} diff -Nru geary-0.12.4/src/engine/imap/command/imap-expunge-command.vala geary-3.32.0/src/engine/imap/command/imap-expunge-command.vala --- geary-0.12.4/src/engine/imap/command/imap-expunge-command.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/command/imap-expunge-command.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,4 +1,5 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. @@ -8,21 +9,19 @@ * See [[http://tools.ietf.org/html/rfc3501#section-6.4.3]] and * [[http://tools.ietf.org/html/rfc4315#section-2.1]] */ - public class Geary.Imap.ExpungeCommand : Command { + public const string NAME = "expunge"; public const string UID_NAME = "uid expunge"; - + public ExpungeCommand() { - base (NAME); + base(NAME); } - + public ExpungeCommand.uid(MessageSet message_set) { - base (UID_NAME); - + base(UID_NAME); assert(message_set.is_uid); - - add(message_set.to_parameter()); + this.args.add(message_set.to_parameter()); } -} +} diff -Nru geary-0.12.4/src/engine/imap/command/imap-fetch-command.vala geary-3.32.0/src/engine/imap/command/imap-fetch-command.vala --- geary-0.12.4/src/engine/imap/command/imap-fetch-command.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/command/imap-fetch-command.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,4 +1,5 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. @@ -16,76 +17,76 @@ * @see FetchedData * @see StoreCommand */ - public class Geary.Imap.FetchCommand : Command { + public const string NAME = "fetch"; public const string UID_NAME = "uid fetch"; - + /** * Non-null if {@link FetchCommand} created for this {@link FetchDataSpecifier}. */ public Gee.List for_data_types { get; private set; default = new Gee.ArrayList(); } - + /** * Non-null if {@link FetchCommand} created for this {@link FetchBodyDataSpecifier}. */ public Gee.List for_body_data_specifiers { get; private set; default = new Gee.ArrayList(); } - + public FetchCommand(MessageSet msg_set, Gee.List? data_items, Gee.List? body_data_items) { base (msg_set.is_uid ? UID_NAME : NAME); - - add(msg_set.to_parameter()); - + + this.args.add(msg_set.to_parameter()); + int data_items_length = (data_items != null) ? data_items.size : 0; int body_items_length = (body_data_items != null) ? body_data_items.size : 0; - + // see note in unadorned ctor for reasoning here if (data_items_length == 1 && body_items_length == 0) { - add(data_items[0].to_parameter()); + this.args.add(data_items[0].to_parameter()); } else if (data_items_length == 0 && body_items_length == 1) { - add(body_data_items[0].to_request_parameter()); + this.args.add(body_data_items[0].to_request_parameter()); } else { ListParameter list = new ListParameter(); - + if (data_items_length > 0) { foreach (FetchDataSpecifier data_item in data_items) list.add(data_item.to_parameter()); } - + if (body_items_length > 0) { foreach (FetchBodyDataSpecifier body_item in body_data_items) list.add(body_item.to_request_parameter()); } - - add(list); + + this.args.add(list); } - + if (data_items != null) for_data_types.add_all(data_items); - + if (body_data_items != null) for_body_data_specifiers.add_all(body_data_items); } - + public FetchCommand.data_type(MessageSet msg_set, FetchDataSpecifier data_type) { base (msg_set.is_uid ? UID_NAME : NAME); - + for_data_types.add(data_type); - - add(msg_set.to_parameter()); - add(data_type.to_parameter()); + + this.args.add(msg_set.to_parameter()); + this.args.add(data_type.to_parameter()); } - + public FetchCommand.body_data_type(MessageSet msg_set, FetchBodyDataSpecifier body_data_specifier) { base (msg_set.is_uid ? UID_NAME : NAME); - + for_body_data_specifiers.add(body_data_specifier); - - add(msg_set.to_parameter()); - add(body_data_specifier.to_request_parameter()); + + this.args.add(msg_set.to_parameter()); + this.args.add(body_data_specifier.to_request_parameter()); } -} +} diff -Nru geary-0.12.4/src/engine/imap/command/imap-id-command.vala geary-3.32.0/src/engine/imap/command/imap-id-command.vala --- geary-0.12.4/src/engine/imap/command/imap-id-command.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/command/imap-id-command.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,4 +1,5 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. @@ -7,26 +8,25 @@ /** * See [[http://www.ietf.org/rfc/rfc2971.txt]] */ - public class Geary.Imap.IdCommand : Command { + public const string NAME = "id"; - + public IdCommand(Gee.HashMap fields) { - base (NAME); - + base(NAME); + ListParameter list = new ListParameter(); foreach (string key in fields.keys) { list.add(new QuotedStringParameter(key)); list.add(new QuotedStringParameter(fields.get(key))); } - - add(list); + + this.args.add(list); } - + public IdCommand.nil() { - base (NAME); - - add(NilParameter.instance); + base(NAME); + this.args.add(NilParameter.instance); } -} +} diff -Nru geary-0.12.4/src/engine/imap/command/imap-idle-command.vala geary-3.32.0/src/engine/imap/command/imap-idle-command.vala --- geary-0.12.4/src/engine/imap/command/imap-idle-command.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/command/imap-idle-command.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,20 +1,96 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. + * Copyright 2018 Michael Gratton * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ /** - * See [[http://tools.ietf.org/html/rfc2177]] + * The IMAP IDLE command. * - * @see NoopCommand + * See [[http://tools.ietf.org/html/rfc2177]] */ - public class Geary.Imap.IdleCommand : Command { - public const string NAME = "idle"; - + + public const string NAME = "IDLE"; + + private const string DONE = "DONE"; + + /** Determines if the server has acknowledged the IDLE request. */ + public bool idle_started { get; private set; default = false; } + + private bool serialised = false; + private Geary.Nonblocking.Spinlock? exit_lock; + private GLib.Cancellable? exit_cancellable = new GLib.Cancellable(); + + public IdleCommand() { - base (NAME); + base(NAME); + this.exit_lock = new Geary.Nonblocking.Spinlock(this.exit_cancellable); } -} + /** Causes the idle command to exit, if currently executing. **/ + public void exit_idle() { + this.exit_lock.blind_notify(); + } + + /** Waits after serialisation has completed for {@link exit_idle}. */ + internal override async void send(Serializer ser, + GLib.Cancellable cancellable) + throws GLib.Error { + // Need to manually flush here since Dovecot doesn't like + // getting IDLE in the same buffer as other commands. + yield ser.flush_stream(cancellable); + + yield base.send(ser, cancellable); + this.serialised = true; + + // Need to manually flush again since the connection will be + // waiting for all pending commands to complete before + // flushing it itself + yield ser.flush_stream(cancellable); + } + + internal override async void send_wait(Serializer ser, + GLib.Cancellable cancellable) + throws GLib.Error { + // Wait for exit_idle() to be called, the server to send a + // status response, or everything to be cancelled. + yield this.exit_lock.wait_async(cancellable); + + // If we aren't done already, send DONE to exit IDLE. Restart + // the response timer so we get a timeout if DONE is not + // received in good time. + if (this.status == null) { + this.response_timer.start(); + ser.push_unquoted_string(DONE); + ser.push_eol(cancellable); + yield ser.flush_stream(cancellable); + } + + // Wait until we get a status response so no other command is + // sent between DONE and the status response. + yield wait_until_complete(cancellable); + } + + internal override void continuation_requested(ContinuationResponse response) + throws ImapError { + if (!this.serialised) { + // Allow any args sent as literals to be processed + // normally + base.continuation_requested(response); + } else { + this.idle_started = true; + // Reset the timer here since we know the command was + // received fine. + this.response_timer.reset(); + } + } + + protected override void cancel_send() { + base.cancel_send(); + this.exit_cancellable.cancel(); + } + +} diff -Nru geary-0.12.4/src/engine/imap/command/imap-list-command.vala geary-3.32.0/src/engine/imap/command/imap-list-command.vala --- geary-0.12.4/src/engine/imap/command/imap-list-command.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/command/imap-list-command.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,10 +1,13 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ /** + * The IMAP LIST and proprietary XLIST commands. + * * See [[http://tools.ietf.org/html/rfc3501#section-6.3.8]] * * Some implementations may return the mailbox name itself when using wildcarding. For example: @@ -14,11 +17,13 @@ * * @see MailboxInformation */ - public class Geary.Imap.ListCommand : Command { - public const string NAME = "list"; + + + public const string NAME = "LIST"; public const string XLIST_NAME = "xlist"; - + + /** * LIST a particular mailbox by {@link MailboxSpecifier}. * @@ -34,26 +39,26 @@ * See [[http://redmine.yorba.org/issues/7624]] for more information. */ public ListCommand(MailboxSpecifier mailbox, bool use_xlist, ListReturnParameter? return_param) { - base (use_xlist ? XLIST_NAME : NAME, { "" }); - - add(mailbox.to_parameter()); + base(use_xlist ? XLIST_NAME : NAME, { "" }); + + this.args.add(mailbox.to_parameter()); add_return_parameter(return_param); } - + public ListCommand.wildcarded(string reference, MailboxSpecifier mailbox, bool use_xlist, ListReturnParameter? return_param) { - base (use_xlist ? XLIST_NAME : NAME, { reference }); - - add(mailbox.to_parameter()); + base(use_xlist ? XLIST_NAME : NAME, { reference }); + + this.args.add(mailbox.to_parameter()); add_return_parameter(return_param); } - + private void add_return_parameter(ListReturnParameter? return_param) { if (return_param == null || return_param.size == 0) return; - - add(StringParameter.get_best_for_unchecked("return")); - add(return_param); + + this.args.add(StringParameter.get_best_for_unchecked("return")); + this.args.add(return_param); } -} +} diff -Nru geary-0.12.4/src/engine/imap/command/imap-list-return-parameter.vala geary-3.32.0/src/engine/imap/command/imap-list-return-parameter.vala --- geary-0.12.4/src/engine/imap/command/imap-list-return-parameter.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/command/imap-list-return-parameter.vala 2019-03-17 13:39:29.000000000 +0000 @@ -20,7 +20,7 @@ * See [[https://tools.ietf.org/html/rfc6154]] */ public const string SPECIAL_USE = "special-use"; - + /** * Creates an empty {@link ListReturnParameter}. * @@ -28,7 +28,7 @@ */ public ListReturnParameter() { } - + public void add_special_use() { add(StringParameter.get_best_for_unchecked(SPECIAL_USE)); } diff -Nru geary-0.12.4/src/engine/imap/command/imap-login-command.vala geary-3.32.0/src/engine/imap/command/imap-login-command.vala --- geary-0.12.4/src/engine/imap/command/imap-login-command.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/command/imap-login-command.vala 2019-03-17 13:39:29.000000000 +0000 @@ -10,11 +10,11 @@ public class Geary.Imap.LoginCommand : Command { public const string NAME = "login"; - + public LoginCommand(string user, string pass) { base (NAME, { user, pass }); } - + public override string to_string() { return "%s %s ".printf(tag.to_string(), name); } diff -Nru geary-0.12.4/src/engine/imap/command/imap-logout-command.vala geary-3.32.0/src/engine/imap/command/imap-logout-command.vala --- geary-0.12.4/src/engine/imap/command/imap-logout-command.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/command/imap-logout-command.vala 2019-03-17 13:39:29.000000000 +0000 @@ -10,7 +10,7 @@ public class Geary.Imap.LogoutCommand : Command { public const string NAME = "logout"; - + public LogoutCommand() { base (NAME); } diff -Nru geary-0.12.4/src/engine/imap/command/imap-message-set.vala geary-3.32.0/src/engine/imap/command/imap-message-set.vala --- geary-0.12.4/src/engine/imap/command/imap-message-set.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/command/imap-message-set.vala 2019-03-17 13:39:29.000000000 +0000 @@ -19,97 +19,97 @@ // is set to keep max. command length somewhere under 1K (including tag, command, parameters, // etc.) private const int MAX_SPARSE_VALUES_PER_SET = 50; - + private delegate void ParserCallback(int64 value) throws ImapError; - + /** * True if the {@link MessageSet} was created with a UID or a UID range. * * For {@link Command}s that accept MessageSets, they will use a UID variant */ public bool is_uid { get; private set; default = false; } - + private string value { get; private set; } - + public MessageSet(SequenceNumber seq_num) { assert(seq_num.value > 0); - + value = seq_num.serialize(); } - + public MessageSet.uid(UID uid) { assert(uid.value > 0); - + value = uid.serialize(); is_uid = true; } - + public MessageSet.range_by_count(SequenceNumber low_seq_num, int count) { assert(low_seq_num.value > 0); assert(count > 0); - + value = (count > 1) ? "%s:%s".printf(low_seq_num.value.to_string(), (low_seq_num.value + count - 1).to_string()) : low_seq_num.serialize(); } - + public MessageSet.range_by_first_last(SequenceNumber low_seq_num, SequenceNumber high_seq_num) { assert(low_seq_num.value > 0); assert(high_seq_num.value > 0); - + // correct range problems (i.e. last before first) if (low_seq_num.value > high_seq_num.value) { SequenceNumber swap = low_seq_num; low_seq_num = high_seq_num; high_seq_num = swap; } - + value = (!low_seq_num.equal_to(high_seq_num)) ? "%s:%s".printf(low_seq_num.serialize(), high_seq_num.serialize()) : low_seq_num.serialize(); } - + public MessageSet.uid_range(UID low, UID high) { assert(low.value > 0); assert(high.value > 0); - + // correct ordering if (low.value > high.value) { UID swap = low; low = high; high = swap; } - + if (low.equal_to(high)) value = low.serialize(); else value = "%s:%s".printf(low.serialize(), high.serialize()); - + is_uid = true; } - + public MessageSet.range_to_highest(SequenceNumber low_seq_num) { assert(low_seq_num.value > 0); - + value = "%s:*".printf(low_seq_num.serialize()); } - + public MessageSet.uid_range_to_highest(UID low) { assert(low.value > 0); - + value = "%s:*".printf(low.serialize()); is_uid = true; } - + public MessageSet.custom(string custom) { value = custom; } - + public MessageSet.uid_custom(string custom) { value = custom; is_uid = true; } - + /** * Parses a string representing a {@link MessageSet} into a List of {@link SequenceNumber}s. * @@ -122,10 +122,10 @@ public static Gee.List? parse(string str) throws ImapError { Gee.List seq_nums = new Gee.ArrayList(); parse_string(str, (value) => { seq_nums.add(new SequenceNumber.checked(value)); }); - + return seq_nums.size > 0 ? seq_nums : null; } - + /** * Parses a string representing a {@link MessageSet} into a List of {@link UID}s. * @@ -145,69 +145,69 @@ public static Gee.List? uid_parse(string str) throws ImapError { Gee.List uids = new Gee.ArrayList(); parse_string(str, (value) => { uids.add(new UID.checked(value)); }); - + return uids.size > 0 ? uids : null; } - + private static void parse_string(string str, ParserCallback cb) throws ImapError { StringBuilder acc = new StringBuilder(); int64 start_range = -1; bool in_range = false; - + unichar ch; int index = 0; while (str.get_next_char(ref index, out ch)) { // if number, add to accumulator if (ch.isdigit()) { acc.append_unichar(ch); - + continue; } - + // look for special characters and deal with them switch (ch) { case ':': // range separator if (in_range) throw new ImapError.INVALID("Bad range specifier in message set \"%s\"", str); - + in_range = true; - + // store current accumulated value as start of range start_range = int64.parse(acc.str); acc = new StringBuilder(); break; - + case ',': // number separator - + // if in range, treat as end-of-range if (in_range) { // don't be forgiving here if (String.is_empty(acc.str)) throw new ImapError.INVALID("Bad range specifier in message set \"%s\"", str); - + process_range(start_range, int64.parse(acc.str), cb); in_range = false; } else { // Be forgiving here if (String.is_empty(acc.str)) continue; - + cb(int64.parse(acc.str)); } - + // reset accumulator acc = new StringBuilder(); break; - + default: // unknown character, treat with great violence throw new ImapError.INVALID("Bad character '%s' in message set \"%s\"", ch.to_string(), str); } } - + // report last bit remaining in accumulator if (!String.is_empty(acc.str)) { if (in_range) @@ -218,13 +218,13 @@ throw new ImapError.INVALID("Incomplete range specifier in message set \"%s\"", str); } } - + private static void process_range(int64 start, int64 end, ParserCallback cb) throws ImapError { int64 count_by = (start <= end) ? 1 : -1; for (int64 ctr = start; ctr != end + count_by; ctr += count_by) cb(ctr); } - + /** * Convert a collection of {@link SequenceNumber}s into a list of {@link MessageSet}s. * @@ -234,7 +234,7 @@ public static Gee.List sparse(Gee.Collection seq_nums) { return build_sparse_sets(seq_array_to_int64(seq_nums), false); } - + /** * Convert a collection of {@link UID}s into a list of {@link MessageSet}s. * @@ -244,48 +244,48 @@ public static Gee.List uid_sparse(Gee.Collection msg_uids) { return build_sparse_sets(uid_array_to_int64(msg_uids), true); } - + // create zero or more MessageSets of no more than MAX_SPARSE_VALUES_PER_SET UIDs/sequence // numbers private static Gee.List build_sparse_sets(int64[] sorted, bool is_uid) { Gee.List list = new Gee.ArrayList(); - + int start = 0; for (;;) { if (start >= sorted.length) break; - + int end = (start + MAX_SPARSE_VALUES_PER_SET).clamp(0, sorted.length); unowned int64[] slice = sorted[start:end]; - + string sparse_range = build_sparse_range(slice); list.add(is_uid ? new MessageSet.uid_custom(sparse_range) : new MessageSet.custom(sparse_range)); - + start = end; } - + return list; } - + // Builds sparse range of either UID values or sequence numbers. Values should be sorted before // calling to maximum finding runs. private static string build_sparse_range(int64[] seq_nums) { assert(seq_nums.length > 0); - + int64 start_of_span = -1; int64 last_seq_num = -1; int span_count = 0; StringBuilder builder = new StringBuilder(); foreach (int64 seq_num in seq_nums) { assert(seq_num >= 0); - + // the first number is automatically the start of a span, although it may be a span of one // (start_of_span < 0 should only happen on first iteration; can't easily break out of // loop because foreach/Iterator would still require a special case to skip it) if (start_of_span < 0) { // start of first span builder.append(seq_num.to_string()); - + start_of_span = seq_num; span_count = 1; } else if ((start_of_span + span_count) == seq_num) { @@ -293,7 +293,7 @@ span_count++; } else { assert(span_count >= 1); - + // span ends, another begins if (span_count == 1) { builder.append_printf(",%s", seq_num.to_string()); @@ -304,56 +304,56 @@ builder.append_printf(":%s,%s", (start_of_span + span_count - 1).to_string(), seq_num.to_string()); } - + start_of_span = seq_num; span_count = 1; } - + last_seq_num = seq_num; } - + // there should always be one seq_num in sorted, so the loop should exit with some state assert(start_of_span >= 0); assert(span_count > 0); assert(last_seq_num >= 0); - + // look for open-ended span if (span_count == 2) builder.append_printf(",%s", last_seq_num.to_string()); else if (last_seq_num != start_of_span) builder.append_printf(":%s", last_seq_num.to_string()); - + return builder.str; } - + private static int64[] seq_array_to_int64(Gee.Collection seq_nums) { // guarantee sorted (to maximum finding runs in build_sparse_range()) Gee.TreeSet sorted = new Gee.TreeSet(); sorted.add_all(seq_nums); - + // build sorted array int64[] ret = new int64[sorted.size]; int index = 0; foreach (SequenceNumber seq_num in sorted) ret[index++] = (int64) seq_num.value; - + return ret; } - + private static int64[] uid_array_to_int64(Gee.Collection msg_uids) { // guarantee sorted (to maximize finding runs in build_sparse_range()) Gee.TreeSet sorted = new Gee.TreeSet(); sorted.add_all(msg_uids); - + // build sorted array int64[] ret = new int64[sorted.size]; int index = 0; foreach (UID uid in sorted) ret[index++] = uid.value; - + return ret; } - + /** * Returns the {@link MessageSet} as a {@link Parameter} suitable for inclusion in a * {@link Command}. @@ -363,14 +363,14 @@ // be a Gmailism...) return new UnquotedStringParameter(value); } - + /** * Returns the {@link MessageSet} in a Gee.List. */ public Gee.List to_list() { return iterate(this).to_array_list(); } - + public string to_string() { return "%s::%s".printf(is_uid ? "UID" : "pos", value); } diff -Nru geary-0.12.4/src/engine/imap/command/imap-namespace-command.vala geary-3.32.0/src/engine/imap/command/imap-namespace-command.vala --- geary-0.12.4/src/engine/imap/command/imap-namespace-command.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/src/engine/imap/command/imap-namespace-command.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,24 @@ +/* + * Copyright 2016 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +/** + * The RFC 2342 NAMESPACE command. + * + * Determines the mailbox name prefix and hierarchy delimiter for the + * personal, other user's and public namespaces. + * + * See [[https://tools.ietf.org/html/rfc2342|RFC 2342]] for details. + */ +public class Geary.Imap.NamespaceCommand : Command { + + public const string NAME = "NAMESPACE"; + + public NamespaceCommand() { + base(NAME); + } + +} diff -Nru geary-0.12.4/src/engine/imap/command/imap-noop-command.vala geary-3.32.0/src/engine/imap/command/imap-noop-command.vala --- geary-0.12.4/src/engine/imap/command/imap-noop-command.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/command/imap-noop-command.vala 2019-03-17 13:39:29.000000000 +0000 @@ -12,7 +12,7 @@ public class Geary.Imap.NoopCommand : Command { public const string NAME = "noop"; - + public NoopCommand() { base (NAME); } diff -Nru geary-0.12.4/src/engine/imap/command/imap-search-command.vala geary-3.32.0/src/engine/imap/command/imap-search-command.vala --- geary-0.12.4/src/engine/imap/command/imap-search-command.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/command/imap-search-command.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,4 +1,5 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. @@ -9,25 +10,27 @@ * * See [[http://tools.ietf.org/html/rfc3501#section-6.4.4]]. */ - public class Geary.Imap.SearchCommand : Command { + public const string NAME = "search"; public const string UID_NAME = "uid search"; - + public SearchCommand(SearchCriteria criteria) { - base (NAME); - - // append rather than add the criteria, so the top-level criterion appear in the top-level - // list and not as a child list - append(criteria); + base(NAME); + + // Extend rather than append the criteria, so the top-level + // criterion appear in the top-level list and not as a child + // list + this.args.extend(criteria); } - + public SearchCommand.uid(SearchCriteria criteria) { - base (UID_NAME); - - // append rather than add the criteria, so the top-level criterion appear in the top-level - // list and not as a child list - append(criteria); + base(UID_NAME); + + // Extend rather than append the criteria, so the top-level + // criterion appear in the top-level list and not as a child + // list + this.args.extend(criteria); } -} +} diff -Nru geary-0.12.4/src/engine/imap/command/imap-search-criteria.vala geary-3.32.0/src/engine/imap/command/imap-search-criteria.vala --- geary-0.12.4/src/engine/imap/command/imap-search-criteria.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/command/imap-search-criteria.vala 2019-03-17 13:39:29.000000000 +0000 @@ -27,7 +27,7 @@ if (first != null) add_all(first.to_parameters()); } - + /** * Clears the {@link SearchCriteria} and sets the supplied {@link SearchCriterion} to the first * in the list. @@ -37,10 +37,10 @@ public unowned SearchCriteria is_(SearchCriterion first) { clear(); add_all(first.to_parameters()); - + return this; } - + /** * AND another {@link SearchCriterion} to the {@link SearchCriteria}. * @@ -48,10 +48,10 @@ */ public unowned SearchCriteria and(SearchCriterion next) { add_all(next.to_parameters()); - + return this; } - + /** * OR two {@link SearchCriterion}s to the {@link SearchCriteria}. * @@ -59,10 +59,10 @@ */ public unowned SearchCriteria or(SearchCriterion a, SearchCriterion b) { add_all(SearchCriterion.or(a, b).to_parameters()); - + return this; } - + /** * NOT another {@link SearchCriterion} to the {@link SearchCriteria}. * @@ -70,7 +70,7 @@ */ public unowned SearchCriteria not(SearchCriterion next) { add_all(SearchCriterion.not(next).to_parameters()); - + return this; } } diff -Nru geary-0.12.4/src/engine/imap/command/imap-search-criterion.vala geary-3.32.0/src/engine/imap/command/imap-search-criterion.vala --- geary-0.12.4/src/engine/imap/command/imap-search-criterion.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/command/imap-search-criterion.vala 2019-03-17 13:39:29.000000000 +0000 @@ -16,7 +16,7 @@ public class Geary.Imap.SearchCriterion : BaseObject { private Gee.List parameters = new Gee.ArrayList(); - + /** * Create a single simple criterion for the {@link SearchCommand}. */ @@ -24,14 +24,14 @@ if (parameter != null) parameters.add(parameter); } - + /** * Creates a simple search criterion. */ public SearchCriterion.simple(string name) { parameters.add(prep_name(name)); } - + /** * Create a single criterion with a simple name and custom value. */ @@ -39,7 +39,7 @@ parameters.add(prep_name(name)); parameters.add(value); } - + /** * Create a single criterion with a simple name and custom value. */ @@ -47,58 +47,58 @@ parameters.add(prep_name(name)); parameters.add(Parameter.get_for_string(value)); } - + private static Parameter prep_name(string name) { Parameter? namep = StringParameter.try_get_best_for(name); if (namep == null) { warning("Using a search name that requires a literal parameter: %s", name); namep = new LiteralParameter(new Memory.StringBuffer(name)); } - + return namep; } - + /** * The IMAP SEARCH ALL criterion. */ public static SearchCriterion all() { return new SearchCriterion.simple("all"); } - + /** * The IMAP SEARCH OR criterion, which operates on other {@link SearchCriterion}. */ public static SearchCriterion or(SearchCriterion a, SearchCriterion b) { SearchCriterion criterion = new SearchCriterion.simple("or"); - + // add each set of Parameters as lists (which are AND-ed) criterion.parameters.add(a.to_list_parameter()); criterion.parameters.add(b.to_list_parameter()); - + return criterion; } - + /** * The IMAP SEARCH NOT criterion. */ public static SearchCriterion not(SearchCriterion a) { return new SearchCriterion.parameter_value("not", a.to_list_parameter()); } - + /** * The IMAP SEARCH NEW criterion. */ public static SearchCriterion new_messages() { return new SearchCriterion.simple("new"); } - + /** * The IMAP SEARCH OLD criterion. */ public static SearchCriterion old_messages() { return new SearchCriterion.simple("old"); } - + /** * The IMAP SEARCH KEYWORD criterion, or if the {@link MessageFlag} has a macro, that value. */ @@ -106,10 +106,10 @@ string? keyword = flag.get_search_keyword(true); if (keyword != null) return new SearchCriterion.simple(keyword); - + return new SearchCriterion.parameter_value("keyword", flag.to_parameter()); } - + /** * The IMAP SEARCH UNKEYWORD criterion, or if the {@link MessageFlag} has a macro, that value. */ @@ -117,59 +117,59 @@ string? keyword = flag.get_search_keyword(false); if (keyword != null) return new SearchCriterion.simple(keyword); - + return new SearchCriterion.parameter_value("unkeyword", flag.to_parameter()); } - + /** * The IMAP SEARCH BEFORE criterion. */ public static SearchCriterion before_internaldate(InternalDate internaldate) { return new SearchCriterion.parameter_value("before", internaldate.to_search_parameter()); } - + /** * The IMAP SEARCH ON criterion. */ public static SearchCriterion on_internaldate(InternalDate internaldate) { return new SearchCriterion.parameter_value("on", internaldate.to_search_parameter()); } - + /** * The IMAP SEARCH SINCE criterion. */ public static SearchCriterion since_internaldate(InternalDate internaldate) { return new SearchCriterion.parameter_value("since", internaldate.to_search_parameter()); } - + /** * The IMAP SEARCH BODY criterion, which searches the body for the string. */ public static SearchCriterion body(string value) { return new SearchCriterion.string_value("body", value); } - + /** * The IMAP SEARCH TEXT criterion, which searches the header and body for the string. */ public static SearchCriterion text(string value) { return new SearchCriterion.string_value("text", value); } - + /** * The IMAP SEARCH SMALLER criterion. */ public static SearchCriterion smaller(uint32 value) { return new SearchCriterion.parameter_value("smaller", new NumberParameter.uint32(value)); } - + /** * The IMAP SEARCH LARGER criterion. */ public static SearchCriterion larger(uint32 value) { return new SearchCriterion.parameter_value("larger", new NumberParameter.uint32(value)); } - + /** * Specifies messages (by sequence number or UID) to limit the IMAP SEARCH to. */ @@ -177,7 +177,7 @@ return msg_set.is_uid ? new SearchCriterion.parameter_value("uid", msg_set.to_parameter()) : new SearchCriterion(msg_set.to_parameter()); } - + /** * Returns the {@link SearchCriterion} as one or more IMAP {@link Parameter}s. * @@ -189,7 +189,7 @@ public Gee.List to_parameters() { return parameters; } - + /** * Return {@link Parameter}s as a {@link ListParameter} if multiple, a single Parameter * otherwise. @@ -197,13 +197,13 @@ public Parameter to_list_parameter() { if (parameters.size == 1) return parameters[0]; - + ListParameter listp = new ListParameter(); listp.add_all(parameters); - + return listp; } - + public string to_string() { return to_list_parameter().to_string(); } diff -Nru geary-0.12.4/src/engine/imap/command/imap-select-command.vala geary-3.32.0/src/engine/imap/command/imap-select-command.vala --- geary-0.12.4/src/engine/imap/command/imap-select-command.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/command/imap-select-command.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,4 +1,5 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. @@ -9,18 +10,16 @@ * * @see ExamineCommand */ - public class Geary.Imap.SelectCommand : Command { + public const string NAME = "select"; - + public MailboxSpecifier mailbox { get; private set; } - + public SelectCommand(MailboxSpecifier mailbox) { - base (NAME); - + base(NAME); this.mailbox = mailbox; - - add(mailbox.to_parameter()); + this.args.add(mailbox.to_parameter()); } -} +} diff -Nru geary-0.12.4/src/engine/imap/command/imap-starttls-command.vala geary-3.32.0/src/engine/imap/command/imap-starttls-command.vala --- geary-0.12.4/src/engine/imap/command/imap-starttls-command.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/command/imap-starttls-command.vala 2019-03-17 13:39:29.000000000 +0000 @@ -9,8 +9,8 @@ */ public class Geary.Imap.StarttlsCommand : Command { - public const string NAME = "starttls"; - + public const string NAME = "STARTTLS"; + public StarttlsCommand() { base (NAME); } diff -Nru geary-0.12.4/src/engine/imap/command/imap-status-command.vala geary-3.32.0/src/engine/imap/command/imap-status-command.vala --- geary-0.12.4/src/engine/imap/command/imap-status-command.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/command/imap-status-command.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,29 +1,34 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ /** + * The IMAP STATUS command. + * * See [[http://tools.ietf.org/html/rfc3501#section-6.3.10]] * * @see StatusData */ - public class Geary.Imap.StatusCommand : Command { - public const string NAME = "status"; - + + + public const string NAME = "STATUS"; + + public StatusCommand(MailboxSpecifier mailbox, StatusDataType[] data_items) { base (NAME); - - add(mailbox.to_parameter()); - + + this.args.add(mailbox.to_parameter()); + assert(data_items.length > 0); ListParameter data_item_list = new ListParameter(); foreach (StatusDataType data_item in data_items) data_item_list.add(data_item.to_parameter()); - - add(data_item_list); + + this.args.add(data_item_list); } -} +} diff -Nru geary-0.12.4/src/engine/imap/command/imap-store-command.vala geary-3.32.0/src/engine/imap/command/imap-store-command.vala --- geary-0.12.4/src/engine/imap/command/imap-store-command.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/command/imap-store-command.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,4 +1,5 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. @@ -10,11 +11,11 @@ * @see FetchCommand * @see FetchedData */ - public class Geary.Imap.StoreCommand : Command { + public const string NAME = "store"; public const string UID_NAME = "uid store"; - + /** * Options indicating functionality of the {@link StoreCommand}. * @@ -28,21 +29,22 @@ ADD_FLAGS, SILENT } - + public StoreCommand(MessageSet message_set, Gee.List flag_list, Option options) { base (message_set.is_uid ? UID_NAME : NAME); - + bool add_flag = (options & Option.ADD_FLAGS) != 0; bool silent = (options & Option.SILENT) != 0; - - add(message_set.to_parameter()); - add(new AtomParameter("%sflags%s".printf(add_flag ? "+" : "-", silent ? ".silent" : ""))); - + + this.args.add(message_set.to_parameter()); + this.args.add(new AtomParameter("%sflags%s".printf(add_flag ? "+" : "-", silent ? ".silent" : ""))); + ListParameter list = new ListParameter(); - foreach(MessageFlag flag in flag_list) + foreach(MessageFlag flag in flag_list) { list.add(new AtomParameter(flag.value)); - - add(list); + } + + this.args.add(list); } -} +} diff -Nru geary-0.12.4/src/engine/imap/imap.vala geary-3.32.0/src/engine/imap/imap.vala --- geary-0.12.4/src/engine/imap/imap.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/imap.vala 2019-03-17 13:39:29.000000000 +0000 @@ -11,7 +11,7 @@ internal void init() { if (init_count++ != 0) return; - + MessageFlag.init(); MailboxAttribute.init(); Tag.init(); diff -Nru geary-0.12.4/src/engine/imap/message/imap-data-format.vala geary-3.32.0/src/engine/imap/message/imap-data-format.vala --- geary-0.12.4/src/engine/imap/message/imap-data-format.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/message/imap-data-format.vala 2019-03-17 13:39:29.000000000 +0000 @@ -61,75 +61,31 @@ public Quoting is_quoting_required(string str) { if (String.is_empty(str)) return Quoting.REQUIRED; - + int index = 0; for (;;) { char ch = str[index++]; if (ch == String.EOS) break; - + if (ch > 0x7F) return Quoting.UNALLOWED; - + switch (ch) { case '\n': case '\r': return Quoting.UNALLOWED; - + default: if (is_atom_special(ch)) return Quoting.REQUIRED; break; } } - + return Quoting.OPTIONAL; } -/** - * Converts the supplied string to a quoted string and returns whether or not the quoted format - * is required on the wire. If Quoting.UNALLOWED is returned, the only way to represent the string - * is with a literal. - */ -public Quoting convert_to_quoted(string str, out string quoted) { - Quoting requirement = String.is_empty(str) ? Quoting.REQUIRED : Quoting.OPTIONAL; - quoted = ""; - - StringBuilder builder = new StringBuilder("\""); - int index = 0; - for (;;) { - char ch = str[index++]; - if (ch == String.EOS) - break; - - if (ch > 0x7F) - return Quoting.UNALLOWED; - - switch (ch) { - case '\n': - case '\r': - return Quoting.UNALLOWED; - - case '"': - case '\\': - requirement = Quoting.REQUIRED; - builder.append_c('\\'); - builder.append_c(ch); - break; - - default: - if (is_atom_special(ch)) - requirement = Quoting.REQUIRED; - - builder.append_c(ch); - break; - } - } - - quoted = builder.append_c('"').str; - - return requirement; -} } diff -Nru geary-0.12.4/src/engine/imap/message/imap-fetch-body-data-specifier.vala geary-3.32.0/src/engine/imap/message/imap-fetch-body-data-specifier.vala --- geary-0.12.4/src/engine/imap/message/imap-fetch-body-data-specifier.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/message/imap-fetch-body-data-specifier.vala 2019-03-17 13:39:29.000000000 +0000 @@ -48,62 +48,62 @@ HEADER_FIELDS_NOT, MIME, TEXT; - + public string serialize() { switch (this) { case NONE: return ""; - + case HEADER: return "header"; - + case HEADER_FIELDS: return "header.fields"; - + case HEADER_FIELDS_NOT: return "header.fields.not"; - + case MIME: return "mime"; - + case TEXT: return "text"; - + default: assert_not_reached(); } } - + public static SectionPart deserialize(string value) throws ImapError { if (String.is_empty(value)) return NONE; - + switch (Ascii.strdown(value)) { case "header": return HEADER; - + case "header.fields": return HEADER_FIELDS; - + case "header.fields.not": return HEADER_FIELDS_NOT; - + case "mime": return MIME; - + case "text": return TEXT; - + default: throw new ImapError.PARSE_ERROR("Invalid SectionPart name \"%s\"", value); } } - + public string to_string() { return serialize(); } } - + /** * The {@link SectionPart} for this FETCH BODY specifier. * @@ -111,7 +111,7 @@ * well in the future, if necessary. */ public SectionPart section_part { get; private set; } - + /** * When false, indicates that the FETCH BODY specifier is using a hack to operate with * non-conformant servers. @@ -122,14 +122,14 @@ * @see omit_request_header_fields_space */ public bool request_header_fields_space { get; private set; default = true; } - + private int[]? part_number; private int subset_start; private int subset_count; private Gee.TreeSet? field_names; private bool is_peek; private string hashable; - + /** * Create a FetchBodyDataType with the various required and optional parameters specified. * @@ -143,7 +143,7 @@ int subset_count, string[]? field_names) { init(section_part, part_number, subset_start, subset_count, field_names, false, false); } - + /** * Like FetchBodyDataType, but the /Seen flag will not be set when used on a message. */ @@ -151,12 +151,12 @@ int subset_count, string[]? field_names) { init(section_part, part_number, subset_start, subset_count, field_names, true, false); } - + public FetchBodyDataSpecifier.response(SectionPart section_part, int[]? part_number, int subset_start, string[]? field_names) { init(section_part, part_number, subset_start, -1, field_names, false, true); } - + private void init(SectionPart section_part, int[]? part_number, int subset_start, int subset_count, string[]? field_names, bool is_peek, bool is_response) { switch (section_part) { @@ -164,37 +164,37 @@ case SectionPart.HEADER_FIELDS_NOT: assert(field_names != null && field_names.length > 0); break; - + default: assert(field_names == null); break; } - + if (subset_start >= 0 && !is_response) assert(subset_count > 0); - + this.section_part = section_part; this.part_number = part_number; this.subset_start = subset_start; this.subset_count = subset_count; this.is_peek = is_peek; - + if (field_names != null && field_names.length > 0) { this.field_names = new Gee.TreeSet(Ascii.strcmp); foreach (string field_name in field_names) { string converted = Ascii.strdown(field_name.strip()); - + if (!String.is_empty(converted)) this.field_names.add(converted); } } else { this.field_names = null; } - + // see equal_to() for why the response version is used hashable = serialize_response(); } - + /** * Returns the {@link FetchBodyDataSpecifier} in a string ready for a {@link Command}. * @@ -208,7 +208,7 @@ serialize_field_names(), serialize_subset(true)); } - + /** * Returns the {@link FetchBodyDataSpecifier} in a string as it might appear in a * {@link ServerResponse}. @@ -228,34 +228,34 @@ serialize_field_names(), serialize_subset(false)); } - + public Parameter to_request_parameter() { return new AtomParameter(serialize_request()); } - + private string serialize_part_number() { if (part_number == null || part_number.length == 0) return ""; - + StringBuilder builder = new StringBuilder(); foreach (int part in part_number) { if (builder.len > 0) builder.append_c('.'); - + builder.append_printf("%d", part); } - + // if there's a SectionPart that follows, append a period as a separator if (section_part != SectionPart.NONE) builder.append_c('.'); - + return builder.str; } - + private string serialize_field_names() { if (field_names == null || field_names.size == 0) return ""; - + // note that the leading space is supplied here StringBuilder builder = new StringBuilder(request_header_fields_space ? " (" : "("); Gee.Iterator iter = field_names.iterator(); @@ -265,10 +265,10 @@ builder.append_c(' '); } builder.append_c(')'); - + return builder.str; } - + // See note at serialize_response for reason is_request is necessary. // Note that this could've been formed with the .response() ctor, which doesn't pass along // subset_count (because it's unknown), so this simply uses subset_start in that case @@ -278,7 +278,7 @@ else return (subset_start < 0) ? "" : "<%d>".printf(subset_start); } - + /** * Returns true if the {@link StringParameter} is formatted like a * {@link FetchBodyDataSpecifier}. @@ -291,10 +291,10 @@ */ public static bool is_fetch_body_data_specifier(StringParameter stringp) { string strd = stringp.as_lower().strip(); - + return strd.has_prefix("body[") || strd.has_prefix("body.peek["); } - + /** * Attempts to convert a {@link StringParameter} into a {@link FetchBodyDataSpecifier}. * @@ -311,7 +311,7 @@ // * Remove quoting (some servers return field names quoted, some don't, Geary never uses them // when requesting) string strd = stringp.as_lower().replace("\"", "").strip(); - + // Convert full form into two sections: "body[SECTION_STRING]" // ^^^^^^^^^^^^^^ optional // response never contains ".peek", even if specified in request @@ -326,17 +326,17 @@ section_string = (string) section_chars; octet_string = null; break; - + case 2: section_string = (string) section_chars; octet_string = (string) octet_chars; break; - + default: throw new ImapError.PARSE_ERROR("%s is not a FETCH body data type %d", stringp.to_string(), count); } - + // convert section string into its parts: // "PART_STRING (FIELDS_STRING)" // ^^^^^^^^^^^^^^^^ optional @@ -347,14 +347,14 @@ if (section_string.contains("(")) { if (section_string.scanf("%[^(](%[^)])", part_chars, fields_chars) != 2) throw new ImapError.PARSE_ERROR("%s: malformed part/header names", stringp.to_string()); - + part_string = (string) part_chars; fields_string = (string?) fields_chars; } else { part_string = section_string; fields_string = null; } - + // convert part_string into its part number and section part name // "#.#.#.SECTION_PART" // ^^^^^^ optional @@ -365,13 +365,13 @@ bool no_more = false; for (int ctr = 0; ctr < part_number_tokens.length; ctr++) { string token = part_number_tokens[ctr]; - + // stop treating as numbers when non-digit found (SectionParts contain periods // too and must be preserved); if (!no_more && Ascii.is_numeric(token)) { if (part_number == null) part_number = new int[0]; - + part_number += int.parse(token); } else { no_more = true; @@ -383,9 +383,9 @@ } else { section_part_builder.append(part_string); } - + SectionPart section_part = SectionPart.deserialize(section_part_builder.str.strip()); - + // Convert optional fields_string into an array of field names string[]? field_names = null; if (fields_string != null) { @@ -393,7 +393,7 @@ if (field_names.length == 0) field_names = null; } - + // If octet_string found, trim surrounding brackets and convert to integer // The span in the returned response is merely the offset ("1.15" becomes "1") because the // associated literal data specifies its own length) @@ -403,17 +403,17 @@ throw new ImapError.PARSE_ERROR("Improperly formed octet \"%s\" in %s", octet_string, stringp.to_string()); } - + if (subset_start < 0) { throw new ImapError.PARSE_ERROR("Invalid octet count %d in %s", subset_start, stringp.to_string()); } } - + return new FetchBodyDataSpecifier.response(section_part, part_number, subset_start, field_names); } - + /** * Omit the space between "header.fields" and the list of email headers. * @@ -432,7 +432,7 @@ public void omit_request_header_fields_space() { request_header_fields_space = false; } - + /** * {@link FetchBodyDataSpecifier}s are considered equal if they're serialized responses are * equal. @@ -445,14 +445,14 @@ public bool equal_to(FetchBodyDataSpecifier other) { if (this == other) return true; - + return hashable == other.hashable; } - + public uint hash() { return str_hash(hashable); } - + // Return serialize_request() because it's a more fuller representation than what the server returns public string to_string() { return serialize_request(); diff -Nru geary-0.12.4/src/engine/imap/message/imap-fetch-data-specifier.vala geary-3.32.0/src/engine/imap/message/imap-fetch-data-specifier.vala --- geary-0.12.4/src/engine/imap/message/imap-fetch-data-specifier.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/message/imap-fetch-data-specifier.vala 2019-03-17 13:39:29.000000000 +0000 @@ -30,53 +30,53 @@ FAST, ALL, FULL; - + public string to_string() { switch (this) { case UID: return "uid"; - + case FLAGS: return "flags"; - + case INTERNALDATE: return "internaldate"; - + case ENVELOPE: return "envelope"; - + case BODYSTRUCTURE: return "bodystructure"; - + case BODY: return "body"; - + case RFC822: return "rfc822"; - + case RFC822_HEADER: return "rfc822.header"; - + case RFC822_SIZE: return "rfc822.size"; - + case RFC822_TEXT: return "rfc822.text"; - + case FAST: return "fast"; - + case ALL: return "all"; - + case FULL: return "full"; - + default: assert_not_reached(); } } - + /** * Decodes a {@link StringParameter} into a {@link FetchDataSpecifier}. */ @@ -84,56 +84,56 @@ switch (strparam.as_lower()) { case "uid": return UID; - + case "flags": return FLAGS; - + case "internaldate": return INTERNALDATE; - + case "envelope": return ENVELOPE; - + case "bodystructure": return BODYSTRUCTURE; - + case "body": return BODY; - + case "rfc822": return RFC822; - + case "rfc822.header": return RFC822_HEADER; - + case "rfc822.size": return RFC822_SIZE; - + case "rfc822.text": return RFC822_TEXT; - + case "fast": return FAST; - + case "all": return ALL; - + case "full": return FULL; - + default: throw new ImapError.PARSE_ERROR("\"%s\" is not a valid fetch-command data item", strparam.to_string()); } } - + /** * Turns this {@link FetchDataSpecifier} into a {@link StringParameter} for transmission. */ public StringParameter to_parameter() { return new AtomParameter(to_string()); } - + /** * Returns the appropriate {@link FetchDataDecoder} for this {@link FetchDataSpecifier}. * @@ -146,28 +146,28 @@ switch (this) { case UID: return new UIDDecoder(); - + case FLAGS: return new MessageFlagsDecoder(); - + case ENVELOPE: return new EnvelopeDecoder(); - + case INTERNALDATE: return new InternalDateDecoder(); - + case RFC822_SIZE: return new RFC822SizeDecoder(); - + case RFC822_HEADER: return new RFC822HeaderDecoder(); - + case RFC822_TEXT: return new RFC822TextDecoder(); - + case RFC822: return new RFC822FullDecoder(); - + default: return null; } diff -Nru geary-0.12.4/src/engine/imap/message/imap-flags.vala geary-3.32.0/src/engine/imap/message/imap-flags.vala --- geary-0.12.4/src/engine/imap/message/imap-flags.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/message/imap-flags.vala 2019-03-17 13:39:29.000000000 +0000 @@ -11,22 +11,22 @@ public abstract class Geary.Imap.Flags : Geary.MessageData.AbstractMessageData, Geary.Imap.MessageData, Gee.Hashable { public int size { get { return list.size; } } - + protected Gee.Set list; - - public Flags(Gee.Collection flags) { + + protected Flags(Gee.Collection flags) { list = new Gee.HashSet(); list.add_all(flags); } - + public bool contains(Flag flag) { return list.contains(flag); } - + public Gee.Set get_all() { return list.read_only_view; } - + /** * Returns the flags in serialized form, which is each flag separated by a space (legal in * IMAP, as flags must be atoms and atoms prohibit spaces). @@ -34,7 +34,7 @@ public virtual string serialize() { return to_string(); } - + /** * Returns a {@link ListParameter} representation of these flags. * @@ -50,32 +50,32 @@ message("Unable to parameterize flag \"%s\": %s", flag.to_string(), ierr.message); } } - + return listp; } - + public bool equal_to(Geary.Imap.Flags other) { if (this == other) return true; - + if (other.size != size) return false; - + return Geary.traverse(list).all(f => other.contains(f)); } - + public override string to_string() { StringBuilder builder = new StringBuilder(); foreach (Flag flag in list) { if (!String.is_empty(builder.str)) builder.append_c(' '); - + builder.append(flag.value); } - + return builder.str; } - + public uint hash() { return Ascii.stri_hash(to_string()); } diff -Nru geary-0.12.4/src/engine/imap/message/imap-flag.vala geary-3.32.0/src/engine/imap/message/imap-flag.vala --- geary-0.12.4/src/engine/imap/message/imap-flag.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/message/imap-flag.vala 2019-03-17 13:39:29.000000000 +0000 @@ -21,18 +21,18 @@ * * The given keyword must be an IMAP atom. */ - public Flag(string name) { + protected Flag(string name) { this.value = name; } public bool is_system() { return value[0] == '\\'; } - + public bool equals_string(string value) { return Ascii.stri_equal(this.value, value); } - + public bool equal_to(Geary.Imap.Flag flag) { return (flag == this) ? true : flag.equals_string(value); } @@ -47,7 +47,7 @@ public uint hash() { return Ascii.stri_hash(value); } - + public string to_string() { return value; } diff -Nru geary-0.12.4/src/engine/imap/message/imap-internal-date.vala geary-3.32.0/src/engine/imap/message/imap-internal-date.vala --- geary-0.12.4/src/engine/imap/message/imap-internal-date.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/message/imap-internal-date.vala 2019-03-17 13:39:29.000000000 +0000 @@ -22,30 +22,30 @@ private const string[] EN_US_MON = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; - + private const string[] EN_US_MON_DOWN = { "jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec" }; - + public DateTime value { get; private set; } public string? original { get; private set; default = null; } - + private InternalDate(string original, DateTime datetime) { this.original = original; value = datetime; } - + public InternalDate.from_date_time(DateTime datetime) throws ImapError { value = datetime; } - + public static InternalDate decode(string internaldate) throws ImapError { if (String.is_empty(internaldate)) throw new ImapError.PARSE_ERROR("Invalid INTERNALDATE: empty string"); - + if (internaldate.length > 64) throw new ImapError.PARSE_ERROR("Invalid INTERNALDATE: too long (%d)", internaldate.length); - + // Alas, GMime.utils_header_decode_date() is too forgiving for our needs, so do it manually int day, year, hour, min, sec; char mon[4] = { 0 }; @@ -54,7 +54,7 @@ out min, out sec, tz); if (count != 6 && count != 7) throw new ImapError.PARSE_ERROR("Invalid INTERNALDATE \"%s\": too few fields (%d)", internaldate, count); - + // check numerical ranges; this does not verify this is an actual date, DateTime will do // that (and round upward, which has to be accepted) if (!Numeric.int_in_range_inclusive(day, 1, 31) @@ -64,30 +64,30 @@ || year < 1970) { throw new ImapError.PARSE_ERROR("Invalid INTERNALDATE \"%s\": bad numerical range", internaldate); } - + // check month (this catches localization problems) int month = -1; string mon_down = Ascii.strdown(((string) mon)); for (int ctr = 0; ctr < EN_US_MON_DOWN.length; ctr++) { if (mon_down == EN_US_MON_DOWN[ctr]) { month = ctr; - + break; } } - + if (month < 0) throw new ImapError.PARSE_ERROR("Invalid INTERNALDATE \"%s\": bad month", internaldate); - + // TODO: verify timezone - + // if no timezone listed, ISO 8601 says to use local time TimeZone timezone = (tz[0] != '\0') ? new TimeZone((string) tz) : new TimeZone.local(); - + // assemble into DateTime, which validates the time as well (this is why we want to keep // original around, for other reasons) ... month is 1-based in DateTime DateTime datetime = new DateTime(timezone, year, month + 1, day, hour, min, sec); - + return new InternalDate(internaldate, datetime); } @@ -97,14 +97,14 @@ public time_t to_time_t () { return Time.datetime_to_time_t(this.value); } - + /** * Returns the {@link InternalDate} as a {@link Parameter}. */ public Parameter to_parameter() { return Parameter.get_for_string(serialize()); } - + /** * Returns the {@link InternalDate} as a {@link Parameter} for a {@link SearchCriterion}. * @@ -113,7 +113,7 @@ public Parameter to_search_parameter() { return Parameter.get_for_string(serialize_for_search()); } - + /** * Returns the {@link InternalDate}'s string representation. * @@ -122,7 +122,7 @@ public string serialize() { return original ?? value.format("%d-%%s-%Y %H:%M:%S %z").printf(get_en_us_mon()); } - + /** * Returns the {@link InternalDate}'s string representation for a SEARCH command. * @@ -135,7 +135,7 @@ public string serialize_for_search() { return value.format("%d-%%s-%Y").printf(get_en_us_mon()); } - + /** * Because IMAP's INTERNALDATE strings are ''never'' localized (as best as I can gather), so * need to use en_US appreviated month names, as that's the only value in INTERNALDATE that is @@ -144,22 +144,22 @@ private string get_en_us_mon() { // month is 1-based inside of DateTime int mon = (value.get_month() - 1).clamp(0, EN_US_MON.length - 1); - + return EN_US_MON[mon]; } - + public uint hash() { return value.hash(); } - + public bool equal_to(InternalDate other) { return value.equal(other.value); } - + public int compare_to(InternalDate other) { return value.compare(other.value); } - + public override string to_string() { return serialize(); } diff -Nru geary-0.12.4/src/engine/imap/message/imap-mailbox-specifier.vala geary-3.32.0/src/engine/imap/message/imap-mailbox-specifier.vala --- geary-0.12.4/src/engine/imap/message/imap-mailbox-specifier.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/message/imap-mailbox-specifier.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,4 +1,5 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. @@ -11,12 +12,16 @@ * * See [[http://tools.ietf.org/html/rfc3501#section-5.1]] */ - public class Geary.Imap.MailboxSpecifier : BaseObject, Gee.Hashable, Gee.Comparable { - // all references to Inbox are converted to this string, purely for sanity sake when dealing - // with Inbox's case issues + + /** + * Canonical name used for the IMAP Inbox for an account. + * + * All references to Inbox are converted to this string, purely + * for sanity sake when dealing with Inbox's case issues. + */ public const string CANONICAL_INBOX_NAME = "INBOX"; - + /** * An instance of an Inbox MailboxSpecifier. * @@ -29,12 +34,12 @@ return (_inbox != null) ? _inbox : _inbox = new MailboxSpecifier(CANONICAL_INBOX_NAME); } } - + /** * Decoded mailbox path name. */ public string name { get; private set; } - + /** * Indicates this is the {@link StatusData} for Inbox. * @@ -64,11 +69,12 @@ } catch (ConvertError err) { // Could no decode the name as IMAP modified UTF7, so per // https://imapwiki.org/ClientImplementation/MailboxList - // assume UTF8. + // assume UTF8 and repair to make sure it's valid at + // least. debug( "Error decoding mailbox name, assuming UTF-8: %s", err.message ); - name = param.ascii; + name = param.ascii.make_valid(); } init(name); @@ -78,14 +84,14 @@ * Returns true if the {@link Geary.FolderPath} points to the IMAP Inbox. */ public static bool folder_path_is_inbox(FolderPath path) { - return path.is_root() && is_inbox_name(path.basename); + return path.is_top_level && is_inbox_name(path.name); } - + /** * Returns true if the string is the name of the IMAP Inbox. * * This accounts for IMAP's Inbox name being case-insensitive. This is only for comparing - * folder basenames; this does not account for path delimiters. + * folder names; this does not account for path delimiters. * * See [[http://tools.ietf.org/html/rfc3501#section-5.1]] * @@ -94,7 +100,7 @@ public static bool is_inbox_name(string name) { return Ascii.stri_equal(name, CANONICAL_INBOX_NAME); } - + /** * Returns true if the string is the ''canonical'' name of the IMAP Inbox. * @@ -109,19 +115,55 @@ public static bool is_canonical_inbox_name(string name) { return Ascii.str_equal(name, CANONICAL_INBOX_NAME); } - + /** * Converts a generic {@link FolderPath} into an IMAP mailbox specifier. */ - public MailboxSpecifier.from_folder_path(FolderPath path, string delim) throws ImapError { - init(path.get_fullpath(delim)); + public MailboxSpecifier.from_folder_path(FolderPath path, + MailboxSpecifier inbox, + string? delim) + throws ImapError { + if (path.is_root) { + throw new ImapError.INVALID( + "Cannot convert root path into a mailbox" + ); + } + + string[] parts = path.as_array(); + if (parts.length > 1 && delim == null) { + throw new ImapError.NOT_SUPPORTED( + "Path has more than one part but no delimiter given" + ); + } + + if (String.is_empty_or_whitespace(parts[0])) { + throw new ImapError.NOT_SUPPORTED( + "Path contains empty base part: '%s'", path.to_string() + ); + } + + StringBuilder builder = new StringBuilder( + is_inbox_name(parts[0]) ? inbox.name : parts[0] + ); + + foreach (string name in parts[1:parts.length]) { + if (String.is_empty_or_whitespace(name)) { + throw new ImapError.NOT_SUPPORTED( + "Path contains empty part: '%s'", path.to_string() + ); + } + builder.append(delim); + builder.append(name); + } + + init(builder.str); } - + private void init(string decoded) { name = decoded; is_inbox = is_inbox_name(decoded); } - + /** * The mailbox's path as a list of strings. * @@ -129,8 +171,8 @@ * the name is returned as a single element. */ public Gee.List to_list(string? delim) { - Gee.List path = new Gee.ArrayList(); - + Gee.List path = new Gee.LinkedList(); + if (!String.is_empty(delim)) { string[] split = name.split(delim); foreach (string str in split) { @@ -138,39 +180,42 @@ path.add(str); } } - + if (path.size == 0) path.add(name); - + return path; } - + /** - * Converts the {@link MailboxSpecifier} into a {@link FolderPath}. + * Converts the mailbox into a folder path. * - * If the inbox_specifier is supplied, if the root element matches it, the canonical Inbox - * name is used in its place. This is useful for XLIST where that command returns a translated - * name but the standard IMAP name ("INBOX") must be used in addressing its children. - */ - public FolderPath to_folder_path(string? delim, MailboxSpecifier? inbox_specifier) { - // convert path to list of elements + * If the inbox_specifier is supplied and the first element + * matches it, the canonical Inbox name is used in its place. + * This is useful for XLIST where that command returns a + * translated name but the standard IMAP name ("INBOX") must be + * used in addressing its children. + */ + public FolderPath to_folder_path(FolderRoot root, + string? delim, + MailboxSpecifier? inbox_specifier) { Gee.List list = to_list(delim); - - // if root element is same as supplied inbox specifier, use canonical inbox name, otherwise - // keep - FolderPath path; - if (inbox_specifier != null && list[0] == inbox_specifier.name) - path = new Imap.FolderRoot(CANONICAL_INBOX_NAME); - else - path = new Imap.FolderRoot(list[0]); - - // walk down rest of elements adding as we go - for (int ctr = 1; ctr < list.size; ctr++) - path = path.get_child(list[ctr]); - + + // If the first element is same as supplied inbox specifier, + // use canonical inbox name, otherwise keep + FolderPath? path = ( + (inbox_specifier != null && list[0] == inbox_specifier.name) + ? root.get_child(CANONICAL_INBOX_NAME) + : root.get_child(list[0]) + ); + list.remove_at(0); + + foreach (string name in list) { + path = path.get_child(name); + } return path; } - + /** * The mailbox's name without parent folders. * @@ -180,13 +225,13 @@ public string get_basename(string? delim) { if (String.is_empty(delim)) return name; - + int index = name.last_index_of(delim); if (index < 0) return name; - + string basename = name.substring(index + 1); - + return !String.is_empty(basename) ? basename : name; } @@ -204,27 +249,27 @@ public uint hash() { return is_inbox ? Ascii.stri_hash(name) : Ascii.str_hash(name); } - + public bool equal_to(MailboxSpecifier other) { if (this == other) return true; - + if (is_inbox) return Ascii.stri_equal(name, other.name); - + return Ascii.str_equal(name, other.name); } - + public int compare_to(MailboxSpecifier other) { if (this == other) return 0; - + if (is_inbox && other.is_inbox) return 0; - + return Ascii.strcmp(name, other.name); } - + public string to_string() { return name; } diff -Nru geary-0.12.4/src/engine/imap/message/imap-message-flags.vala geary-3.32.0/src/engine/imap/message/imap-message-flags.vala --- geary-0.12.4/src/engine/imap/message/imap-message-flags.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/message/imap-message-flags.vala 2019-03-17 13:39:29.000000000 +0000 @@ -16,7 +16,7 @@ public MessageFlags(Gee.Collection flags) { base (flags); } - + /** * Create {@link MessageFlags} from a {@link ListParameter} of flag strings. */ @@ -24,30 +24,30 @@ Gee.Collection list = new Gee.ArrayList(); for (int ctr = 0; ctr < listp.size; ctr++) list.add(new MessageFlag(listp.get_as_string(ctr).ascii)); - + return new MessageFlags(list); } - + /** * Create {@link MessageFlags} from a flat string of space-delimited flags. */ public static MessageFlags deserialize(string? str) { if (String.is_empty(str)) return new MessageFlags(new Gee.ArrayList()); - + string[] tokens = str.split(" "); - + Gee.Collection flags = new Gee.ArrayList(); foreach (string token in tokens) flags.add(new MessageFlag(token)); - + return new MessageFlags(flags); } - + internal void add(MessageFlag flag) { list.add(flag); } - + internal void remove(MessageFlag flag) { list.remove(flag); } diff -Nru geary-0.12.4/src/engine/imap/message/imap-message-flag.vala geary-3.32.0/src/engine/imap/message/imap-message-flag.vala --- geary-0.12.4/src/engine/imap/message/imap-message-flag.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/message/imap-message-flag.vala 2019-03-17 13:39:29.000000000 +0000 @@ -19,73 +19,73 @@ public static MessageFlag ANSWERED { get { if (_answered == null) _answered = new MessageFlag("\\answered"); - + return _answered; } } - + private static MessageFlag? _deleted = null; public static MessageFlag DELETED { get { if (_deleted == null) _deleted = new MessageFlag("\\deleted"); - + return _deleted; } } - + private static MessageFlag? _draft = null; public static MessageFlag DRAFT { get { if (_draft == null) _draft = new MessageFlag("\\draft"); - + return _draft; } } - + private static MessageFlag? _flagged = null; public static MessageFlag FLAGGED { get { if (_flagged == null) _flagged = new MessageFlag("\\flagged"); - + return _flagged; } } - + private static MessageFlag? _recent = null; public static MessageFlag RECENT { get { if (_recent == null) _recent = new MessageFlag("\\recent"); - + return _recent; } } - + private static MessageFlag? _seen = null; public static MessageFlag SEEN { get { if (_seen == null) _seen = new MessageFlag("\\seen"); - + return _seen; } } - + private static MessageFlag? _allows_new = null; public static MessageFlag ALLOWS_NEW { get { if (_allows_new == null) _allows_new = new MessageFlag("\\*"); - + return _allows_new; } } - + private static MessageFlag? _load_remote_images = null; public static MessageFlag LOAD_REMOTE_IMAGES { get { if (_load_remote_images == null) _load_remote_images = new MessageFlag("LoadRemoteImages"); - + return _load_remote_images; } } - + /** * Creates an IMAP message (email) named flag. */ public MessageFlag(string value) { base (value); } - + // Call these at init time to prevent thread issues internal static void init() { MessageFlag to_init = ANSWERED; @@ -97,10 +97,10 @@ to_init = ALLOWS_NEW; to_init = LOAD_REMOTE_IMAGES; } - + // Converts a list of email flags to add and remove to a list of message // flags to add and remove. - public static void from_email_flags(Geary.EmailFlags? email_flags_add, + public static void from_email_flags(Geary.EmailFlags? email_flags_add, Geary.EmailFlags? email_flags_remove, out Gee.List msg_flags_add, out Gee.List msg_flags_remove) { msg_flags_add = new Gee.ArrayList(); @@ -115,6 +115,8 @@ msg_flags_add.add(MessageFlag.LOAD_REMOTE_IMAGES); if (email_flags_add.contains(Geary.EmailFlags.DRAFT)) msg_flags_add.add(MessageFlag.DRAFT); + if (email_flags_add.contains(Geary.EmailFlags.DELETED)) + msg_flags_add.add(MessageFlag.DELETED); } if (email_flags_remove != null) { @@ -126,9 +128,11 @@ msg_flags_remove.add(MessageFlag.LOAD_REMOTE_IMAGES); if (email_flags_remove.contains(Geary.EmailFlags.DRAFT)) msg_flags_remove.add(MessageFlag.DRAFT); + if (email_flags_remove.contains(Geary.EmailFlags.DELETED)) + msg_flags_remove.add(MessageFlag.DELETED); } } - + /** * Returns a keyword suitable for the IMAP SEARCH command. * @@ -141,22 +145,22 @@ public string? get_search_keyword(bool present) { if (equal_to(ANSWERED)) return present ? "answered" : "unanswered"; - + if (equal_to(DELETED)) return present ? "deleted" : "undeleted"; - + if (equal_to(DRAFT)) return present ? "draft" : "undraft"; - + if (equal_to(FLAGGED)) return present ? "flagged" : "unflagged"; - + if (equal_to(RECENT)) return present ? "recent" : null; - + if (equal_to(SEEN)) return present ? "seen" : "unseen"; - + return null; } } diff -Nru geary-0.12.4/src/engine/imap/message/imap-namespace.vala geary-3.32.0/src/engine/imap/message/imap-namespace.vala --- geary-0.12.4/src/engine/imap/message/imap-namespace.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/src/engine/imap/message/imap-namespace.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,29 @@ +/* + * Copyright 2016 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +/** + * Namespace component in a response for a NAMESPACE command. + * + * @see Geary.Imap.NamespaceCommand + */ +public class Geary.Imap.Namespace : BaseObject { + + + public string prefix { get; private set; } + public string? delim { get; private set; } + + + public Namespace(string prefix, string? delim) { + this.prefix = prefix; + this.delim = delim; + } + + public string to_string() { + return "(%s,%s)".printf(this.prefix, this.delim ?? "NIL"); + } + +} diff -Nru geary-0.12.4/src/engine/imap/message/imap-sequence-number.vala geary-3.32.0/src/engine/imap/message/imap-sequence-number.vala --- geary-0.12.4/src/engine/imap/message/imap-sequence-number.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/message/imap-sequence-number.vala 2019-03-17 13:39:29.000000000 +0000 @@ -20,12 +20,12 @@ * See [[http://tools.ietf.org/html/rfc3501#section-2.3.1.2]] */ public const int64 MIN = 1; - + /** * Upper limit of a valid {@link SequenceNumber}. */ public const int64 MAX = 0xFFFFFFFF; - + /** * Create a new {@link SequenceNumber}. * @@ -37,7 +37,7 @@ public SequenceNumber(int64 value) { base (value); } - + /** * Create a new {@link SequenceNumber}, throwing {@link ImapError.INVALID} if an invalid value * is passed in. @@ -48,24 +48,24 @@ public SequenceNumber.checked(int64 value) throws ImapError { if (!is_value_valid(value)) throw new ImapError.INVALID("Invalid sequence number %s", value.to_string()); - + base (value); } - + /** * Defined as {@link MessageData.Int64MessageData.value} >= {@link MIN} and <= {@link MAX}. */ public static bool is_value_valid(int64 value) { return value >= MIN && value <= MAX; } - + /** * Defined as {@link MessageData.Int64MessageData.value} >= {@link MIN} and <= {@link MAX}. */ public bool is_valid() { return is_value_valid(value); } - + /** * Returns a new {@link SequenceNumber} that is one lower than this value. * @@ -74,7 +74,7 @@ public SequenceNumber? dec() { return (value > MIN) ? new SequenceNumber(value - 1) : null; } - + /** * Returns a new {@link SequenceNumber} that is one lower than this value. * @@ -83,7 +83,7 @@ public SequenceNumber dec_clamped() { return (value > MIN) ? new SequenceNumber(value - 1) : new SequenceNumber(MIN); } - + /** * Returns the {@link SequenceNumber} after the suppled SequenceNumber has been removed from * the vector of messages. @@ -98,21 +98,21 @@ // shifts downward ... dec() returns null if dropping below 1, which is suitable here return dec(); } - + if (comparison == 0) { // the appended message was removed outright, so drop from new list return null; } - + // otherwise, removed position was higher than appended message's, so it doesn't // shift return this; } - + public virtual int compare_to(SequenceNumber other) { return (int) (value - other.value).clamp(-1, 1); } - + public string serialize() { return value.to_string(); } diff -Nru geary-0.12.4/src/engine/imap/message/imap-status-data-type.vala geary-3.32.0/src/engine/imap/message/imap-status-data-type.vala --- geary-0.12.4/src/engine/imap/message/imap-status-data-type.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/message/imap-status-data-type.vala 2019-03-17 13:39:29.000000000 +0000 @@ -11,62 +11,61 @@ * * @see StatusData */ - public enum Geary.Imap.StatusDataType { MESSAGES, RECENT, UIDNEXT, UIDVALIDITY, UNSEEN; - + public static StatusDataType[] all() { return { MESSAGES, RECENT, UIDNEXT, UIDVALIDITY, UNSEEN }; } - + public string to_string() { switch (this) { case MESSAGES: - return "messages"; - + return "MESSAGES"; + case RECENT: - return "recent"; - + return "RECENT"; + case UIDNEXT: - return "uidnext"; - + return "UIDNEXT"; + case UIDVALIDITY: - return "uidvalidity"; - + return "UIDVALIDITY"; + case UNSEEN: - return "unseen"; - + return "UNSEEN"; + default: assert_not_reached(); } } - + public static StatusDataType from_parameter(StringParameter stringp) throws ImapError { switch (stringp.as_lower()) { case "messages": return MESSAGES; - + case "recent": return RECENT; - + case "uidnext": return UIDNEXT; - + case "uidvalidity": return UIDVALIDITY; - + case "unseen": return UNSEEN; - + default: throw new ImapError.PARSE_ERROR("Unknown status data type \"%s\"", stringp.to_string()); } } - + public StringParameter to_parameter() { return new AtomParameter(to_string()); } diff -Nru geary-0.12.4/src/engine/imap/message/imap-tag.vala geary-3.32.0/src/engine/imap/message/imap-tag.vala --- geary-0.12.4/src/engine/imap/message/imap-tag.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/message/imap-tag.vala 2019-03-17 13:39:29.000000000 +0000 @@ -19,93 +19,93 @@ public const string UNTAGGED_VALUE = "*"; public const string CONTINUATION_VALUE = "+"; public const string UNASSIGNED_VALUE = "----"; - + private static Tag? untagged = null; private static Tag? unassigned = null; private static Tag? continuation = null; - + public Tag(string ascii) { base (ascii); } - + public Tag.from_parameter(StringParameter strparam) { base (strparam.ascii); } - + internal static void init() { get_untagged(); get_continuation(); get_unassigned(); } - + public static Tag get_untagged() { if (untagged == null) untagged = new Tag(UNTAGGED_VALUE); - + return untagged; } - + public static Tag get_continuation() { if (continuation == null) continuation = new Tag(CONTINUATION_VALUE); - + return continuation; } - + public static Tag get_unassigned() { if (unassigned == null) unassigned = new Tag(UNASSIGNED_VALUE); - + return unassigned; } - + /** * Returns true if the StringParameter resembles a tag token: an unquoted non-empty string - * that either matches the untagged or continuation special tags or + * that either matches the untagged or continuation special tags or */ public static bool is_tag(StringParameter stringp) { if (stringp is QuotedStringParameter) return false; - + if (stringp.is_empty()) return false; - + if (stringp.equals_cs(UNTAGGED_VALUE) || stringp.equals_cs(CONTINUATION_VALUE)) return true; - + int index = 0; for (;;) { char ch = stringp.ascii[index++]; if (ch == String.EOS) break; - + if (DataFormat.is_tag_special(ch)) return false; } - + return true; } - + public bool is_tagged() { return !equals_cs(UNTAGGED_VALUE) && !equals_cs(CONTINUATION_VALUE) && !equals_cs(UNASSIGNED_VALUE); } - + public bool is_continuation() { return equals_cs(CONTINUATION_VALUE); } - + public bool is_assigned() { return !equals_cs(UNASSIGNED_VALUE) && !equals_cs(CONTINUATION_VALUE); } - + public uint hash() { return Ascii.str_hash(ascii); } - + public bool equal_to(Geary.Imap.Tag tag) { if (this == tag) return true; - + return equals_cs(tag.ascii); } } diff -Nru geary-0.12.4/src/engine/imap/message/imap-uid.vala geary-3.32.0/src/engine/imap/message/imap-uid.vala --- geary-0.12.4/src/engine/imap/message/imap-uid.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/message/imap-uid.vala 2019-03-17 13:39:29.000000000 +0000 @@ -18,17 +18,17 @@ * Minimum valid value for a {@link UID}. */ public const int64 MIN = 1; - + /** * Maximum valid value for a {@link UID}. */ public const int64 MAX = 0xFFFFFFFF; - + /** * Invalid (placeholder) {@link UID} value. */ public const int64 INVALID = -1; - + /** * Creates a new {@link UID} without checking for validity. * @@ -38,7 +38,7 @@ public UID(int64 value) { base (value); } - + /** * Creates a new {@link UID}, throwing an {@link ImapError.INVALID} if the supplied value is * not a positive unsigned 32-bit integer. @@ -48,24 +48,24 @@ public UID.checked(int64 value) throws ImapError { if (!is_value_valid(value)) throw new ImapError.INVALID("Invalid UID %s", value.to_string()); - + base (value); } - + /** * @see is_value_valid */ public bool is_valid() { return is_value_valid(value); } - + /** * Returns true if the supplied value is between {@link MIN} and {@link MAX}, inclusive. */ public static bool is_value_valid(int64 val) { return Numeric.int64_in_range_inclusive(val, MIN, MAX); } - + /** * Returns the UID logically next (or after) this one. * @@ -80,7 +80,7 @@ public UID next(bool clamped) { return clamped ? new UID((value + 1).clamp(MIN, MAX)) : new UID(value + 1); } - + /** * Returns the UID logically previous (or before) this one. * @@ -95,11 +95,11 @@ public UID previous(bool clamped) { return clamped ? new UID((value - 1).clamp(MIN, MAX)) : new UID(value - 1); } - + public virtual int compare_to(Geary.Imap.UID other) { return (int) (value - other.value).clamp(-1, 1); } - + public string serialize() { return value.to_string(); } diff -Nru geary-0.12.4/src/engine/imap/message/imap-uid-validity.vala geary-3.32.0/src/engine/imap/message/imap-uid-validity.vala --- geary-0.12.4/src/engine/imap/message/imap-uid-validity.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/message/imap-uid-validity.vala 2019-03-17 13:39:29.000000000 +0000 @@ -17,7 +17,7 @@ * Minimum valid value for a {@link UIDValidity}. */ public const int64 MIN = 1; - + /** * Maximum valid value for a {@link UIDValidity}. * @@ -31,7 +31,7 @@ * Invalid (placeholder) {@link UIDValidity} value. */ public const int64 INVALID = -1; - + /** * Creates a new {@link UIDValidity} without checking for valid values. * @@ -40,7 +40,7 @@ public UIDValidity(int64 value) { base (value); } - + /** * Creates a new {@link UIDValidity}, throwing {@link ImapError.INVALID} if the supplied value * is invalid. @@ -50,17 +50,17 @@ public UIDValidity.checked(int64 value) throws ImapError { if (!is_value_valid(value)) throw new ImapError.INVALID("Invalid UIDVALIDITY %s", value.to_string()); - + base (value); } - + /** * @see is_value_valid */ public bool is_valid() { return is_value_valid(value); } - + /** * Returns true if the supplied value is between {@link MIN} and {@link MAX}, inclusive. */ diff -Nru geary-0.12.4/src/engine/imap/parameter/imap-atom-parameter.vala geary-3.32.0/src/engine/imap/parameter/imap-atom-parameter.vala --- geary-0.12.4/src/engine/imap/parameter/imap-atom-parameter.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/parameter/imap-atom-parameter.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,4 +1,5 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. @@ -16,17 +17,18 @@ * * See [[http://tools.ietf.org/html/rfc3501#section-4.1]] */ - public class Geary.Imap.AtomParameter : Geary.Imap.UnquotedStringParameter { + public AtomParameter(string value) { base (value); } - + /** * {@inheritDoc} */ - public override void serialize(Serializer ser, Tag tag) throws Error { + public override void serialize(Serializer ser, GLib.Cancellable cancellable) + throws GLib.Error { ser.push_unquoted_string(ascii); } -} +} diff -Nru geary-0.12.4/src/engine/imap/parameter/imap-list-parameter.vala geary-3.32.0/src/engine/imap/parameter/imap-list-parameter.vala --- geary-0.12.4/src/engine/imap/parameter/imap-list-parameter.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/parameter/imap-list-parameter.vala 2019-03-17 13:39:29.000000000 +0000 @@ -16,7 +16,7 @@ * in the StringParameter getters. */ public const int MAX_STRING_LITERAL_LENGTH = 4096; - + /** * Returns the number of {@link Parameter}s held in this {@link ListParameter}. */ @@ -25,81 +25,42 @@ return list.size; } } - - /** - * Returns null if no parent (top-level list). - * - * In a fully-formed set of {@link Parameter}s, this means this {@link ListParameter} is - * probably a {@link RootParameters}. - */ - public weak ListParameter? parent { get; private set; default = null; } - + private Gee.List list = new Gee.ArrayList(); - - /** - * Creates an empty ListParameter with no parent. - */ - public ListParameter() { - } - - ~ListParameter() { - // Drop back links because, although it's a weak ref, sometimes ListParameters are temporarily - // made and current Vala doesn't reset weak refs - foreach (Parameter param in list) { - ListParameter? listp = param as ListParameter; - if (listp != null) { - assert(listp.parent == this); - - listp.parent = null; - } - } - } - + + /** - * Adds the {@link Parameter} to the end of the {@link ListParameter}. - * - * If the Parameter is itself a ListParameter, it's {@link parent} will be set to this - * ListParameter. + * Appends a parameter to the end of this list. * - * The same {@link Parameter} can't be added more than once to the same {@link ListParameter}. - * There are no other restrictions, however. + * The same {@link Parameter} can't be added more than once to the + * same {@link ListParameter}. There are no other restrictions, + * however. * * @return true if added. */ public bool add(Parameter param) { - // if adding a ListParameter, set its parent - ListParameter? listp = param as ListParameter; - if (listp != null) { - if (listp.parent != null) - listp.parent.list.remove(listp); - - listp.parent = this; - } - - return list.add(param); + return this.list.add(param); } - + /** - * Adds all the {@link Parameter}s to the end of the {@link ListParameter}. - * - * If any Parameter is itself a ListParameter, it's {@link parent} will be set to this - * ListParameter. + * Adds all parameters in the given collection to this list. * - * The same {@link Parameter} can't be added more than once to the same {@link ListParameter}. - * There are no other restrictions, however. + * The same {@link Parameter} can't be added more than once to the + * same {@link ListParameter}. There are no other restrictions, + * however. * * @return number of Parameters added. */ public int add_all(Gee.Collection params) { int count = 0; - foreach (Parameter param in params) + foreach (Parameter param in params) { count += add(param) ? 1 : 0; - + } return count; } - + /** - * Appends the {@link ListParameter} to the end of this ListParameter. + * Adds all elements in the given list to the end of this list. * * The difference between this call and {@link add} is that add() will simply insert the * {@link Parameter} to the tail of the list. Thus, add(ListParameter) will add a child list @@ -107,51 +68,29 @@ * * (one two (three)) * - * append(ListParameter("three")) adds each element of the ListParameter to this one, not + * Instead, extend(ListParameter("three")) adds each element of the ListParameter to this one, not * creating a child: * * (one two three) * - * Thus, each element of the list is moved ("adopted") by this list, and the supplied list - * returns empty. This is slightly different than {@link adopt_children}, which preserves the - * list structure. - * - * @return Number of added elements. append() will not abort if an element fails to add. - */ - public int append(ListParameter listp) { - // snap the child list off the supplied ListParameter so it's wiped clean - Gee.List to_append = listp.list; - listp.list = new Gee.ArrayList(); - - int count = 0; - foreach (Parameter param in to_append) { - if (add(param)) - count++; - } - - return count; + * @return Number of added elements. This will not abort if an + * element fails to be added. + */ + public int extend(ListParameter listp) { + return add_all(listp.list); } - + /** * Clears the {@link ListParameter} of all its children. - * - * This also clears (sets to null) the parents of all ListParameter's children. */ public void clear() { - // sever ties to ListParameter children - foreach (Parameter param in list) { - ListParameter? listp = param as ListParameter; - if (listp != null) - listp.parent = null; - } - list.clear(); } - + // // Parameter retrieval // - + /** * Returns the {@link Parameter} at the index in the list, null if index is out of range. * @@ -162,7 +101,7 @@ public new Parameter? get(int index) { return ((index >= 0) && (index < list.size)) ? list.get(index) : null; } - + /** * Returns the Parameter at the index. Throws an ImapError.TYPE_ERROR if the index is out of * range. @@ -174,14 +113,14 @@ public Parameter get_required(int index) throws ImapError { if ((index < 0) || (index >= list.size)) throw new ImapError.TYPE_ERROR("No parameter at index %d", index); - + Parameter? param = list.get(index); if (param == null) throw new ImapError.TYPE_ERROR("No parameter at index %d", index); - + return param; } - + /** * Returns {@link Parameter} at index if in range and of Type type, otherwise throws an * {@link ImapError.TYPE_ERROR}. @@ -191,16 +130,16 @@ public Parameter get_as(int index, Type type) throws ImapError { if (!type.is_a(typeof(Parameter))) throw new ImapError.TYPE_ERROR("Attempting to cast non-Parameter at index %d", index); - + Parameter param = get_required(index); if (!param.get_type().is_a(type)) { throw new ImapError.TYPE_ERROR("Parameter %d is not of type %s (is %s)", index, type.name(), param.get_type().name()); } - + return param; } - + /** * Like {@link get_as}, but returns null if the {@link Parameter} at index is a * {@link NilParameter}. @@ -210,25 +149,25 @@ public Parameter? get_as_nullable(int index, Type type) throws ImapError { if (!type.is_a(typeof(Parameter))) throw new ImapError.TYPE_ERROR("Attempting to cast non-Parameter at index %d", index); - + Parameter param = get_required(index); if (param is NilParameter) return null; - + // Because Deserializer doesn't produce NilParameters, check manually if this Parameter // can legally be NIL according to IMAP grammer. StringParameter? stringp = param as StringParameter; if (stringp != null && NilParameter.is_nil(stringp)) return null; - + if (!param.get_type().is_a(type)) { throw new ImapError.TYPE_ERROR("Parameter %d is not of type %s (is %s)", index, type.name(), param.get_type().name()); } - + return param; } - + /** * Like {@link get}, but returns null if {@link Parameter} at index is not of the specified type. * @@ -237,18 +176,18 @@ public Parameter? get_if(int index, Type type) { if (!type.is_a(typeof(Parameter))) return null; - + Parameter? param = get(index); if (param == null || !param.get_type().is_a(type)) return null; - + return param; } - + // // String retrieval // - + /** * Returns a {@link StringParameter} only if the {@link Parameter} at index is a StringParameter. * @@ -257,7 +196,7 @@ public StringParameter? get_if_string(int index) { return (StringParameter?) get_if(index, typeof(StringParameter)); } - + /** * Returns a {@link StringParameter} for the value at the index only if the {@link Parameter} * is a StringParameter or a {@link LiteralParameter} with a length less than or equal to @@ -272,19 +211,19 @@ */ public StringParameter get_as_string(int index) throws ImapError { Parameter param = get_required(index); - + StringParameter? stringp = param as StringParameter; if (stringp != null) return stringp; - + LiteralParameter? literalp = param as LiteralParameter; if (literalp != null && literalp.get_size() <= MAX_STRING_LITERAL_LENGTH) return literalp.coerce_to_string_parameter(); - + throw new ImapError.TYPE_ERROR("Parameter %d not of type string or literal (is %s)", index, param.get_type().name()); } - + /** * Returns a {@link StringParameter} for the value at the index only if the {@link Parameter} * is a StringParameter or a {@link LiteralParameter} with a length less than or equal to @@ -301,33 +240,33 @@ Parameter? param = get_as_nullable(index, typeof(Parameter)); if (param == null) return null; - + StringParameter? stringp = param as StringParameter; if (stringp != null) return stringp; - + LiteralParameter? literalp = param as LiteralParameter; if (literalp != null && literalp.get_size() <= MAX_STRING_LITERAL_LENGTH) return literalp.coerce_to_string_parameter(); - + throw new ImapError.TYPE_ERROR("Parameter %d not of type string or literal (is %s)", index, param.get_type().name()); } - + /** * Much like get_as_nullable_string() but returns an empty StringParameter (rather than null) * if the parameter at index is a NilParameter. */ public StringParameter get_as_empty_string(int index) throws ImapError { StringParameter? stringp = get_as_nullable_string(index); - + return stringp ?? StringParameter.get_best_for(""); } - + // // Number retrieval // - + /** * Returns a {@link NumberParameter} at index, null if not of that type. * @@ -336,7 +275,7 @@ public NumberParameter? get_if_number(int index) { return (NumberParameter?) get_if(index, typeof(NumberParameter)); } - + /** * Returns a {@link NumberParameter} at index. * @@ -346,26 +285,26 @@ */ public NumberParameter get_as_number(int index) throws ImapError { Parameter param = get_required(index); - + NumberParameter? numberp = param as NumberParameter; if (numberp != null) return numberp; - + StringParameter? stringp = param as StringParameter; if (stringp != null) { numberp = stringp.coerce_to_number_parameter(); if (numberp != null) return numberp; } - + throw new ImapError.TYPE_ERROR("Parameter %d not of type number or string (is %s)", index, param.get_type().name()); } - + // // List retrieval // - + /** * Returns a {@link ListParameter} at index. * @@ -374,7 +313,7 @@ public ListParameter get_as_list(int index) throws ImapError { return (ListParameter) get_as(index, typeof(ListParameter)); } - + /** * Returns a {@link ListParameter} at index, null if NIL. * @@ -383,19 +322,16 @@ public ListParameter? get_as_nullable_list(int index) throws ImapError { return (ListParameter?) get_as_nullable(index, typeof(ListParameter)); } - + /** - * Returns [@link ListParameter} at index, an empty list if NIL. - * - * If an empty ListParameter has to be manufactured in place of a NIL parameter, its parent - * will be null. + * Returns {@link ListParameter} at index, an empty list if NIL. */ public ListParameter get_as_empty_list(int index) throws ImapError { ListParameter? param = get_as_nullable_list(index); - + return param ?? new ListParameter(); } - + /** * Returns a {@link ListParameter} at index, null if not a list. * @@ -404,11 +340,11 @@ public ListParameter? get_if_list(int index) { return (ListParameter?) get_if(index, typeof(ListParameter)); } - + // // Literal retrieval // - + /** * Returns a {@link LiteralParameter} at index. * @@ -417,7 +353,7 @@ public LiteralParameter get_as_literal(int index) throws ImapError { return (LiteralParameter) get_as(index, typeof(LiteralParameter)); } - + /** * Returns a {@link LiteralParameter} at index, null if NIL. * @@ -426,7 +362,7 @@ public LiteralParameter? get_as_nullable_literal(int index) throws ImapError { return (LiteralParameter?) get_as_nullable(index, typeof(LiteralParameter)); } - + /** * Returns a {@link LiteralParameter} at index, null if not a list. * @@ -435,16 +371,16 @@ public LiteralParameter? get_if_literal(int index) { return (LiteralParameter?) get_if(index, typeof(LiteralParameter)); } - + /** * Returns [@link LiteralParameter} at index, an empty list if NIL. */ public LiteralParameter get_as_empty_literal(int index) throws ImapError { LiteralParameter? param = get_as_nullable_literal(index); - + return param ?? new LiteralParameter(Geary.Memory.EmptyBuffer.instance); } - + /** * Returns a {@link Memory.Buffer} for the {@link Parameter} at position index. * @@ -455,14 +391,14 @@ LiteralParameter? literalp = get_if_literal(index); if (literalp != null) return literalp.get_buffer(); - + StringParameter? stringp = get_if_string(index); if (stringp != null) return stringp.as_buffer(); - + return null; } - + /** * Returns a {@link Memory.Buffer} for the {@link Parameter} at position index. * @@ -472,14 +408,14 @@ public Memory.Buffer get_as_empty_buffer(int index) throws ImapError { return get_as_nullable_buffer(index) ?? Memory.EmptyBuffer.instance; } - + /** * Returns a read-only List of {@link Parameter}s. */ public Gee.List get_all() { return list.read_only_view; } - + /** * Returns the replaced Paramater. Throws ImapError.TYPE_ERROR if no Parameter exists at the * index. @@ -487,27 +423,12 @@ public Parameter replace(int index, Parameter parameter) throws ImapError { if (list.size <= index) throw new ImapError.TYPE_ERROR("No parameter at index %d", index); - - Parameter old = list[index]; - list[index] = parameter; - - // add parent to new Parameter if a list - ListParameter? listp = parameter as ListParameter; - if (listp != null) { - if (listp.parent != null) - listp.parent.list.remove(listp); - - listp.parent = this; - } - - // clear parent of old Parameter if a list - listp = old as ListParameter; - if (listp != null) - listp.parent = null; - + + Parameter old = this.list[index]; + this.list[index] = parameter; return old; } - + /** * Moves all child parameters from the supplied list into this list, clearing this list first. * @@ -516,50 +437,52 @@ */ public void adopt_children(ListParameter src) { clear(); - + Gee.List src_children = new Gee.ArrayList(); src_children.add_all(src.list); src.clear(); - + add_all(src_children); } - + protected string stringize_list() { StringBuilder builder = new StringBuilder(); - + int length = list.size; for (int ctr = 0; ctr < length; ctr++) { builder.append(list[ctr].to_string()); if (ctr < (length - 1)) builder.append_c(' '); } - + return builder.str; } - + /** * {@inheritDoc} */ public override string to_string() { return "(%s)".printf(stringize_list()); } - - protected void serialize_list(Serializer ser, Tag tag) throws Error { + + /** + * {@inheritDoc} + */ + public override void serialize(Serializer ser, GLib.Cancellable cancellable) + throws GLib.Error { + ser.push_ascii('(', cancellable); + serialize_list(ser, cancellable); + ser.push_ascii(')', cancellable); + } + + protected void serialize_list(Serializer ser, GLib.Cancellable cancellable) + throws GLib.Error { int length = list.size; for (int ctr = 0; ctr < length; ctr++) { - list[ctr].serialize(ser, tag); + list[ctr].serialize(ser, cancellable); if (ctr < (length - 1)) - ser.push_space(); + ser.push_space(cancellable); } } - - /** - * {@inheritDoc} - */ - public override void serialize(Serializer ser, Tag tag) throws Error { - ser.push_ascii('('); - serialize_list(ser, tag); - ser.push_ascii(')'); - } -} +} diff -Nru geary-0.12.4/src/engine/imap/parameter/imap-literal-parameter.vala geary-3.32.0/src/engine/imap/parameter/imap-literal-parameter.vala --- geary-0.12.4/src/engine/imap/parameter/imap-literal-parameter.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/parameter/imap-literal-parameter.vala 2019-03-17 13:39:29.000000000 +0000 @@ -16,25 +16,25 @@ public class Geary.Imap.LiteralParameter : Geary.Imap.Parameter { private Memory.Buffer buffer; - + public LiteralParameter(Memory.Buffer buffer) { this.buffer = buffer; } - + /** * Returns the number of bytes in the literal parameter's buffer. */ public size_t get_size() { return buffer.size; } - + /** * Returns the literal paremeter's buffer. */ public Memory.Buffer get_buffer() { return buffer; } - + /** * Returns the {@link LiteralParameter} as though it had been a {@link StringParameter} on the * wire. @@ -47,21 +47,30 @@ public StringParameter coerce_to_string_parameter() { return new UnquotedStringParameter(buffer.get_valid_utf8()); } - + /** * {@inheritDoc} */ public override string to_string() { return "{literal/%lub}".printf(get_size()); } - + /** * {@inheritDoc} */ - public override void serialize(Serializer ser, Tag tag) throws Error { - ser.push_unquoted_string("{%lu}".printf(get_size())); - ser.push_eol(); - ser.push_synchronized_literal_data(tag, buffer); + public override void serialize(Serializer ser, GLib.Cancellable cancellable) + throws GLib.Error { + ser.push_unquoted_string("{%lu}".printf(get_size()), cancellable); + ser.push_eol(cancellable); + } + + /** + * Serialises the literal parameter data. + */ + public async void serialize_data(Serializer ser, + GLib.Cancellable cancellable) + throws GLib.Error { + yield ser.push_literal_data(buffer, cancellable); } -} +} diff -Nru geary-0.12.4/src/engine/imap/parameter/imap-nil-parameter.vala geary-3.32.0/src/engine/imap/parameter/imap-nil-parameter.vala --- geary-0.12.4/src/engine/imap/parameter/imap-nil-parameter.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/parameter/imap-nil-parameter.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,4 +1,5 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. @@ -19,20 +20,20 @@ public class Geary.Imap.NilParameter : Geary.Imap.Parameter { public const string VALUE = "NIL"; - + private static NilParameter? _instance = null; public static NilParameter instance { get { if (_instance == null) _instance = new NilParameter(); - + return _instance; } } - + private NilParameter() { } - + /** * See note at {@link NilParameter} for comparison rules of "NIL". * @@ -43,19 +44,20 @@ public static bool is_nil(StringParameter stringp) { return stringp.equals_ci(VALUE); } - + /** * {@inheritDoc} */ - public override void serialize(Serializer ser, Tag tag) throws Error { - ser.push_nil(); + public override void serialize(Serializer ser, GLib.Cancellable cancellable) + throws GLib.Error { + ser.push_nil(cancellable); } - + /** * {@inheritDoc} */ public override string to_string() { return VALUE; } -} +} diff -Nru geary-0.12.4/src/engine/imap/parameter/imap-number-parameter.vala geary-3.32.0/src/engine/imap/parameter/imap-number-parameter.vala --- geary-0.12.4/src/engine/imap/parameter/imap-number-parameter.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/parameter/imap-number-parameter.vala 2019-03-17 13:39:29.000000000 +0000 @@ -15,27 +15,27 @@ public NumberParameter(int num) { base (num.to_string()); } - + public NumberParameter.uint(uint num) { base (num.to_string()); } - + public NumberParameter.int32(int32 num) { base (num.to_string()); } - + public NumberParameter.uint32(uint32 num) { base (num.to_string()); } - + public NumberParameter.int64(int64 num) { base (num.to_string()); } - + public NumberParameter.uint64(uint64 num) { base (num.to_string()); } - + /** * Creates a {@link NumberParameter} for a string representation of a number. * @@ -45,7 +45,7 @@ public NumberParameter.from_ascii(string ascii) { base (ascii); } - + /** * Returns true if the string is composed of numeric 7-bit characters. * @@ -61,40 +61,40 @@ */ public static bool is_ascii_numeric(string ascii, out bool is_negative) { is_negative = false; - + string str = ascii.strip(); - + if (String.is_empty(str)) return false; - + bool has_nonzero = false; int index = 0; for (;;) { char ch = str[index++]; if (ch == String.EOS) break; - + if (index == 1 && ch == '-') { is_negative = true; - + continue; } - + if (!ch.isdigit()) return false; - + if (ch != '0') has_nonzero = true; } - + // watch for negative but no numeric portion if (is_negative && str.length == 1) return false; - + // no such thing as negative zero if (is_negative && !has_nonzero) is_negative = false; - + return true; } } diff -Nru geary-0.12.4/src/engine/imap/parameter/imap-parameter.vala geary-3.32.0/src/engine/imap/parameter/imap-parameter.vala --- geary-0.12.4/src/engine/imap/parameter/imap-parameter.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/parameter/imap-parameter.vala 2019-03-17 13:39:29.000000000 +0000 @@ -29,20 +29,24 @@ return new LiteralParameter(new Memory.StringBuffer(value)); } } - + /** - * Invoked when the {@link Parameter} is to be serialized out to the network. + * Invoked when this parameter is to be serialized out to the network. * - * The supplied Tag will have (or will be) assigned to the message, so it should be passed - * to all serialize() calls this call may make. The {@link Parameter} should not use its own - * internal Tag object, if it has a reference to one. + * This method is intended to be used for serialising IMAP command + * lines, which are typically short, single lines. Hence this + * method is not asynchronous since the serialiser will buffer + * writes to it. Any parameters with large volumes of data to + * serialise (typically {@link LiteralParameter}) are specially + * handled by {@link Command} when serialising. */ - public abstract void serialize(Serializer ser, Tag tag) throws Error; - + public abstract void serialize(Serializer ser, GLib.Cancellable cancellable) + throws GLib.Error; + /** * Returns a representation of the {@link Parameter} suitable for logging and debugging, * but should not be relied upon for wire or persistent representation. */ public abstract string to_string(); -} +} diff -Nru geary-0.12.4/src/engine/imap/parameter/imap-quoted-string-parameter.vala geary-3.32.0/src/engine/imap/parameter/imap-quoted-string-parameter.vala --- geary-0.12.4/src/engine/imap/parameter/imap-quoted-string-parameter.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/parameter/imap-quoted-string-parameter.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,4 +1,5 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. @@ -14,24 +15,24 @@ * * See [[http://tools.ietf.org/html/rfc3501#section-4.3]]. */ - public class Geary.Imap.QuotedStringParameter : Geary.Imap.StringParameter { public QuotedStringParameter(string ascii) { base (ascii); } - + /** * {@inheritDoc} */ public override string to_string() { return "\"%s\"".printf(ascii); } - + /** * {@inheritDoc} */ - public override void serialize(Serializer ser, Tag tag) throws Error { - ser.push_quoted_string(ascii); + public override void serialize(Serializer ser, GLib.Cancellable cancellable) + throws GLib.Error { + ser.push_quoted_string(ascii, cancellable); } -} +} diff -Nru geary-0.12.4/src/engine/imap/parameter/imap-root-parameters.vala geary-3.32.0/src/engine/imap/parameter/imap-root-parameters.vala --- geary-0.12.4/src/engine/imap/parameter/imap-root-parameters.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/parameter/imap-root-parameters.vala 2019-03-17 13:39:29.000000000 +0000 @@ -17,7 +17,7 @@ public class Geary.Imap.RootParameters : Geary.Imap.ListParameter { public RootParameters() { } - + /** * Moves all contained {@link Parameter} objects inside the supplied RootParameters into a * new RootParameters. @@ -27,7 +27,7 @@ public RootParameters.migrate(RootParameters root) { adopt_children(root); } - + /** * Returns null if the first parameter is not a StringParameter that resembles a Tag. */ @@ -35,13 +35,13 @@ StringParameter? strparam = get_if_string(0); if (strparam == null) return null; - + if (!Tag.is_tag(strparam)) return null; - + return new Tag.from_parameter(strparam); } - + /** * Returns true if the first parameter is a StringParameter that resembles a Tag. */ @@ -49,23 +49,24 @@ StringParameter? strparam = get_if_string(0); if (strparam == null) return false; - + return (strparam != null) ? Tag.is_tag(strparam) : false; } - + /** * {@inheritDoc} */ public override string to_string() { return stringize_list(); } - + /** * {@inheritDoc} */ - public override void serialize(Serializer ser, Tag tag) throws Error { - serialize_list(ser, tag); - ser.push_eol(); + public override void serialize(Serializer ser, GLib.Cancellable cancellable) + throws GLib.Error { + serialize_list(ser, cancellable); + ser.push_eol(cancellable); } -} +} diff -Nru geary-0.12.4/src/engine/imap/parameter/imap-string-parameter.vala geary-3.32.0/src/engine/imap/parameter/imap-string-parameter.vala --- geary-0.12.4/src/engine/imap/parameter/imap-string-parameter.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/parameter/imap-string-parameter.vala 2019-03-17 13:39:29.000000000 +0000 @@ -24,7 +24,7 @@ * The unquoted, decoded string as 7-bit ASCII. */ public string ascii { get; private set; } - + /** * Returns {@link ascii} or null if value is empty (zero-length). */ @@ -33,11 +33,11 @@ return String.is_empty(ascii) ? null : ascii; } } - + protected StringParameter(string ascii) { this.ascii = ascii; } - + /** * Returns a {@link StringParameter} appropriate for the contents of value. * @@ -54,22 +54,22 @@ public static StringParameter get_best_for(string value) throws ImapError { if (NumberParameter.is_ascii_numeric(value, null)) return new NumberParameter.from_ascii(value); - + switch (DataFormat.is_quoting_required(value)) { case DataFormat.Quoting.REQUIRED: return new QuotedStringParameter(value); - + case DataFormat.Quoting.OPTIONAL: return new UnquotedStringParameter(value); - + case DataFormat.Quoting.UNALLOWED: throw new ImapError.NOT_SUPPORTED("String must be a literal parameter"); - + default: assert_not_reached(); } } - + /** * Like {@link get_best_for} but the library will panic if the value cannot be turned into * a {@link StringParameter}. @@ -83,7 +83,7 @@ error("Unable to create StringParameter for \"%s\": %s", value, ierr.message); } } - + /** * Like {@link get_best_for} but returns null if the value cannot be stored as a * {@link StringParameter}. @@ -97,72 +97,74 @@ return null; } } - + /** * Can be used by subclasses to properly serialize the string value according to quoting rules. * * NOTE: Literal data is not currently supported. */ - protected void serialize_string(Serializer ser) throws Error { + protected void serialize_string(Serializer ser, + GLib.Cancellable cancellable) + throws GLib.Error { switch (DataFormat.is_quoting_required(ascii)) { case DataFormat.Quoting.REQUIRED: - ser.push_quoted_string(ascii); + ser.push_quoted_string(ascii, cancellable); break; - + case DataFormat.Quoting.OPTIONAL: - ser.push_unquoted_string(ascii); + ser.push_unquoted_string(ascii, cancellable); break; - + case DataFormat.Quoting.UNALLOWED: error("Unable to serialize literal data"); - + default: assert_not_reached(); } } - + /** * Returns the string as a {@link Memory.Buffer}. */ public Memory.Buffer as_buffer() { return new Memory.StringBuffer(ascii); } - + /** * Returns true if the string is empty (zero-length). */ public bool is_empty() { return String.is_empty(ascii); } - + /** * Case-sensitive comparison. */ public bool equals_cs(string value) { return Ascii.str_equal(ascii, value); } - + /** * Case-insensitive comparison. */ public bool equals_ci(string value) { return Ascii.stri_equal(ascii, value); } - + /** * Returns the string lowercased. */ public string as_lower() { return Ascii.strdown(ascii); } - + /** * Returns the string uppercased. */ public string as_upper() { return Ascii.strup(ascii); } - + /** * Converts the {@link ascii} to a signed 32-bit integer, clamped between clamp_min and * clamp_max. @@ -173,10 +175,10 @@ public int32 as_int32(int32 clamp_min = int32.MIN, int32 clamp_max = int32.MAX) throws ImapError { if (!NumberParameter.is_ascii_numeric(ascii, null)) throw new ImapError.INVALID("Cannot convert \"%s\" to int32: not numeric", ascii); - + return (int32) int64.parse(ascii).clamp(clamp_min, clamp_max); } - + /** * Converts the {@link ascii} to a signed 64-bit integer, clamped between clamp_min and * clamp_max. @@ -187,10 +189,10 @@ public int64 as_int64(int64 clamp_min = int64.MIN, int64 clamp_max = int64.MAX) throws ImapError { if (!NumberParameter.is_ascii_numeric(ascii, null)) throw new ImapError.INVALID("Cannot convert \"%s\" to int64: not numeric", ascii); - + return int64.parse(ascii).clamp(clamp_min, clamp_max); } - + /** * Attempts to coerce a {@link StringParameter} into a {@link NumberParameter}. * @@ -202,10 +204,10 @@ NumberParameter? numberp = this as NumberParameter; if (numberp != null) return numberp; - + if (NumberParameter.is_ascii_numeric(ascii, null)) return new NumberParameter.from_ascii(ascii); - + return null; } } diff -Nru geary-0.12.4/src/engine/imap/parameter/imap-unquoted-string-parameter.vala geary-3.32.0/src/engine/imap/parameter/imap-unquoted-string-parameter.vala --- geary-0.12.4/src/engine/imap/parameter/imap-unquoted-string-parameter.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/parameter/imap-unquoted-string-parameter.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,4 +1,5 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. @@ -16,24 +17,25 @@ * * See [[http://tools.ietf.org/html/rfc3501#section-4.1]] */ - public class Geary.Imap.UnquotedStringParameter : Geary.Imap.StringParameter { + public UnquotedStringParameter(string ascii) { base (ascii); } - + /** * {@inheritDoc} */ - public override void serialize(Serializer ser, Tag tag) throws Error { - ser.push_unquoted_string(ascii); + public override void serialize(Serializer ser, GLib.Cancellable cancellable) + throws GLib.Error { + ser.push_unquoted_string(ascii, cancellable); } - + /** * {@inheritDoc} */ public override string to_string() { return ascii; } -} +} diff -Nru geary-0.12.4/src/engine/imap/response/imap-capabilities.vala geary-3.32.0/src/engine/imap/response/imap-capabilities.vala --- geary-0.12.4/src/engine/imap/response/imap-capabilities.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/response/imap-capabilities.vala 2019-03-17 13:39:29.000000000 +0000 @@ -5,19 +5,27 @@ */ public class Geary.Imap.Capabilities : Geary.GenericCapabilities { - public const string IDLE = "IDLE"; - public const string STARTTLS = "STARTTLS"; - public const string XLIST = "XLIST"; + + + public const string AUTH = "AUTH"; + public const string AUTH_XOAUTH2 = "XOAUTH2"; + public const string CREATE_SPECIAL_USE = "CREATE-SPECIAL-USE"; public const string COMPRESS = "COMPRESS"; public const string DEFLATE_SETTING = "DEFLATE"; - public const string UIDPLUS = "UIDPLUS"; + public const string IDLE = "IDLE"; + public const string NAMESPACE = "NAMESPACE"; public const string SPECIAL_USE = "SPECIAL-USE"; - + public const string STARTTLS = "STARTTLS"; + public const string UIDPLUS = "UIDPLUS"; + public const string XLIST = "XLIST"; + public const string NAME_SEPARATOR = "="; public const string? VALUE_SEPARATOR = null; - + + public int revision { get; private set; } - + + /** * Creates an empty set of capabilities. revision represents the different variations of * capabilities that an IMAP session might offer (i.e. changes after login or STARTTLS, for @@ -25,7 +33,7 @@ */ public Capabilities(int revision) { base (NAME_SEPARATOR, VALUE_SEPARATOR); - + this.revision = revision; } @@ -36,7 +44,7 @@ public override string to_string() { return "#%d: %s".printf(revision, base.to_string()); } - + /** * Indicates the {@link ClientSession} reported support for IDLE. * @@ -45,7 +53,7 @@ public bool supports_idle() { return has_capability(IDLE); } - + /** * Indicates the {@link ClientSession} reported support for UIDPLUS. * @@ -54,7 +62,7 @@ public bool supports_uidplus() { return has_capability(UIDPLUS); } - + /** * Indicates the {@link ClientSession} reported support for SPECIAL-USE. * diff -Nru geary-0.12.4/src/engine/imap/response/imap-continuation-response.vala geary-3.32.0/src/engine/imap/response/imap-continuation-response.vala --- geary-0.12.4/src/engine/imap/response/imap-continuation-response.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/response/imap-continuation-response.vala 2019-03-17 13:39:29.000000000 +0000 @@ -18,7 +18,7 @@ private ContinuationResponse() { base (Tag.get_continuation()); } - + /** * Converts the {@link RootParameters} into a {@link ContinuationResponse}. * @@ -27,17 +27,17 @@ */ public ContinuationResponse.migrate(RootParameters root) throws ImapError { base.migrate(root); - + if (!tag.is_continuation()) throw new ImapError.INVALID("Tag %s is not a continuation", tag.to_string()); } - + /** * Returns true if the {@link RootParameters}'s {@link Tag} is a continuation character ("+"). */ public static bool is_continuation_response(RootParameters root) { Tag? tag = root.get_tag(); - + return tag != null ? tag.is_continuation() : false; } } diff -Nru geary-0.12.4/src/engine/imap/response/imap-fetch-data-decoder.vala geary-3.32.0/src/engine/imap/response/imap-fetch-data-decoder.vala --- geary-0.12.4/src/engine/imap/response/imap-fetch-data-decoder.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/response/imap-fetch-data-decoder.vala 2019-03-17 13:39:29.000000000 +0000 @@ -18,11 +18,11 @@ public abstract class Geary.Imap.FetchDataDecoder : BaseObject { public FetchDataSpecifier data_item { get; private set; } - - public FetchDataDecoder(FetchDataSpecifier data_item) { + + protected FetchDataDecoder(FetchDataSpecifier data_item) { this.data_item = data_item; } - + /* * The default implementation determines the type of the parameter and calls the appropriate * virtual function; most implementations of a FetchResponseDecoder shouldn't need to override @@ -32,11 +32,11 @@ StringParameter? stringp = param as StringParameter; if (stringp != null) return decode_string(stringp); - + ListParameter? listp = param as ListParameter; if (listp != null) return decode_list(listp); - + LiteralParameter? literalp = param as LiteralParameter; if (literalp != null) { // because this method is called without the help of get_as_string() (which converts @@ -50,30 +50,30 @@ if (!(imap_err is ImapError.TYPE_ERROR)) throw imap_err; } - + return decode_literal(literalp); } - + NilParameter? nilp = param as NilParameter; if (nilp != null) return decode_nil(nilp); - + // bad news; this means this function isn't handling a Parameter type properly assert_not_reached(); } - + protected virtual MessageData decode_string(StringParameter param) throws ImapError { throw new ImapError.TYPE_ERROR("%s does not accept a string parameter", data_item.to_string()); } - + protected virtual MessageData decode_list(ListParameter list) throws ImapError { throw new ImapError.TYPE_ERROR("%s does not accept a list parameter", data_item.to_string()); } - + protected virtual MessageData decode_literal(LiteralParameter literal) throws ImapError { throw new ImapError.TYPE_ERROR("%s does not accept a literal parameter", data_item.to_string()); } - + protected virtual MessageData decode_nil(NilParameter nil) throws ImapError { throw new ImapError.TYPE_ERROR("%s does not accept a nil parameter", data_item.to_string()); } @@ -83,7 +83,7 @@ public UIDDecoder() { base (FetchDataSpecifier.UID); } - + protected override MessageData decode_string(StringParameter stringp) throws ImapError { return new UID.checked(stringp.as_int64()); } @@ -93,12 +93,12 @@ public MessageFlagsDecoder() { base (FetchDataSpecifier.FLAGS); } - + protected override MessageData decode_list(ListParameter listp) throws ImapError { Gee.List flags = new Gee.ArrayList(); for (int ctr = 0; ctr < listp.size; ctr++) flags.add(new MessageFlag(listp.get_as_string(ctr).ascii)); - + return new MessageFlags(flags); } } @@ -107,7 +107,7 @@ public InternalDateDecoder() { base (FetchDataSpecifier.INTERNALDATE); } - + protected override MessageData decode_string(StringParameter stringp) throws ImapError { return InternalDate.decode(stringp.ascii); } @@ -117,7 +117,7 @@ public RFC822SizeDecoder() { base (FetchDataSpecifier.RFC822_SIZE); } - + protected override MessageData decode_string(StringParameter stringp) throws ImapError { return new RFC822Size(stringp.as_int64(0, int64.MAX)); } @@ -127,7 +127,7 @@ public EnvelopeDecoder() { base (FetchDataSpecifier.ENVELOPE); } - + protected override MessageData decode_list(ListParameter listp) throws ImapError { StringParameter? sent = listp.get_as_nullable_string(0); StringParameter subject = listp.get_as_empty_string(1); @@ -139,22 +139,35 @@ ListParameter? bcc = listp.get_as_nullable_list(7); StringParameter? in_reply_to = listp.get_as_nullable_string(8); StringParameter? message_id = listp.get_as_nullable_string(9); - + // Although Message-ID is required to be returned by IMAP, it may be blank if the email // does not supply it (optional according to RFC822); deal with this cognitive dissonance if (message_id != null && message_id.is_empty()) message_id = null; - - return new Envelope((sent != null) ? new Geary.RFC822.Date(sent.ascii) : null, + + Geary.RFC822.Date? sent_date = null; + if (sent != null) { + try { + sent_date = new RFC822.Date(sent.ascii); + } catch (GLib.Error err) { + debug( + "Error parsing sent date from FETCH envelope: %s", + err.message + ); + } + } + + return new Envelope( + sent_date, new Geary.RFC822.Subject.decode(subject.ascii), parse_addresses(from), parse_addresses(sender), parse_addresses(reply_to), - (to != null) ? parse_addresses(to) : null, + (to != null) ? parse_addresses(to) : null, (cc != null) ? parse_addresses(cc) : null, (bcc != null) ? parse_addresses(bcc) : null, (in_reply_to != null) ? new Geary.RFC822.MessageIDList.from_rfc822_string(in_reply_to.ascii) : null, (message_id != null) ? new Geary.RFC822.MessageID(message_id.ascii) : null); } - + // TODO: This doesn't handle group lists (see Johnson, p.268) -- this will throw an // ImapError.TYPE_ERROR if this occurs. private Geary.RFC822.MailboxAddresses? parse_addresses(ListParameter listp) throws ImapError { @@ -165,7 +178,7 @@ StringParameter? source_route = fields.get_as_nullable_string(1); StringParameter mailbox = fields.get_as_empty_string(2); StringParameter domain = fields.get_as_empty_string(3); - + Geary.RFC822.MailboxAddress addr = new Geary.RFC822.MailboxAddress.imap( (name != null) ? name.nullable_ascii : null, (source_route != null) ? source_route.nullable_ascii : null, @@ -173,7 +186,7 @@ domain.ascii); list.add(addr); } - + return new Geary.RFC822.MailboxAddresses(list); } } @@ -182,7 +195,7 @@ public RFC822HeaderDecoder() { base (FetchDataSpecifier.RFC822_HEADER); } - + protected override MessageData decode_literal(LiteralParameter literalp) throws ImapError { return new Geary.Imap.RFC822Header(literalp.get_buffer()); } @@ -192,11 +205,11 @@ public RFC822TextDecoder() { base (FetchDataSpecifier.RFC822_TEXT); } - + protected override MessageData decode_literal(LiteralParameter literalp) throws ImapError { return new Geary.Imap.RFC822Text(literalp.get_buffer()); } - + protected override MessageData decode_nil(NilParameter nilp) throws ImapError { return new Geary.Imap.RFC822Text(Memory.EmptyBuffer.instance); } @@ -206,7 +219,7 @@ public RFC822FullDecoder() { base (FetchDataSpecifier.RFC822); } - + protected override MessageData decode_literal(LiteralParameter literalp) throws ImapError { return new Geary.Imap.RFC822Full(literalp.get_buffer()); } diff -Nru geary-0.12.4/src/engine/imap/response/imap-fetched-data.vala geary-3.32.0/src/engine/imap/response/imap-fetched-data.vala --- geary-0.12.4/src/engine/imap/response/imap-fetched-data.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/response/imap-fetched-data.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,7 +1,7 @@ /* Copyright 2016 Software Freedom Conservancy Inc. * * This software is licensed under the GNU Lesser General Public License - * (version 2.1 or later). See the COPYING file in this distribution. + * (version 2.1 or later). See the COPYING file in this distribution. */ /** @@ -17,7 +17,7 @@ * The positional address of the email in the mailbox. */ public SequenceNumber seq_num { get; private set; } - + /** * A Map of {@link FetchDataSpecifier}s to their {@link Imap.MessageData} for this email. * @@ -25,17 +25,17 @@ */ public Gee.Map data_map { get; private set; default = new Gee.HashMap(); } - + /** * List of {@link FetchBodyDataSpecifier} responses. */ public Gee.Map body_data_map { get; private set; default = new Gee.HashMap(); } - + public FetchedData(SequenceNumber seq_num) { this.seq_num = seq_num; } - + /** * Decodes {@link ServerData} into a FetchedData representation. * @@ -48,25 +48,25 @@ public static FetchedData decode(ServerData server_data) throws ImapError { if (!server_data.get_as_string(2).equals_ci(FetchCommand.NAME)) throw new ImapError.PARSE_ERROR("Not FETCH data: %s", server_data.to_string()); - + FetchedData fetched_data = new FetchedData( new SequenceNumber.checked(server_data.get_as_string(1).as_int64())); - + // walk the list for each returned fetch data item, which is paired by its data item name // and the structured data itself ListParameter list = server_data.get_as_list(3); for (int ctr = 0; ctr < list.size; ctr += 2) { StringParameter data_item_param = list.get_as_string(ctr); - + // watch for truncated lists, which indicate an empty return value bool has_value = (ctr < (list.size - 1)); - + if (FetchBodyDataSpecifier.is_fetch_body_data_specifier(data_item_param)) { // "fake" the identifier by merely dropping in the StringParameter wholesale ... // this works because FetchBodyDataIdentifier does case-insensitive comparisons ... // other munging may be required if this isn't sufficient FetchBodyDataSpecifier specifier = FetchBodyDataSpecifier.deserialize_response(data_item_param); - + if (has_value) fetched_data.body_data_map.set(specifier, list.get_as_empty_buffer(ctr + 1)); else @@ -77,10 +77,10 @@ if (decoder == null) { debug("Unable to decode fetch response for \"%s\": No decoder available", data_item.to_string()); - + continue; } - + // watch for empty return values if (has_value) fetched_data.data_map.set(data_item, decoder.decode(list.get_required(ctr + 1))); @@ -88,10 +88,10 @@ fetched_data.data_map.set(data_item, decoder.decode(NilParameter.instance)); } } - + return fetched_data; } - + /** * Returns the merge of this {@link FetchedData} and the supplied one. * @@ -105,7 +105,7 @@ public FetchedData? combine(FetchedData other) { if (!seq_num.equal_to(other.seq_num)) return null; - + FetchedData combined = new FetchedData(seq_num); Collection.map_set_all(combined.data_map, data_map); Collection.map_set_all(combined.data_map, other.data_map); @@ -113,21 +113,21 @@ body_data_map); Collection.map_set_all(combined.body_data_map, other.body_data_map); - + return combined; } - + public string to_string() { StringBuilder builder = new StringBuilder(); - + builder.append_printf("[%s] ", seq_num.to_string()); - + foreach (FetchDataSpecifier data_type in data_map.keys) builder.append_printf("%s=%s ", data_type.to_string(), data_map.get(data_type).to_string()); - + foreach (FetchBodyDataSpecifier specifier in body_data_map.keys) builder.append_printf("%s=%lu ", specifier.to_string(), body_data_map.get(specifier).size); - + return builder.str; } } diff -Nru geary-0.12.4/src/engine/imap/response/imap-mailbox-attributes.vala geary-3.32.0/src/engine/imap/response/imap-mailbox-attributes.vala --- geary-0.12.4/src/engine/imap/response/imap-mailbox-attributes.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/response/imap-mailbox-attributes.vala 2019-03-17 13:39:29.000000000 +0000 @@ -22,11 +22,11 @@ public bool is_no_select { get { return contains(MailboxAttribute.NO_SELECT) || contains(MailboxAttribute.NONEXISTENT); } } - + public MailboxAttributes(Gee.Collection attrs) { base (attrs); } - + /** * Create {@link MailboxAttributes} from a {@link ListParameter} of attribute strings. */ @@ -34,26 +34,26 @@ Gee.Collection list = new Gee.ArrayList(); for (int ctr = 0; ctr < listp.size; ctr++) list.add(new MailboxAttribute(listp.get_as_string(ctr).ascii)); - + return new MailboxAttributes(list); } - + /** * Create {@link MailboxAttributes} from a flat string of space-delimited attributes. */ public static MailboxAttributes deserialize(string? str) { if (String.is_empty(str)) return new MailboxAttributes(new Gee.ArrayList()); - + string[] tokens = str.split(" "); - + Gee.Collection attrs = new Gee.ArrayList(); foreach (string token in tokens) attrs.add(new MailboxAttribute(token)); - + return new MailboxAttributes(attrs); } - + /** * Search the {@link MailboxAttributes} looking for an XLIST-style * {@link Geary.SpecialFolderType}. @@ -61,40 +61,37 @@ public Geary.SpecialFolderType get_special_folder_type() { if (contains(MailboxAttribute.SPECIAL_FOLDER_INBOX)) return Geary.SpecialFolderType.INBOX; - + if (contains(MailboxAttribute.SPECIAL_FOLDER_ALL_MAIL)) return Geary.SpecialFolderType.ALL_MAIL; - + if (contains(MailboxAttribute.SPECIAL_FOLDER_TRASH)) return Geary.SpecialFolderType.TRASH; - + if (contains(MailboxAttribute.SPECIAL_FOLDER_DRAFTS)) return Geary.SpecialFolderType.DRAFTS; - + if (contains(MailboxAttribute.SPECIAL_FOLDER_SENT)) return Geary.SpecialFolderType.SENT; - + + if (contains(MailboxAttribute.SPECIAL_FOLDER_JUNK)) + return Geary.SpecialFolderType.SPAM; + if (contains(MailboxAttribute.SPECIAL_FOLDER_SPAM)) return Geary.SpecialFolderType.SPAM; - + if (contains(MailboxAttribute.SPECIAL_FOLDER_STARRED)) return Geary.SpecialFolderType.FLAGGED; - + if (contains(MailboxAttribute.SPECIAL_FOLDER_IMPORTANT)) return Geary.SpecialFolderType.IMPORTANT; - - if (contains(MailboxAttribute.SPECIAL_FOLDER_ALL)) - return Geary.SpecialFolderType.ALL_MAIL; - + if (contains(MailboxAttribute.SPECIAL_FOLDER_ARCHIVE)) return Geary.SpecialFolderType.ARCHIVE; - + if (contains(MailboxAttribute.SPECIAL_FOLDER_FLAGGED)) return Geary.SpecialFolderType.FLAGGED; - - if (contains(MailboxAttribute.SPECIAL_FOLDER_JUNK)) - return Geary.SpecialFolderType.SPAM; - + return Geary.SpecialFolderType.NONE; } } diff -Nru geary-0.12.4/src/engine/imap/response/imap-mailbox-attribute.vala geary-3.32.0/src/engine/imap/response/imap-mailbox-attribute.vala --- geary-0.12.4/src/engine/imap/response/imap-mailbox-attribute.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/response/imap-mailbox-attribute.vala 2019-03-17 13:39:29.000000000 +0000 @@ -18,92 +18,92 @@ public static MailboxAttribute NO_INFERIORS { get { if (_no_inferiors == null) _no_inferiors = new MailboxAttribute("\\noinferiors"); - + return _no_inferiors; } } - + private static MailboxAttribute? _nonexistent = null; public static MailboxAttribute NONEXISTENT { get { return (_nonexistent != null) ? _nonexistent : _nonexistent = new MailboxAttribute("\\NonExistent"); } } - + private static MailboxAttribute? _no_select = null; public static MailboxAttribute NO_SELECT { get { if (_no_select == null) _no_select = new MailboxAttribute("\\noselect"); - + return _no_select; } } - + private static MailboxAttribute? _marked = null; public static MailboxAttribute MARKED { get { if (_marked == null) _marked = new MailboxAttribute("\\marked"); - + return _marked; } } - + private static MailboxAttribute? _unmarked = null; public static MailboxAttribute UNMARKED { get { if (_unmarked == null) _unmarked = new MailboxAttribute("\\unmarked"); - + return _unmarked; } } - + private static MailboxAttribute? _has_no_children = null; public static MailboxAttribute HAS_NO_CHILDREN { get { if (_has_no_children == null) _has_no_children = new MailboxAttribute("\\hasnochildren"); - + return _has_no_children; } } - + private static MailboxAttribute? _has_children = null; public static MailboxAttribute HAS_CHILDREN { get { if (_has_children == null) _has_children = new MailboxAttribute("\\haschildren"); - + return _has_children; } } - + private static MailboxAttribute? _allows_new = null; public static MailboxAttribute ALLOWS_NEW { get { if (_allows_new == null) _allows_new = new MailboxAttribute("\\*"); - + return _allows_new; } } - + private static MailboxAttribute? _xlist_inbox = null; public static MailboxAttribute SPECIAL_FOLDER_INBOX { get { if (_xlist_inbox == null) _xlist_inbox = new MailboxAttribute("\\Inbox"); - + return _xlist_inbox; } } - + private static MailboxAttribute? _xlist_all_mail = null; public static MailboxAttribute SPECIAL_FOLDER_ALL_MAIL { get { if (_xlist_all_mail == null) _xlist_all_mail = new MailboxAttribute("\\AllMail"); - + return _xlist_all_mail; } } - + private static MailboxAttribute? _xlist_trash = null; public static MailboxAttribute SPECIAL_FOLDER_TRASH { get { if (_xlist_trash == null) _xlist_trash = new MailboxAttribute("\\Trash"); - + return _xlist_trash; } } - + private static MailboxAttribute? _xlist_drafts = null; public static MailboxAttribute SPECIAL_FOLDER_DRAFTS { get { if (_xlist_drafts == null) _xlist_drafts = new MailboxAttribute("\\Drafts"); - + return _xlist_drafts; } } @@ -111,7 +111,7 @@ public static MailboxAttribute SPECIAL_FOLDER_SENT { get { if (_xlist_sent == null) _xlist_sent = new MailboxAttribute("\\Sent"); - + return _xlist_sent; } } @@ -119,54 +119,54 @@ public static MailboxAttribute SPECIAL_FOLDER_SPAM { get { if (_xlist_spam == null) _xlist_spam = new MailboxAttribute("\\Spam"); - + return _xlist_spam; } } - + private static MailboxAttribute? _xlist_starred = null; public static MailboxAttribute SPECIAL_FOLDER_STARRED { get { if (_xlist_starred == null) _xlist_starred = new MailboxAttribute("\\Starred"); - + return _xlist_starred; } } - + private static MailboxAttribute? _xlist_important = null; public static MailboxAttribute SPECIAL_FOLDER_IMPORTANT { get { if (_xlist_important == null) _xlist_important = new MailboxAttribute("\\Important"); - + return _xlist_important; } } - + private static MailboxAttribute? _special_use_all = null; public static MailboxAttribute SPECIAL_FOLDER_ALL { get { return (_special_use_all != null) ? _special_use_all : _special_use_all = new MailboxAttribute("\\All"); } } - + private static MailboxAttribute? _special_use_archive = null; public static MailboxAttribute SPECIAL_FOLDER_ARCHIVE { get { return (_special_use_archive != null) ? _special_use_archive : _special_use_archive = new MailboxAttribute("\\Archive"); } } - + private static MailboxAttribute? _special_use_flagged = null; public static MailboxAttribute SPECIAL_FOLDER_FLAGGED { get { return (_special_use_flagged != null) ? _special_use_flagged : _special_use_flagged = new MailboxAttribute("\\Flagged"); } } - + private static MailboxAttribute? _special_use_junk = null; public static MailboxAttribute SPECIAL_FOLDER_JUNK { get { return (_special_use_junk != null) ? _special_use_junk : _special_use_junk = new MailboxAttribute("\\Junk"); } } - + public MailboxAttribute(string value) { base (value); } - + // Call these at init time to prevent thread issues internal static void init() { MailboxAttribute to_init = NO_INFERIORS; diff -Nru geary-0.12.4/src/engine/imap/response/imap-mailbox-information.vala geary-3.32.0/src/engine/imap/response/imap-mailbox-information.vala --- geary-0.12.4/src/engine/imap/response/imap-mailbox-information.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/response/imap-mailbox-information.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,7 +1,7 @@ /* Copyright 2016 Software Freedom Conservancy Inc. * * This software is licensed under the GNU Lesser General Public License - * (version 2.1 or later). See the COPYING file in this distribution. + * (version 2.1 or later). See the COPYING file in this distribution. */ /** @@ -19,23 +19,23 @@ * Name of the mailbox. */ public MailboxSpecifier mailbox { get; private set; } - + /** * The (optional) delimiter specified by the server. */ public string? delim { get; private set; } - + /** * Folder attributes returned by the server. */ public MailboxAttributes attrs { get; private set; } - + public MailboxInformation(MailboxSpecifier mailbox, string? delim, MailboxAttributes attrs) { this.mailbox = mailbox; this.delim = delim; this.attrs = attrs; } - + /** * Decodes {@link ServerData} into a MailboxInformation representation. * @@ -52,7 +52,7 @@ StringParameter cmd = server_data.get_as_string(1); if (!cmd.equals_ci(ListCommand.NAME) && !cmd.equals_ci(ListCommand.XLIST_NAME)) throw new ImapError.PARSE_ERROR("Not LIST or XLIST data: %s", server_data.to_string()); - + // Build list of attributes ListParameter attrs = server_data.get_as_list(2); Gee.Collection attrlist = new Gee.ArrayList(); @@ -61,13 +61,13 @@ if (stringp == null) { debug("Bad list attribute \"%s\": Attribute not a string value", server_data.to_string()); - + continue; } - + attrlist.add(new MailboxAttribute(stringp.ascii)); } - + // decode everything MailboxAttributes attributes = new MailboxAttributes(attrlist); StringParameter? delim = server_data.get_as_nullable_string(3); @@ -86,19 +86,8 @@ ); } - /** - * The {@link Geary.FolderPath} for the {@link mailbox}. - * - * This is constructed from the supplied {@link mailbox} and {@link delim} returned from the - * server. If the mailbox is the same as the supplied inbox_specifier, a canonical name for - * the Inbox is returned. - */ - public Geary.FolderPath get_path(MailboxSpecifier? inbox_specifier) { - return mailbox.to_folder_path(delim, inbox_specifier); - } - public string to_string() { return "%s/%s".printf(mailbox.to_string(), attrs.to_string()); } -} +} diff -Nru geary-0.12.4/src/engine/imap/response/imap-namespace-response.vala geary-3.32.0/src/engine/imap/response/imap-namespace-response.vala --- geary-0.12.4/src/engine/imap/response/imap-namespace-response.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/src/engine/imap/response/imap-namespace-response.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,86 @@ +/* + * Copyright 2016 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +/** + * Response for a NAMESPACE command. + * + * @see Geary.Imap.NamespaceCommand + */ +public class Geary.Imap.NamespaceResponse : BaseObject { + + + public Gee.List? personal { get; private set; default = null; } + public Gee.List? user { get; private set; default = null; } + public Gee.List? shared { get; private set; default = null; } + + + /** + * Decodes {@link ServerData} into a NamespaceResponse representation. + * + * The ServerData must be the response to a NAMESPACE command. + * + * @see ServerData.get_list + */ + public static NamespaceResponse decode(ServerData server_data) throws ImapError { + StringParameter cmd = server_data.get_as_string(1); + if (!cmd.equals_ci(NamespaceCommand.NAME)) + throw new ImapError.PARSE_ERROR( + "Not NAMESPACE data: %s", server_data.to_string() + ); + + if (server_data.size <= 2) { + throw new ImapError.PARSE_ERROR( + "No NAMESPACEs provided: %s", server_data.to_string() + ); + } + + ListParameter? personal = server_data.get_as_nullable_list(2); + ListParameter? user = null; + if (server_data.size >= 4) { + user = server_data.get_as_nullable_list(3); + } + ListParameter? shared = null; + if (server_data.size >= 5) { + shared = server_data.get_as_nullable_list(4); + } + + return new NamespaceResponse( + parse_namespaces(personal), + user != null ? parse_namespaces(user) : null, + shared != null ? parse_namespaces(shared) : null + ); + } + + private static Gee.List? parse_namespaces(ListParameter? list) throws ImapError { + Gee.List? nss = null; + if (list != null) { + nss = new Gee.ArrayList(); + for (int i = 0; i < list.size; i++) { + nss.add(parse_namespace(list.get_as_list(i))); + } + } + return nss; + } + + private static Namespace? parse_namespace(ListParameter? list) throws ImapError { + Namespace? ns = null; + if (list != null && list.size >= 1) { + ns = new Namespace( + list.get_as_string(0).ascii, + list.get_as_nullable_string(1).nullable_ascii + ); + } + return ns; + } + + public NamespaceResponse(Gee.List? personal, Gee.List? user, Gee.List? shared) { + this.personal = personal; + this.user = user; + this.shared = shared; + } + +} diff -Nru geary-0.12.4/src/engine/imap/response/imap-response-code-type.vala geary-3.32.0/src/engine/imap/response/imap-response-code-type.vala --- geary-0.12.4/src/engine/imap/response/imap-response-code-type.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/response/imap-response-code-type.vala 2019-03-17 13:39:29.000000000 +0000 @@ -37,18 +37,18 @@ public const string UIDNEXT = "uidnext"; public const string UNAVAILABLE = "unavailable"; public const string UNSEEN = "unseen"; - + /** * The original response code value submitted to the object (possibly off-the-wire). */ public string original { get; private set; } - + /** * The response code value set to lowercase, making it easy to compare to constant strings * in a uniform way. */ public string value { get; private set; } - + /** * Throws an {@link ImapError.INVALID} if the string cannot be represented as an * {link ResponseCodeType}. @@ -56,7 +56,7 @@ public ResponseCodeType(string value) throws ImapError { init(value); } - + /** * Throws an {@link ImapError.INVALID} if the {@link StringParameter} cannot be represented as * an {link ResponseCodeType}. @@ -64,33 +64,33 @@ public ResponseCodeType.from_parameter(StringParameter stringp) throws ImapError { init(stringp.ascii); } - + private void init(string ascii) throws ImapError { // note that is_quoting_required() also catches empty strings (as they require quoting) if (DataFormat.is_quoting_required(ascii) != DataFormat.Quoting.OPTIONAL) throw new ImapError.INVALID("\"%s\" cannot be represented as a ResponseCodeType", ascii); - + // store lowercased so it's easily compared with const strings above original = ascii; value = Ascii.strdown(ascii); } - + public bool is_value(string str) { return Ascii.stri_equal(value, str); } - + public StringParameter to_parameter() { return new AtomParameter(original); } - + public bool equal_to(ResponseCodeType other) { return (this == other) ? true : Ascii.stri_equal(value, other.value); } - + public uint hash() { return Ascii.stri_hash(value); } - + public string to_string() { return value; } diff -Nru geary-0.12.4/src/engine/imap/response/imap-response-code.vala geary-3.32.0/src/engine/imap/response/imap-response-code.vala --- geary-0.12.4/src/engine/imap/response/imap-response-code.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/response/imap-response-code.vala 2019-03-17 13:39:29.000000000 +0000 @@ -13,11 +13,11 @@ public class Geary.Imap.ResponseCode : Geary.Imap.ListParameter { public ResponseCode() { } - + public ResponseCodeType get_response_code_type() throws ImapError { return new ResponseCodeType.from_parameter(get_as_string(0)); } - + /** * Converts the {@link ResponseCode} into a UIDNEXT {@link UID}, if possible. * @@ -26,10 +26,10 @@ public UID get_uid_next() throws ImapError { if (!get_response_code_type().is_value(ResponseCodeType.UIDNEXT)) throw new ImapError.INVALID("Not UIDNEXT: %s", to_string()); - + return new UID.checked(get_as_string(1).as_int64()); } - + /** * Converts the {@link ResponseCode} into a {@link UIDValidity}, if possible. * @@ -38,10 +38,10 @@ public UIDValidity get_uid_validity() throws ImapError { if (!get_response_code_type().is_value(ResponseCodeType.UIDVALIDITY)) throw new ImapError.INVALID("Not UIDVALIDITY: %s", to_string()); - + return new UIDValidity.checked(get_as_string(1).as_int64()); } - + /** * Converts the {@link ResponseCode} into an UNSEEN value, if possible. * @@ -50,10 +50,10 @@ public int get_unseen() throws ImapError { if (!get_response_code_type().is_value(ResponseCodeType.UNSEEN)) throw new ImapError.INVALID("Not UNSEEN: %s", to_string()); - + return get_as_string(1).as_int32(0, int.MAX); } - + /** * Converts the {@link ResponseCode} into PERMANENTFLAGS {@link MessageFlags}, if possible. * @@ -62,10 +62,10 @@ public MessageFlags get_permanent_flags() throws ImapError { if (!get_response_code_type().is_value(ResponseCodeType.PERMANENT_FLAGS)) throw new ImapError.INVALID("Not PERMANENTFLAGS: %s", to_string()); - + return MessageFlags.from_list(get_as_list(1)); } - + /** * Parses the {@link ResponseCode} into {@link Capabilities}, if possible. * @@ -78,17 +78,17 @@ public Capabilities get_capabilities(ref int next_revision) throws ImapError { if (!get_response_code_type().is_value(ResponseCodeType.CAPABILITY)) throw new ImapError.INVALID("Not CAPABILITY response code: %s", to_string()); - + Capabilities capabilities = new Capabilities(next_revision++); for (int ctr = 1; ctr < size; ctr++) { StringParameter? param = get_if_string(ctr); if (param != null) capabilities.add_parameter(param); } - + return capabilities; } - + /** * Parses the {@link ResponseCode} into UIDPLUS' COPYUID response, if possible. * @@ -103,20 +103,20 @@ out Gee.List? destination_uids) throws ImapError { if (!get_response_code_type().is_value(ResponseCodeType.COPYUID)) throw new ImapError.INVALID("Not COPYUID response code: %s", to_string()); - + uidvalidity = new UIDValidity.checked(get_as_number(1).as_int64()); source_uids = MessageSet.uid_parse(get_as_string(2).ascii); destination_uids = MessageSet.uid_parse(get_as_string(3).ascii); } - + public override string to_string() { return "[%s]".printf(stringize_list()); } - - public override void serialize(Serializer ser, Tag tag) throws Error { - ser.push_ascii('['); - serialize_list(ser, tag); - ser.push_ascii(']'); + + public override void serialize(Serializer ser, GLib.Cancellable cancellable) + throws GLib.Error { + ser.push_ascii('[', cancellable); + serialize_list(ser, cancellable); + ser.push_ascii(']', cancellable); } } - diff -Nru geary-0.12.4/src/engine/imap/response/imap-server-data-type.vala geary-3.32.0/src/engine/imap/response/imap-server-data-type.vala --- geary-0.12.4/src/engine/imap/response/imap-server-data-type.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/response/imap-server-data-type.vala 2019-03-17 13:39:29.000000000 +0000 @@ -18,51 +18,55 @@ FLAGS, LIST, LSUB, + NAMESPACE, RECENT, SEARCH, STATUS, XLIST; - + public string to_string() { switch (this) { case CAPABILITY: return "capability"; - + case EXISTS: return "exists"; - + case EXPUNGE: return "expunge"; - + case FETCH: return "fetch"; - + case FLAGS: return "flags"; - + case LIST: return "list"; - + case LSUB: return "lsub"; - + + case NAMESPACE: + return "namespace"; + case RECENT: return "recent"; - + case SEARCH: return "search"; - + case STATUS: return "status"; - + case XLIST: return "xlist"; - + default: assert_not_reached(); } } - + /** * Convert a {@link StringParameter} into a ServerDataType. * @@ -72,47 +76,50 @@ switch (param.as_lower()) { case "capability": return CAPABILITY; - + case "exists": return EXISTS; - + case "expunge": case "expunged": return EXPUNGE; - + case "fetch": return FETCH; - + case "flags": return FLAGS; - + case "list": return LIST; - + case "lsub": return LSUB; - + + case "namespace": + return NAMESPACE; + case "recent": return RECENT; - + case "search": return SEARCH; - + case "status": return STATUS; - + case "xlist": return XLIST; - + default: throw new ImapError.PARSE_ERROR("\"%s\" is not a valid server data type", param.to_string()); } } - + public StringParameter to_parameter() { return new AtomParameter(to_string()); } - + /** * Examines the {@link RootParameters} looking for a ServerDataType. * @@ -127,53 +134,56 @@ switch (firstparam.as_lower()) { case "capability": return CAPABILITY; - + case "flags": return FLAGS; - + case "list": return LIST; - + case "lsub": return LSUB; - + + case "namespace": + return NAMESPACE; + case "search": return SEARCH; - + case "status": return STATUS; - + case "xlist": return XLIST; - + default: // fall-through break; } } - + StringParameter? secondparam = root.get_if_string(2); if (secondparam != null) { switch (secondparam.as_lower()) { case "exists": return EXISTS; - + case "expunge": case "expunged": return EXPUNGE; - + case "fetch": return FETCH; - + case "recent": return RECENT; - + default: // fall-through break; } } - + throw new ImapError.PARSE_ERROR("\"%s\" unrecognized server data", root.to_string()); } } diff -Nru geary-0.12.4/src/engine/imap/response/imap-server-data.vala geary-3.32.0/src/engine/imap/response/imap-server-data.vala --- geary-0.12.4/src/engine/imap/response/imap-server-data.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/response/imap-server-data.vala 2019-03-17 13:39:29.000000000 +0000 @@ -12,13 +12,13 @@ public class Geary.Imap.ServerData : ServerResponse { public ServerDataType server_data_type { get; private set; } - + private ServerData(Tag tag, ServerDataType server_data_type) { base (tag); - + this.server_data_type = server_data_type; } - + /** * Converts the {@link RootParameters} into {@link ServerData}. * @@ -27,26 +27,26 @@ */ public ServerData.migrate(RootParameters root) throws ImapError { base.migrate(root); - + server_data_type = ServerDataType.from_response(this); } - + /** * Returns true if {@link RootParameters} is recognized by {@link ServerDataType.from_response}. */ public static bool is_server_data(RootParameters root) { if (!root.has_tag()) return false; - + try { ServerDataType.from_response(root); - + return true; } catch (ImapError ierr) { return false; } } - + /** * Parses the {@link ServerData} into {@link Capabilities}, if possible. * @@ -59,17 +59,17 @@ public Capabilities get_capabilities(ref int next_revision) throws ImapError { if (server_data_type != ServerDataType.CAPABILITY) throw new ImapError.INVALID("Not CAPABILITY data: %s", to_string()); - + Capabilities capabilities = new Capabilities(next_revision++); for (int ctr = 2; ctr < size; ctr++) { StringParameter? param = get_if_string(ctr); if (param != null) capabilities.add_parameter(param); } - + return capabilities; } - + /** * Parses the {@link ServerData} into an {@link ServerDataType.EXISTS} value, if possible. * @@ -78,10 +78,10 @@ public int get_exists() throws ImapError { if (server_data_type != ServerDataType.EXISTS) throw new ImapError.INVALID("Not EXISTS data: %s", to_string()); - + return get_as_string(1).as_int32(0); } - + /** * Parses the {@link ServerData} into an expunged {@link SequenceNumber}, if possible. * @@ -90,10 +90,10 @@ public SequenceNumber get_expunge() throws ImapError { if (server_data_type != ServerDataType.EXPUNGE) throw new ImapError.INVALID("Not EXPUNGE data: %s", to_string()); - + return new SequenceNumber.checked(get_as_string(1).as_int64()); } - + /** * Parses the {@link ServerData} into {@link FetchedData}, if possible. * @@ -102,10 +102,10 @@ public FetchedData get_fetch() throws ImapError { if (server_data_type != ServerDataType.FETCH) throw new ImapError.INVALID("Not FETCH data: %s", to_string()); - + return FetchedData.decode(this); } - + /** * Parses the {@link ServerData} into {@link MailboxAttributes}, if possible. * @@ -114,10 +114,10 @@ public MailboxAttributes get_flags() throws ImapError { if (server_data_type != ServerDataType.FLAGS) throw new ImapError.INVALID("Not FLAGS data: %s", to_string()); - + return MailboxAttributes.from_list(get_as_list(2)); } - + /** * Parses the {@link ServerData} into {@link MailboxInformation}, if possible. * @@ -126,10 +126,22 @@ public MailboxInformation get_list() throws ImapError { if (server_data_type != ServerDataType.LIST && server_data_type != ServerDataType.XLIST) throw new ImapError.INVALID("Not LIST/XLIST data: %s", to_string()); - + return MailboxInformation.decode(this, true); } - + + /** + * Parses the {@link ServerData} into {@link MailboxInformation}, if possible. + * + * @throws ImapError.INVALID if not a NAMESPACE response. + */ + public NamespaceResponse get_namespace() throws ImapError { + if (server_data_type != ServerDataType.NAMESPACE) + throw new ImapError.INVALID("Not NAMESPACE data: %s", to_string()); + + return NamespaceResponse.decode(this); + } + /** * Parses the {@link ServerData} into a {@link ServerDataType.RECENT} value, if possible. * @@ -138,10 +150,10 @@ public int get_recent() throws ImapError { if (server_data_type != ServerDataType.RECENT) throw new ImapError.INVALID("Not RECENT data: %s", to_string()); - + return get_as_string(1).as_int32(0); } - + /** * Parses the {@link ServerData} into a {@link ServerDataType.SEARCH} value, if possible. * @@ -150,17 +162,17 @@ public int64[] get_search() throws ImapError { if (server_data_type != ServerDataType.SEARCH) throw new ImapError.INVALID("Not SEARCH data: %s", to_string()); - + if (size <= 2) return new int64[0]; - + int64[] results = new int64[size - 2]; for (int ctr = 2; ctr < size; ctr++) results[ctr - 2] = get_as_string(ctr).as_int64(0); - + return results; } - + /** * Parses the {@link ServerData} into {@link StatusData}, if possible. * @@ -169,7 +181,7 @@ public StatusData get_status() throws ImapError { if (server_data_type != ServerDataType.STATUS) throw new ImapError.INVALID("Not STATUS data: %s", to_string()); - + return StatusData.decode(this); } } diff -Nru geary-0.12.4/src/engine/imap/response/imap-server-response.vala geary-3.32.0/src/engine/imap/response/imap-server-response.vala --- geary-0.12.4/src/engine/imap/response/imap-server-response.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/response/imap-server-response.vala 2019-03-17 13:39:29.000000000 +0000 @@ -15,25 +15,25 @@ public abstract class Geary.Imap.ServerResponse : RootParameters { public Tag tag { get; private set; } - + protected ServerResponse(Tag tag) { this.tag = tag; } - + /** * Converts the {@link RootParameters} into a ServerResponse. * * The supplied root is "stripped" of its children. */ - public ServerResponse.migrate(RootParameters root) throws ImapError { + protected ServerResponse.migrate(RootParameters root) throws ImapError { base.migrate(root); - + if (!has_tag()) throw new ImapError.INVALID("Server response does not have a tag token: %s", to_string()); - + tag = get_tag(); } - + /** * Migrate the contents of RootParameters into a new, properly-typed ServerResponse. * @@ -47,13 +47,13 @@ public static ServerResponse migrate_from_server(RootParameters root) throws ImapError { if (ContinuationResponse.is_continuation_response(root)) return new ContinuationResponse.migrate(root); - + if (StatusResponse.is_status_response(root)) return new StatusResponse.migrate(root); - + if (ServerData.is_server_data(root)) return new ServerData.migrate(root); - + throw new ImapError.PARSE_ERROR("Unknown server response: %s", root.to_string()); } } diff -Nru geary-0.12.4/src/engine/imap/response/imap-status-data.vala geary-3.32.0/src/engine/imap/response/imap-status-data.vala --- geary-0.12.4/src/engine/imap/response/imap-status-data.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/response/imap-status-data.vala 2019-03-17 13:39:29.000000000 +0000 @@ -16,41 +16,41 @@ // NOTE: This must be negative one; other values won't work well due to how the values are // decoded public const int UNSET = -1; - + /** * Name of the mailbox. */ public MailboxSpecifier mailbox { get; private set; } - + /** * {@link UNSET} if not set. */ public int messages { get; private set; } - + /** * {@link UNSET} if not set. */ public int recent { get; private set; } - + /** * The UIDNEXT of the mailbox, if returned. * * See [[http://tools.ietf.org/html/rfc3501#section-2.3.1.1]] */ public UID? uid_next { get; private set; } - + /** * The UIDVALIDITY of the mailbox, if returned. * * See [[http://tools.ietf.org/html/rfc3501#section-2.3.1.1]] */ public UIDValidity? uid_validity { get; private set; } - + /** * {@link UNSET} if not set. */ public int unseen { get; private set; } - + public StatusData(MailboxSpecifier mailbox, int messages, int recent, UID? uid_next, UIDValidity? uid_validity, int unseen) { this.mailbox = mailbox; @@ -60,7 +60,7 @@ this.uid_validity = uid_validity; this.unseen = unseen; } - + /** * Decodes {@link ServerData} into a StatusData representation. * @@ -82,37 +82,37 @@ UID? uid_next = null; UIDValidity? uid_validity = null; int unseen = UNSET; - + ListParameter values = server_data.get_as_list(3); for (int ctr = 0; ctr < values.size; ctr += 2) { try { StringParameter typep = values.get_as_string(ctr); StringParameter valuep = values.get_as_string(ctr + 1); - + switch (StatusDataType.from_parameter(typep)) { case StatusDataType.MESSAGES: // see note at UNSET messages = valuep.as_int32(-1, int.MAX); break; - + case StatusDataType.RECENT: // see note at UNSET recent = valuep.as_int32(-1, int.MAX); break; - + case StatusDataType.UIDNEXT: uid_next = new UID.checked(valuep.as_int64()); break; - + case StatusDataType.UIDVALIDITY: uid_validity = new UIDValidity.checked(valuep.as_int64()); break; - + case StatusDataType.UNSEEN: // see note at UNSET unseen = valuep.as_int32(-1, int.MAX); break; - + default: message("Bad STATUS data type %s", typep.to_string()); break; @@ -122,11 +122,11 @@ server_data.to_string(), ierr.message); } } - + return new StatusData(new MailboxSpecifier.from_parameter(mailbox_param), messages, recent, uid_next, uid_validity, unseen); } - + public string to_string() { return "%s/%d/UIDNEXT=%s/UIDVALIDITY=%s".printf(mailbox.to_string(), messages, (uid_next != null) ? uid_next.to_string() : "(none)", diff -Nru geary-0.12.4/src/engine/imap/response/imap-status-response.vala geary-3.32.0/src/engine/imap/response/imap-status-response.vala --- geary-0.12.4/src/engine/imap/response/imap-status-response.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/response/imap-status-response.vala 2019-03-17 13:39:29.000000000 +0000 @@ -23,25 +23,25 @@ * {@link Status.OK}, {@link Status.NO}, or {@link Status.BAD}. */ public bool is_completion { get; private set; default = false; } - + /** * The {@link Status} being reported by the server in this {@link ServerResponse}. */ public Status status { get; private set; } - + /** * An optional {@link ResponseCode} reported by the server in this {@link ServerResponse}. */ public ResponseCode? response_code { get; private set; } - + private StatusResponse(Tag tag, Status status, ResponseCode? response_code) { base (tag); - + this.status = status; this.response_code = response_code; update_is_completion(); } - + /** * Converts the {@link RootParameters} into a {@link StatusResponse}. * @@ -50,12 +50,12 @@ */ public StatusResponse.migrate(RootParameters root) throws ImapError { base.migrate(root); - + status = Status.from_parameter(get_as_string(1)); response_code = get_if_list(2) as ResponseCode; update_is_completion(); } - + private void update_is_completion() { // TODO: Is this too stringent? It means a faulty server could send back a completion // with another Status code and cause the client to treat the command as "unanswered", @@ -68,14 +68,14 @@ case Status.BAD: is_completion = true; break; - + default: // fall through break; } } } - + /** * Returns optional text provided by the server. Note that this text is not internationalized * and probably in English, and is not standard or uniformly declared. It's not recommended @@ -93,20 +93,20 @@ builder.append_c(' '); } } - + return !String.is_empty(builder.str) ? builder.str : null; } - + /** * Returns true if {@link RootParameters} holds a {@link Status} parameter. */ public static bool is_status_response(RootParameters root) { if (!root.has_tag()) return false; - + try { Status.from_parameter(root.get_as_string(1)); - + return true; } catch (ImapError err) { return false; diff -Nru geary-0.12.4/src/engine/imap/response/imap-status.vala geary-3.32.0/src/engine/imap/response/imap-status.vala --- geary-0.12.4/src/engine/imap/response/imap-status.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/response/imap-status.vala 2019-03-17 13:39:29.000000000 +0000 @@ -16,51 +16,51 @@ BAD, PREAUTH, BYE; - + public string to_string() { switch (this) { case OK: return "ok"; - + case NO: return "no"; - + case BAD: return "bad"; - + case PREAUTH: return "preauth"; - + case BYE: return "bye"; - + default: assert_not_reached(); } } - + public static Status from_parameter(StringParameter strparam) throws ImapError { switch (strparam.as_lower()) { case "ok": return OK; - + case "no": return NO; - + case "bad": return BAD; - + case "preauth": return PREAUTH; - + case "bye": return BYE; - + default: throw new ImapError.PARSE_ERROR("Unrecognized status response \"%s\"", strparam.to_string()); } } - + public Parameter to_parameter() { return new AtomParameter(to_string()); } diff -Nru geary-0.12.4/src/engine/imap/transport/imap-client-connection.vala geary-3.32.0/src/engine/imap/transport/imap-client-connection.vala --- geary-0.12.4/src/engine/imap/transport/imap-client-connection.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/transport/imap-client-connection.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,381 +1,224 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. + * Copyright 2018 Michael Gratton * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ public class Geary.Imap.ClientConnection : BaseObject { - public const uint16 DEFAULT_PORT = 143; - public const uint16 DEFAULT_PORT_SSL = 993; - + /** - * This is set very high to allow for IDLE connections to remain connected even when - * there is no traffic on them. The side-effect is that if the physical connection is dropped, - * no error is reported and the connection won't know about it until the next send operation. + * Default socket timeout duration. + * + * This is set to the highest value required by RFC 3501 to allow + * for IDLE connections to remain connected even when there is no + * traffic on them. The side-effect is that if the physical + * connection is dropped, no error is reported and the connection + * won't know about it until the next send operation. * - * RECOMMENDED_TIMEOUT_SEC is more realistic in that if a connection is hung it's important - * to detect it early and drop it, at the expense of more keepalive traffic. + * {@link RECOMMENDED_TIMEOUT_SEC} is more realistic in that if a + * connection is hung it's important to detect it early and drop + * it, at the expense of more keepalive traffic. * - * In general, whatever timeout is used for the ClientConnection must be slightly higher than - * the keepalive timeout used by ClientSession, otherwise the ClientConnection will be dropped - * before the keepalive is sent. + * In general, whatever timeout is used for the ClientConnection + * must be slightly higher than the keepalive timeout used by + * {@link ClientSession}, otherwise the ClientConnection will be + * dropped before the keepalive is sent. */ - public const uint DEFAULT_TIMEOUT_SEC = ClientSession.MIN_KEEPALIVE_SEC + 15; + public const uint DEFAULT_TIMEOUT_SEC = ClientSession.MAX_KEEPALIVE_SEC; + + /** Recommended socket timeout duration. */ public const uint RECOMMENDED_TIMEOUT_SEC = ClientSession.RECOMMENDED_KEEPALIVE_SEC + 15; - + /** - * The default timeout for an issued command to result in a response code from the server. - * - * @see command_timeout_sec + * Default timeout to wait for another command before going idle. */ - public const uint DEFAULT_COMMAND_TIMEOUT_SEC = 30; - - private const int FLUSH_TIMEOUT_MSEC = 10; - - // At least one server out there requires this to be in caps - private const string IDLE_DONE = "DONE"; - - private enum State { - UNCONNECTED, - CONNECTED, - IDLING, - IDLE, - DEIDLING, - DEIDLING_SYNCHRONIZING, - SYNCHRONIZING, - DISCONNECTED, - - COUNT - } - - private static string state_to_string(uint state) { - return ((State) state).to_string(); - } - - private enum Event { - CONNECTED, - DISCONNECTED, - - // Use issue_conditional_event() for SEND events, using the result to determine whether - // or not to continue; the transition handlers do no signalling or I/O - SEND, - SEND_IDLE, - - // To initiate a command continuation request - SYNCHRONIZE, - - // RECVD_* will emit appropriate signals inside their transition handlers; do *not* use - // issue_conditional_event() for these events - RECVD_STATUS_RESPONSE, - RECVD_SERVER_DATA, - RECVD_CONTINUATION_RESPONSE, - - COUNT - } - - private static string event_to_string(uint event) { - return ((Event) event).to_string(); - } - - private static Geary.State.MachineDescriptor machine_desc = new Geary.State.MachineDescriptor( - "Geary.Imap.ClientConnection", State.UNCONNECTED, State.COUNT, Event.COUNT, - state_to_string, event_to_string); - + public const uint DEFAULT_IDLE_TIMEOUT_SEC = 2; + // Used solely for debugging private static int next_cx_id = 0; - - /** - * The timeout in seconds before an uncompleted {@link Command} is considered abandoned. - * - * ClientConnection does not time out the initial greeting from the server (as there's no - * command associated with it). That's the responsibility of the caller. - * - * A timed-out command will result in the connection being forcibly closed. - */ - public uint command_timeout_sec { get; set; default = DEFAULT_COMMAND_TIMEOUT_SEC; } - + + /** * This identifier is used only for debugging, to differentiate connections from one another * in logs and debug output. */ public int cx_id { get; private set; } - + + /** + * Determines if the connection will use IMAP IDLE when idle. + * + * If //true//, when the connection is not sending commands + * ("quiet"), it will issue an IDLE command to enter a state where + * unsolicited server data may be sent from the server without + * resorting to NOOP keepalives. (Note that keepalives are still + * required to hold the connection open, according to the IMAP + * specification.) + */ + public bool idle_when_quiet { get; private set; default = false; } + private Geary.Endpoint endpoint; - private Geary.State.Machine fsm; private SocketConnection? cx = null; private IOStream? ios = null; private Serializer? ser = null; + private BufferedOutputStream? ser_buffer = null; private Deserializer? des = null; - private Geary.Nonblocking.Mutex send_mutex = new Geary.Nonblocking.Mutex(); - private Geary.Nonblocking.Semaphore synchronized_notifier = new Geary.Nonblocking.Semaphore(); - private Geary.Nonblocking.Event idle_notifier = new Geary.Nonblocking.Event(); + private int tag_counter = 0; private char tag_prefix = 'a'; - private uint flush_timeout_id = 0; - private bool idle_when_quiet = false; - private Gee.HashSet posted_idle_tags = new Gee.HashSet(); - private int outstanding_idle_dones = 0; - private Tag? posted_synchronization_tag = null; - private StatusResponse? synchronization_status_response = null; - private bool waiting_for_idle_to_synchronize = false; - private uint timeout_id = 0; - private uint timeout_cmd_count = 0; - private int outstanding_cmds = 0; - + + private Geary.Nonblocking.Queue pending_queue = + new Geary.Nonblocking.Queue.fifo(); + private Gee.Queue sent_queue = new Gee.LinkedList(); + private Command? current_command = null; + private uint command_timeout; + + private TimeoutManager idle_timer; + + private GLib.Cancellable? open_cancellable = null; + + public virtual signal void connected() { Logging.debug(Logging.Flag.NETWORK, "[%s] connected to %s", to_string(), endpoint.to_string()); } - + public virtual signal void disconnected() { Logging.debug(Logging.Flag.NETWORK, "[%s] disconnected from %s", to_string(), endpoint.to_string()); } - + public virtual signal void sent_command(Command cmd) { Logging.debug(Logging.Flag.NETWORK, "[%s S] %s", to_string(), cmd.to_string()); - - // track outstanding Command count to force switching to IDLE only when nothing outstanding - outstanding_cmds++; - } - - public virtual signal void in_idle(bool idling) { - Logging.debug(Logging.Flag.NETWORK, "[%s] in idle: %s", to_string(), idling.to_string()); - - // fire the Event every time the IDLE state changes - idle_notifier.blind_notify(); } - + public virtual signal void received_status_response(StatusResponse status_response) { Logging.debug(Logging.Flag.NETWORK, "[%s R] %s", to_string(), status_response.to_string()); - - // look for command completion, if no outstanding, schedule a flush timeout to switch to - // IDLE mode - if (status_response.is_completion) { - if (--outstanding_cmds == 0) - reschedule_flush_timeout(); - } } - + public virtual signal void received_server_data(ServerData server_data) { Logging.debug(Logging.Flag.NETWORK, "[%s R] %s", to_string(), server_data.to_string()); } - + public virtual signal void received_continuation_response(ContinuationResponse continuation_response) { Logging.debug(Logging.Flag.NETWORK, "[%s R] %s", to_string(), continuation_response.to_string()); } - + public virtual signal void received_bytes(size_t bytes) { // this generates a *lot* of debug logging if one was placed here, so it's not } - + public virtual signal void received_bad_response(RootParameters root, ImapError err) { Logging.debug(Logging.Flag.NETWORK, "[%s] recv bad response %s: %s", to_string(), root.to_string(), err.message); } - + public virtual signal void recv_closed() { Logging.debug(Logging.Flag.NETWORK, "[%s] recv closed", to_string()); } - + public virtual signal void send_failure(Error err) { Logging.debug(Logging.Flag.NETWORK, "[%s] send failure: %s", to_string(), err.message); } - + public virtual signal void receive_failure(Error err) { Logging.debug(Logging.Flag.NETWORK, "[%s] recv failure: %s", to_string(), err.message); } - + public virtual signal void deserialize_failure(Error err) { Logging.debug(Logging.Flag.NETWORK, "[%s] deserialize failure: %s", to_string(), err.message); } - + public virtual signal void close_error(Error err) { Logging.debug(Logging.Flag.NETWORK, "[%s] close error: %s", to_string(), err.message); } - - public ClientConnection(Geary.Endpoint endpoint) { + + + public ClientConnection( + Geary.Endpoint endpoint, + uint command_timeout = Command.DEFAULT_RESPONSE_TIMEOUT_SEC, + uint idle_timeout_sec = DEFAULT_IDLE_TIMEOUT_SEC) { this.endpoint = endpoint; - cx_id = next_cx_id++; - - Geary.State.Mapping[] mappings = { - new Geary.State.Mapping(State.UNCONNECTED, Event.CONNECTED, on_connected), - new Geary.State.Mapping(State.UNCONNECTED, Event.DISCONNECTED, on_disconnected), - - new Geary.State.Mapping(State.CONNECTED, Event.SEND, on_proceed), - new Geary.State.Mapping(State.CONNECTED, Event.SEND_IDLE, on_send_idle), - new Geary.State.Mapping(State.CONNECTED, Event.SYNCHRONIZE, on_synchronize), - new Geary.State.Mapping(State.CONNECTED, Event.RECVD_STATUS_RESPONSE, on_status_response), - new Geary.State.Mapping(State.CONNECTED, Event.RECVD_SERVER_DATA, on_server_data), - new Geary.State.Mapping(State.CONNECTED, Event.RECVD_CONTINUATION_RESPONSE, on_continuation), - new Geary.State.Mapping(State.CONNECTED, Event.DISCONNECTED, on_disconnected), - - new Geary.State.Mapping(State.IDLING, Event.SEND, on_idle_send), - new Geary.State.Mapping(State.IDLING, Event.SEND_IDLE, on_no_proceed), - new Geary.State.Mapping(State.IDLING, Event.SYNCHRONIZE, on_idle_synchronize), - new Geary.State.Mapping(State.IDLING, Event.RECVD_STATUS_RESPONSE, on_idle_status_response), - new Geary.State.Mapping(State.IDLING, Event.RECVD_SERVER_DATA, on_server_data), - new Geary.State.Mapping(State.IDLING, Event.RECVD_CONTINUATION_RESPONSE, on_idling_deidling_continuation), - new Geary.State.Mapping(State.IDLING, Event.DISCONNECTED, on_disconnected), - - new Geary.State.Mapping(State.IDLE, Event.SEND, on_idle_send), - new Geary.State.Mapping(State.IDLE, Event.SEND_IDLE, on_no_proceed), - new Geary.State.Mapping(State.IDLE, Event.SYNCHRONIZE, on_idle_synchronize), - new Geary.State.Mapping(State.IDLE, Event.RECVD_STATUS_RESPONSE, on_idle_status_response), - new Geary.State.Mapping(State.IDLE, Event.RECVD_SERVER_DATA, on_server_data), - new Geary.State.Mapping(State.IDLE, Event.RECVD_CONTINUATION_RESPONSE, on_idle_continuation), - new Geary.State.Mapping(State.IDLE, Event.DISCONNECTED, on_disconnected), - - new Geary.State.Mapping(State.DEIDLING, Event.SEND, on_proceed), - new Geary.State.Mapping(State.DEIDLING, Event.SEND_IDLE, on_send_idle), - new Geary.State.Mapping(State.DEIDLING, Event.SYNCHRONIZE, on_deidling_synchronize), - new Geary.State.Mapping(State.DEIDLING, Event.RECVD_STATUS_RESPONSE, on_idle_status_response), - new Geary.State.Mapping(State.DEIDLING, Event.RECVD_SERVER_DATA, on_server_data), - new Geary.State.Mapping(State.DEIDLING, Event.RECVD_CONTINUATION_RESPONSE, on_idling_deidling_continuation), - new Geary.State.Mapping(State.DEIDLING, Event.DISCONNECTED, on_disconnected), - - new Geary.State.Mapping(State.DEIDLING_SYNCHRONIZING, Event.SEND, on_proceed), - new Geary.State.Mapping(State.DEIDLING_SYNCHRONIZING, Event.SEND_IDLE, on_no_proceed), - new Geary.State.Mapping(State.DEIDLING_SYNCHRONIZING, Event.RECVD_STATUS_RESPONSE, - on_deidling_synchronizing_status_response), - new Geary.State.Mapping(State.DEIDLING_SYNCHRONIZING, Event.RECVD_SERVER_DATA, on_server_data), - new Geary.State.Mapping(State.DEIDLING_SYNCHRONIZING, Event.RECVD_CONTINUATION_RESPONSE, - on_synchronize_continuation), - new Geary.State.Mapping(State.DEIDLING_SYNCHRONIZING, Event.DISCONNECTED, on_disconnected), - - new Geary.State.Mapping(State.SYNCHRONIZING, Event.SEND, on_proceed), - new Geary.State.Mapping(State.SYNCHRONIZING, Event.SEND_IDLE, on_no_proceed), - new Geary.State.Mapping(State.SYNCHRONIZING, Event.RECVD_STATUS_RESPONSE, - on_synchronize_status_response), - new Geary.State.Mapping(State.SYNCHRONIZING, Event.RECVD_SERVER_DATA, on_server_data), - new Geary.State.Mapping(State.SYNCHRONIZING, Event.RECVD_CONTINUATION_RESPONSE, - on_synchronize_continuation), - new Geary.State.Mapping(State.SYNCHRONIZING, Event.DISCONNECTED, on_disconnected), - - // TODO: A DISCONNECTING state would be helpful here, allowing for responses and data - // received from the server after a send error caused a disconnect to be signalled to - // subscribers before moving to the DISCONNECTED state. That would require more work, - // allowing for the caller (ClientSession) to close the receive channel and wait for - // everything to flush out before it shifted to a DISCONNECTED state as well. - new Geary.State.Mapping(State.DISCONNECTED, Event.SEND, on_no_proceed), - new Geary.State.Mapping(State.DISCONNECTED, Event.SEND_IDLE, on_no_proceed), - new Geary.State.Mapping(State.DISCONNECTED, Event.SYNCHRONIZE, on_no_proceed), - new Geary.State.Mapping(State.DISCONNECTED, Event.RECVD_STATUS_RESPONSE, Geary.State.nop), - new Geary.State.Mapping(State.DISCONNECTED, Event.RECVD_SERVER_DATA, Geary.State.nop), - new Geary.State.Mapping(State.DISCONNECTED, Event.RECVD_CONTINUATION_RESPONSE, Geary.State.nop), - new Geary.State.Mapping(State.DISCONNECTED, Event.DISCONNECTED, Geary.State.nop) - }; - - fsm = new Geary.State.Machine(machine_desc, mappings, on_bad_transition); - fsm.set_logging(false); + this.cx_id = next_cx_id++; + this.command_timeout = command_timeout; + this.idle_timer = new TimeoutManager.seconds( + idle_timeout_sec, on_idle_timeout + ); } - - /** - * Generates a unique tag for the IMAP connection in the form of "<000-999>". - */ - private Tag generate_tag() { - // watch for odometer rollover - if (++tag_counter >= 1000) { - tag_counter = 0; - tag_prefix = (tag_prefix != 'z') ? tag_prefix + 1 : 'a'; - } - - // TODO This could be optimized, but we'll leave it for now. - return new Tag("%c%03d".printf(tag_prefix, tag_counter)); - } - - /** - * If true, when the connection is not sending commands ("quiet"), it will issue an IDLE command - * to enter a state where unsolicited server data may be sent from the server without resorting - * to NOOP keepalives. (Note that keepalives are still required to hold the connection open, - * according to the IMAP specification.) - * - * Note that this will *not* break a connection out of IDLE state alone; a command needs to be - * flushed down the pipe to do that. (NOOP would be a good choice.) Nor will this initiate - * an IDLE command either; it can only do that after sending a command (again, NOOP would be - * a good choice). - */ - public void set_idle_when_quiet(bool idle_when_quiet) { - this.idle_when_quiet = idle_when_quiet; - } - - public bool get_idle_when_quiet() { - return idle_when_quiet; - } - + public SocketAddress? get_remote_address() { if (cx == null) return null; - + try { return cx.get_remote_address(); } catch (Error err) { debug("Unable to retrieve remote address: %s", err.message); } - + return null; } - + public SocketAddress? get_local_address() { if (cx == null) return null; - + try { return cx.get_local_address(); } catch (Error err) { debug("Unable to retrieve local address: %s", err.message); } - + return null; } - + + /** + * Determines if the connection has an outstanding IDLE command. + */ + public bool is_in_idle() { + return (this.current_command is IdleCommand); + } + /** - * Returns true if the connection is in an IDLE state. The or_idling parameter means to return - * true if the connection is working toward an IDLE state (but additional responses are being - * returned from the server before getting there). + * Sets whether this connection should automatically IDLE. + * + * If true, this will cause the connection to send an IDLE command + * when no other commands have been sent after a short period of + * time + * + * If false, any existing IDLE command will be cancelled, and the + * connection will no longer be automatically sent. */ - public bool is_in_idle(bool or_idling) { - switch (fsm.get_state()) { - case State.IDLE: - return true; - - case State.IDLING: - return or_idling; - - default: - return false; - } - } - - public bool install_send_converter(Converter converter) { - return ser.install_converter(converter); - } - - public bool install_recv_converter(Converter converter) { - return des.install_converter(converter); + public void enable_idle_when_quiet(bool do_idle) { + this.idle_when_quiet = do_idle; + if (do_idle) { + if (!this.idle_timer.is_running) { + this.idle_timer.start(); + } + } else { + cancel_idle(); + } } - + /** * Returns silently if a connection is already established. */ public async void connect_async(Cancellable? cancellable = null) throws Error { - if (cx != null) { + if (this.cx != null) { debug("Already connected/connecting to %s", to_string()); - return; } - - cx = yield endpoint.connect_async(cancellable); - ios = cx; - outstanding_cmds = 0; - - // issue CONNECTED event and fire signal because the moment the channels are hooked up, - // data can start flowing - fsm.issue(Event.CONNECTED); - + + this.cx = yield endpoint.connect_async(cancellable); + this.ios = cx; + + this.pending_queue.clear(); + this.sent_queue.clear(); + connected(); - + try { yield open_channels_async(); } catch (Error err) { @@ -386,36 +229,43 @@ } catch (Error close_err) { // ignored } - - fsm.issue(Event.DISCONNECTED); - - cx = null; - ios = null; - + + this.cx = null; + this.ios = null; + receive_failure(err); - + throw err; } + + if (this.idle_when_quiet) { + this.idle_timer.start(); + } } - + public async void disconnect_async(Cancellable? cancellable = null) throws Error { if (cx == null) return; - + + this.idle_timer.reset(); + // To guard against reentrancy SocketConnection close_cx = cx; cx = null; - // unschedule before yielding to stop the Deserializer - unschedule_flush_timeout(); - - // cancel all outstanding commmand timeouts - cancel_timeout(); - // close the Serializer and Deserializer yield close_channels_async(cancellable); - outstanding_cmds = 0; - + + // Cancel any pending commands + foreach (Command pending in this.pending_queue.get_all()) { + debug( + "[%s] Cancelling pending command: %s", + to_string(), pending.to_string() + ); + pending.cancel_command(); + } + this.pending_queue.clear(); + // close the actual streams and the connection itself Error? close_err = null; try { @@ -429,40 +279,116 @@ } finally { ios = null; - fsm.issue(Event.DISCONNECTED); - if (close_err != null) close_error(close_err); - + disconnected(); } } - + + public async void starttls_async(Cancellable? cancellable = null) throws Error { + if (cx == null) + throw new ImapError.NOT_SUPPORTED("[%s] Unable to enable TLS: no connection", to_string()); + + // (mostly) silent fail in this case + if (cx is TlsClientConnection) { + debug("[%s] Already TLS connection", to_string()); + + return; + } + + // Close the Serializer/Deserializer, as need to use the TLS streams + debug("[%s] Closing serializer to switch to TLS", to_string()); + yield close_channels_async(cancellable); + + // wrap connection with TLS connection + TlsClientConnection tls_cx = yield endpoint.starttls_handshake_async(cx, cancellable); + + ios = tls_cx; + + // re-open Serializer/Deserializer with the new streams + yield open_channels_async(); + } + + public void send_command(Command new_command) throws ImapError { + check_connection(); + + this.pending_queue.send(new_command); + + // Exit IDLE so we can get on with life + cancel_idle(); + } + + public string to_string() { + return "%04X/%s/%s".printf( + cx_id, + endpoint.to_string(), + this.cx != null ? "Connected" : "Disconnected" + ); + } + + /** + * Returns the command that has been sent with the given tag. + * + * This should be private, but is internal for the + * ClientSession.on_received_status_response IDLE workaround. + */ + internal Command? get_sent_command(Tag tag) { + Command? sent = null; + if (tag.is_tagged()) { + foreach (Command queued in this.sent_queue) { + if (tag.equal_to(queued.tag)) { + sent = queued; + break; + } + } + } + return sent; + } + private async void open_channels_async() throws Error { assert(ios != null); assert(ser == null); assert(des == null); - + + this.open_cancellable = new GLib.Cancellable(); + // Not buffering the Deserializer because it uses a DataInputStream, which is buffered - BufferedOutputStream buffered_outs = new BufferedOutputStream(ios.output_stream); - buffered_outs.set_close_base_stream(false); - + ser_buffer = new BufferedOutputStream(ios.output_stream); + ser_buffer.set_close_base_stream(false); + // Use ClientConnection cx_id for debugging aid with Serializer/Deserializer string id = "%04d".printf(cx_id); - ser = new Serializer(id, buffered_outs); + ser = new Serializer(id, ser_buffer); des = new Deserializer(id, ios.input_stream); - + des.parameters_ready.connect(on_parameters_ready); des.bytes_received.connect(on_bytes_received); des.receive_failure.connect(on_receive_failure); des.deserialize_failure.connect(on_deserialize_failure); des.eos.connect(on_eos); - + + // Start this running in the "background", it will stop when + // open_cancellable is cancelled + this.send_loop.begin(); + yield des.start_async(); } - - // Closes the Serializer and Deserializer, but does NOT close the underlying streams + + /** Disconnect and deallocates the Serializer and Deserializer. */ private async void close_channels_async(Cancellable? cancellable) throws Error { + // Cancel all current and pending commands because the + // underlying streams are going away. + this.open_cancellable.cancel(); + foreach (Command sent in this.sent_queue) { + debug( + "[%s] Cancelling sent command: %s", + to_string(), sent.to_string() + ); + sent.cancel_command(); + } + this.sent_queue.clear(); + // disconnect from Deserializer before yielding to stop it if (des != null) { des.parameters_ready.disconnect(on_parameters_ready); @@ -470,626 +396,235 @@ des.receive_failure.disconnect(on_receive_failure); des.deserialize_failure.disconnect(on_deserialize_failure); des.eos.disconnect(on_eos); - + yield des.stop_async(); } - - ser = null; des = null; + ser = null; + // Close the Serializer's buffered stream after it as been + // deallocated so it can't possibly write to the stream again, + // and so the stream's async thread doesn't attempt to flush + // its buffers from its finaliser at some later unspecified + // point, possibly writing to an invalid underlying stream. + if (ser_buffer != null) { + yield ser_buffer.close_async(GLib.Priority.DEFAULT, cancellable); + ser_buffer = null; + } } - - public async void starttls_async(Cancellable? cancellable = null) throws Error { - if (cx == null) - throw new ImapError.NOT_SUPPORTED("[%s] Unable to enable TLS: no connection", to_string()); - - // (mostly) silent fail in this case - if (cx is TlsClientConnection) { - debug("[%s] Already TLS connection", to_string()); - - return; + + private inline void cancel_idle() { + this.idle_timer.reset(); + IdleCommand? idle = this.current_command as IdleCommand; + if (idle != null) { + idle.exit_idle(); + } + } + + // Generates a unique tag for the IMAP connection in the form of + // "<000-999>". + private Tag generate_tag() { + // watch for odometer rollover + if (++tag_counter >= 1000) { + tag_counter = 0; + tag_prefix = (tag_prefix != 'z') ? tag_prefix + 1 : 'a'; + } + + // TODO This could be optimized, but we'll leave it for now. + return new Tag("%c%03d".printf(tag_prefix, tag_counter)); + } + + /** Long lived method to send commands as they are queued. */ + private async void send_loop() { + while (!this.open_cancellable.is_cancelled()) { + try { + GLib.Cancellable cancellable = this.open_cancellable; + Command pending = yield this.pending_queue.receive( + this.open_cancellable + ); + + // Only send IDLE commands if they are the last in the + // queue, there's no point otherwise. + if (!(pending is IdleCommand) || this.pending_queue.is_empty) { + yield flush_command(pending, cancellable); + } + + // Check the queue is still empty after sending the + // command, since that might have changed. + if (this.pending_queue.is_empty) { + yield this.ser.flush_stream(cancellable); + } + } catch (GLib.Error err) { + if (!(err is GLib.IOError.CANCELLED)) { + send_failure(err); + } + } + } + } + + // Only ever call this from flush_commands, to ensure serial + // assignment of tags and only one command gets flushed at a + // time. This blocks asynchronously while serialising a command, + // including while waiting for continuation request responses when + // sending literals. + private async void flush_command(Command command, Cancellable cancellable) + throws GLib.Error { + GLib.Error? ser_error = null; + try { + // Assign a new tag; Commands with pre-assigned Tags + // should not be re-sent. (Do this inside the critical + // section to ensure commands go out in Tag order; + // this is not an IMAP requirement but makes tracing + // commands easier.) + command.assign_tag(generate_tag()); + + // Set timeout per session policy + command.response_timeout = this.command_timeout; + + this.current_command = command; + this.sent_queue.add(command); + yield command.send(this.ser, cancellable); + sent_command(command); + yield command.send_wait(this.ser, cancellable); + } catch (GLib.Error err) { + ser_error = err; + } + + this.current_command = null; + + if (ser_error != null) { + this.sent_queue.remove(command); + throw ser_error; + } + } + + private void check_connection() throws ImapError { + if (this.cx == null) { + throw new ImapError.NOT_CONNECTED( + "Not connected to %s", to_string() + ); } - - // Close the Serializer/Deserializer, as need to use the TLS streams - yield close_channels_async(cancellable); - - // wrap connection with TLS connection - TlsClientConnection tls_cx = yield endpoint.starttls_handshake_async(cx, cancellable); - - ios = tls_cx; - - // re-open Serializer/Deserializer with the new streams - yield open_channels_async(); } - + private void on_parameters_ready(RootParameters root) { - ServerResponse response; try { - response = ServerResponse.migrate_from_server(root); + ServerResponse response = ServerResponse.migrate_from_server(root); + GLib.Type type = response.get_type(); + if (type == typeof(StatusResponse)) { + on_status_response((StatusResponse) response); + } else if (type == typeof(ServerData)) { + on_server_data((ServerData) response); + } else if (type == typeof(ContinuationResponse)) { + on_continuation_response((ContinuationResponse) response); + } else { + warning( + "[%s] Unknown ServerResponse of type %s received: %s:", + to_string(), response.get_type().name(), + response.to_string() + ); + } } catch (ImapError err) { received_bad_response(root, err); - - return; } - - StatusResponse? status_response = response as StatusResponse; - if (status_response != null) { - fsm.issue(Event.RECVD_STATUS_RESPONSE, null, status_response); - - return; + + + if (this.pending_queue.is_empty && this.sent_queue.is_empty) { + // There's nothing remaining to send, and every sent + // command has been dealt with, so ready an IDLE command. + if (this.idle_when_quiet) { + this.idle_timer.start(); + } } - - ServerData? server_data = response as ServerData; - if (server_data != null) { - fsm.issue(Event.RECVD_SERVER_DATA, null, server_data); - - return; + } + + private void on_status_response(StatusResponse status) + throws ImapError { + // Emit this first since the code blow may throw errors + received_status_response(status); + + if (status.is_completion) { + Command? sent = get_sent_command(status.tag); + if (sent == null) { + throw new ImapError.SERVER_ERROR( + "Unexpected status response: %s", status.to_string() + ); + } + this.sent_queue.remove(sent); + sent.response_timed_out.disconnect(on_command_timeout); + // This could throw an error so call it after cleaning up + sent.completed(status); } - - ContinuationResponse? continuation_response = response as ContinuationResponse; - if (continuation_response != null) { - fsm.issue(Event.RECVD_CONTINUATION_RESPONSE, null, continuation_response); - - return; + } + + private void on_server_data(ServerData data) + throws ImapError { + Command? sent = get_sent_command(data.tag); + if (sent != null) { + sent.data_received(data); } - - error("[%s] Unknown ServerResponse of type %s received: %s:", to_string(), response.get_type().name(), - response.to_string()); + + received_server_data(data); + } + + private void on_continuation_response(ContinuationResponse continuation) + throws ImapError { + Command? current = this.current_command; + if (current == null) { + throw new ImapError.SERVER_ERROR( + "Unexpected continuation request response: %s", + continuation.to_string() + ); + } + current.continuation_requested(continuation); + + received_continuation_response(continuation); } - + private void on_bytes_received(size_t bytes) { - // as long as receiving someone on the connection, keep the outstanding command timeouts - // alive ... this primarily prevents against the case where a command that generates a long - // download doesn't timeout the commands behind it - increase_timeout(); - received_bytes(bytes); } - + private void on_receive_failure(Error err) { receive_failure(err); } - + private void on_deserialize_failure() { - deserialize_failure(new ImapError.PARSE_ERROR("Unable to deserialize from %s", to_string())); + deserialize_failure( + new ImapError.PARSE_ERROR( + "Unable to deserialize from %s", to_string() + ) + ); } - + private void on_eos() { recv_closed(); } - - public async void send_async(Command cmd, Cancellable? cancellable = null) throws Error { - check_for_connection(); - - // need to run this in critical section because Serializer requires it (don't want to be - // pushing data while a flush_async() is occurring) - int token = yield send_mutex.claim_async(cancellable); - - // This needs to happen inside mutex because flush async also manipulates FSM - if (!issue_conditional_event(Event.SEND)) { - debug("[%s] Send async not allowed", to_string()); - - send_mutex.release(ref token); - - throw new ImapError.NOT_CONNECTED("Send not allowed: connection in %s state", - fsm.get_state_string(fsm.get_state())); - } - - // Always assign a new tag; Commands with pre-assigned Tags should not be re-sent. - // (Do this inside the critical section to ensure commands go out in Tag order; this is not - // an IMAP requirement but makes tracing commands easier.) - cmd.assign_tag(generate_tag()); - - // set the timeout on this command; note that a zero-second timeout means no timeout, - // and that there's no timeout on serialization - cmd_started_timeout(); - - Error? ser_err = null; - try { - // watch for disconnect while waiting for mutex - if (ser != null) { - cmd.serialize(ser, cmd.tag); - } else { - ser_err = new ImapError.NOT_CONNECTED("Send not allowed: connection in %s state", - fsm.get_state_string(fsm.get_state())); - } - } catch (Error err) { - debug("[%s] Error serializing command: %s", to_string(), err.message); - ser_err = err; - } - - send_mutex.release(ref token); - - if (ser_err != null) { - send_failure(ser_err); - - throw ser_err; - } - - // Reset flush timer so it only fires after n msec after last command pushed out to stream - reschedule_flush_timeout(); - - // TODO: technically lying a little bit here; since ClientSession keepalives are rescheduled - // by this signal, will want to tighten this up a bit in the future - sent_command(cmd); - } - - private void reschedule_flush_timeout() { - unschedule_flush_timeout(); - - if (flush_timeout_id == 0) - flush_timeout_id = Timeout.add_full(Priority.LOW, FLUSH_TIMEOUT_MSEC, on_flush_timeout); - } - - private void unschedule_flush_timeout() { - if (flush_timeout_id != 0) { - Source.remove(flush_timeout_id); - flush_timeout_id = 0; - } - } - - private bool on_flush_timeout() { - do_flush_async.begin(); - - flush_timeout_id = 0; - - return false; - } - - private async void do_flush_async() { - // Like send_async(), need to use mutex when flushing as Serializer must be accessed in - // serialized fashion - // - // NOTE: Because this is happening in the background, it's possible for ser to go to null - // after any yield (if a close occurs while blocking); this is why all the checking is - // required - int token = Nonblocking.Mutex.INVALID_TOKEN; - try { - token = yield send_mutex.claim_async(); - - // Dovecot will hang the connection (not send any replies) if IDLE is sent in the - // same buffer as normal commands, so flush the buffer first, enqueue IDLE, and - // flush that behind the first - bool is_synchronized = false; - while (ser != null) { - // prepare for upcoming synchronization point (continuation response could be - // recv'd before flush_async() completes) and reset prior synchronization response - posted_synchronization_tag = ser.next_synchronized_message(); - synchronization_status_response = null; - - Tag? synchronize_tag; - yield ser.flush_async(is_synchronized, out synchronize_tag); - - // if no tag returned, all done, otherwise synchronization required - if (synchronize_tag == null) - break; - - // no longer synchronized - is_synchronized = false; - - // synchronization is not always possible - if (!issue_conditional_event(Event.SYNCHRONIZE)) { - debug("[%s] Unable to synchronize, exiting do_flush_async", to_string()); - - return; - } - - // the expectation that the send_async() command which enqueued the data buffers - // requiring synchronization closed the IDLE first, but it's possible the response - // has not been received from the server yet, so wait now ... even possible the - // connection is still in the IDLING state, so wait for IDLING -> IDLE -> SYNCHRONIZING - while (is_in_idle(true)) { - debug("[%s] Waiting to exit IDLE for synchronization...", to_string()); - yield idle_notifier.wait_async(); - debug("[%s] Finished waiting to exit IDLE for synchronization", to_string()); - } - - // wait for synchronization point to be reached - debug("[%s] Synchronizing...", to_string()); - yield synchronized_notifier.wait_async(); - - // since can be set before reaching wait_async() (due to waiting for IDLE to - // exit), need to manually reset for next time (can't use auto-reset) - synchronized_notifier.reset(); - - // watch for the synchronization request to be thwarted - if (synchronization_status_response != null) { - debug("[%s]: Failed to synchronize command continuation: %s", to_string(), - synchronization_status_response.to_string()); - - // skip pass current message, this one's done - if (ser != null) - ser.fast_forward_queue(); - } else { - debug("[%s] Synchronized, ready to continue", to_string()); - - // now synchronized, ready to continue - is_synchronized = true; - } - } - - // reset synchronization state - posted_synchronization_tag = null; - synchronization_status_response = null; - - // as connection is "quiet" (haven't seen new command in n msec), go into IDLE state - // if (a) allowed by owner, (b) allowed by state machine, and (c) no commands outstanding - if (ser != null && idle_when_quiet && outstanding_cmds == 0 && issue_conditional_event(Event.SEND_IDLE)) { - IdleCommand idle_cmd = new IdleCommand(); - idle_cmd.assign_tag(generate_tag()); - - // store IDLE tag to watch for response later (many responses could arrive before it) - bool added = posted_idle_tags.add(idle_cmd.tag); - assert(added); - - Logging.debug(Logging.Flag.NETWORK, "[%s] Initiating IDLE: %s", to_string(), - idle_cmd.to_string()); - - idle_cmd.serialize(ser, idle_cmd.tag); - - Tag? synchronize_tag; - yield ser.flush_async(false, out synchronize_tag); - - // flushing IDLE should never require synchronization - assert(synchronize_tag == null); - } - } catch (Error err) { - send_failure(err); - } finally { - if (token != Nonblocking.Mutex.INVALID_TOKEN) { - try { - send_mutex.release(ref token); - } catch (Error err2) { - // ignored - } - } - } - } - - private void check_for_connection() throws Error { - if (cx == null) - throw new ImapError.NOT_CONNECTED("Not connected to %s", to_string()); - } - - private void cmd_started_timeout() { - timeout_cmd_count++; - - if (timeout_id == 0) - timeout_id = Timeout.add_seconds(command_timeout_sec, on_cmd_timeout); - } - - private void cmd_completed_timeout() { - if (timeout_cmd_count > 0) - timeout_cmd_count--; - - if (timeout_cmd_count == 0 && timeout_id != 0) { - Source.remove(timeout_id); - timeout_id = 0; - } - } - - private void increase_timeout() { - if (timeout_id != 0) { - Source.remove(timeout_id); - timeout_id = Timeout.add_seconds(command_timeout_sec, on_cmd_timeout); - } - } - - private void cancel_timeout() { - if (timeout_id != 0) - Source.remove(timeout_id); - - timeout_id = 0; - timeout_cmd_count = 0; - } - - private bool on_cmd_timeout() { - debug("[%s] on_cmd_timeout", to_string()); - - // turn off graceful disconnect ... if the connection is hung, don't want to be stalled - // trying to flush the pipe + + private void on_command_timeout(Command command) { + this.sent_queue.remove(command); + command.response_timed_out.disconnect(on_command_timeout); + + // turn off graceful disconnect ... if the connection is hung, + // don't want to be stalled trying to flush the pipe TcpConnection? tcp_cx = cx as TcpConnection; if (tcp_cx != null) tcp_cx.set_graceful_disconnect(false); - - timeout_id = 0; - timeout_cmd_count = 0; - - receive_failure(new ImapError.TIMED_OUT("No response to command(s) after %u seconds", - command_timeout_sec)); - - return false; - } - - public string to_string() { - if (cx != null) { - try { - return "%04X/%s/%s".printf(cx_id, - Inet.address_to_string((InetSocketAddress) cx.get_remote_address()), - fsm.get_state_string(fsm.get_state())); - } catch (Error err) { - // fall through - } - } - - return "%04X/%s/%s".printf(cx_id, endpoint.to_string(), fsm.get_state_string(fsm.get_state())); + + receive_failure( + new ImapError.TIMED_OUT( + "No response to command after %u seconds: %s", + command.response_timeout, + command.to_string() + ) + ); } - - // - // transition handlers - // - - private bool issue_conditional_event(Event event) { - bool proceed = false; - fsm.issue(event, &proceed); - - return proceed; - } - - private void signal_server_data(void *user, Object? object) { - received_server_data((ServerData) object); - } - - private void signal_status_response(void *user, Object? object) { - StatusResponse? status_response = object as StatusResponse; - if (status_response != null && status_response.is_completion) { - // stop the countdown timer on the associated command - cmd_completed_timeout(); - } - - received_status_response((StatusResponse) object); - } - - private void signal_continuation(void *user, Object? object) { - received_continuation_response((ContinuationResponse) object); - } - - private void signal_entered_idle() { - in_idle(true); - } - - private void signal_left_idle() { - in_idle(false); - } - - private uint do_proceed(uint state, void *user) { - *((bool *) user) = true; - - return state; - } - - private uint do_no_proceed(uint state, void *user) { - *((bool *) user) = false; - - return state; - } - - private uint on_proceed(uint state, uint event, void *user) { - return do_proceed(state, user); - } - - private uint on_no_proceed(uint state, uint event, void *user) { - return do_no_proceed(state, user); - } - - private uint on_connected(uint state, uint event, void *user) { - // don't stay in connected state if IDLE is to be used; schedule an IDLE command (which - // may be rescheduled if commands immediately start being issued, which they most likely - // will) - if (idle_when_quiet) - reschedule_flush_timeout(); - - return State.CONNECTED; - } - - private uint on_disconnected(uint state, uint event, void *user) { - unschedule_flush_timeout(); - - return State.DISCONNECTED; - } - - private uint on_send_idle(uint state, uint event, void *user) { - return do_proceed(State.IDLING, user); - } - - private uint on_synchronize(uint state, uint event, void *user) { - return do_proceed(State.SYNCHRONIZING, user); - } - - private uint on_idle_synchronize(uint state, uint event, void *user) { - // technically this could be accomplished by introducing an IDLE_SYNCHRONZING (and probably - // an IDLING_SYNCHRONIZING) state to indicate that flush_async() is waiting for the FSM - // to transition from IDLING/IDLE to SYNCHRONIZING so it can send a large buffer, but - // that introduces a lot of complication for a rather quick state used infrequently; this - // simple flag does the trick (see on_idle_status_response for how it's used and cleared) - waiting_for_idle_to_synchronize = true; - - return do_proceed(state, user); - } - - private uint on_deidling_synchronize(uint state, uint event, void *user) { - return do_proceed(State.DEIDLING_SYNCHRONIZING, user); - } - - private uint on_status_response(uint state, uint event, void *user, Object? object) { - fsm.do_post_transition(signal_status_response, user, object); - - return state; - } - - private uint on_server_data(uint state, uint event, void *user, Object? object) { - fsm.do_post_transition(signal_server_data, user, object); - - return state; - } - - private uint on_continuation(uint state, uint event, void *user, Object? object) { - fsm.do_post_transition(signal_continuation, user, object); - - return state; - } - - private uint on_idling_deidling_continuation(uint state, uint event, void *user, Object? object) { - ContinuationResponse continuation = (ContinuationResponse) object; - - Logging.debug(Logging.Flag.NETWORK, "[%s R] %s", to_string(), continuation.to_string()); - - // if deidling and a DONE is outstanding, keep waiting for it to complete -- don't go to - // IDLE, as that will cause another spurious DONE to be issued - if (state == State.DEIDLING && outstanding_idle_dones > 0) - return state; - - // only signal entering IDLE state if that's the case - if (state != State.IDLE) - fsm.do_post_transition(signal_entered_idle); - - return State.IDLE; - } - - private uint on_idle_send(uint state, uint event, void *user) { - Logging.debug(Logging.Flag.NETWORK, "[%s] Closing IDLE", to_string()); - - // TODO: Because there is no DISCONNECTING state, need to watch for the Serializer - // disappearing during a disconnect while in a "normal" state - if (ser == null) { - debug("[%s] Unable to close IDLE: no serializer", to_string()); - - return do_no_proceed(state, user); - } - + + private void on_idle_timeout() { + Logging.debug(Logging.Flag.NETWORK, "[%s] Initiating IDLE", to_string()); try { - Logging.debug(Logging.Flag.NETWORK, "[%s S] %s", to_string(), IDLE_DONE); - ser.push_unquoted_string(IDLE_DONE); - ser.push_eol(); - - // track the number of DONE's outstanding, as their responses are pipelined as well - // (this prevents issuing more than one DONE when the idle continuation response comes - // in *after* issuing the DONE) - outstanding_idle_dones++; - } catch (Error err) { - debug("[%s] Unable to close IDLE: %s", to_string(), err.message); - - return do_no_proceed(state, user); - } - - // only signal leaving IDLE state if that's the case - if (state == State.IDLE) - fsm.do_post_transition(signal_left_idle); - - return do_proceed(State.DEIDLING, user); - } - - private uint on_idle_status_response(uint state, uint event, void *user, Object? object) { - StatusResponse status_response = (StatusResponse) object; - - // if not a posted IDLE tag, then treat as external status response - if (!posted_idle_tags.remove(status_response.tag)) { - fsm.do_post_transition(signal_status_response, user, object); - - return state; - } - - // StatusResponse for one of our IDLE commands; either way, no longer in IDLE mode - if (status_response.status == Status.OK) { - Logging.debug(Logging.Flag.NETWORK, "[%s] Leaving IDLE (%d outstanding): %s", to_string(), - posted_idle_tags.size, status_response.to_string()); - } else { - Logging.debug(Logging.Flag.NETWORK, "[%s] Unable to enter IDLE (%d outstanding): %s", to_string(), - posted_idle_tags.size, status_response.to_string()); - } - - // DONE has round-tripped (but watch for underflows, especially if server "forces" an IDLE - // to complete) - outstanding_idle_dones = Numeric.int_floor(outstanding_idle_dones - 1, 0); - - // Only return to CONNECTED if no other IDLE commands are outstanding (and only signal - // if leaving IDLE state for another) - uint next = (posted_idle_tags.size == 0) ? State.CONNECTED : state; - - // don't signal about the StatusResponse, it's in response to a Command generated - // internally (IDLE) and will confuse watchers who receive StatusResponse for a Command - // they didn't issue - - // However, need to signal about leaving idle - if (state == State.IDLE && next != State.IDLE) - fsm.do_post_transition(signal_left_idle); - - // If leaving IDLE for CONNECTED but user has asked to stay in IDLE whenever quiet, reschedule - // flush (which will automatically send IDLE command) - if (next == State.CONNECTED && idle_when_quiet) - reschedule_flush_timeout(); - - // if flush_async() is waiting for a synchronization point and deidling back to CONNECTED, - // go to SYNCHRONIZING so it can push its buffer to the server - if (waiting_for_idle_to_synchronize && next == State.CONNECTED) { - next = State.SYNCHRONIZING; - waiting_for_idle_to_synchronize = false; - } - - return next; - } - - private uint on_idle_continuation(uint state, uint event, void *user, Object? object) { - if (posted_idle_tags.size == 0) { - debug("[%s] Bad continuation received during IDLE: %s", to_string(), - ((ContinuationResponse) object).to_string()); - } - - return state; - } - - private uint on_deidling_synchronizing_status_response(uint state, uint event, void *user, - Object? object) { - // piggyback on on_idle_status_response, but instead of jumping to CONNECTED, jump to - // SYNCHRONIZING (because IDLE has completed) - return (on_idle_status_response(state, event, user, object) == State.CONNECTED) - ? State.SYNCHRONIZING : state; - } - - private uint on_synchronize_status_response(uint state, uint event, void *user, Object? object) { - StatusResponse status_response = (StatusResponse) object; - - // waiting for status response to synchronization message, treat others normally - if (posted_synchronization_tag == null || !posted_synchronization_tag.equal_to(status_response.tag)) { - fsm.do_post_transition(signal_status_response, user, object); - - return state; - } - - // receive status response while waiting for synchronization of a command; this means the - // server has rejected it - debug("[%s] Command continuation rejected: %s", to_string(), status_response.to_string()); - - // save result and notify sleeping flush_async() - synchronization_status_response = status_response; - synchronized_notifier.blind_notify(); - - return State.CONNECTED; - } - - private uint on_synchronize_continuation(uint state, uint event, void *user, Object? object) { - ContinuationResponse continuation = (ContinuationResponse) object; - - if (posted_synchronization_tag == null) { - debug("[%s] Bad command continuation received: %s", to_string(), - continuation.to_string()); - } else { - debug("[%s] Command continuation received for %s: %s", to_string(), - posted_synchronization_tag.to_string(), continuation.to_string()); + this.send_command(new IdleCommand()); + } catch (ImapError err) { + debug("[%s] Error sending IDLE: %s", to_string(), err.message); } - - // wake up the sleeping flush_async() call so it will continue - synchronization_status_response = null; - synchronized_notifier.blind_notify(); - - // There is no SYNCHRONIZED state, which is kind of fleeting; the moment the flush_async() - // call continues, no longer synchronized - return State.CONNECTED; - } - - private uint on_bad_transition(uint state, uint event, void *user) { - warning("[%s] Bad cx state transition %s", to_string(), fsm.get_event_issued_string(state, event)); - - return on_no_proceed(state, event, user); } -} +} diff -Nru geary-0.12.4/src/engine/imap/transport/imap-client-session-manager.vala geary-3.32.0/src/engine/imap/transport/imap-client-session-manager.vala --- geary-0.12.4/src/engine/imap/transport/imap-client-session-manager.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/transport/imap-client-session-manager.vala 1970-01-01 00:00:00.000000000 +0000 @@ -1,518 +0,0 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. - * - * This software is licensed under the GNU Lesser General Public License - * (version 2.1 or later). See the COPYING file in this distribution. - */ - -public class Geary.Imap.ClientSessionManager : BaseObject { - private const int DEFAULT_MIN_POOL_SIZE = 1; - private const int AUTHORIZED_SESSION_ERROR_MIN_RETRY_TIMEOUT_SEC = 1; - private const int AUTHORIZED_SESSION_ERROR_MAX_RETRY_TIMEOUT_SEC = 10; - - public bool is_open { get; private set; default = false; } - - /** - * Set to zero or negative value if keepalives should be disabled when a connection has not - * selected a mailbox. (This is not recommended.) - * - * This only affects newly created sessions or sessions leaving the selected/examined state - * and returning to an authorized state. - */ - public uint unselected_keepalive_sec { get; set; default = ClientSession.DEFAULT_UNSELECTED_KEEPALIVE_SEC; } - - /** - * Set to zero or negative value if keepalives should be disabled when a mailbox is selected - * or examined. (This is not recommended.) - * - * This only affects newly selected/examined sessions. - */ - public uint selected_keepalive_sec { get; set; default = ClientSession.DEFAULT_SELECTED_KEEPALIVE_SEC; } - - /** - * Set to zero or negative value if keepalives should be disabled when a mailbox is selected - * or examined and IDLE is supported. (This is not recommended.) - * - * This only affects newly selected/examined sessions. - */ - public uint selected_with_idle_keepalive_sec { get; set; default = ClientSession.DEFAULT_SELECTED_WITH_IDLE_KEEPALIVE_SEC; } - - /** - * ClientSessionManager attempts to maintain a minimum number of open sessions with the server - * so they're immediately ready for use. - * - * Setting this does not immediately adjust the pool size in either direction. Adjustment will - * happen as connections are needed or closed. - */ - public int min_pool_size { get; set; default = DEFAULT_MIN_POOL_SIZE; } - - /** - * Indicates if the {@link Endpoint} the {@link ClientSessionManager} connects to is reachable, - * according to NetworkMonitor. - * - * By default, this is false, pessimistic that the network is reachable. It is updated even if the - * {@link ClientSessionManager} is not open, maintained for the lifetime of the object. - */ - public bool is_endpoint_reachable { get; private set; default = false; } - - private AccountInformation account_information; - private Endpoint endpoint; - private ConnectivityManager connectivity; - private Gee.HashSet sessions = new Gee.HashSet(); - private int pending_sessions = 0; - private Nonblocking.Mutex sessions_mutex = new Nonblocking.Mutex(); - private Gee.HashSet reserved_sessions = new Gee.HashSet(); - private bool authentication_failed = false; - private bool untrusted_host = false; - private uint authorized_session_error_retry_timeout_id = 0; - private int authorized_session_retry_sec = AUTHORIZED_SESSION_ERROR_MIN_RETRY_TIMEOUT_SEC; - - public signal void login_failed(StatusResponse? response); - - public ClientSessionManager(AccountInformation account_information) { - this.account_information = account_information; - this.account_information.notify["imap-credentials"].connect(on_imap_credentials_notified); - - // NOTE: This works because AccountInformation guarantees the IMAP endpoint not to change - // for the lifetime of the AccountInformation object; if this ever changes, will need to - // refactor for that - this.endpoint = account_information.get_imap_endpoint(); - this.endpoint.notify[Endpoint.PROP_TRUST_UNTRUSTED_HOST].connect(on_imap_trust_untrusted_host); - this.endpoint.untrusted_host.connect(on_imap_untrusted_host); - - this.connectivity = new ConnectivityManager(this.endpoint); - this.connectivity.notify["is-reachable"].connect(on_connectivity_change); - this.connectivity.check_reachable.begin(); - } - - ~ClientSessionManager() { - if (is_open) - warning("Destroying opened ClientSessionManager"); - - account_information.notify["imap-credentials"].disconnect(on_imap_credentials_notified); - endpoint.untrusted_host.disconnect(on_imap_untrusted_host); - endpoint.notify[Endpoint.PROP_TRUST_UNTRUSTED_HOST].disconnect(on_imap_trust_untrusted_host); - this.connectivity.cancel_check(); - this.connectivity = null; - } - - public async void open_async(Cancellable? cancellable) throws Error { - if (is_open) - throw new EngineError.ALREADY_OPEN("ClientSessionManager already open"); - - is_open = true; - - adjust_session_pool.begin(); - } - - public async void close_async(Cancellable? cancellable) throws Error { - if (!is_open) - return; - - is_open = false; - - // to avoid locking down the sessions table while scheduling disconnects, make a copy - // and work off of that - ClientSession[]? sessions_copy = sessions.to_array(); - - // disconnect all existing sessions at once; don't wait for each, since as they disconnect - // they'll remove themselves from the sessions list and cause this foreach to explode - foreach (ClientSession session in sessions_copy) - session.disconnect_async.begin(); - - // free copy - sessions_copy = null; - - // TODO: This isn't the best (deterministic) way to deal with this, but it's easy and works - // for now - int attempts = 0; - while (sessions.size > 0) { - debug("Waiting for ClientSessions to disconnect from ClientSessionManager..."); - Timeout.add(250, close_async.callback); - yield; - - // give up after three seconds - if (++attempts > 12) - break; - } - } - - private void on_imap_credentials_notified() { - authentication_failed = false; - - if (is_open) - adjust_session_pool.begin(); - } - - private void check_open() throws Error { - if (!is_open) - throw new EngineError.OPEN_REQUIRED("ClientSessionManager is not open"); - } - - // TODO: Need a more thorough and bulletproof system for maintaining a pool of ready - // authorized sessions. - private async void adjust_session_pool() { - if (!this.is_open) - return; - - int token; - try { - token = yield sessions_mutex.claim_async(); - } catch (Error claim_err) { - debug("Unable to claim session table mutex for adjusting pool: %s", claim_err.message); - - return; - } - - while ((sessions.size + pending_sessions) < min_pool_size - && !authentication_failed - && is_open - && !untrusted_host - && is_endpoint_reachable) { - pending_sessions++; - create_new_authorized_session.begin(null, on_created_new_authorized_session); - } - - try { - sessions_mutex.release(ref token); - } catch (Error release_err) { - debug("Unable to release session table mutex after adjusting pool: %s", release_err.message); - } - } - - private void on_created_new_authorized_session(Object? source, AsyncResult result) { - pending_sessions--; - - try { - create_new_authorized_session.end(result); - } catch (Error err) { - debug("Unable to create authorized session to %s: %s", endpoint.to_string(), err.message); - - // try again after a slight delay and bump up delay - if (authorized_session_error_retry_timeout_id != 0) - Source.remove(authorized_session_error_retry_timeout_id); - - authorized_session_error_retry_timeout_id = Timeout.add_seconds( - authorized_session_retry_sec, on_authorized_session_error_retry_timeout); - - authorized_session_retry_sec = (authorized_session_retry_sec * 2).clamp( - AUTHORIZED_SESSION_ERROR_MIN_RETRY_TIMEOUT_SEC, AUTHORIZED_SESSION_ERROR_MAX_RETRY_TIMEOUT_SEC); - } - } - - private bool on_authorized_session_error_retry_timeout() { - authorized_session_error_retry_timeout_id = 0; - - adjust_session_pool.begin(); - - return false; - } - - private async ClientSession create_new_authorized_session(Cancellable? cancellable) throws Error { - if (authentication_failed) - throw new ImapError.UNAUTHENTICATED("Invalid ClientSessionManager credentials"); - - if (untrusted_host) - throw new ImapError.UNAUTHENTICATED("Untrusted host %s", endpoint.to_string()); - - if (!is_endpoint_reachable) - throw new ImapError.UNAVAILABLE("Host at %s is unreachable", endpoint.to_string()); - - ClientSession new_session = new ClientSession(endpoint); - - // add session to pool before launching all the connect activity so error cases can properly - // back it out - if (sessions_mutex.is_locked()) - locked_add_session(new_session); - else - yield unlocked_add_session_async(new_session); - - try { - yield new_session.connect_async(cancellable); - } catch (Error err) { - debug("[%s] Connect failure: %s", new_session.to_string(), err.message); - - bool removed; - if (sessions_mutex.is_locked()) - removed = locked_remove_session(new_session); - else - removed = yield unlocked_remove_session_async(new_session); - assert(removed); - - throw err; - } - - try { - yield new_session.initiate_session_async(account_information.imap_credentials, cancellable); - } catch (Error err) { - debug("[%s] Initiate session failure: %s", new_session.to_string(), err.message); - - // need to disconnect before throwing error ... don't honor Cancellable here, it's - // important to disconnect the client before dropping the ref - try { - yield new_session.disconnect_async(); - } catch (Error disconnect_err) { - debug("[%s] Error disconnecting due to session initiation failure, ignored: %s", - new_session.to_string(), disconnect_err.message); - } - - bool removed; - if (sessions_mutex.is_locked()) - removed = locked_remove_session(new_session); - else - removed = yield unlocked_remove_session_async(new_session); - assert(removed); - - throw err; - } - - // reset delay - authorized_session_retry_sec = AUTHORIZED_SESSION_ERROR_MIN_RETRY_TIMEOUT_SEC; - - // do this after logging in - new_session.enable_keepalives(selected_keepalive_sec, unselected_keepalive_sec, - selected_with_idle_keepalive_sec); - - // since "disconnected" is used to remove the ClientSession from the sessions list, want - // to only connect to the signal once the object has been added to the list; otherwise it's - // possible a cancel during the connect or login will result in a "disconnected" signal, - // removing the session before it's added - new_session.disconnected.connect(on_disconnected); - - return new_session; - } - - public async ClientSession claim_authorized_session_async(Cancellable? cancellable) throws Error { - check_open(); - - int token = yield sessions_mutex.claim_async(cancellable); - - ClientSession? found_session = null; - foreach (ClientSession session in sessions) { - MailboxSpecifier? mailbox; - if (!reserved_sessions.contains(session) && - (session.get_protocol_state(out mailbox) == ClientSession.ProtocolState.AUTHORIZED)) { - found_session = session; - - break; - } - } - - Error? err = null; - try { - if (found_session == null) - found_session = yield create_new_authorized_session(cancellable); - } catch (Error create_err) { - debug("Error creating session: %s", create_err.message); - err = create_err; - } - - // claim it now - if (found_session != null) { - bool added = reserved_sessions.add(found_session); - assert(added); - } - - try { - sessions_mutex.release(ref token); - } catch (Error release_err) { - debug("Error releasing sessions table mutex: %s", release_err.message); - } - - if (err != null) - throw err; - - return found_session; - } - - public async void release_session_async(ClientSession session, Cancellable? cancellable) - throws Error { - // Don't check_open(), it's valid for this to be called when is_open is false, that happens - // during mop-up - - MailboxSpecifier? mailbox; - ClientSession.ProtocolState context = session.get_protocol_state(out mailbox); - - bool unreserve = false; - switch (context) { - case ClientSession.ProtocolState.AUTHORIZED: - case ClientSession.ProtocolState.CLOSING_MAILBOX: - // keep as-is, but remove from the reserved list - unreserve = true; - break; - - // ClientSessionManager is tasked with holding onto a pool of authorized connections, - // so if one is released outside that state, pessimistically drop it - case ClientSession.ProtocolState.CONNECTING: - case ClientSession.ProtocolState.AUTHORIZING: - case ClientSession.ProtocolState.UNAUTHORIZED: - yield force_disconnect_async(session, true); - break; - - case ClientSession.ProtocolState.UNCONNECTED: - yield force_disconnect_async(session, false); - break; - - case ClientSession.ProtocolState.SELECTED: - case ClientSession.ProtocolState.SELECTING: - debug("[%s] Closing mailbox for released session %s", to_string(), session.to_string()); - - // always close mailbox to return to authorized state - try { - yield session.close_mailbox_async(cancellable); - } catch (ImapError imap_error) { - debug("Error attempting to close released session %s: %s", session.to_string(), - imap_error.message); - } - - // if not in authorized state now, drop it, otherwise remove from reserved list - if (session.get_protocol_state(out mailbox) == ClientSession.ProtocolState.AUTHORIZED) - unreserve = true; - else - yield force_disconnect_async(session, true); - break; - - default: - assert_not_reached(); - } - - if (!unreserve) - return; - - // if not open, disconnect, which will remove from the reserved pool anyway - if (!is_open) { - yield force_disconnect_async(session, true); - } else { - debug("[%s] Unreserving session %s", to_string(), session.to_string()); - - try { - // don't respect Cancellable because this *must* happen; don't want this lingering - // on the reserved list forever - int token = yield sessions_mutex.claim_async(); - - bool removed = reserved_sessions.remove(session); - assert(removed); - - sessions_mutex.release(ref token); - } catch (Error err) { - message("Unable to remove %s from reserved list: %s", session.to_string(), err.message); - } - } - } - - // It's possible this will be called more than once on the same session, especially in the case of a - // remote close on reserved ClientSession, so this code is forgiving. - private async void force_disconnect_async(ClientSession session, bool do_disconnect) { - debug("[%s] Dropping session %s (disconnecting=%s)", to_string(), - session.to_string(), do_disconnect.to_string()); - - int token; - try { - token = yield sessions_mutex.claim_async(); - } catch (Error err) { - debug("Unable to acquire sessions mutex: %s", err.message); - - return; - } - - locked_remove_session(session); - - if (do_disconnect) { - try { - yield session.disconnect_async(); - } catch (Error err) { - // ignored - } - } - - try { - sessions_mutex.release(ref token); - } catch (Error err) { - debug("Unable to release sessions mutex: %s", err.message); - } - - adjust_session_pool.begin(); - } - - private void on_disconnected(ClientSession session, ClientSession.DisconnectReason reason) { - force_disconnect_async.begin(session, false); - } - - private void on_login_failed(ClientSession session, StatusResponse? response) { - authentication_failed = true; - - login_failed(response); - - session.disconnect_async.begin(); - } - - // Only call with sessions mutex locked - private void locked_add_session(ClientSession session) { - sessions.add(session); - - // See create_new_authorized_session() for why the "disconnected" signal is not subscribed - // to here (but *is* unsubscribed to in remove_session()) - session.login_failed.connect(on_login_failed); - } - - private async void unlocked_add_session_async(ClientSession session) throws Error { - int token = yield sessions_mutex.claim_async(); - locked_add_session(session); - sessions_mutex.release(ref token); - } - - // Only call with sessions mutex locked - private bool locked_remove_session(ClientSession session) { - bool removed = sessions.remove(session); - if (removed) { - session.disconnected.disconnect(on_disconnected); - session.login_failed.disconnect(on_login_failed); - } - - reserved_sessions.remove(session); - - return removed; - } - - private async bool unlocked_remove_session_async(ClientSession session) throws Error { - int token = yield sessions_mutex.claim_async(); - bool removed = locked_remove_session(session); - sessions_mutex.release(ref token); - - return removed; - } - - private void on_imap_untrusted_host() { - // this is called any time trust issues are detected, so immediately clutch in to stop - // retries - untrusted_host = true; - } - - private void on_imap_trust_untrusted_host() { - // fired when the trust_untrusted_host property changes, indicating if the user has agreed - // to ignore the trust problems and continue connecting - if (untrusted_host && endpoint.trust_untrusted_host == Trillian.TRUE) { - untrusted_host = false; - - if (is_open) - adjust_session_pool.begin(); - } - } - - private void on_connectivity_change() { - this.is_endpoint_reachable = this.connectivity.is_reachable; - debug("Host %s became %s", - this.endpoint.to_string(), - this.is_endpoint_reachable ? "reachable" : "unreachable"); - if (this.is_endpoint_reachable) { - this.adjust_session_pool.begin(); - } - } - - /** - * Use only for debugging and logging. - */ - public string to_string() { - return "ClientSessionManager/%s %d sessions, %d reserved".printf(endpoint.to_string(), - sessions.size, reserved_sessions.size); - } -} diff -Nru geary-0.12.4/src/engine/imap/transport/imap-client-session.vala geary-3.32.0/src/engine/imap/transport/imap-client-session.vala --- geary-0.12.4/src/engine/imap/transport/imap-client-session.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/transport/imap-client-session.vala 2019-03-17 13:39:29.000000000 +0000 @@ -4,26 +4,77 @@ * (version 2.1 or later). See the COPYING file in this distribution. */ +/** + * High-level interface to a single IMAP server connection. + * + * The client session is responsible for opening, maintaining and + * closing a TCP connection to an IMAP server. When opening, the + * session will obtain and maintain capabilities, establish a StartTLS + * session if appropriate, authenticate, and obtain + * connection-specific information about the server such as the name + * used for the INBOX and any mailbox namespaces. When connecting has + * completed successfully, the connection will be in the IMAP + * authenticated state. + * + * Any IMAP commands that affect the IMAP connection's state (LOGIN, + * LOGOUT, SELECT, etc) must be executed by calling the appropriate + * method on this object. For example, call `login_async` rather than + * sending a {@link LoginCommand}. Other commands can be sent via + * {@link send_command_async} and {@link send_multiple_commands_async}. + */ public class Geary.Imap.ClientSession : BaseObject { - // 30 min keepalive required to maintain session - public const uint MIN_KEEPALIVE_SEC = 30 * 60; - - // 10 minutes is more realistic, as underlying sockets will not necessarily report errors if - // physical connection is lost - public const uint RECOMMENDED_KEEPALIVE_SEC = 10 * 60; - - // A more aggressive keepalive will detect when a connection has died, thereby giving the client - // a chance to reestablish a connection without long lags. - public const uint AGGRESSIVE_KEEPALIVE_SEC = 5 * 60; - - // NOOP is only sent after this amount of time has passed since the last received - // message on the connection dependent on connection state (selected/examined vs. authorized) + + /** + * Maximum keep-alive interval required to maintain a session. + * + * RFC 3501 requires servers have a minimum idle timeout of 30 + * minutes, so the keep-alive interval should be set to less than + * this. + */ + public const uint MAX_KEEPALIVE_SEC = 30 * 60; + + /** + * Recommended keep-alive interval required to maintain a session. + * + * Although many servers will allow a timeout of at least what RFC + * 3501 requires, devices in between (e.g. NAT gateways) may have + * much shorter timeouts. Thus this is set much lower than what + * the RFC allows. + */ + public const uint RECOMMENDED_KEEPALIVE_SEC = (10 * 60) - 30; + + /** + * An aggressive keep-alive interval for polling for updates. + * + * Since a server may respond to NOOP with untagged responses for + * new messages or status updates, this is a useful timeout for + * polling for changes. + */ + public const uint AGGRESSIVE_KEEPALIVE_SEC = 2 * 60; + + /** + * Default keep-alive interval in the Selected state. + * + * This uses @{link AGGRESSIVE_KEEPALIVE_SEC} so that without IMAP + * IDLE, changes to the mailbox are still noticed without too much + * delay. + */ public const uint DEFAULT_SELECTED_KEEPALIVE_SEC = AGGRESSIVE_KEEPALIVE_SEC; + + /** + * Default keep-alive interval in the Selected state with IDLE. + * + * This uses @{link RECOMMENDED_KEEPALIVE_SEC} because IMAP IDLE + * will notify about changes to the mailbox as it happens . + */ + public const uint DEFAULT_SELECTED_WITH_IDLE_KEEPALIVE_SEC = RECOMMENDED_KEEPALIVE_SEC; + + /** Default keep-alive interval when not in the Selected state. */ public const uint DEFAULT_UNSELECTED_KEEPALIVE_SEC = RECOMMENDED_KEEPALIVE_SEC; - public const uint DEFAULT_SELECTED_WITH_IDLE_KEEPALIVE_SEC = AGGRESSIVE_KEEPALIVE_SEC; - - private const uint GREETING_TIMEOUT_SEC = ClientConnection.DEFAULT_COMMAND_TIMEOUT_SEC; - + + private const uint GREETING_TIMEOUT_SEC = Command.DEFAULT_RESPONSE_TIMEOUT_SEC; + + /** * The various states an IMAP {@link ClientSession} may be in at any moment. * @@ -51,66 +102,66 @@ */ CLOSING_MAILBOX } - + public enum DisconnectReason { LOCAL_CLOSE, LOCAL_ERROR, REMOTE_CLOSE, REMOTE_ERROR; - + public bool is_error() { return (this == LOCAL_ERROR) || (this == REMOTE_ERROR); } - + public bool is_remote() { return (this == REMOTE_CLOSE) || (this == REMOTE_ERROR); } } - + // Many of the async commands go through the FSM, and this is used to pass state in and out of // the it private class MachineParams : Object { // IN public Command? cmd; - + // OUT public Error? err = null; public bool proceed = false; - + public MachineParams(Command? cmd) { this.cmd = cmd; } } - + // Need this because delegates with targets cannot be stored in ADTs. private class CommandCallback { public unowned SourceFunc callback; - + public CommandCallback(SourceFunc callback) { this.callback = callback; } } - + private class SendCommandOperation : Nonblocking.BatchOperation { // IN public ClientSession owner; public Command cmd; - + // OUT public StatusResponse response; - + public SendCommandOperation(ClientSession owner, Command cmd) { this.owner = owner; this.cmd = cmd; } - + public override async Object? execute_async(Cancellable? cancellable) throws Error { response = yield owner.command_transaction_async(cmd, cancellable); - + return response; } } - + private enum State { // canonical IMAP session states UNCONNECTED, @@ -118,24 +169,24 @@ AUTHORIZED, SELECTED, LOGGED_OUT, - + // transitional states CONNECTING, AUTHORIZING, SELECTING, CLOSING_MAILBOX, LOGGING_OUT, - + // terminal state BROKEN, - + COUNT } - + private static string state_to_string(uint state) { return ((State) state).to_string(); } - + private enum Event { // user-initiated events CONNECT, @@ -145,30 +196,30 @@ CLOSE_MAILBOX, LOGOUT, DISCONNECT, - + // server events CONNECTED, DISCONNECTED, RECV_STATUS, RECV_COMPLETION, - + // I/O errors RECV_ERROR, SEND_ERROR, - + TIMEOUT, - + COUNT; } - + private static string event_to_string(uint event) { return ((Event) event).to_string(); } - + private static Geary.State.MachineDescriptor machine_desc = new Geary.State.MachineDescriptor( "Geary.Imap.ClientSession", State.UNCONNECTED, State.COUNT, Event.COUNT, state_to_string, event_to_string); - + /** * {@link ClientSession} tracks server extensions reported via the CAPABILITY server data * response. @@ -177,73 +228,112 @@ * (specifically for IDLE support). */ public Capabilities capabilities { get; private set; default = new Capabilities(0); } - + + /** Determines if this session supports the IMAP IDLE extension. */ + public bool is_idle_supported { + get { return this.capabilities.has_capability(Capabilities.IDLE); } + } + + /** + * Determines when the last successful command response was received. + * + * Returns the system wall clock time the last successful command + * response was received, in microseconds since the UNIX epoch. + */ + public int64 last_seen = 0; + + + // While the following inbox and namespace data should be server + // specific, there is a small chance they will differ between + // connections if the connections connect to different servers in + // a cluster, or if configuration changes between connections. We + // do assume however that once connected, this information will + // remain the same. This information becomes current only after + // initiate_session_async() has successfully completed. + + /** Records the actual name and delimiter used for the inbox */ + internal MailboxInformation? inbox = null; + + /** The locations personal mailboxes on this connection. */ + internal Gee.List personal_namespaces = new Gee.ArrayList(); + + /** The locations of other user's mailboxes on this connection. */ + internal Gee.List user_namespaces = new Gee.ArrayList(); + + /** The locations of shared mailboxes on this connection. */ + internal Gee.List shared_namespaces = new Gee.ArrayList(); + + private Endpoint imap_endpoint; private Geary.State.Machine fsm; private ClientConnection? cx = null; + private MailboxSpecifier? current_mailbox = null; private bool current_mailbox_readonly = false; - private Gee.HashMap seen_completion_responses = new Gee.HashMap< - Tag, StatusResponse>(); - private Gee.HashMap waiting_for_completion = new Gee.HashMap< - Tag, CommandCallback>(); - private int next_capabilities_revision = 1; + private uint keepalive_id = 0; private uint selected_keepalive_secs = 0; private uint unselected_keepalive_secs = 0; private uint selected_with_idle_keepalive_secs = 0; - private bool allow_idle = true; + private Command? state_change_cmd = null; private Nonblocking.Semaphore? connect_waiter = null; private Error? connect_err = null; + private int next_capabilities_revision = 1; + private Gee.Map namespaces = new Gee.HashMap(); + + + // // Connection state changes // - + public signal void connected(); - + public signal void session_denied(string? reason); - + public signal void authorized(); - + public signal void logged_out(); - + public signal void login_failed(StatusResponse? response); - + public signal void disconnected(DisconnectReason reason); - + public signal void status_response_received(StatusResponse status_response); - + /** - * Fired before the specific {@link ServerData} signals (i.e. {@link capability}, {@link exists} + * Fired after the specific {@link ServerData} signals (i.e. {@link capability}, {@link exists} * {@link expunge}, etc.) */ public signal void server_data_received(ServerData server_data); - + public signal void capability(Capabilities capabilities); - + public signal void exists(int count); - + public signal void expunge(SequenceNumber seq_num); - + public signal void fetch(FetchedData fetched_data); - + public signal void flags(MailboxAttributes mailbox_attrs); - + /** * Fired when a LIST or XLIST {@link ServerData} is returned from the server. */ public signal void list(MailboxInformation mailbox_info); - + // TODO: LSUB results - + public signal void recent(int count); - + public signal void search(int64[] seq_or_uid); - + public signal void status(StatusData status_data); - + + public signal void @namespace(NamespaceResponse namespace); + /** * If the mailbox name is null it indicates the type of state change that has occurred * (authorized -> selected/examined or vice-versa). If new_name is null readonly should be @@ -251,10 +341,10 @@ */ public signal void current_mailbox_changed(MailboxSpecifier? old_name, MailboxSpecifier? new_name, bool readonly); - + public ClientSession(Endpoint imap_endpoint) { this.imap_endpoint = imap_endpoint; - + Geary.State.Mapping[] mappings = { new Geary.State.Mapping(State.UNCONNECTED, Event.CONNECT, on_connect), new Geary.State.Mapping(State.UNCONNECTED, Event.LOGIN, on_early_command), @@ -263,7 +353,7 @@ new Geary.State.Mapping(State.UNCONNECTED, Event.CLOSE_MAILBOX, on_early_command), new Geary.State.Mapping(State.UNCONNECTED, Event.LOGOUT, on_early_command), new Geary.State.Mapping(State.UNCONNECTED, Event.DISCONNECT, Geary.State.nop), - + new Geary.State.Mapping(State.CONNECTING, Event.CONNECT, on_already_connected), new Geary.State.Mapping(State.CONNECTING, Event.LOGIN, on_early_command), new Geary.State.Mapping(State.CONNECTING, Event.SEND_CMD, on_early_command), @@ -277,7 +367,7 @@ new Geary.State.Mapping(State.CONNECTING, Event.SEND_ERROR, on_connecting_send_recv_error), new Geary.State.Mapping(State.CONNECTING, Event.RECV_ERROR, on_connecting_send_recv_error), new Geary.State.Mapping(State.CONNECTING, Event.TIMEOUT, on_connecting_timeout), - + new Geary.State.Mapping(State.NOAUTH, Event.CONNECT, on_already_connected), new Geary.State.Mapping(State.NOAUTH, Event.LOGIN, on_login), new Geary.State.Mapping(State.NOAUTH, Event.SEND_CMD, on_send_command), @@ -289,7 +379,7 @@ new Geary.State.Mapping(State.NOAUTH, Event.RECV_COMPLETION, on_recv_status), new Geary.State.Mapping(State.NOAUTH, Event.SEND_ERROR, on_send_error), new Geary.State.Mapping(State.NOAUTH, Event.RECV_ERROR, on_recv_error), - + new Geary.State.Mapping(State.AUTHORIZING, Event.CONNECT, on_already_connected), new Geary.State.Mapping(State.AUTHORIZING, Event.LOGIN, on_logging_in), new Geary.State.Mapping(State.AUTHORIZING, Event.SEND_CMD, on_unauthenticated), @@ -301,7 +391,7 @@ new Geary.State.Mapping(State.AUTHORIZING, Event.RECV_COMPLETION, on_login_recv_completion), new Geary.State.Mapping(State.AUTHORIZING, Event.SEND_ERROR, on_send_error), new Geary.State.Mapping(State.AUTHORIZING, Event.RECV_ERROR, on_recv_error), - + new Geary.State.Mapping(State.AUTHORIZED, Event.CONNECT, on_already_connected), new Geary.State.Mapping(State.AUTHORIZED, Event.LOGIN, on_already_logged_in), new Geary.State.Mapping(State.AUTHORIZED, Event.SEND_CMD, on_send_command), @@ -313,7 +403,7 @@ new Geary.State.Mapping(State.AUTHORIZED, Event.RECV_COMPLETION, on_recv_status), new Geary.State.Mapping(State.AUTHORIZED, Event.SEND_ERROR, on_send_error), new Geary.State.Mapping(State.AUTHORIZED, Event.RECV_ERROR, on_recv_error), - + new Geary.State.Mapping(State.SELECTING, Event.CONNECT, on_already_connected), new Geary.State.Mapping(State.SELECTING, Event.LOGIN, on_already_logged_in), new Geary.State.Mapping(State.SELECTING, Event.SEND_CMD, on_send_command), @@ -322,10 +412,11 @@ new Geary.State.Mapping(State.SELECTING, Event.LOGOUT, on_logout), new Geary.State.Mapping(State.SELECTING, Event.DISCONNECT, on_disconnect), new Geary.State.Mapping(State.SELECTING, Event.RECV_STATUS, on_recv_status), + new Geary.State.Mapping(State.SELECTING, Event.RECV_COMPLETION, on_selecting_recv_completion), new Geary.State.Mapping(State.SELECTING, Event.SEND_ERROR, on_send_error), new Geary.State.Mapping(State.SELECTING, Event.RECV_ERROR, on_recv_error), - + new Geary.State.Mapping(State.SELECTED, Event.CONNECT, on_already_connected), new Geary.State.Mapping(State.SELECTED, Event.LOGIN, on_already_logged_in), new Geary.State.Mapping(State.SELECTED, Event.SEND_CMD, on_send_command), @@ -337,7 +428,7 @@ new Geary.State.Mapping(State.SELECTED, Event.RECV_COMPLETION, on_recv_status), new Geary.State.Mapping(State.SELECTED, Event.SEND_ERROR, on_send_error), new Geary.State.Mapping(State.SELECTED, Event.RECV_ERROR, on_recv_error), - + new Geary.State.Mapping(State.CLOSING_MAILBOX, Event.CONNECT, on_already_connected), new Geary.State.Mapping(State.CLOSING_MAILBOX, Event.LOGIN, on_already_logged_in), new Geary.State.Mapping(State.CLOSING_MAILBOX, Event.SEND_CMD, on_send_command), @@ -349,7 +440,7 @@ new Geary.State.Mapping(State.CLOSING_MAILBOX, Event.RECV_COMPLETION, on_closing_recv_completion), new Geary.State.Mapping(State.CLOSING_MAILBOX, Event.SEND_ERROR, on_send_error), new Geary.State.Mapping(State.CLOSING_MAILBOX, Event.RECV_ERROR, on_recv_error), - + new Geary.State.Mapping(State.LOGGING_OUT, Event.CONNECT, on_already_connected), new Geary.State.Mapping(State.LOGGING_OUT, Event.LOGIN, on_already_logged_in), new Geary.State.Mapping(State.LOGGING_OUT, Event.SEND_CMD, on_late_command), @@ -361,7 +452,7 @@ new Geary.State.Mapping(State.LOGGING_OUT, Event.RECV_COMPLETION, on_logging_out_recv_completion), new Geary.State.Mapping(State.LOGGING_OUT, Event.RECV_ERROR, on_recv_error), new Geary.State.Mapping(State.LOGGING_OUT, Event.SEND_ERROR, on_send_error), - + new Geary.State.Mapping(State.LOGGED_OUT, Event.CONNECT, on_already_connected), new Geary.State.Mapping(State.LOGGED_OUT, Event.LOGIN, on_already_logged_in), new Geary.State.Mapping(State.LOGGED_OUT, Event.SEND_CMD, on_late_command), @@ -373,7 +464,7 @@ new Geary.State.Mapping(State.LOGGED_OUT, Event.RECV_COMPLETION, on_dropped_response), new Geary.State.Mapping(State.LOGGED_OUT, Event.RECV_ERROR, on_recv_error), new Geary.State.Mapping(State.LOGGED_OUT, Event.SEND_ERROR, on_send_error), - + new Geary.State.Mapping(State.BROKEN, Event.CONNECT, on_late_command), new Geary.State.Mapping(State.BROKEN, Event.LOGIN, on_late_command), new Geary.State.Mapping(State.BROKEN, Event.SEND_CMD, on_late_command), @@ -387,75 +478,152 @@ new Geary.State.Mapping(State.BROKEN, Event.SEND_ERROR, Geary.State.nop), new Geary.State.Mapping(State.BROKEN, Event.RECV_ERROR, Geary.State.nop), }; - + fsm = new Geary.State.Machine(machine_desc, mappings, on_ignored_transition); fsm.set_logging(false); } - + ~ClientSession() { switch (fsm.get_state()) { case State.UNCONNECTED: case State.BROKEN: // no problem-o break; - + default: - error("[%s] ClientSession ref dropped while still active", to_string()); + warning("[%s] ClientSession ref dropped while still active", to_string()); } - + debug("DTOR: ClientSession %s", to_string()); } - + public MailboxSpecifier? get_current_mailbox() { return current_mailbox; } - + public bool is_current_mailbox_readonly() { return current_mailbox_readonly; } - + + /** + * Determines the SELECT-able mailbox name for a specific folder path. + */ + public MailboxSpecifier get_mailbox_for_path(FolderPath path) + throws ImapError { + string? delim = get_delimiter_for_path(path); + return new MailboxSpecifier.from_folder_path(path, this.inbox.mailbox, delim); + } + + /** + * Determines the folder path for a mailbox name. + */ + public FolderPath get_path_for_mailbox(FolderRoot root, + MailboxSpecifier mailbox) + throws ImapError { + string? delim = get_delimiter_for_mailbox(mailbox); + return mailbox.to_folder_path(root, delim, this.inbox.mailbox); + } + + /** + * Determines the mailbox hierarchy delimiter for a given folder path. + * + * The returned delimiter be null if a namespace (INBOX, personal, + * etc) for the path does not exist, or if the namespace is flat. + */ + public string? get_delimiter_for_path(FolderPath path) + throws ImapError { + string? delim = null; + + FolderRoot root = (FolderRoot) path.get_root(); + if (root.inbox.equal_to(path) || + root.inbox.is_descendant(path)) { + delim = this.inbox.delim; + } else { + Namespace? ns = null; + FolderPath? search = path; + while (ns == null && search != null) { + ns = this.namespaces.get(search.name); + search = search.parent; + } + if (ns == null) { + // fall back to the default personal namespace + ns = this.personal_namespaces[0]; + } + + delim = ns.delim; + } + return delim; + } + + /** + * Determines the mailbox hierarchy delimiter for a given mailbox name. + * + * The returned delimiter be null if a namespace (INBOX, personal, + * etc) for the mailbox does not exist, or if the namespace is flat. + */ + public string? get_delimiter_for_mailbox(MailboxSpecifier mailbox) + throws ImapError { + string name = mailbox.name; + string? delim = null; + + string inbox_name = this.inbox.mailbox.name; + string? inbox_delim = this.inbox.delim; + if (inbox_name == name || + (inbox_delim != null && inbox_name.has_prefix(name + inbox_delim))) { + delim = this.inbox.delim; + } else { + foreach (Namespace ns in this.namespaces.values) { + if (name.has_prefix(ns.prefix)) { + delim = ns.delim; + break; + } + } + } + return delim; + } + /** * Returns the current {@link ProtocolState} of the {@link ClientSession} and, if selected, * the current mailbox. */ public ProtocolState get_protocol_state(out MailboxSpecifier? current_mailbox) { current_mailbox = null; - + switch (fsm.get_state()) { case State.UNCONNECTED: case State.LOGGED_OUT: case State.LOGGING_OUT: case State.BROKEN: return ProtocolState.UNCONNECTED; - + case State.NOAUTH: return ProtocolState.UNAUTHORIZED; - + case State.AUTHORIZED: return ProtocolState.AUTHORIZED; - + case State.SELECTED: current_mailbox = this.current_mailbox; - + return ProtocolState.SELECTED; - + case State.CONNECTING: return ProtocolState.CONNECTING; - + case State.AUTHORIZING: return ProtocolState.AUTHORIZING; - + case State.SELECTING: return ProtocolState.SELECTING; - + case State.CLOSING_MAILBOX: return ProtocolState.CLOSING_MAILBOX; - + default: assert_not_reached(); } } - + // Some commands require waiting for a completion response in order to shift the state machine's // State; this allocates such a wait, returning false if another command is outstanding also // waiting for one to finish @@ -464,33 +632,33 @@ params.proceed = false; params.err = new ImapError.NOT_SUPPORTED("Cannot perform operation %s while session is %s", fsm.get_event_string(event), fsm.get_state_string(state)); - + return false; } - + state_change_cmd = params.cmd; params.proceed = true; - + return true; } - + // This is the complement to reserve_state_change_cmd(), returning true if the response represents // the pending state change Command (and clearing it if it is) private bool validate_state_change_cmd(ServerResponse response, out Command? cmd = null) { cmd = state_change_cmd; - + if (state_change_cmd == null || !state_change_cmd.tag.equal_to(response.tag)) return false; - + state_change_cmd = null; - + return true; } - + // // connect // - + /** * Connect to the server. * @@ -506,38 +674,43 @@ * even if the error was from the server (that is, not a network problem). The * {@link ClientSession} should be discarded. */ - public async void connect_async(Cancellable? cancellable = null) throws Error { + public async void connect_async(GLib.Cancellable? cancellable) + throws GLib.Error { MachineParams params = new MachineParams(null); fsm.issue(Event.CONNECT, null, params); - + if (params.err != null) throw params.err; - + assert(params.proceed); - + // ClientConnection and the connection waiter should exist at this point assert(cx != null); assert(connect_waiter != null); - + // connect and let ClientConnection's signals drive the show try { yield cx.connect_async(cancellable); } catch (Error err) { fsm.issue(Event.SEND_ERROR, null, null, err); - + throw err; } - + // set up timer to wait for greeting from server Scheduler.Scheduled timeout = Scheduler.after_sec(GREETING_TIMEOUT_SEC, on_greeting_timeout); - + // wait for the initial greeting or a timeout ... this prevents the caller from turning // around and issuing a command while still in CONNECTING state - yield connect_waiter.wait_async(cancellable); - + try { + yield connect_waiter.wait_async(cancellable); + } catch (GLib.IOError.CANCELLED err) { + connect_err = err; + } + // cancel the timeout, if it's not already fired timeout.cancel(); - + // if session was denied or timeout, ensure the session is disconnected and throw the // original Error ... connect_async shouldn't leave the session in a LOGGED_OUT state, // but completely disconnected if unsuccessful @@ -548,22 +721,22 @@ debug("[%s] Error disconnecting after a failed connect attempt: %s", to_string(), err.message); } - + throw connect_err; } } - + private bool on_greeting_timeout() { // if still in CONNECTING state, the greeting never arrived if (fsm.get_state() == State.CONNECTING) fsm.issue(Event.TIMEOUT); - + return false; } - + private uint on_connect(uint state, uint event, void *user, Object? object) { MachineParams params = (MachineParams) object; - + assert(cx == null); cx = new ClientConnection(imap_endpoint); cx.connected.connect(on_network_connected); @@ -572,28 +745,26 @@ cx.send_failure.connect(on_network_send_error); cx.received_status_response.connect(on_received_status_response); cx.received_server_data.connect(on_received_server_data); + cx.received_continuation_response.connect(on_received_continuation_response); cx.received_bytes.connect(on_received_bytes); cx.received_bad_response.connect(on_received_bad_response); cx.recv_closed.connect(on_received_closed); cx.receive_failure.connect(on_network_receive_failure); cx.deserialize_failure.connect(on_network_receive_failure); - + assert(connect_waiter == null); connect_waiter = new Nonblocking.Semaphore(); - - // only use IDLE when in SELECTED or EXAMINED state - cx.set_idle_when_quiet(false); - + params.proceed = true; - + return State.CONNECTING; } - + // this is used internally to tear-down the ClientConnection object and unhook it from // ClientSession private void drop_connection() { unschedule_keepalive(); - + if (cx != null) { cx.connected.disconnect(on_network_connected); cx.disconnected.disconnect(on_network_disconnected); @@ -601,35 +772,27 @@ cx.send_failure.disconnect(on_network_send_error); cx.received_status_response.disconnect(on_received_status_response); cx.received_server_data.disconnect(on_received_server_data); + cx.received_continuation_response.disconnect(on_received_continuation_response); cx.received_bytes.disconnect(on_received_bytes); cx.received_bad_response.disconnect(on_received_bad_response); cx.recv_closed.disconnect(on_received_closed); cx.receive_failure.disconnect(on_network_receive_failure); cx.deserialize_failure.disconnect(on_network_receive_failure); - + cx = null; } - - // if there are any outstanding commands waiting for responses, wake them up now - if (waiting_for_completion.size > 0) { - debug("[%s] Cancelling %d pending commands", to_string(), waiting_for_completion.size); - foreach (CommandCallback cmd_cb in waiting_for_completion.values) - Scheduler.on_idle(cmd_cb.callback); - - waiting_for_completion.clear(); - } } - + private uint on_connected(uint state, uint event) { debug("[%s] Connected", to_string()); - + // stay in current state -- wait for initial status response to move into NOAUTH or LOGGED OUT return state; } - + private uint on_connecting_recv_status(uint state, uint event, void *user, Object? object) { StatusResponse status_response = (StatusResponse) object; - + // see on_connected() why signals and semaphore are delayed for this event try { connect_waiter.notify(); @@ -637,21 +800,21 @@ message("[%s] Unable to notify connect_waiter of connection: %s", to_string(), err.message); } - + if (status_response.status == Status.OK) { fsm.do_post_transition(() => { connected(); }); - + return State.NOAUTH; } - + debug("[%s] Connect denied: %s", to_string(), status_response.to_string()); - + fsm.do_post_transition(() => { session_denied(status_response.get_text()); }); connect_err = new ImapError.SERVER_ERROR("Session denied: %s", status_response.get_text()); - + return State.LOGGED_OUT; } - + private uint on_connecting_timeout(uint state, uint event) { // wake up the waiting task in connect_async try { @@ -660,45 +823,91 @@ message("[%s] Unable to notify connect_waiter of timeout: %s", to_string(), err.message); } - + debug("[%s] Connect timed-out", to_string()); - + connect_err = new IOError.TIMED_OUT("Session greeting not seen in %u seconds", GREETING_TIMEOUT_SEC); - + return State.LOGGED_OUT; } - - // - // login - // - + /** * Performs the LOGIN command using the supplied credentials. * * @see initiate_session_async */ - public async StatusResponse login_async(Geary.Credentials credentials, Cancellable? cancellable = null) - throws Error { - if (!credentials.is_complete()) { - login_failed(null); - throw new ImapError.UNAUTHENTICATED("No credentials provided for account: %s", credentials.to_string()); - } - - LoginCommand cmd = new LoginCommand(credentials.user, credentials.pass); - + public async StatusResponse login_async(Geary.Credentials credentials, + GLib.Cancellable? cancellable) + throws GLib.Error { + Command? cmd = null; + switch (credentials.supported_method) { + case Geary.Credentials.Method.PASSWORD: + cmd = new LoginCommand( + credentials.user, credentials.token + ); + break; + + case Geary.Credentials.Method.OAUTH2: + if (!capabilities.has_setting(Capabilities.AUTH, + Capabilities.AUTH_XOAUTH2)) { + throw new ImapError.UNAUTHENTICATED( + "OAuth2 authentication not supported for %s", to_string() + ); + } + cmd = new AuthenticateCommand.oauth2( + credentials.user, credentials.token + ); + break; + + default: + throw new ImapError.UNAUTHENTICATED( + "Credentials method %s not supported for: %s", + credentials.supported_method.to_string(), + to_string() + ); + } + MachineParams params = new MachineParams(cmd); fsm.issue(Event.LOGIN, null, params); - + if (params.err != null) throw params.err; - + // should always proceed; only an Error could change this assert(params.proceed); - - return yield command_transaction_async(cmd, cancellable); + + GLib.Error? login_err = null; + try { + yield command_transaction_async(cmd, cancellable); + } catch (Error err) { + login_err = err; + } + + if (login_err != null) { + // Throw an error indicating auth failed here, unless + // there is a status response and it indicates that the + // server is merely reporting login as being unavailable, + // then don't since the creds might actually be fine. + ResponseCodeType? code_type = null; + if (cmd.status != null) { + ResponseCode? code = cmd.status.response_code; + if (code != null) { + code_type = code.get_response_code_type(); + } + } + + if (code_type == null || + code_type.value != ResponseCodeType.UNAVAILABLE) { + throw new ImapError.UNAUTHENTICATED(login_err.message); + } else { + throw login_err; + } + } + + return cmd.status; } - + /** * Prepares the connection and performs a login using the supplied credentials. * @@ -706,127 +915,191 @@ * {@link Capabilities} are also retrieved automatically at the right time to ensure the best * results are available with {@link capabilities}. */ - public async void initiate_session_async(Geary.Credentials credentials, Cancellable? cancellable = null) - throws Error { + public async void initiate_session_async(Geary.Credentials credentials, + GLib.Cancellable? cancellable) + throws GLib.Error { // If no capabilities available, get them now if (capabilities.is_empty()) - yield send_command_async(new CapabilityCommand()); - + yield send_command_async(new CapabilityCommand(), cancellable); + // store them for comparison later Imap.Capabilities caps = capabilities; - - debug("[%s] use_starttls=%s is_ssl=%s starttls=%s", to_string(), imap_endpoint.use_starttls.to_string(), - imap_endpoint.is_ssl.to_string(), caps.has_capability(Capabilities.STARTTLS).to_string()); - switch (imap_endpoint.attempt_starttls(caps.has_capability(Capabilities.STARTTLS))) { - case Endpoint.AttemptStarttls.YES: - debug("[%s] Attempting STARTTLS...", to_string()); - StatusResponse resp; - try { - resp = yield send_command_async(new StarttlsCommand()); - } catch (Error err) { - debug("Error attempting STARTTLS command on %s: %s", to_string(), err.message); - - throw err; - } - - if (resp.status == Status.OK) { - yield cx.starttls_async(cancellable); - debug("[%s] STARTTLS completed", to_string()); - } else { - debug("[%s} STARTTLS refused: %s", to_string(), resp.status.to_string()); - - // throw an exception and fail rather than send credentials under suspect - // conditions - throw new ImapError.NOT_SUPPORTED("STARTTLS refused by %s: %s", to_string(), - resp.status.to_string()); - } - break; - - case Endpoint.AttemptStarttls.NO: - debug("[%s] No STARTTLS attempted", to_string()); - break; - - case Endpoint.AttemptStarttls.HALT: - throw new ImapError.NOT_SUPPORTED("STARTTLS unavailable for %s", to_string()); - - default: - assert_not_reached(); + + if (imap_endpoint.tls_method == TlsNegotiationMethod.START_TLS) { + if (!caps.has_capability(Capabilities.STARTTLS)) { + throw new ImapError.NOT_SUPPORTED( + "STARTTLS unavailable for %s", to_string()); + } + + debug("[%s] Attempting STARTTLS...", to_string()); + StatusResponse resp; + try { + resp = yield send_command_async( + new StarttlsCommand(), cancellable + ); + } catch (Error err) { + debug( + "Error attempting STARTTLS command on %s: %s", + to_string(), err.message + ); + throw err; + } + + if (resp.status == Status.OK) { + yield cx.starttls_async(cancellable); + debug("[%s] STARTTLS completed", to_string()); + } else { + debug( + "[%s} STARTTLS refused: %s", + to_string(), resp.status.to_string() + ); + // Throw an exception and fail rather than send + // credentials under suspect conditions + throw new ImapError.NOT_SUPPORTED( + "STARTTLS refused by %s: %s", to_string(), + resp.status.to_string() + ); + } } - + // Login after STARTTLS - StatusResponse login_resp = yield login_async(credentials, cancellable); - if (login_resp.status != Status.OK) { - throw new ImapError.UNAUTHENTICATED("Unable to login to %s with supplied credentials", - to_string()); - } - + yield login_async(credentials, cancellable); + // if new capabilities not offered after login, get them now - if (caps.revision == capabilities.revision) - yield send_command_async(new CapabilityCommand()); - + if (caps.revision == capabilities.revision) { + yield send_command_async(new CapabilityCommand(), cancellable); + } + // either way, new capabilities should be available caps = capabilities; - - // Attempt compression (usually only available after authentication) - if (caps.has_setting(Capabilities.COMPRESS, Capabilities.DEFLATE_SETTING)) { - StatusResponse resp = yield send_command_async( - new CompressCommand(CompressCommand.ALGORITHM_DEFLATE)); - if (resp.status == Status.OK) { - install_send_converter(new ZlibCompressor(ZlibCompressorFormat.RAW)); - install_recv_converter(new ZlibDecompressor(ZlibCompressorFormat.RAW)); - debug("[%s] Compression started", to_string()); + + Gee.List server_data = new Gee.ArrayList(); + ulong data_id = this.server_data_received.connect((data) => { server_data.add(data); }); + try { + // Determine what this connection calls the inbox + Imap.StatusResponse response = yield send_command_async( + new ListCommand(MailboxSpecifier.inbox, false, null), + cancellable + ); + if (response.status == Status.OK && !server_data.is_empty) { + this.inbox = server_data[0].get_list(); + debug("[%s] Using as INBOX: %s", to_string(), this.inbox.to_string()); } else { - debug("[%s] Unable to start compression: %s", to_string(), resp.to_string()); + throw new ImapError.INVALID("Unable to find INBOX"); + } + + // Try to determine what the connection's namespaces are + server_data.clear(); + if (caps.has_capability(Capabilities.NAMESPACE)) { + response = yield send_command_async( + new NamespaceCommand(), + cancellable + ); + if (response.status == Status.OK && !server_data.is_empty) { + NamespaceResponse ns = server_data[0].get_namespace(); + update_namespaces(ns.personal, this.personal_namespaces); + update_namespaces(ns.user, this.user_namespaces); + update_namespaces(ns.shared, this.shared_namespaces); + } else { + debug("[%s] NAMESPACE command failed", to_string()); + } + } + server_data.clear(); + if (!this.personal_namespaces.is_empty) { + debug("[%s] Default personal namespace: %s", to_string(), this.personal_namespaces[0].to_string()); + } else { + debug("[%s] Personal namespace not found, guessing it", to_string()); + string? prefix = ""; + string? delim = this.inbox.delim; + if (!this.inbox.attrs.contains(MailboxAttribute.NO_INFERIORS) && + this.inbox.delim == ".") { + // We're probably on an ancient Cyrus install that + // doesn't support NAMESPACE, so assume they go in the inbox + prefix = this.inbox.mailbox.name + "."; + } + + if (delim == null) { + // We still don't know what the delim is, so fetch + // it. In particular, uw-imap sends a null prefix + // for the inbox. + response = yield send_command_async( + new ListCommand(new MailboxSpecifier(prefix), false, null), + cancellable + ); + if (response.status == Status.OK && !server_data.is_empty) { + MailboxInformation list = server_data[0].get_list(); + delim = list.delim; + } else { + throw new ImapError.INVALID("Unable to determine personal namespace delimiter"); + } + } + + this.personal_namespaces.add(new Namespace(prefix, delim)); + debug("[%s] Personal namespace guessed as: %s", + to_string(), this.personal_namespaces[0].to_string()); + } + } finally { + disconnect(data_id); + } + } + + private inline void update_namespaces(Gee.List? response, Gee.List list) { + if (response != null) { + foreach (Namespace ns in response) { + list.add(ns); + string prefix = ns.prefix; + string? delim = ns.delim; + if (delim != null && prefix.has_suffix(delim)) { + prefix = prefix.substring(0, prefix.length - delim.length); + } + this.namespaces.set(prefix, ns); } - } else { - debug("[%s] No compression available", to_string()); } } - + private uint on_login(uint state, uint event, void *user, Object? object) { MachineParams params = (MachineParams) object; - - assert(params.cmd is LoginCommand); + if (!reserve_state_change_cmd(params, state, event)) return state; - + return State.AUTHORIZING; } - + private uint on_logging_in(uint state, uint event, void *user, Object? object) { MachineParams params = (MachineParams) object; - + params.err = new ImapError.ALREADY_CONNECTED("Already logging in to %s", to_string()); - + return state; } - + private uint on_login_recv_completion(uint state, uint event, void *user, Object? object) { StatusResponse completion_response = (StatusResponse) object; - + if (!validate_state_change_cmd(completion_response)) return state; - + // Remember: only you can prevent firing signals inside state transition handlers switch (completion_response.status) { case Status.OK: fsm.do_post_transition(() => { authorized(); }); - + return State.AUTHORIZED; - + default: debug("[%s] Unable to LOGIN: %s", to_string(), completion_response.to_string()); fsm.do_post_transition((resp) => { login_failed((StatusResponse)resp); }, completion_response); - + return State.NOAUTH; } } - + // // keepalives (nop idling to keep the session alive and to periodically receive notifications // of changes) // - + /** * If seconds is negative or zero, keepalives will be disabled. (This is not recommended.) * @@ -838,59 +1111,73 @@ selected_keepalive_secs = seconds_while_selected; selected_with_idle_keepalive_secs = seconds_while_selected_with_idle; unselected_keepalive_secs = seconds_while_unselected; - + // schedule one now, although will be rescheduled if traffic is received before it fires schedule_keepalive(); } - + /** * Returns true if keepalives are disactivated, false if already disabled. */ public bool disable_keepalives() { return unschedule_keepalive(); } - + private bool unschedule_keepalive() { if (keepalive_id == 0) return false; - + Source.remove(keepalive_id); keepalive_id = 0; - + return true; } - + /** - * If enabled, an IDLE command will be used for notification of unsolicited server data whenever - * a mailbox is selected or examined. IDLE will only be used if ClientSession has seen a - * CAPABILITY server data response with IDLE listed as a supported extension. - * - * This will *not* break a connection out of IDLE mode; a command must be sent as well to force - * the connection back to de-idled state. + * Enables IMAP IDLE for the client session, if supported. * - * Note that this overrides other heuristics ClientSession uses about allowing idle, so use - * with caution. + * If enabled, an IDLE command will be used for notification of + * unsolicited server data whenever a mailbox is selected or + * examined. IDLE will only be used if ClientSession has seen a + * CAPABILITY server data response with IDLE listed as a supported + * extension. */ - public void allow_idle_when_selected(bool allow_idle) { - this.allow_idle = allow_idle; + public async void enable_idle(Cancellable? cancellable) + throws Error { + if (this.is_idle_supported) { + switch (get_protocol_state(null)) { + case ProtocolState.AUTHORIZING: + case ProtocolState.AUTHORIZED: + case ProtocolState.SELECTED: + case ProtocolState.SELECTING: + this.cx.enable_idle_when_quiet(true); + break; + + default: + throw new ImapError.NOT_SUPPORTED( + "IMAP IDLE only supported in AUTHORIZED or SELECTED states" + ); + } + } } - + private void schedule_keepalive() { // if old one was scheduled, unschedule and schedule anew unschedule_keepalive(); - + uint seconds; switch (get_protocol_state(null)) { case ProtocolState.UNCONNECTED: case ProtocolState.CONNECTING: return; - + case ProtocolState.SELECTING: case ProtocolState.SELECTED: - seconds = (allow_idle && supports_idle()) ? selected_with_idle_keepalive_secs + seconds = (this.cx.idle_when_quiet && this.is_idle_supported) + ? selected_with_idle_keepalive_secs : selected_keepalive_secs; break; - + case ProtocolState.UNAUTHORIZED: case ProtocolState.AUTHORIZING: case ProtocolState.AUTHORIZED: @@ -899,28 +1186,28 @@ seconds = unselected_keepalive_secs; break; } - + // Possible to not have keepalives in one state but in another, or for neither // // Yes, we allow keepalive to be set to 1 second. It's their dime. if (seconds > 0) keepalive_id = Timeout.add_seconds(seconds, on_keepalive); } - + private bool on_keepalive() { // by returning false, this will not automatically be called again, so the SourceFunc // is now dead keepalive_id = 0; - + send_command_async.begin(new NoopCommand(), null, on_keepalive_completed); Logging.debug(Logging.Flag.PERIODIC, "[%s] Sending keepalive...", to_string()); - + // No need to reschedule keepalive, as the notification that the command was sent should // do that automatically - + return false; } - + private void on_keepalive_completed(Object? source, AsyncResult result) { try { StatusResponse response = send_command_async.end(result); @@ -930,57 +1217,44 @@ debug("[%s] Keepalive error: %s", to_string(), err.message); } } - - // - // Converters - // - - public bool install_send_converter(Converter converter) { - return (cx != null) ? cx.install_send_converter(converter) : false; - } - - public bool install_recv_converter(Converter converter) { - return (cx != null) ? cx.install_recv_converter(converter) : false; - } - - public bool supports_idle() { - return capabilities.has_capability(Capabilities.IDLE); - } - + // // send commands // - - public async StatusResponse send_command_async(Command cmd, Cancellable? cancellable = null) - throws Error { + + public async StatusResponse send_command_async(Command cmd, + GLib.Cancellable? cancellable) + throws GLib.Error { check_unsupported_send_command(cmd); - + MachineParams params = new MachineParams(cmd); fsm.issue(Event.SEND_CMD, null, params); - + if (params.err != null) throw params.err; - + assert(params.proceed); - + return yield command_transaction_async(cmd, cancellable); } - - public async Gee.Map send_multiple_commands_async( - Gee.Collection cmds, Cancellable? cancellable = null) throws Error { + + public async Gee.Map + send_multiple_commands_async(Gee.Collection cmds, + GLib.Cancellable? cancellable) + throws GLib.Error { if (cmds.size == 0) throw new ImapError.INVALID("Must supply at least one command"); - + foreach (Command cmd in cmds) check_unsupported_send_command(cmd); - + // only issue one event to the state machine for all commands; either all succeed or all fail MachineParams params = new MachineParams(Geary.Collection.get_first(cmds)); fsm.issue(Event.SEND_CMD, null, params); - + if (params.err != null) throw params.err; - + assert(params.proceed); // Issue all at once using a single Nonblocking.Batch unless @@ -1020,6 +1294,7 @@ // // TODO: Convert commands into proper calls to avoid throwing an exception if (cmd.has_name(LoginCommand.NAME) + || cmd.has_name(AuthenticateCommand.NAME) || cmd.has_name(LogoutCommand.NAME) || cmd.has_name(SelectCommand.NAME) || cmd.has_name(ExamineCommand.NAME) @@ -1027,109 +1302,106 @@ throw new ImapError.NOT_SUPPORTED("Use direct calls rather than commands for %s", cmd.name); } } - + private uint on_send_command(uint state, uint event, void *user, Object? object) { MachineParams params = (MachineParams) object; - + params.proceed = true; - + return state; } - + private uint on_recv_status(uint state, uint event, void *user, Object? object) { StatusResponse status_response = (StatusResponse) object; - + switch (status_response.status) { case Status.OK: // some good-feeling text that doesn't need to be handled when in this state break; - + case Status.BYE: debug("[%s] Received BYE from server: %s", to_string(), status_response.to_string()); - + // nothing more we can do; drop connection and report disconnect to user cx.disconnect_async.begin(null, on_bye_disconnect_completed); - + state = State.BROKEN; break; - + default: debug("[%s] Received error from server: %s", to_string(), status_response.to_string()); break; } - + return state; } - + private void on_bye_disconnect_completed(Object? source, AsyncResult result) { dispatch_disconnect_results(DisconnectReason.REMOTE_CLOSE, result); } - + // // select/examine // - - public async StatusResponse select_async(MailboxSpecifier mailbox, Cancellable? cancellable = null) - throws Error { + + public async StatusResponse select_async(MailboxSpecifier mailbox, + GLib.Cancellable? cancellable) + throws GLib.Error { return yield select_examine_async(mailbox, true, cancellable); } - - public async StatusResponse examine_async(MailboxSpecifier mailbox, Cancellable? cancellable = null) - throws Error { + + public async StatusResponse examine_async(MailboxSpecifier mailbox, + GLib.Cancellable? cancellable) + throws GLib.Error { return yield select_examine_async(mailbox, false, cancellable); } - - public async StatusResponse select_examine_async(MailboxSpecifier mailbox, bool is_select, - Cancellable? cancellable) throws Error { + + public async StatusResponse select_examine_async(MailboxSpecifier mailbox, + bool is_select, + GLib.Cancellable? cancellable) + throws GLib.Error { // Ternary troubles Command cmd; if (is_select) cmd = new SelectCommand(mailbox); else cmd = new ExamineCommand(mailbox); - + MachineParams params = new MachineParams(cmd); fsm.issue(Event.SELECT, null, params); - + if (params.err != null) throw params.err; - + assert(params.proceed); - + return yield command_transaction_async(cmd, cancellable); } - + private uint on_select(uint state, uint event, void *user, Object? object) { MachineParams params = (MachineParams) object; - + if (!reserve_state_change_cmd(params, state, event)) return state; - - // Allow IDLE *before* issuing SELECT/EXAMINE because there's no guarantee another command - // will be issued any time soon, which is necessary for the IDLE command to be tacked on - // to the end of it. In other words, telling ClientConnection to go into IDLE after the - // SELECT/EXAMINE command is too late unless another command is sent (set_idle_when_quiet() - // performs no I/O). - cx.set_idle_when_quiet(allow_idle && supports_idle()); - + return State.SELECTING; } - + private uint on_not_selected(uint state, uint event, void *user, Object? object) { MachineParams params = (MachineParams) object; - + params.err = new ImapError.INVALID("Can't close mailbox, not selected"); - + return state; } - + private uint on_selecting_recv_completion(uint state, uint event, void *user, Object? object) { StatusResponse completion_response = (StatusResponse) object; - + Command? cmd; if (!validate_state_change_cmd(completion_response, out cmd)) return state; - + // get the mailbox from the command MailboxSpecifier? mailbox = null; if (cmd is SelectCommand) { @@ -1139,344 +1411,329 @@ mailbox = ((ExamineCommand) cmd).mailbox; current_mailbox_readonly = true; } - + // should only get to this point if cmd was SELECT or EXAMINE assert(mailbox != null); - + switch (completion_response.status) { case Status.OK: // mailbox is SELECTED/EXAMINED, report change after completion of transition MailboxSpecifier? old_mailbox = current_mailbox; current_mailbox = mailbox; - + if (old_mailbox != current_mailbox) fsm.do_post_transition(notify_select_completed, null, old_mailbox); - + return State.SELECTED; - + default: debug("[%s]: Unable to SELECT/EXAMINE: %s", to_string(), completion_response.to_string()); - - // turn off IDLE, not entering SELECTED/EXAMINED state - cx.set_idle_when_quiet(false); - + + // turn off IDLE, client should request it again if desired. + this.cx.enable_idle_when_quiet(false); + return State.AUTHORIZED; } } - + private void notify_select_completed(void *user, Object? object) { current_mailbox_changed((MailboxSpecifier) object, current_mailbox, current_mailbox_readonly); } - + // // close mailbox // - - public async StatusResponse close_mailbox_async(Cancellable? cancellable = null) throws Error { + + public async StatusResponse close_mailbox_async(GLib.Cancellable? cancellable) + throws GLib.Error { CloseCommand cmd = new CloseCommand(); - + MachineParams params = new MachineParams(cmd); fsm.issue(Event.CLOSE_MAILBOX, null, params); - + if (params.err != null) throw params.err; - + return yield command_transaction_async(cmd, cancellable); } - + private uint on_close_mailbox(uint state, uint event, void *user, Object? object) { MachineParams params = (MachineParams) object; - + assert(params.cmd is CloseCommand); if (!reserve_state_change_cmd(params, state, event)) return state; - + // returning to AUTHORIZED state, turn off IDLE - cx.set_idle_when_quiet(false); - + this.cx.enable_idle_when_quiet(false); + return State.CLOSING_MAILBOX; } - + private uint on_closing_recv_completion(uint state, uint event, void *user, Object? object) { StatusResponse completion_response = (StatusResponse) object; - + if (!validate_state_change_cmd(completion_response)) return state; - + switch (completion_response.status) { case Status.OK: MailboxSpecifier? old_mailbox = current_mailbox; current_mailbox = null; - + if (old_mailbox != null) fsm.do_post_transition(notify_mailbox_closed, null, old_mailbox); - + return State.AUTHORIZED; - + default: debug("[%s] Unable to CLOSE: %s", to_string(), completion_response.to_string()); - + return State.SELECTED; } } - + private void notify_mailbox_closed(void *user, Object? object) { current_mailbox_changed((MailboxSpecifier) object, null, false); } - + // // logout // - - public async void logout_async(Cancellable? cancellable = null) throws Error { + + public async void logout_async(GLib.Cancellable? cancellable) + throws GLib.Error { LogoutCommand cmd = new LogoutCommand(); - + MachineParams params = new MachineParams(cmd); fsm.issue(Event.LOGOUT, null, params); - + if (params.err != null) throw params.err; - + if(params.proceed) yield command_transaction_async(cmd, cancellable); } - + private uint on_logout(uint state, uint event, void *user, Object? object) { MachineParams params = (MachineParams) object; - + assert(params.cmd is LogoutCommand); if (!reserve_state_change_cmd(params, state, event)) return state; - + + // Leaving AUTHORIZED state, turn off IDLE + this.cx.enable_idle_when_quiet(false); + return State.LOGGING_OUT; } - + private uint on_logging_out_recv_completion(uint state, uint event, void *user, Object? object) { StatusResponse completion_response = (StatusResponse) object; - + if (!validate_state_change_cmd(completion_response)) return state; - + fsm.do_post_transition(() => { logged_out(); }); - + return State.LOGGED_OUT; } - + // // disconnect // - - public async void disconnect_async(Cancellable? cancellable = null) throws Error { + + public async void disconnect_async(GLib.Cancellable? cancellable) + throws GLib.Error { MachineParams params = new MachineParams(null); fsm.issue(Event.DISCONNECT, null, params); - + if (params.err != null) throw params.err; - + if (!params.proceed) return; - + Error? disconnect_err = null; try { yield cx.disconnect_async(cancellable); } catch (Error err) { disconnect_err = err; } - + drop_connection(); disconnected(DisconnectReason.LOCAL_CLOSE); - + if (disconnect_err != null) throw disconnect_err; } - + private uint on_disconnect(uint state, uint event, void *user, Object? object) { MachineParams params = (MachineParams) object; - + params.proceed = true; - + return State.BROKEN; } - + // // error handling // - + // use different error handler when connecting because, if connect_async() fails, there's no // requirement for the user to call disconnect_async() and clean up... this prevents leaving the // FSM in the CONNECTING state, causing an assertion when this object is destroyed private uint on_connecting_send_recv_error(uint state, uint event, void *user, Object? object, Error? err) { assert(err != null); - + debug("[%s] Connecting send/recv error, dropping client connection: %s", to_string(), err.message); - + fsm.do_post_transition(() => { drop_connection(); }); - + return State.BROKEN; } - + private uint on_send_error(uint state, uint event, void *user, Object? object, Error? err) { assert(err != null); - + if (err is IOError.CANCELLED) return state; - + debug("[%s] Send error, disconnecting: %s", to_string(), err.message); - + cx.disconnect_async.begin(null, on_fire_send_error_signal); - + return State.BROKEN; } - + private void on_fire_send_error_signal(Object? object, AsyncResult result) { dispatch_disconnect_results(DisconnectReason.LOCAL_ERROR, result); } - + private uint on_recv_error(uint state, uint event, void *user, Object? object, Error? err) { assert(err != null); debug("[%s] Receive error, disconnecting: %s", to_string(), err.message); - + cx.disconnect_async.begin(null, on_fire_recv_error_signal); - + return State.BROKEN; } - + private void on_fire_recv_error_signal(Object? object, AsyncResult result) { dispatch_disconnect_results(DisconnectReason.REMOTE_ERROR, result); } - + private void dispatch_disconnect_results(DisconnectReason reason, AsyncResult result) { debug("[%s] Disconnected due to %s", to_string(), reason.to_string()); - + try { cx.disconnect_async.end(result); } catch (Error err) { debug("[%s] Send/recv disconnect failed: %s", to_string(), err.message); } - + drop_connection(); - + disconnected(reason); } - + // This handles the situation where the user submits a command before the connection has been // established private uint on_early_command(uint state, uint event, void *user, Object? object) { assert(object != null); - + MachineParams params = (MachineParams) object; params.err = new ImapError.NOT_CONNECTED("Command %s too early: not connected to %s", params.cmd.name, to_string()); - + return state; } - + // This handles the situation where the user submits a command after the connection has been // logged out, terminated, or errored-out private uint on_late_command(uint state, uint event, void *user, Object? object) { assert(object != null); - + MachineParams params = (MachineParams) object; params.err = new ImapError.NOT_CONNECTED("Connection to %s closing or closed", to_string()); - + return state; } - + private uint on_already_connected(uint state, uint event, void *user, Object? object) { assert(object != null); - + MachineParams params = (MachineParams) object; params.err = new ImapError.ALREADY_CONNECTED("Already connected or connecting to %s", to_string()); - + return state; } - + private uint on_already_logged_in(uint state, uint event, void *user, Object? object) { assert(object != null); - + MachineParams params = (MachineParams) object; params.err = new ImapError.ALREADY_CONNECTED("Already logged in to %s", to_string()); - + return state; } - + // This handles the situation where an unanticipated (or uninteresting) ServerResponse was received private uint on_dropped_response(uint state, uint event, void *user, Object? object) { ServerResponse server_response = (ServerResponse) object; - + debug("[%s] Dropped server response at %s: %s", to_string(), fsm.get_event_issued_string(state, event), server_response.to_string()); - + return state; } - + // This handles commands that the user initiates before the session is in the AUTHENTICATED state private uint on_unauthenticated(uint state, uint event, void *user, Object? object) { assert(object != null); - + MachineParams params = (MachineParams) object; params.err = new ImapError.UNAUTHENTICATED("Not authenticated with %s", to_string()); - + return state; } - + private uint on_ignored_transition(uint state, uint event) { debug("[%s] Ignored transition: %s", to_string(), fsm.get_event_issued_string(state, event)); - + return state; } - + // // command submission // - + private async StatusResponse command_transaction_async(Command cmd, Cancellable? cancellable) throws Error { - if (cx == null) + if (this.cx == null) throw new ImapError.NOT_CONNECTED("Not connected to %s", imap_endpoint.to_string()); - - yield cx.send_async(cmd, cancellable); - - // send_async() should've tagged the Command, otherwise the completion_pending will fail - assert(cmd.tag.is_tagged()); - - // If the command didn't complete (i.e. a CompletionStatusResponse didn't return from the - // server) in the context of send_async(), wait for it now - if (!seen_completion_responses.has_key(cmd.tag)) { - waiting_for_completion.set(cmd.tag, new CommandCallback(command_transaction_async.callback)); - yield; - } - - // it should be seen now; if not, it's because of disconnection cancelling all the outstanding - // requests - StatusResponse? completion_response; - if (!seen_completion_responses.unset(cmd.tag, out completion_response)) { - assert(cx == null); - - throw new ImapError.NOT_CONNECTED("Not connected to %s", imap_endpoint.to_string()); - } - - assert(completion_response != null); - - return completion_response; + + this.cx.send_command(cmd); + yield cmd.wait_until_complete(cancellable); + return cmd.status; } - + // // network connection event handlers // - + private void on_network_connected() { debug("[%s] Connected to %s", to_string(), imap_endpoint.to_string()); - + fsm.issue(Event.CONNECTED); } - + private void on_network_disconnected() { debug("[%s] Disconnected from %s", to_string(), imap_endpoint.to_string()); - + fsm.issue(Event.DISCONNECTED); } - + private void on_network_sent_command(Command cmd) { #if VERBOSE_SESSION debug("[%s] Sent command %s", to_string(), cmd.to_string()); @@ -1484,55 +1741,57 @@ // resechedule keepalive schedule_keepalive(); } - + private void on_network_send_error(Error err) { debug("[%s] Send error: %s", to_string(), err.message); - + fsm.issue(Event.SEND_ERROR, null, null, err); } - + private void on_received_status_response(StatusResponse status_response) { - // reschedule keepalive (traffic seen on channel) + this.last_seen = GLib.get_real_time(); schedule_keepalive(); - - // If a CAPABILITIES ResponseCode, decode and update capabilities ... - // some servers do this to prevent a second round-trip - ResponseCode? response_code = status_response.response_code; - if (response_code != null) { - try { - if (response_code.get_response_code_type().is_value(ResponseCodeType.CAPABILITY)) { - capabilities = response_code.get_capabilities(ref next_capabilities_revision); - debug("[%s] %s %s", to_string(), status_response.status.to_string(), - capabilities.to_string()); - - capability(capabilities); + + // XXX Need to ignore emitted IDLE status responses. They are + // emitted by ClientConnection because it doesn't make any + // sense not to, and so they get logged by that class's + // default handlers, but because they are snooped on here (and + // even worse are used to push FSM transitions, rather relying + // on the actual commands themselves), we need to check for + // IDLE responses and ignore them. + Command? command = this.cx.get_sent_command(status_response.tag); + if (command == null || !(command is IdleCommand)) { + // If a CAPABILITIES ResponseCode, decode and update + // capabilities ... some servers do this to prevent a + // second round-trip + ResponseCode? response_code = status_response.response_code; + if (response_code != null) { + try { + if (response_code.get_response_code_type().is_value(ResponseCodeType.CAPABILITY)) { + capabilities = response_code.get_capabilities(ref next_capabilities_revision); + debug("[%s] %s %s", to_string(), status_response.status.to_string(), + capabilities.to_string()); + + capability(capabilities); + } + } catch (Error err) { + debug("[%s] Unable to convert response code to capabilities: %s", to_string(), + err.message); } - } catch (Error err) { - debug("[%s] Unable to convert response code to capabilities: %s", to_string(), - err.message); } + + // update state machine before notifying subscribers, who + // may turn around and query ClientSession + if (status_response.is_completion) { + fsm.issue(Event.RECV_COMPLETION, null, status_response, null); + } else { + fsm.issue(Event.RECV_STATUS, null, status_response, null); + } + + status_response_received(status_response); } - - // update state machine before notifying subscribers, who may turn around and query ClientSession - if (status_response.is_completion) { - fsm.issue(Event.RECV_COMPLETION, null, status_response, null); - - // Note that this signal could be called in the context of cx.send_async() that sent - // this command to the server ... this mechanism (seen_completion_response and - // waiting_for_completion) assures that in either case issue_command_async() returns - // when the command is completed - seen_completion_responses.set(status_response.tag, status_response); - - CommandCallback? cmd_cb; - if (waiting_for_completion.unset(status_response.tag, out cmd_cb)) - Scheduler.on_idle(cmd_cb.callback); - } else { - fsm.issue(Event.RECV_STATUS, null, status_response, null); - } - - status_response_received(status_response); } - + private void notify_received_data(ServerData server_data) throws ImapError { switch (server_data.server_data_type) { case ServerDataType.CAPABILITY: @@ -1541,43 +1800,47 @@ capabilities = server_data.get_capabilities(ref next_capabilities_revision); debug("[%s] %s %s", to_string(), server_data.server_data_type.to_string(), capabilities.to_string()); - + capability(capabilities); break; - + case ServerDataType.EXISTS: exists(server_data.get_exists()); break; - + case ServerDataType.EXPUNGE: expunge(server_data.get_expunge()); break; - + case ServerDataType.FETCH: fetch(server_data.get_fetch()); break; - + case ServerDataType.FLAGS: flags(server_data.get_flags()); break; - + case ServerDataType.LIST: case ServerDataType.XLIST: list(server_data.get_list()); break; - + case ServerDataType.RECENT: recent(server_data.get_recent()); break; - + case ServerDataType.STATUS: status(server_data.get_status()); break; - + case ServerDataType.SEARCH: search(server_data.get_search()); break; - + + case ServerDataType.NAMESPACE: + namespace(server_data.get_namespace()); + break; + // TODO: LSUB case ServerDataType.LSUB: default: @@ -1586,14 +1849,16 @@ server_data.to_string()); break; } - + server_data_received(server_data); } - + private void on_received_server_data(ServerData server_data) { + this.last_seen = GLib.get_real_time(); + // reschedule keepalive (traffic seen on channel) schedule_keepalive(); - + // send ServerData to upper layers for processing and storage try { notify_received_data(server_data); @@ -1602,16 +1867,25 @@ ierr.message); } } - + + private void on_received_continuation_response(ContinuationResponse response) { + this.last_seen = GLib.get_real_time(); + + // reschedule keepalive (traffic seen on channel) + schedule_keepalive(); + } + private void on_received_bytes(size_t bytes) { + this.last_seen = GLib.get_real_time(); + // reschedule keepalive schedule_keepalive(); } - + private void on_received_bad_response(RootParameters root, ImapError err) { debug("[%s] Received bad response %s: %s", to_string(), root.to_string(), err.message); } - + private void on_received_closed(ClientConnection cx) { #if VERBOSE_SESSION // This currently doesn't generate any Events, but it does mean the connection has closed @@ -1619,13 +1893,13 @@ debug("[%s] Received closed", to_string()); #endif } - + private void on_network_receive_failure(Error err) { debug("[%s] Receive failed: %s", to_string(), err.message); - + fsm.issue(Event.RECV_ERROR, null, null, err); } - + public string to_string() { if (cx == null) { return "%s %s".printf(imap_endpoint.to_string(), fsm.get_state_string(fsm.get_state())); @@ -1635,4 +1909,3 @@ } } } - diff -Nru geary-0.12.4/src/engine/imap/transport/imap-deserializer.vala geary-3.32.0/src/engine/imap/transport/imap-deserializer.vala --- geary-0.12.4/src/engine/imap/transport/imap-deserializer.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/transport/imap-deserializer.vala 2019-03-17 13:39:29.000000000 +0000 @@ -12,7 +12,7 @@ * The Deserializer will only begin reading from the stream when {@link start_async} is called. * Calling {@link stop_async} will halt reading without closing the stream itself. A Deserializer * may not be reused once stop_async has been invoked. - * + * * Since all results from the Deserializer are reported via signals, those signals should be * connected to prior to calling start_async, or the caller risks missing early messages. (Note * that since Deserializer uses async I/O, this isn't technically possible unless the signals are @@ -25,14 +25,14 @@ public class Geary.Imap.Deserializer : BaseObject { private const size_t MAX_BLOCK_READ_SIZE = 4096; - + private enum Mode { LINE, BLOCK, FAILED, CLOSED } - + private enum State { TAG, START_PARAM, @@ -49,11 +49,11 @@ CLOSED, COUNT } - + private static string state_to_string(uint state) { return ((State) state).to_string(); } - + private enum Event { CHAR, EOL, @@ -62,23 +62,26 @@ ERROR, COUNT } - + private static string event_to_string(uint event) { return ((Event) event).to_string(); } - + private static Geary.State.MachineDescriptor machine_desc = new Geary.State.MachineDescriptor( "Geary.Imap.Deserializer", State.TAG, State.COUNT, Event.COUNT, state_to_string, event_to_string); - + private string identifier; private DataInputStream dins; private Geary.State.Machine fsm; + private ListParameter context; + private Gee.LinkedList context_stack = + new Gee.LinkedList(); + private Cancellable? cancellable = null; private Nonblocking.Semaphore closed_semaphore = new Nonblocking.Semaphore(); private Geary.Stream.MidstreamConverter midstream = new Geary.Stream.MidstreamConverter("Deserializer"); - private RootParameters root = new RootParameters(); private StringBuilder? current_string = null; private size_t literal_length_remaining = 0; private Geary.Memory.GrowableBuffer? block_buffer = null; @@ -144,54 +147,52 @@ dins = new DataInputStream(cins); dins.set_newline_type(DataStreamNewlineType.CR_LF); dins.set_close_base_stream(false); - - context = root; - + Geary.State.Mapping[] mappings = { new Geary.State.Mapping(State.TAG, Event.CHAR, on_tag_char), new Geary.State.Mapping(State.TAG, Event.EOS, on_eos), new Geary.State.Mapping(State.TAG, Event.ERROR, on_error), - + new Geary.State.Mapping(State.START_PARAM, Event.CHAR, on_first_param_char), new Geary.State.Mapping(State.START_PARAM, Event.EOL, on_eol), new Geary.State.Mapping(State.START_PARAM, Event.EOS, on_eos), new Geary.State.Mapping(State.START_PARAM, Event.ERROR, on_error), - + new Geary.State.Mapping(State.ATOM, Event.CHAR, on_atom_char), new Geary.State.Mapping(State.ATOM, Event.EOL, on_atom_eol), new Geary.State.Mapping(State.ATOM, Event.EOS, on_eos), new Geary.State.Mapping(State.ATOM, Event.ERROR, on_error), - + new Geary.State.Mapping(State.FLAG, Event.CHAR, on_first_flag_char), new Geary.State.Mapping(State.FLAG, Event.EOL, on_atom_eol), new Geary.State.Mapping(State.FLAG, Event.EOS, on_eos), new Geary.State.Mapping(State.FLAG, Event.ERROR, on_error), - + new Geary.State.Mapping(State.QUOTED, Event.CHAR, on_quoted_char), new Geary.State.Mapping(State.QUOTED, Event.EOS, on_eos), new Geary.State.Mapping(State.QUOTED, Event.ERROR, on_error), - + new Geary.State.Mapping(State.QUOTED_ESCAPE, Event.CHAR, on_quoted_escape_char), new Geary.State.Mapping(State.QUOTED_ESCAPE, Event.EOS, on_eos), new Geary.State.Mapping(State.QUOTED_ESCAPE, Event.ERROR, on_error), - + new Geary.State.Mapping(State.PARTIAL_BODY_ATOM, Event.CHAR, on_partial_body_atom_char), new Geary.State.Mapping(State.PARTIAL_BODY_ATOM, Event.EOS, on_eos), new Geary.State.Mapping(State.PARTIAL_BODY_ATOM, Event.ERROR, on_error), - + new Geary.State.Mapping(State.PARTIAL_BODY_ATOM_TERMINATING, Event.CHAR, on_partial_body_atom_terminating_char), new Geary.State.Mapping(State.PARTIAL_BODY_ATOM_TERMINATING, Event.EOS, on_eos), new Geary.State.Mapping(State.PARTIAL_BODY_ATOM_TERMINATING, Event.ERROR, on_error), - + new Geary.State.Mapping(State.LITERAL, Event.CHAR, on_literal_char), new Geary.State.Mapping(State.LITERAL, Event.EOS, on_eos), new Geary.State.Mapping(State.LITERAL, Event.ERROR, on_error), - + new Geary.State.Mapping(State.LITERAL_DATA_BEGIN, Event.EOL, on_literal_data_begin_eol), new Geary.State.Mapping(State.LITERAL_DATA_BEGIN, Event.EOS, on_eos), new Geary.State.Mapping(State.LITERAL_DATA_BEGIN, Event.ERROR, on_error), - + new Geary.State.Mapping(State.LITERAL_DATA, Event.DATA, on_literal_data), new Geary.State.Mapping(State.LITERAL_DATA, Event.EOS, on_eos), new Geary.State.Mapping(State.LITERAL_DATA, Event.ERROR, on_error), @@ -203,10 +204,12 @@ new Geary.State.Mapping(State.CLOSED, Event.EOS, Geary.State.nop), new Geary.State.Mapping(State.CLOSED, Event.ERROR, Geary.State.nop) }; - - fsm = new Geary.State.Machine(machine_desc, mappings, on_bad_transition); + + this.fsm = new Geary.State.Machine(machine_desc, mappings, on_bad_transition); + + reset_params(); } - + /** * Install a custom Converter into the input stream. * @@ -215,7 +218,7 @@ public bool install_converter(Converter converter) { return midstream.install(converter); } - + /** * Begin deserializing IMAP responses from the input stream. * @@ -224,31 +227,30 @@ public async void start_async(int priority = GLib.Priority.DEFAULT_IDLE) throws Error { if (cancellable != null) throw new EngineError.ALREADY_OPEN("Deserializer already open"); - + Mode mode = get_mode(); - + if (mode == Mode.FAILED) throw new EngineError.ALREADY_CLOSED("Deserializer failed"); - + if ((mode == Mode.CLOSED) || (cancellable != null && cancellable.is_cancelled())) throw new EngineError.ALREADY_CLOSED("Deserializer closed"); - + cancellable = new Cancellable(); ins_priority = priority; - + next_deserialize_step(); } - + public async void stop_async() throws Error { // quietly fail when not opened or already closed if (cancellable == null || cancellable.is_cancelled() || is_halted()) return; - + // cancel any outstanding I/O cancellable.cancel(); - + // wait for outstanding I/O to exit - debug("[%s] Waiting for deserializer to close...", to_string()); yield closed_semaphore.wait_async(); debug("[%s] Deserializer closed", to_string()); } @@ -279,40 +281,40 @@ case Mode.LINE: dins.read_line_async.begin(ins_priority, cancellable, on_read_line); break; - + case Mode.BLOCK: // Can't merely skip zero-byte literal, need to go through async transaction to // properly send events to the FSM assert(literal_length_remaining >= 0); - + if (block_buffer == null) block_buffer = new Geary.Memory.GrowableBuffer(); - + current_buffer = block_buffer.allocate( size_t.min(MAX_BLOCK_READ_SIZE, literal_length_remaining)); - + dins.read_async.begin(current_buffer, ins_priority, cancellable, on_read_block); break; - + case Mode.FAILED: case Mode.CLOSED: // do nothing; Deserializer is effectively closed break; - + default: assert_not_reached(); } } - + private void on_read_line(Object? source, AsyncResult result) { try { size_t bytes_read; string? line = dins.read_line_async.end(result, out bytes_read); if (line == null) { Logging.debug(Logging.Flag.DESERIALIZER, "[%s] line EOS", to_string()); - + push_eos(); - + return; } @@ -323,10 +325,10 @@ push_error(err); return; } - + next_deserialize_step(); } - + private void on_read_block(Object? source, AsyncResult result) { try { // Zero-byte literals are legal (see note in next_deserialize_step()), so EOS only @@ -334,12 +336,12 @@ size_t bytes_read = dins.read_async.end(result); if (bytes_read == 0 && literal_length_remaining > 0) { Logging.debug(Logging.Flag.DESERIALIZER, "[%s] block EOS", to_string()); - + push_eos(); - + return; } - + Logging.debug(Logging.Flag.DESERIALIZER, "[%s] block %lub", to_string(), bytes_read); bytes_received(bytes_read); @@ -349,10 +351,10 @@ push_data(bytes_read); } catch (Error err) { push_error(err); - + return; } - + next_deserialize_step(); } @@ -390,23 +392,23 @@ private void push_eos() { fsm.issue(Event.EOS); } - + // Push an Error event private void push_error(Error err) { fsm.issue(Event.ERROR, null, null, err); } - + private Mode get_mode() { switch (fsm.get_state()) { case State.LITERAL_DATA: return Mode.BLOCK; - + case State.FAILED: return Mode.FAILED; - + case State.CLOSED: return Mode.CLOSED; - + default: return Mode.LINE; } @@ -415,37 +417,37 @@ private bool is_current_string_empty() { return (current_string == null) || String.is_empty(current_string.str); } - + // Case-insensitive compare private bool is_current_string_ci(string cmp) { if (current_string == null || String.is_empty(current_string.str)) return false; - + return Ascii.stri_equal(current_string.str, cmp); } - + private void append_to_string(char ch) { if (current_string == null) current_string = new StringBuilder(); - + current_string.append_c(ch); } - + private void save_string_parameter(bool quoted) { // deal with empty strings if (!quoted && is_current_string_empty()) return; - + // deal with empty quoted strings string str = (quoted && current_string == null) ? "" : current_string.str; - + if (quoted) save_parameter(new QuotedStringParameter(str)); else if (NumberParameter.is_ascii_numeric(str, null)) save_parameter(new NumberParameter.from_ascii(str)); else save_parameter(new UnquotedStringParameter(str)); - + current_string = null; } @@ -453,39 +455,43 @@ save_parameter(new LiteralParameter(block_buffer)); block_buffer = null; } - + private void save_parameter(Parameter param) { context.add(param); } - + // ListParameter's parent *must* be current context private void push(ListParameter child) { - context.add(child); - - context = child; + this.context.add(child); + + this.context_stack.insert(0, child); + this.context = child; } - + private char get_current_context_terminator() { return (context is ResponseCode) ? ']' : ')'; } - + private State pop() { - ListParameter? parent = context.parent; - if (parent == null) { + State ret = State.START_PARAM; + if (this.context_stack.size > 1) { + this.context_stack.remove_at(0); + this.context = this.context_stack[0]; + } else { warning("Attempt to close unopened list/response code"); - - return State.FAILED; + ret = State.FAILED; } - - context = parent; - - return State.START_PARAM; + return ret; } private void flush_params() { bool okay = true; - if (this.context != this.root) { - Logging.debug(Logging.Flag.DESERIALIZER, "[%s] Unclosed list in parameters"); + if (this.context_stack.size > 1) { + Logging.debug( + Logging.Flag.DESERIALIZER, + "[%s] Unclosed list in parameters", + to_string() + ); okay = false; } @@ -498,17 +504,17 @@ okay = false; } - if (okay && this.root != null && root.size > 0) { - parameters_ready(this.root); + if (okay && this.context.size > 0) { + parameters_ready((RootParameters) this.context); } reset_params(); } private void reset_params() { - this.context = this.root = new RootParameters(); - this.current_string = null; - this.literal_length_remaining = 0; + this.context = new RootParameters(); + this.context_stack.clear(); + this.context_stack.add(this.context); } // @@ -572,24 +578,24 @@ return State.FAILED; } } - + private uint on_tag_char(uint state, uint event, void *user) { char ch = *((char *) user); - + // drop if not allowed for tags (allowing for continuations and watching for spaces, which // indicate a change of state) if (DataFormat.is_tag_special(ch, " +")) return State.TAG; - + // space indicates end of tag if (ch == ' ') { save_string_parameter(false); - + return State.START_PARAM; } - + append_to_string(ch); - + return State.TAG; } @@ -682,30 +688,30 @@ private uint on_quoted_char(uint state, uint event, void *user) { char ch = *((char *) user); - + // drop anything above 0x7F, NUL, CR, and LF if (ch > 0x7F || ch == '\0' || ch == '\r' || ch == '\n') return State.QUOTED; - + // look for escaped characters if (ch == '\\') return State.QUOTED_ESCAPE; - + // DQUOTE ends quoted string and return to parsing atoms if (ch == '\"') { save_string_parameter(true); - + return State.START_PARAM; } - + append_to_string(ch); - + return State.QUOTED; } - + private uint on_quoted_escape_char(uint state, uint event, void *user) { char ch = *((char *) user); - + // only two accepted escaped characters: double-quote and backslash // everything else dropped on the floor switch (ch) { @@ -714,19 +720,19 @@ append_to_string(ch); break; } - + return State.QUOTED; } - + private uint on_partial_body_atom_char(uint state, uint event, void *user) { char ch = *((char *) user); - + // decoding the partial body parameter ("BODY[section]" et al.) is simply to locate the // terminating space after the closing square bracket or closing angle bracket // TODO: stricter testing of atom special characters and such (much like on_tag_or_atom_char) // but keeping in mind the looser rules and such with this variation append_to_string(ch); - + // Can't terminate the atom with a close square bracket because the partial span // ("<...>") might be next // @@ -737,27 +743,27 @@ case ']': case '>': return State.PARTIAL_BODY_ATOM_TERMINATING; - + default: return state; } } - + private uint on_partial_body_atom_terminating_char(uint state, uint event, void *user) { char ch = *((char *) user); - + // anything but a space indicates the atom is continuing, therefore return to prior state if (ch != ' ') return on_partial_body_atom_char(State.PARTIAL_BODY_ATOM, event, user); - + save_string_parameter(false); - + return State.START_PARAM; } - + private uint on_literal_char(uint state, uint event, void *user) { char ch = *((char *) user); - + // if close-bracket, end of literal length field -- next event must be EOL if (ch == '}') { // empty literal treated as garbage @@ -773,31 +779,31 @@ return State.LITERAL_DATA_BEGIN; } - + // drop anything non-numeric if (!ch.isdigit()) return State.LITERAL; - + append_to_string(ch); - + return State.LITERAL; } - + private uint on_literal_data_begin_eol(uint state, uint event, void *user) { return State.LITERAL_DATA; } - + private uint on_literal_data(uint state, uint event, void *user) { size_t *bytes_read = (size_t *) user; - + assert(*bytes_read <= literal_length_remaining); literal_length_remaining -= *bytes_read; - + if (literal_length_remaining > 0) return State.LITERAL_DATA; - + save_literal_parameter(); - + return State.START_PARAM; } @@ -818,24 +824,24 @@ private uint on_error(uint state, uint event, void *user, Object? object, Error? err) { assert(err != null); - - debug("[%s] input error: %s", to_string(), err.message); - + // only Cancellable allowed is internal used to notify when closed; all other errors should // be reported - if (!(err is IOError.CANCELLED)) + if (!(err is IOError.CANCELLED)) { + debug("[%s] input error: %s", to_string(), err.message); receive_failure(err); - + } + // always signal as closed and notify closed_semaphore.blind_notify(); eos(); - + return State.CLOSED; } - + private uint on_bad_transition(uint state, uint event, void *user) { warning("Bad event %s at state %s", event_to_string(event), state_to_string(state)); - + return State.FAILED; } } diff -Nru geary-0.12.4/src/engine/imap/transport/imap-serializer.vala geary-3.32.0/src/engine/imap/transport/imap-serializer.vala --- geary-0.12.4/src/engine/imap/transport/imap-serializer.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap/transport/imap-serializer.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,236 +1,132 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. + * Copyright 2018 Michael Gratton * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ /** - * Serializer asynchronously writes serialized IMAP commands to the supplied output stream via a - * queue of buffers. + * Writes IMAP protocol strings to a supplied output stream. * - * Since most IMAP commands are small in size (one line of data, often under 64 bytes), the - * Serializer writes them to a queue of temporary buffers (interspersed with user-supplied buffers - * that are intended to be literal data). The data is only written when {@link flush_async} is - * invoked. - * - * This means that if the caller wants some buffer beyond the steps described above, they should - * pass in a BufferedOutputStream (or one of its subclasses). flush_async() will flush the user's - * OutputStream after writing to it. - * - * Command continuation requires some synchronization between the Serializer and the - * {@link Deserializer}. It also requires some queue management. See {@link fast_forward_queue} - * and {@link next_synchronized_message}. + * This class uses a {@link GLib.DataOutputStream} for writing strings + * to the given stream. Since that does not support asynchronous + * writes, it is highly desirable that the stream passed to this class + * is a {@link GLib.BufferedOutputStream}, or some other type that + * uses a memory buffer large enough to write a typical command + * completely without causing disk or network I/O. * * @see Deserializer */ - public class Geary.Imap.Serializer : BaseObject { - private class SerializedData { - public Memory.Buffer buffer; - public Tag? literal_data_tag; - - public SerializedData(Memory.Buffer buffer, Tag? literal_data_tag) { - this.buffer = buffer; - this.literal_data_tag = literal_data_tag; - } - } - + private string identifier; - private OutputStream outs; - private ConverterOutputStream couts; - private MemoryOutputStream mouts; - private DataOutputStream douts; - private Geary.Stream.MidstreamConverter midstream = new Geary.Stream.MidstreamConverter("Serializer"); - private Gee.Queue datastream = new Gee.LinkedList(); - - public Serializer(string identifier, OutputStream outs) { + private GLib.DataOutputStream output; + + public Serializer(string identifier, GLib.OutputStream output) { this.identifier = identifier; - this.outs = outs; - - // prepare the ConverterOutputStream (which wraps the caller's OutputStream and allows for - // midstream conversion) - couts = new ConverterOutputStream(outs, midstream); - couts.set_close_base_stream(false); - - // prepare the DataOutputStream (which generates buffers for the queue) - mouts = new MemoryOutputStream(null, realloc, free); - douts = new DataOutputStream(mouts); - douts.set_close_base_stream(false); - } - - public bool install_converter(Converter converter) { - return midstream.install(converter); - } - - public void push_ascii(char ch) throws Error { - douts.put_byte(ch, null); - } - - /** - * Pushes the string to the IMAP server with quoting applied whether required or not. Returns - * true if quoting was required. - */ - public bool push_quoted_string(string str) throws Error { - string quoted; - DataFormat.Quoting requirement = DataFormat.convert_to_quoted(str, out quoted); - - douts.put_string(quoted); - - return (requirement == DataFormat.Quoting.REQUIRED); - } - - /** - * This will push the string to IMAP as-is. Use only if you absolutely know what you're doing. - */ - public void push_unquoted_string(string str) throws Error { - douts.put_string(str); - } - - public void push_space() throws Error { - douts.put_byte(' ', null); - } - - public void push_nil() throws Error { - douts.put_string(NilParameter.VALUE, null); - } - - public void push_eol() throws Error { - douts.put_string("\r\n", null); - } - - private void enqueue_current_stream() throws IOError { - size_t length = mouts.get_data_size(); - if (length <= 0) - return; - - // close before converting to Memory.ByteBuffer - mouts.close(); - - SerializedData data = new SerializedData( - new Memory.ByteBuffer.from_memory_output_stream(mouts), null); - datastream.add(data); - - mouts = new MemoryOutputStream(null, realloc, free); - douts = new DataOutputStream(mouts); - douts.set_close_base_stream(false); - } - - /* - * Pushes an {link Memory.Buffer} to the serialized stream that must be synchronized - * with the server before transmission. + this.output = new GLib.DataOutputStream(output); + this.output.set_close_base_stream(false); + } + + /** + * Writes a string without quoting. * - * Literal data may require synchronization with the server and so should only be used when - * necessary. See {link DataFormat.is_quoting_required} to test data. + * It is the caller's responsibility to ensure that the value is + * valid to be written as an unquoted string, instead of with + * quoting or as a literal. + */ + public void push_unquoted_string(string str, + GLib.Cancellable? cancellable = null) + throws GLib.Error { + this.output.put_string(str, cancellable); + } + + /** + * Writes a string with quoting. * - * The supplied buffer must not be mutated once submitted to the {@link Serializer}. + * It is the caller's responsibility to ensure that the value is + * valid to be written as a quoted string, instead of as a + * literal. + */ + public void push_quoted_string(string str, + GLib.Cancellable? cancellable = null) + throws GLib.Error { + this.output.put_byte('"'); + int index = 0; + char ch = str[index]; + while (ch != String.EOS) { + if (ch == '"' || ch == '\\') { + this.output.put_byte('\\'); + } + this.output.put_byte(ch); + ch = str[++index]; + } + this.output.put_byte('"'); + } + + /** + * Writes a single ASCII character. * - * See [[http://tools.ietf.org/html/rfc3501#section-4.3]] and - * [[http://tools.ietf.org/html/rfc3501#section-7.5]] + * It is the caller's responsibility to ensure that the value is + * valid to be written as-is. */ - public void push_synchronized_literal_data(Tag tag, Memory.Buffer buffer) throws Error { - enqueue_current_stream(); - datastream.add(new SerializedData(buffer, tag)); + public void push_ascii(char ch, GLib.Cancellable? cancellable = null) + throws GLib.Error { + this.output.put_byte(ch, cancellable); } - + /** - * Indicates that a complete message has been pushed to the {@link Serializer}. - * - * It's important to delineate messages for the Serializer, as it aids in queue management - * and command continuation (synchronization). + * Writes a single ASCII space character. */ - public void push_end_of_message() throws Error { - enqueue_current_stream(); - datastream.add(null); + public void push_space(GLib.Cancellable? cancellable = null) + throws GLib.Error { + this.output.put_byte(' ', cancellable); } - + /** - * Returns the {@link Tag} for the message with the next synchronization message Tag. - * - * This can be used to prepare for receiving a command continuation failure before sending - * the request via {@link flush_async}, as the response could return before that call completes. + * Writes a NIL atom. */ - public Tag? next_synchronized_message() { - foreach (SerializedData? data in datastream) { - if (data != null && data.literal_data_tag != null) - return data.literal_data_tag; - } - - return null; + public void push_nil(GLib.Cancellable? cancellable = null) + throws GLib.Error { + this.output.put_string(NilParameter.VALUE, cancellable); } - + /** - * Discards all buffers associated with the current message and moves the queue forward to the - * next one. - * - * This is useful when a command continuation is refused by the server and the command must be - * aborted. - * - * Any data currently in the buffer is *not* enqueued, as by definition it has not been marked - * with {@link push_end_of_message}. + * Writes a CRLF sequence. */ - public void fast_forward_queue() { - while (!datastream.is_empty) { - if (datastream.poll() == null) - break; - } + public void push_eol(GLib.Cancellable? cancellable = null) + throws GLib.Error { + this.output.put_string("\r\n", cancellable); } - + /** - * Push all serialized data and buffers onto the wire. - * - * Caller should pass is_synchronized=true if the connection has been synchronized for a command - * continuation. - * - * If synchronize_tag returns non-null, then the flush has not completed. The connection must - * wait for the server to send a continuation response before continuing. When ready, call - * flush_async() again with is_synchronized set to true. The tag is supplied to watch for - * an error condition from the server (which may reject the synchronization request). - */ - public async void flush_async(bool is_synchronized, out Tag? synchronize_tag, - Cancellable? cancellable = null) throws Error { - synchronize_tag = null; - - // commit the last buffer to the queue (although this is best done with push_end_message) - enqueue_current_stream(); - - // walk the SerializedData queue, pushing each out to the wire unless a synchronization - // point is encountered - while (!datastream.is_empty) { - // see if next data buffer is synchronized - SerializedData? data = datastream.peek(); - if (data != null && data.literal_data_tag != null && !is_synchronized) { - // report the Tag that is associated with the continuation - synchronize_tag = data.literal_data_tag; - - // break out to ensure pipe is flushed - break; - } - - // if not, remove and process - data = datastream.poll(); - if (data == null) { - // end of message, move on - continue; - } - - Logging.debug(Logging.Flag.SERIALIZER, "[%s] %s", to_string(), data.buffer.to_string()); - - // splice buffer's InputStream directly into OutputStream - yield couts.splice_async(data.buffer.get_input_stream(), OutputStreamSpliceFlags.NONE, - Priority.DEFAULT, cancellable); - - // if synchronized before, not any more - is_synchronized = false; - } - - // make sure everything is flushed out now ... some trouble with BufferedOutputStreams - // here, so flush ConverterOutputStream and its base stream - yield couts.flush_async(Priority.DEFAULT, cancellable); - yield couts.base_stream.flush_async(Priority.DEFAULT, cancellable); + * Writes literal data to the output stream. + */ + public async void push_literal_data(Memory.Buffer buffer, + GLib.Cancellable? cancellable = null) + throws GLib.Error { + yield this.output.splice_async( + buffer.get_input_stream(), + OutputStreamSpliceFlags.NONE, + Priority.DEFAULT, + cancellable + ); + } + + /** + * Flushes the output stream, ensuring a command has been sent. + */ + public async void flush_stream(GLib.Cancellable? cancellable = null) + throws GLib.Error { + yield this.output.flush_async(Priority.DEFAULT, cancellable); } - + + /** + * Returns a string representation for debugging. + */ public string to_string() { return "ser:%s".printf(identifier); } -} +} diff -Nru geary-0.12.4/src/engine/imap-db/imap-db-account.vala geary-3.32.0/src/engine/imap-db/imap-db-account.vala --- geary-0.12.4/src/engine/imap-db/imap-db-account.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-db/imap-db-account.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,12 +1,14 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. + * Copyright 2019 Michael Gratton . * * This software is licensed under the GNU Lesser General Public License - * (version 2.1 or later). See the COPYING file in this distribution. + * (version 2.1 or later). See the COPYING file in this distribution. */ private class Geary.ImapDB.Account : BaseObject { private const int POPULATE_SEARCH_TABLE_DELAY_SEC = 5; - + // These characters are chosen for being commonly used to continue a single word (such as // extended last names, i.e. "Lars-Eric") or in terms commonly searched for in an email client, // i.e. unadorned mailbox addresses. Note that characters commonly used for wildcards or that @@ -38,12 +40,25 @@ private const string SEARCH_OP_VALUE_STARRED = "starred"; private const string SEARCH_OP_VALUE_UNREAD = "unread"; + // Storage path names + private const string DB_FILENAME = "geary.db"; + private const string ATTACHMENTS_DIR = "attachments"; + + /** + * Returns the on-disk paths used for storage by this account. + */ + public static void get_imap_db_storage_locations(File user_data_dir, out File db_file, + out File attachments_dir) { + db_file = user_data_dir.get_child(DB_FILENAME); + attachments_dir = user_data_dir.get_child(ATTACHMENTS_DIR); + } + private class FolderReference : Geary.SmartReference { public Geary.FolderPath path; - + public FolderReference(ImapDB.Folder folder, Geary.FolderPath path) { base (folder); - + this.path = path; } } @@ -59,24 +74,35 @@ private static Gee.HashMap search_op_is_values = new Gee.HashMap(); - public signal void email_sent(Geary.RFC822.Message rfc822); - + public signal void contacts_loaded(); + + /** + * The root path for all remote IMAP folders. + * + * No folder exists for this path locally or on the remote server, + * it merely exists to provide a common root for the paths of all + * IMAP folders. + * + * @see list_folders_async + */ + public Imap.FolderRoot imap_folder_root { + get; private set; default = new Imap.FolderRoot(); + } + // Only available when the Account is opened - public SmtpOutboxFolder? outbox { get; private set; default = null; } - public Geary.SearchFolder? search_folder { get; private set; default = null; } public ImapEngine.ContactStore contact_store { get; private set; } - public IntervalProgressMonitor search_index_monitor { get; private set; + public IntervalProgressMonitor search_index_monitor { get; private set; default = new IntervalProgressMonitor(ProgressType.SEARCH_INDEX, 0, 0); } public SimpleProgressMonitor upgrade_monitor { get; private set; default = new SimpleProgressMonitor( ProgressType.DB_UPGRADE); } public SimpleProgressMonitor vacuum_monitor { get; private set; default = new SimpleProgressMonitor( ProgressType.DB_VACUUM); } - public SimpleProgressMonitor sending_monitor { get; private set; - default = new SimpleProgressMonitor(ProgressType.ACTIVITY); } - + + /** The backing database for the account. */ + public ImapDB.Database? db { get; private set; default = null; } + private string name; private AccountInformation account_information; - private ImapDB.Database? db = null; private Gee.HashMap folder_refs = new Gee.HashMap(); private Cancellable? background_cancellable = null; @@ -229,54 +255,59 @@ search_op_is_values.set(SEARCH_OP_VALUE_UNREAD, SEARCH_OP_VALUE_UNREAD); } - public Account(Geary.AccountInformation account_information) { - this.account_information = account_information; - contact_store = new ImapEngine.ContactStore(this); - - name = "IMAP database account for %s".printf(account_information.imap_credentials.user); + public Account(AccountInformation config) { + this.account_information = config; + this.contact_store = new ImapEngine.ContactStore(this); + this.name = config.id + ":db"; } - + private void check_open() throws Error { if (db == null) throw new EngineError.OPEN_REQUIRED("Database not open"); } - + private ImapDB.SearchQuery check_search_query(Geary.SearchQuery q) throws Error { ImapDB.SearchQuery? query = q as ImapDB.SearchQuery; if (query == null || query.account != this) throw new EngineError.BAD_PARAMETERS("Geary.SearchQuery not associated with %s", name); - + return query; } - - public static void get_imap_db_storage_locations(File user_data_dir, out File db_file, - out File attachments_dir) { - db_file = ImapDB.Database.get_db_file(user_data_dir); - attachments_dir = ImapDB.Attachment.get_attachments_dir(user_data_dir); - } - + public async void open_async(File user_data_dir, File schema_dir, Cancellable? cancellable) throws Error { - if (db != null) + if (this.db != null) throw new EngineError.ALREADY_OPEN("IMAP database already open"); - - db = new ImapDB.Database(user_data_dir, schema_dir, upgrade_monitor, vacuum_monitor, - account_information.primary_mailbox.address); - + + File db_file; + File attachments_dir; + Account.get_imap_db_storage_locations( + user_data_dir, out db_file, out attachments_dir + ); + + this.db = new ImapDB.Database( + db_file, + schema_dir, + attachments_dir, + upgrade_monitor, + vacuum_monitor, + account_information.primary_mailbox.address + ); + try { - yield db.open_async( + yield db.open( Db.DatabaseFlags.CREATE_DIRECTORY | Db.DatabaseFlags.CREATE_FILE | Db.DatabaseFlags.CHECK_CORRUPTION, cancellable); } catch (Error err) { warning("Unable to open database: %s", err.message); - + // close database before exiting db.close(null); db = null; - + throw err; } - + // have seen cases where multiple "Inbox" folders are created in the root with different // case names, leading to trouble ... this clears out all Inboxes that don't match our // "canonical" name @@ -287,7 +318,7 @@ FROM FolderTable WHERE parent_id IS NULL """); - + Db.Result results = stmt.exec(cancellable); while (!results.finished) { string name = results.string_for("name"); @@ -296,99 +327,92 @@ debug("%s: Removing duplicate INBOX \"%s\"", this.name, name); do_delete_folder(cx, results.rowid_for("id"), cancellable); } - + results.next(cancellable); } - + return Db.TransactionOutcome.COMMIT; }, cancellable); } catch (Error err) { debug("Error trimming duplicate INBOX from database: %s", err.message); - + // drop database to indicate closed db = null; - + throw err; } - - Geary.Account account; - try { - account = Geary.Engine.instance.get_account_instance(account_information); - } catch (Error e) { - // If they're opening an account, the engine should already be - // open, and there should be no reason for this to fail. Thus, if - // we get here, it's a programmer error. - - error("Error finding account from its information: %s", e.message); - } - + background_cancellable = new Cancellable(); - + // Kick off a background update of the search table, but since the database is getting // hammered at startup, wait a bit before starting the update ... use the ordinal to // stagger these being fired off (important for users with many accounts registered) int account_sec = account_information.ordinal.clamp(0, 10); Timeout.add_seconds(POPULATE_SEARCH_TABLE_DELAY_SEC + account_sec, () => { populate_search_table_async.begin(background_cancellable); - + return false; }); - + initialize_contacts(cancellable); - - // ImapDB.Account holds the Outbox, which is tied to the database it maintains - outbox = new SmtpOutboxFolder(db, account, sending_monitor); - outbox.email_sent.connect(on_outbox_email_sent); - - // Search folder - search_folder = ((ImapEngine.GenericAccount) account).new_search_folder(); } - + public async void close_async(Cancellable? cancellable) throws Error { if (db == null) return; - + // close and always drop reference try { db.close(cancellable); } finally { db = null; } - - background_cancellable.cancel(); - background_cancellable = null; - - outbox.email_sent.disconnect(on_outbox_email_sent); - outbox = null; - search_folder = null; - } - - private void on_outbox_email_sent(Geary.RFC822.Message rfc822) { - email_sent(rfc822); + + this.background_cancellable.cancel(); + this.background_cancellable = null; + + this.folder_refs.clear(); } - - public async void clone_folder_async(Geary.Imap.Folder imap_folder, Cancellable? cancellable = null) - throws Error { + + public async Folder clone_folder_async(Geary.Imap.Folder imap_folder, + GLib.Cancellable? cancellable = null) + throws GLib.Error { check_open(); - + Geary.Imap.FolderProperties properties = imap_folder.properties; Geary.FolderPath path = imap_folder.path; - + + // XXX this should really be a db table constraint + Geary.ImapDB.Folder? folder = get_local_folder(path); + if (folder != null) { + throw new EngineError.ALREADY_EXISTS( + "Folder with path already exists: %s", path.to_string() + ); + } + + if (Imap.MailboxSpecifier.folder_path_is_inbox(path) && + !Imap.MailboxSpecifier.is_canonical_inbox_name(path.name)) { + // Don't add faux inboxes + throw new ImapError.NOT_SUPPORTED( + "Inbox has : %s", path.to_string() + ); + } + yield db.exec_transaction_async(Db.TransactionType.RW, (cx) => { // get the parent of this folder, creating parents if necessary ... ok if this fails, // that just means the folder has no parents int64 parent_id = Db.INVALID_ROWID; if (!do_fetch_parent_id(cx, path, true, out parent_id, cancellable)) { debug("Unable to find parent ID to %s clone folder", path.to_string()); - + return Db.TransactionOutcome.ROLLBACK; } - + // create the folder object Db.Statement stmt = cx.prepare( "INSERT INTO FolderTable (name, parent_id, last_seen_total, last_seen_status_total, " + "uid_validity, uid_next, attributes, unread_count) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"); - stmt.bind_string(0, path.basename); + stmt.bind_string(0, path.name); stmt.bind_rowid(1, parent_id); stmt.bind_int(2, Numeric.int_floor(properties.select_examine_messages, 0)); stmt.bind_int(3, Numeric.int_floor(properties.status_messages, 0)); @@ -398,197 +422,52 @@ : Imap.UID.INVALID); stmt.bind_string(6, properties.attrs.serialize()); stmt.bind_int(7, properties.email_unread); - + stmt.exec(cancellable); - + return Db.TransactionOutcome.COMMIT; }, cancellable); + + // XXX can't we create this from the INSERT above? + return yield fetch_folder_async(path, cancellable); } - - public async void delete_folder_async(Geary.Folder folder, Cancellable? cancellable) - throws Error { + + public async void delete_folder_async(Geary.FolderPath path, + GLib.Cancellable? cancellable) + throws GLib.Error { check_open(); - - Geary.FolderPath path = folder.path; - yield db.exec_transaction_async(Db.TransactionType.RW, (cx) => { int64 folder_id; do_fetch_folder_id(cx, path, false, out folder_id, cancellable); - if (folder_id == Db.INVALID_ROWID) - return Db.TransactionOutcome.ROLLBACK; - + if (folder_id == Db.INVALID_ROWID) { + throw new EngineError.NOT_FOUND( + "Folder not found: %s", path.to_string() + ); + } + if (do_has_children(cx, folder_id, cancellable)) { - debug("Can't delete folder %s because it has children", folder.to_string()); - return Db.TransactionOutcome.ROLLBACK; + throw new ImapError.NOT_SUPPORTED( + "Folder has children: %s", path.to_string() + ); } - + do_delete_folder(cx, folder_id, cancellable); - - return Db.TransactionOutcome.COMMIT; - }, cancellable); - } - - /** - * Only updates folder's STATUS message count, attributes, recent, and unseen; UIDVALIDITY and UIDNEXT - * updated when the folder is SELECT/EXAMINED (see update_folder_select_examine_async()) unless - * update_uid_info is true. - */ - public async void update_folder_status_async(Geary.Imap.Folder imap_folder, bool update_uid_info, - bool respect_marked_for_remove, Cancellable? cancellable) throws Error { - check_open(); - - Geary.Imap.FolderProperties properties = imap_folder.properties; - Geary.FolderPath path = imap_folder.path; - - // adjust for marked remove, but don't write these adjustments to the database -- they're - // only reflected in memory via the properties - int adjust_unseen = 0; - int adjust_total = 0; - - yield db.exec_transaction_async(Db.TransactionType.RW, (cx) => { - int64 parent_id; - if (!do_fetch_parent_id(cx, path, true, out parent_id, cancellable)) { - debug("Unable to find parent ID of %s to update properties", path.to_string()); - - return Db.TransactionOutcome.ROLLBACK; - } - - int64 folder_id; - if (!do_fetch_folder_id(cx, path, false, out folder_id, cancellable)) - folder_id = Db.INVALID_ROWID; - - if (respect_marked_for_remove && folder_id != Db.INVALID_ROWID) { - Db.Statement stmt = cx.prepare(""" - SELECT flags - FROM MessageTable - WHERE id IN ( - SELECT message_id - FROM MessageLocationTable - WHERE folder_id = ? AND remove_marker = ? - ) - """); - stmt.bind_rowid(0, folder_id); - stmt.bind_bool(1, true); - - Db.Result results = stmt.exec(cancellable); - while (!results.finished) { - adjust_total++; - - Imap.EmailFlags flags = new Imap.EmailFlags(Imap.MessageFlags.deserialize( - results.string_at(0))); - if (flags.contains(EmailFlags.UNREAD)) - adjust_unseen++; - - results.next(cancellable); - } - } - - Db.Statement stmt; - if (parent_id != Db.INVALID_ROWID) { - stmt = cx.prepare( - "UPDATE FolderTable SET attributes=?, unread_count=? WHERE parent_id=? AND name=?"); - stmt.bind_string(0, properties.attrs.serialize()); - stmt.bind_int(1, properties.email_unread); - stmt.bind_rowid(2, parent_id); - stmt.bind_string(3, path.basename); - } else { - stmt = cx.prepare( - "UPDATE FolderTable SET attributes=?, unread_count=? WHERE parent_id IS NULL AND name=?"); - stmt.bind_string(0, properties.attrs.serialize()); - stmt.bind_int(1, properties.email_unread); - stmt.bind_string(2, path.basename); - } - - stmt.exec(cancellable); - - if (update_uid_info) - do_update_uid_info(cx, properties, parent_id, path, cancellable); - - if (properties.status_messages >= 0) { - do_update_last_seen_status_total(cx, parent_id, path.basename, properties.status_messages, - cancellable); - } - - return Db.TransactionOutcome.COMMIT; - }, cancellable); - - // update appropriate properties in the local folder - ImapDB.Folder? db_folder = get_local_folder(path); - if (db_folder != null) { - Imap.FolderProperties local_properties = db_folder.get_properties(); - - local_properties.set_status_unseen(Numeric.int_floor(properties.unseen - adjust_unseen, 0)); - local_properties.recent = properties.recent; - local_properties.attrs = properties.attrs; - - if (update_uid_info) { - local_properties.uid_validity = properties.uid_validity; - local_properties.uid_next = properties.uid_next; - } - - // only update STATUS MESSAGES count if previously set, but use this count as the - // "authoritative" value until another SELECT/EXAMINE or MESSAGES response - if (properties.status_messages >= 0) { - local_properties.set_status_message_count( - Numeric.int_floor(properties.status_messages - adjust_total, 0), true); - } - } - } - - /** - * Updates folder's SELECT/EXAMINE message count, UIDVALIDITY, UIDNEXT, unseen, and recent. - * See also update_folder_status_async(). - */ - public async void update_folder_select_examine_async(Geary.Imap.Folder imap_folder, Cancellable? cancellable) - throws Error { - check_open(); - - Geary.Imap.FolderProperties properties = imap_folder.properties; - Geary.FolderPath path = imap_folder.path; - - yield db.exec_transaction_async(Db.TransactionType.RW, (cx) => { - int64 parent_id; - if (!do_fetch_parent_id(cx, path, true, out parent_id, cancellable)) { - debug("Unable to find parent ID of %s to update properties", path.to_string()); - - return Db.TransactionOutcome.ROLLBACK; - } - - do_update_uid_info(cx, properties, parent_id, path, cancellable); - - if (properties.select_examine_messages >= 0) { - do_update_last_seen_select_examine_total(cx, parent_id, path.basename, - properties.select_examine_messages, cancellable); - } - + this.folder_refs.unset(path); + return Db.TransactionOutcome.COMMIT; }, cancellable); - - // update appropriate properties in the local folder - ImapDB.Folder? db_folder = get_local_folder(path); - if (db_folder != null) { - Imap.FolderProperties local_properties = db_folder.get_properties(); - - local_properties.set_status_unseen(properties.unseen); - local_properties.recent = properties.recent; - local_properties.uid_validity = properties.uid_validity; - local_properties.uid_next = properties.uid_next; - - if (properties.select_examine_messages >= 0) - local_properties.set_select_examine_message_count(properties.select_examine_messages); - } } - + private void initialize_contacts(Cancellable? cancellable = null) throws Error { check_open(); - + Gee.Collection contacts = new Gee.LinkedList(); Db.TransactionOutcome outcome = db.exec_transaction(Db.TransactionType.RO, (context) => { Db.Statement statement = context.prepare( "SELECT email, real_name, highest_importance, normalized_email, flags " + "FROM ContactTable"); - + Db.Result result = statement.exec(cancellable); while (!result.finished) { try { @@ -600,21 +479,31 @@ // problem with one. debug("Problem loading contact: %s", err.message); } - + result.next(); } - + return Db.TransactionOutcome.DONE; }, cancellable); - - if (outcome == Db.TransactionOutcome.DONE) + + if (outcome == Db.TransactionOutcome.DONE) { contact_store.update_contacts(contacts); + contacts_loaded(); + } } - - public async Gee.Collection list_folders_async(Geary.FolderPath? parent, - Cancellable? cancellable = null) throws Error { + + /** + * Lists all children of a given folder. + * + * To list all top-level folders, pass in {@link imap_folder_root} + * as the parent. + */ + public async Gee.Collection + list_folders_async(Geary.FolderPath parent, + GLib.Cancellable? cancellable) + throws GLib.Error { check_open(); - + // TODO: A better solution here would be to only pull the FolderProperties if the Folder // object itself doesn't already exist Gee.HashMap id_map = new Gee.HashMap< @@ -623,17 +512,14 @@ Geary.FolderPath, Geary.Imap.FolderProperties>(); yield db.exec_transaction_async(Db.TransactionType.RO, (cx) => { int64 parent_id = Db.INVALID_ROWID; - if (parent != null) { - if (!do_fetch_folder_id(cx, parent, false, out parent_id, cancellable)) { - debug("Unable to find folder ID for %s to list folders", parent.to_string()); - - return Db.TransactionOutcome.ROLLBACK; - } - - if (parent_id == Db.INVALID_ROWID) - throw new EngineError.NOT_FOUND("Folder %s not found", parent.to_string()); + if (!parent.is_root && + !do_fetch_folder_id( + cx, parent, false, out parent_id, cancellable + )) { + debug("Unable to find folder ID for \"%s\" to list folders", parent.to_string()); + return Db.TransactionOutcome.ROLLBACK; } - + Db.Statement stmt; if (parent_id != Db.INVALID_ROWID) { stmt = cx.prepare( @@ -645,209 +531,184 @@ "SELECT id, name, last_seen_total, unread_count, last_seen_status_total, " + "uid_validity, uid_next, attributes FROM FolderTable WHERE parent_id IS NULL"); } - + Db.Result result = stmt.exec(cancellable); while (!result.finished) { string basename = result.string_for("name"); - - // ignore anything that's not canonical Inbox - if (parent == null - && Imap.MailboxSpecifier.is_inbox_name(basename) - && !Imap.MailboxSpecifier.is_canonical_inbox_name(basename)) { - result.next(cancellable); - - continue; - } - - Geary.FolderPath path = (parent != null) - ? parent.get_child(basename) - : new Imap.FolderRoot(basename); - - Geary.Imap.FolderProperties properties = new Geary.Imap.FolderProperties( - result.int_for("last_seen_total"), result.int_for("unread_count"), 0, + Geary.FolderPath path = parent.get_child(basename); + Geary.Imap.FolderProperties properties = new Geary.Imap.FolderProperties.from_imapdb( + Geary.Imap.MailboxAttributes.deserialize(result.string_for("attributes")), + result.int_for("last_seen_total"), + result.int_for("unread_count"), new Imap.UIDValidity(result.int64_for("uid_validity")), - new Imap.UID(result.int64_for("uid_next")), - Geary.Imap.MailboxAttributes.deserialize(result.string_for("attributes"))); + new Imap.UID(result.int64_for("uid_next")) + ); // due to legacy code, can't set last_seen_total to -1 to indicate that the folder // hasn't been SELECT/EXAMINE'd yet, so the STATUS count should be used as the // authoritative when the other is zero ... this is important when first creating a // folder, as the STATUS is the count that is known first properties.set_status_message_count(result.int_for("last_seen_status_total"), (properties.select_examine_messages == 0)); - + id_map.set(path, result.rowid_for("id")); prop_map.set(path, properties); - + result.next(cancellable); } - + return Db.TransactionOutcome.DONE; }, cancellable); - + assert(id_map.size == prop_map.size); - + if (id_map.size == 0) { - throw new EngineError.NOT_FOUND("No local folders in %s", - (parent != null) ? parent.to_string() : "root"); + throw new EngineError.NOT_FOUND( + "No local folders under \"%s\"", parent.to_string() + ); } - + Gee.Collection folders = new Gee.ArrayList(); foreach (Geary.FolderPath path in id_map.keys) { Geary.ImapDB.Folder? folder = get_local_folder(path); if (folder == null && id_map.has_key(path) && prop_map.has_key(path)) folder = create_local_folder(path, id_map.get(path), prop_map.get(path)); - + folders.add(folder); } - + return folders; } - - public async bool folder_exists_async(Geary.FolderPath path, Cancellable? cancellable = null) - throws Error { - check_open(); - - bool exists = false; - yield db.exec_transaction_async(Db.TransactionType.RO, (cx) => { - try { - int64 folder_id; - do_fetch_folder_id(cx, path, false, out folder_id, cancellable); - - exists = (folder_id != Db.INVALID_ROWID); - } catch (EngineError err) { - // treat NOT_FOUND as non-exceptional situation - if (!(err is EngineError.NOT_FOUND)) - throw err; - } - - return Db.TransactionOutcome.DONE; - }, cancellable); - - return exists; - } - + public async Geary.ImapDB.Folder fetch_folder_async(Geary.FolderPath path, Cancellable? cancellable) throws Error { check_open(); - + // check references table first Geary.ImapDB.Folder? folder = get_local_folder(path); if (folder != null) return folder; - + int64 folder_id = Db.INVALID_ROWID; Imap.FolderProperties? properties = null; yield db.exec_transaction_async(Db.TransactionType.RO, (cx) => { if (!do_fetch_folder_id(cx, path, false, out folder_id, cancellable)) return Db.TransactionOutcome.DONE; - + if (folder_id == Db.INVALID_ROWID) return Db.TransactionOutcome.DONE; - + Db.Statement stmt = cx.prepare( "SELECT last_seen_total, unread_count, last_seen_status_total, uid_validity, uid_next, " + "attributes FROM FolderTable WHERE id=?"); stmt.bind_rowid(0, folder_id); - + Db.Result results = stmt.exec(cancellable); if (!results.finished) { - properties = new Imap.FolderProperties(results.int_for("last_seen_total"), - results.int_for("unread_count"), 0, + properties = new Imap.FolderProperties.from_imapdb( + Geary.Imap.MailboxAttributes.deserialize(results.string_for("attributes")), + results.int_for("last_seen_total"), + results.int_for("unread_count"), new Imap.UIDValidity(results.int64_for("uid_validity")), - new Imap.UID(results.int64_for("uid_next")), - Geary.Imap.MailboxAttributes.deserialize(results.string_for("attributes"))); + new Imap.UID(results.int64_for("uid_next")) + ); // due to legacy code, can't set last_seen_total to -1 to indicate that the folder // hasn't been SELECT/EXAMINE'd yet, so the STATUS count should be used as the // authoritative when the other is zero ... this is important when first creating a // folder, as the STATUS is the count that is known first - properties.set_status_message_count(results.int_for("last_seen_status_total"), - (properties.select_examine_messages == 0)); + properties.set_status_message_count( + results.int_for("last_seen_status_total"), + (properties.select_examine_messages == 0) + ); } - + return Db.TransactionOutcome.DONE; }, cancellable); - + if (folder_id == Db.INVALID_ROWID || properties == null) throw new EngineError.NOT_FOUND("%s not found in local database", path.to_string()); - + return create_local_folder(path, folder_id, properties); } - + private Geary.ImapDB.Folder? get_local_folder(Geary.FolderPath path) { FolderReference? folder_ref = folder_refs.get(path); if (folder_ref == null) return null; - + ImapDB.Folder? folder = (Geary.ImapDB.Folder?) folder_ref.get_reference(); if (folder == null) return null; - + return folder; } - + private Geary.ImapDB.Folder create_local_folder(Geary.FolderPath path, int64 folder_id, Imap.FolderProperties properties) throws Error { // return current if already created ImapDB.Folder? folder = get_local_folder(path); if (folder != null) { - // update properties folder.set_properties(properties); - - return folder; + } else { + folder = new Geary.ImapDB.Folder( + db, + path, + db.attachments_path, + contact_store, + account_information.primary_mailbox.address, + folder_id, + properties + ); + + // build a reference to it + FolderReference folder_ref = new FolderReference(folder, path); + folder_ref.reference_broken.connect(on_folder_reference_broken); + + // add to the references table + folder_refs.set(folder_ref.path, folder_ref); + + folder.unread_updated.connect(on_unread_updated); } - - // create folder - folder = new Geary.ImapDB.Folder(db, path, contact_store, account_information.primary_mailbox.address, folder_id, - properties); - - // build a reference to it - FolderReference folder_ref = new FolderReference(folder, path); - folder_ref.reference_broken.connect(on_folder_reference_broken); - - // add to the references table - folder_refs.set(folder_ref.path, folder_ref); - - folder.unread_updated.connect(on_unread_updated); - return folder; } - + private void on_folder_reference_broken(Geary.SmartReference reference) { FolderReference folder_ref = (FolderReference) reference; - + // drop from folder references table, all cleaned up folder_refs.unset(folder_ref.path); } - + public async Gee.MultiMap? search_message_id_async( Geary.RFC822.MessageID message_id, Geary.Email.Field requested_fields, bool partial_ok, Gee.Collection? folder_blacklist, Geary.EmailFlags? flag_blacklist, Cancellable? cancellable = null) throws Error { check_open(); - + Gee.HashMultiMap messages = new Gee.HashMultiMap(); - + if (flag_blacklist != null) requested_fields = requested_fields | Geary.Email.Field.FLAGS; - + yield db.exec_transaction_async(Db.TransactionType.RO, (cx) => { Db.Statement stmt = cx.prepare("SELECT id FROM MessageTable WHERE message_id = ? OR in_reply_to = ?"); stmt.bind_string(0, message_id.value); stmt.bind_string(1, message_id.value); - + Db.Result result = stmt.exec(cancellable); while (!result.finished) { int64 id = result.int64_at(0); Geary.Email.Field db_fields; MessageRow row = Geary.ImapDB.Folder.do_fetch_message_row( cx, id, requested_fields, out db_fields, cancellable); - + // Ignore any messages that don't have the required fields. if (partial_ok || row.fields.fulfills(requested_fields)) { Geary.Email email = row.to_email(new Geary.ImapDB.EmailIdentifier(id, null)); - Geary.ImapDB.Folder.do_add_attachments(cx, email, id, cancellable); - + Attachment.add_attachments( + cx, this.db.attachments_path, email, id, cancellable + ); + Gee.Set? folders = do_find_email_folders(cx, id, true, cancellable); if (folders == null) { if (folder_blacklist == null || !folder_blacklist.contains(null)) @@ -864,19 +725,19 @@ } } } - + // Check for blacklisted flags. if (flag_blacklist != null && email.email_flags != null && email.email_flags.contains_any(flag_blacklist)) messages.remove_all(email); } - + result.next(cancellable); } - + return Db.TransactionOutcome.DONE; }, cancellable); - + return (messages.size == 0 ? null : messages); } @@ -942,11 +803,11 @@ private string? stem_search_term(ImapDB.SearchQuery query, string term) { if (!query.allow_stemming) return null; - + int term_length = term.length; if (term_length < query.min_term_length_for_stemming) return null; - + string? stemmed = null; try { Db.Statement stmt = db.prepare(""" @@ -955,7 +816,7 @@ WHERE input=? """); stmt.bind_string(0, term); - + // get stemmed string; if no result, fall through Db.Result result = stmt.exec(); if (!result.finished) @@ -964,37 +825,37 @@ debug("No stemmed term returned for \"%s\"", term); } catch (Error err) { debug("Unable to query tokenizer table for stemmed term for \"%s\": %s", term, err.message); - + // fall-through } - + if (String.is_empty(stemmed)) { debug("Empty stemmed term returned for \"%s\"", term); - + return null; } - + // If same term returned, treat as non-stemmed if (stemmed == term) return null; - + // Don't search for stemmed words that are significantly shorter than the user's search term if (term_length - stemmed.length > query.max_difference_term_stem_lengths) { debug("Stemmed \"%s\" dropped searching for \"%s\": too much distance in terms", stemmed, term); - + return null; } - + debug("Search processing: term -> stem is \"%s\" -> \"%s\"", term, stemmed); - + return stemmed; } - + private void prepare_search_query(ImapDB.SearchQuery query) { if (query.parsed) return; - + // A few goals here: // 1) Append an * after every term so it becomes a prefix search // (see ) @@ -1006,7 +867,7 @@ // We ignore everything inside quotes to give the user a way to // override our algorithm here. The idea is to offer one search query // syntax for Geary that we can use locally and via IMAP, etc. - + string quote_balanced = query.raw; if (Geary.String.count_char(query.raw, '"') % 2 != 0) { // Remove the last quote if it's not balanced. This has the @@ -1015,20 +876,20 @@ assert(last_quote >= 0); quote_balanced = query.raw.splice(last_quote, last_quote + 1, " "); } - + string[] words = quote_balanced.split_set(" \t\r\n()%*\\"); bool in_quote = false; foreach (string s in words) { string? field = null; - + s = s.strip(); - + int quotes = Geary.String.count_char(s, '"'); if (!in_quote && quotes > 0) { in_quote = true; --quotes; } - + SearchTerm? term; if (in_quote) { // HACK: this helps prevent a syntax error when the user types @@ -1050,19 +911,19 @@ case "not": case "near": continue; - + default: if (lower.has_prefix("near/")) continue; break; } - + if (s.has_prefix("-")) s = s.substring(1); - + if (s == "") continue; - + // TODO: support quotes after : string[] parts = s.split(":", 2); if (parts.length > 1) @@ -1099,28 +960,28 @@ term = new SearchTerm(original, s, stemmed, sql_s, sql_stemmed); } } - + if (in_quote && quotes % 2 != 0) in_quote = false; - + query.add_search_term(field, term); } - + assert(!in_quote); - + query.parsed = true; } - + // Return a map of column -> phrase, to use as WHERE column MATCH 'phrase'. private Gee.HashMap get_query_phrases(ImapDB.SearchQuery query) { prepare_search_query(query); - + Gee.HashMap phrases = new Gee.HashMap(); foreach (string? field in query.get_fields()) { Gee.List? terms = query.get_search_terms(field); if (terms == null || terms.size == 0 || field == "is") continue; - + // Each SearchTerm is an AND but the SQL text within in are OR ... this allows for // each user term to be AND but the variants of each term are or. So, if terms are // [party] and [eventful] and stems are [parti] and [event], the search would be: @@ -1141,7 +1002,7 @@ foreach (SearchTerm term in terms) { if (term.sql.size == 0) continue; - + if (term.is_exact) { builder.append_printf("%s ", term.parsed); } else { @@ -1149,19 +1010,19 @@ foreach (string sql in term.sql) { if (!is_first_sql) builder.append(" OR "); - + builder.append_printf("%s ", sql); is_first_sql = false; } } } - + phrases.set(field ?? "MessageSearchTable", builder.str); } - + return phrases; } - + private void sql_add_query_phrases(StringBuilder sql, Gee.HashMap query_phrases, string operator, string columns, string condition) { bool is_first_field = true; @@ -1180,7 +1041,7 @@ is_first_field = false; } } - + private int sql_bind_query_phrases(Db.Statement stmt, int start_index, Gee.HashMap query_phrases) throws Geary.DatabaseError { int i = start_index; @@ -1191,25 +1052,25 @@ stmt.bind_string(i++, query_phrases.get(field)); return i - start_index; } - + // Append each id in the collection to the StringBuilder, in a format // suitable for use in an SQL statement IN (...) clause. private void sql_append_ids(StringBuilder s, Gee.Iterable ids) { bool first = true; foreach (int64? id in ids) { assert(id != null); - + if (!first) s.append(", "); s.append(id.to_string()); first = false; } } - + private string? get_search_ids_sql(Gee.Collection? search_ids) throws Error { if (search_ids == null) return null; - + Gee.ArrayList ids = new Gee.ArrayList(); foreach (Geary.EmailIdentifier id in search_ids) { ImapDB.EmailIdentifier? imapdb_id = id as ImapDB.EmailIdentifier; @@ -1217,21 +1078,22 @@ throw new EngineError.BAD_PARAMETERS( "search_ids must contain only Geary.ImapDB.EmailIdentifiers"); } - + ids.add(imapdb_id.message_id); } - + StringBuilder sql = new StringBuilder(); sql_append_ids(sql, ids); return sql.str; } - + public async Gee.Collection? search_async(Geary.SearchQuery q, int limit = 100, int offset = 0, Gee.Collection? folder_blacklist = null, Gee.Collection? search_ids = null, Cancellable? cancellable = null) throws Error { - debug("Search: %s", q.to_string()); + debug("Search terms, offset/limit: %s %d/%d", + q.to_string(), offset, limit); check_open(); ImapDB.SearchQuery query = check_search_query(q); @@ -1260,32 +1122,15 @@ // Do this outside of transaction to catch invalid search ids up-front string? search_ids_sql = get_search_ids_sql(search_ids); - - // for some searches, results are stripped if they're too "greedy", but this requires - // examining the matched text, which has an expense to fetch, so avoid doing so unless - // necessary - bool strip_results = true; - - // HORIZON strategy is configured in such a way to allow all stemmed variants to match, - // so don't do any stripping in that case - // - // If any of the search terms is exact-match (no prefix matching) or none have stemmed - // variants, then don't do stripping of "greedy" stemmed matching (because in both cases, - // there are none) - if (query.strategy == Geary.SearchQuery.Strategy.HORIZON) - strip_results = false; - else if (traverse(query.get_all_terms()).any(term => term.stemmed == null || term.is_exact)) - strip_results = false; - debug(strip_results ? "Stripping results..." : "Not stripping results..."); + bool strip_greedy = should_strip_greedy_results(query); + Gee.Set matching_ids = new Gee.HashSet(); + Gee.Map>? search_matches = null; - Gee.Set unstripped_ids = new Gee.HashSet(); - Gee.Map>? search_results = null; - yield db.exec_transaction_async(Db.TransactionType.RO, (cx) => { string blacklisted_ids_sql = do_get_blacklisted_message_ids_sql( folder_blacklist, cx, cancellable); - + // Every mutation of this query we could think of has been tried, // and this version was found to minimize running time. We // discovered that just doing a JOIN between the MessageTable and @@ -1312,7 +1157,7 @@ sql.append(")"); } else sql.append(" WHERE 1=1"); - + if (blacklisted_ids_sql != "") sql.append(" AND id NOT IN (%s)".printf(blacklisted_ids_sql)); if (!Geary.String.is_empty(search_ids_sql)) @@ -1320,64 +1165,55 @@ sql.append(" ORDER BY internaldate_time_t DESC"); if (limit > 0) sql.append(" LIMIT ? OFFSET ?"); - + Db.Statement stmt = cx.prepare(sql.str); int bind_index = sql_bind_query_phrases(stmt, 0, query_phrases); if (limit > 0) { stmt.bind_int(bind_index++, limit); stmt.bind_int(bind_index++, offset); } - + Gee.HashMap id_map = new Gee.HashMap( Collection.int64_hash_func, Collection.int64_equal_func); - + Db.Result result = stmt.exec(cancellable); while (!result.finished) { int64 message_id = result.int64_at(0); int64 internaldate_time_t = result.int64_at(1); DateTime? internaldate = (internaldate_time_t == -1 ? null : new DateTime.from_unix_local(internaldate_time_t)); - + ImapDB.EmailIdentifier id = new ImapDB.SearchEmailIdentifier(message_id, internaldate); - - unstripped_ids.add(id); + matching_ids.add(id); id_map.set(message_id, id); - + result.next(cancellable); } - - if (!strip_results) - return Db.TransactionOutcome.DONE; - - search_results = do_get_search_matches(cx, query, id_map, cancellable); - + + + if (strip_greedy && !id_map.is_empty) { + search_matches = do_get_search_matches( + cx, query, id_map, cancellable + ); + } + return Db.TransactionOutcome.DONE; }, cancellable); - - if (unstripped_ids == null || unstripped_ids.size == 0) - return null; - - // at this point, there should be some "full" search results to strip from - if (strip_results) - assert(search_results != null && search_results.size > 0); - + + debug("Matching emails found: %d", matching_ids.size); + if (!removal_conditions.is_empty) { - if (!strip_results) { - search_results = new Gee.HashMap>(); - foreach (ImapDB.EmailIdentifier id in unstripped_ids) - search_results.set(id, new Gee.HashSet()); - } - yield remove_results(query, search_results, removal_conditions, cancellable); - if (!strip_results) - return search_results.size == 0 ? null : search_results.keys; - } - - if (!strip_results) - return unstripped_ids; - - strip_greedy_results(query, search_results); - - return search_results.size == 0 ? null : search_results.keys; + yield strip_removal_conditions( + query, matching_ids, removal_conditions, cancellable + ); + } + + if (strip_greedy && search_matches != null) { + strip_greedy_results(query, matching_ids, search_matches); + } + + debug("Final search matches: %d", matching_ids.size); + return matching_ids.is_empty ? null : matching_ids; } private Gee.Map get_removal_conditions(ImapDB.SearchQuery query) { @@ -1397,18 +1233,21 @@ return removal_conditions; } - private async void remove_results(ImapDB.SearchQuery query, - Gee.Map> search_results, - Gee.Map removal_conditions, Cancellable? cancellable = null) { - Geary.Email.Field required_fields = Geary.Email.Field.FLAGS; - Gee.MapIterator> iter = search_results.map_iterator(); + // Strip out from the given collection any email that matches the + // given removal conditions + private async void strip_removal_conditions(ImapDB.SearchQuery query, + Gee.Collection matches, + Gee.Map conditions, + GLib.Cancellable? cancellable = null) { + Email.Field required_fields = Geary.Email.Field.FLAGS; + Gee.Iterator iter = matches.iterator(); while (iter.next()) { try { - ImapDB.EmailIdentifier id = iter.get_key(); + ImapDB.EmailIdentifier id = iter.get(); Geary.Email email = yield fetch_email_async(id, required_fields, cancellable); - foreach (Geary.NamedFlag flag in removal_conditions.keys) - if (email.email_flags.contains(flag) == removal_conditions.get(flag)) { - iter.unset(); + foreach (Geary.NamedFlag flag in conditions.keys) + if (email.email_flags.contains(flag) == conditions.get(flag)) { + iter.remove(); break; } } catch (Error e) { @@ -1416,84 +1255,117 @@ } } } - - // Strip out search results that only contain a hit due to "greedy" matching of the stemmed - // variants on all search terms - private void strip_greedy_results(ImapDB.SearchQuery query, - Gee.Map> search_results) { - int prestripped_results = search_results.size; - Gee.MapIterator> iter = search_results.map_iterator(); + + // For some searches, results are stripped if they're too + // "greedy", but this requires examining the matched text, which + // has an expense to fetch, so avoid doing so unless necessary + private bool should_strip_greedy_results(SearchQuery query) { + // HORIZON strategy is configured in such a way to allow all + // stemmed variants to match, so don't do any stripping in + // that case + // + // If any of the search terms is exact-match (no prefix + // matching) or none have stemmed variants, then don't do + // stripping of "greedy" stemmed matching (because in both + // cases, there are none) + + bool strip_results = true; + if (query.strategy == Geary.SearchQuery.Strategy.HORIZON) + strip_results = false; + else if (traverse(query.get_all_terms()).any( + term => term.stemmed == null || term.is_exact)) { + strip_results = false; + } + return strip_results; + } + + // Strip out from the given collection of matching ids and results + // for any search results that only contain a hit due to "greedy" + // matching of the stemmed variants on all search terms. + private void strip_greedy_results(SearchQuery query, + Gee.Collection matches, + Gee.Map> results) { + int prestripped_results = matches.size; + Gee.Iterator iter = matches.iterator(); while (iter.next()) { // For each matched string in this message, retain the message in the search results // if it prefix-matches any of the straight-up parsed terms or matches a stemmed // variant (with only max. difference in their lengths allowed, i.e. not a "greedy" // match) + EmailIdentifier id = iter.get(); bool good_match_found = false; - foreach (string match in iter.get_value()) { - foreach (SearchTerm term in query.get_all_terms()) { - // if prefix-matches parsed term, then don't strip - if (match.has_prefix(term.parsed)) { - good_match_found = true; - - break; - } - - // if prefix-matches stemmed term w/o doing so greedily, then don't strip - if (term.stemmed != null && match.has_prefix(term.stemmed)) { - int diff = match.length - term.stemmed.length; - if (diff <= query.max_difference_match_stem_lengths) { + Gee.Set? result = results.get(id); + if (result != null) { + foreach (string match in result) { + foreach (SearchTerm term in query.get_all_terms()) { + // if prefix-matches parsed term, then don't strip + if (match.has_prefix(term.parsed)) { good_match_found = true; - break; } + + // if prefix-matches stemmed term w/o doing so + // greedily, then don't strip + if (term.stemmed != null && match.has_prefix(term.stemmed)) { + int diff = match.length - term.stemmed.length; + if (diff <= query.max_difference_match_stem_lengths) { + good_match_found = true; + break; + } + } } } - - if (good_match_found) + + if (good_match_found) { break; + } + } + + if (!good_match_found) { + iter.remove(); + matches.remove(id); } - - if (!good_match_found) - iter.unset(); } - + debug("Stripped %d emails from search for [%s] due to greedy stem matching", - prestripped_results - search_results.size, query.raw); + prestripped_results - matches.size, query.raw); } - + public async Gee.Set? get_search_matches_async(Geary.SearchQuery q, Gee.Collection ids, Cancellable? cancellable = null) throws Error { check_open(); ImapDB.SearchQuery query = check_search_query(q); - + Gee.Set? search_matches = null; yield db.exec_transaction_async(Db.TransactionType.RO, (cx) => { Gee.HashMap id_map = new Gee.HashMap< int64?, ImapDB.EmailIdentifier>(Collection.int64_hash_func, Collection.int64_equal_func); foreach (ImapDB.EmailIdentifier id in ids) id_map.set(id.message_id, id); - + Gee.Map>? match_map = do_get_search_matches(cx, query, id_map, cancellable); if (match_map == null || match_map.size == 0) return Db.TransactionOutcome.DONE; - - strip_greedy_results(query, match_map); - + + if (should_strip_greedy_results(query)) { + strip_greedy_results(query, ids, match_map); + } + search_matches = new Gee.HashSet(); foreach (Gee.Set matches in match_map.values) search_matches.add_all(matches); - + return Db.TransactionOutcome.DONE; }, cancellable); - + return search_matches; } - + public async Geary.Email fetch_email_async(ImapDB.EmailIdentifier email_id, Geary.Email.Field required_fields, Cancellable? cancellable = null) throws Error { check_open(); - + Geary.Email? email = null; yield db.exec_transaction_async(Db.TransactionType.RO, (cx) => { // TODO: once we have a way of deleting messages, we won't be able @@ -1502,50 +1374,39 @@ Geary.Email.Field db_fields; MessageRow row = Geary.ImapDB.Folder.do_fetch_message_row( cx, email_id.message_id, required_fields, out db_fields, cancellable); - + if (!row.fields.fulfills(required_fields)) throw new EngineError.INCOMPLETE_MESSAGE( "Message %s only fulfills %Xh fields (required: %Xh)", email_id.to_string(), row.fields, required_fields); - + email = row.to_email(email_id); - Geary.ImapDB.Folder.do_add_attachments(cx, email, email_id.message_id, cancellable); - + Attachment.add_attachments( + cx, this.db.attachments_path, email, email_id.message_id, cancellable + ); + return Db.TransactionOutcome.DONE; }, cancellable); - + assert(email != null); return email; } - + public async void update_contact_flags_async(Geary.Contact contact, Cancellable? cancellable) throws Error{ check_open(); - + yield db.exec_transaction_async(Db.TransactionType.RW, (cx, cancellable) => { Db.Statement update_stmt = cx.prepare("UPDATE ContactTable SET flags=? WHERE email=?"); update_stmt.bind_string(0, contact.contact_flags.serialize()); update_stmt.bind_string(1, contact.email); update_stmt.exec(cancellable); - + return Db.TransactionOutcome.COMMIT; }, cancellable); } - - public async int get_email_count_async(Cancellable? cancellable) throws Error { - check_open(); - - int count = 0; - yield db.exec_transaction_async(Db.TransactionType.RO, (cx) => { - count = do_get_email_count(cx, cancellable); - - return Db.TransactionOutcome.SUCCESS; - }, cancellable); - - return count; - } - + /** * Return a map of each passed-in email identifier to the set of folders * that contain it. If an email id doesn't appear in the resulting map, @@ -1553,18 +1414,18 @@ * would be empty. Only throw database errors et al., not errors due to * the email id not being found. */ - public async Gee.MultiMap? get_containing_folders_async( - Gee.Collection ids, Cancellable? cancellable) throws Error { + public async void + get_containing_folders_async(Gee.Collection ids, + Gee.MultiMap? map, + GLib.Cancellable? cancellable) + throws GLib.Error { check_open(); - - Gee.HashMultiMap map - = new Gee.HashMultiMap(); yield db.exec_transaction_async(Db.TransactionType.RO, (cx, cancellable) => { foreach (Geary.EmailIdentifier id in ids) { ImapDB.EmailIdentifier? imap_db_id = id as ImapDB.EmailIdentifier; if (imap_db_id == null) continue; - + Gee.Set? folders = do_find_email_folders( cx, imap_db_id.message_id, false, cancellable); if (folders != null) { @@ -1572,15 +1433,11 @@ Geary.FolderPath>(map, id, folders); } } - + return Db.TransactionOutcome.DONE; }, cancellable); - - yield outbox.add_to_containing_folders_async(ids, map, cancellable); - - return (map.size == 0 ? null : map); } - + private async void populate_search_table_async(Cancellable? cancellable) { debug("%s: Populating search table", account_information.id); try { @@ -1595,13 +1452,13 @@ } catch (Error e) { debug("Error populating %s search table: %s", account_information.id, e.message); } - + if (search_index_monitor.is_in_progress) search_index_monitor.notify_finish(); - + debug("%s: Done populating search table", account_information.id); } - + private static Gee.HashSet do_build_rowid_set(Db.Result result, Cancellable? cancellable) throws Error { Gee.HashSet rowid_set = new Gee.HashSet(Collection.int64_hash_func, @@ -1610,16 +1467,16 @@ rowid_set.add(result.rowid_at(0)); result.next(cancellable); } - + return rowid_set; } - + private async bool populate_search_table_batch_async(int limit, Cancellable? cancellable) throws Error { check_open(); debug("%s: Searching for up to %d missing indexed messages...", account_information.id, limit); - + int count = 0, total_unindexed = 0; yield db.exec_transaction_async(Db.TransactionType.RW, (cx, cancellable) => { // Embedding a SELECT within a SELECT is painfully slow @@ -1653,25 +1510,27 @@ foreach (int64 message_id in message_ids) { if (search_ids.contains(message_id)) continue; - + unindexed_message_ids.add(message_id); if (unindexed_message_ids.size >= limit) break; } - + // For all remaining MessageTable rowid's, generate search table entry foreach (int64 message_id in unindexed_message_ids) { try { Geary.Email.Field search_fields = Geary.Email.REQUIRED_FOR_MESSAGE | Geary.Email.Field.ORIGINATORS | Geary.Email.Field.RECEIVERS | Geary.Email.Field.SUBJECT; - + Geary.Email.Field db_fields; MessageRow row = Geary.ImapDB.Folder.do_fetch_message_row( cx, message_id, search_fields, out db_fields, cancellable); Geary.Email email = row.to_email(new Geary.ImapDB.EmailIdentifier(message_id, null)); - Geary.ImapDB.Folder.do_add_attachments(cx, email, message_id, cancellable); - + Attachment.add_attachments( + cx, this.db.attachments_path, email, message_id, cancellable + ); + Geary.ImapDB.Folder.do_add_email_to_search_table(cx, message_id, email, cancellable); } catch (Error e) { // This is a somewhat serious issue since we rely on @@ -1680,32 +1539,32 @@ warning("Error adding message %s to the search table: %s", message_id.to_string(), e.message); } - + ++count; } - + return Db.TransactionOutcome.DONE; }, cancellable); - + if (count > 0) { debug("%s: Found %d/%d missing indexed messages, %d remaining...", account_information.id, count, limit, total_unindexed); - + if (!search_index_monitor.is_in_progress) { search_index_monitor.set_interval(0, total_unindexed); search_index_monitor.notify_start(); } - + search_index_monitor.increment(count); } - + return (count < limit); } - + // // Transaction helper methods // - + private void do_delete_folder(Db.Connection cx, int64 folder_id, Cancellable? cancellable) throws Error { Db.Statement msg_loc_stmt = cx.prepare(""" @@ -1713,34 +1572,39 @@ WHERE folder_id = ? """); msg_loc_stmt.bind_rowid(0, folder_id); - + msg_loc_stmt.exec(cancellable); - + Db.Statement folder_stmt = cx.prepare(""" DELETE FROM FolderTable WHERE id = ? """); folder_stmt.bind_rowid(0, folder_id); - + folder_stmt.exec(cancellable); } - - // If the FolderPath has no parent, returns true and folder_id will be set to Db.INVALID_ROWID. - // If cannot create path or there is a logical problem traversing it, returns false with folder_id - // set to Db.INVALID_ROWID. - private bool do_fetch_folder_id(Db.Connection cx, Geary.FolderPath path, bool create, out int64 folder_id, - Cancellable? cancellable) throws Error { - int length = path.get_path_length(); - if (length < 0) - throw new EngineError.BAD_PARAMETERS("Invalid path %s", path.to_string()); - - folder_id = Db.INVALID_ROWID; + + // If the FolderPath has no parent, returns true and folder_id + // will be set to Db.INVALID_ROWID. If cannot create path or + // there is a logical problem traversing it, returns false with + // folder_id set to Db.INVALID_ROWID. + internal bool do_fetch_folder_id(Db.Connection cx, + Geary.FolderPath path, + bool create, + out int64 folder_id, + GLib.Cancellable? cancellable) + throws GLib.Error { + if (path.is_root) { + throw new EngineError.BAD_PARAMETERS( + "Cannot fetch folder for root path" + ); + } + + string[] parts = path.as_array(); int64 parent_id = Db.INVALID_ROWID; - - // walk the folder tree to the final node (which is at length - 1 - 1) - for (int ctr = 0; ctr < length; ctr++) { - string basename = path.get_folder_at(ctr).basename; - + folder_id = Db.INVALID_ROWID; + + foreach (string basename in parts) { Db.Statement stmt; if (parent_id != Db.INVALID_ROWID) { stmt = cx.prepare("SELECT id FROM FolderTable WHERE parent_id=? AND name=?"); @@ -1750,9 +1614,9 @@ stmt = cx.prepare("SELECT id FROM FolderTable WHERE parent_id IS NULL AND name=?"); stmt.bind_string(0, basename); } - + int64 id = Db.INVALID_ROWID; - + Db.Result result = stmt.exec(cancellable); if (!result.finished) { id = result.rowid_at(0); @@ -1764,47 +1628,56 @@ "INSERT INTO FolderTable (name, parent_id) VALUES (?, ?)"); create_stmt.bind_string(0, basename); create_stmt.bind_rowid(1, parent_id); - + id = create_stmt.exec_insert(cancellable); } - + // watch for path loops, real bad if it happens ... could be more thorough here, but at // least one level of checking is better than none if (id == parent_id) { warning("Loop found in database: parent of %s is %s in FolderTable", parent_id.to_string(), id.to_string()); - + return false; } - + parent_id = id; } - + // parent_id is now the folder being searched for folder_id = parent_id; - + return true; } - - // See do_fetch_folder_id() for return semantics. - private bool do_fetch_parent_id(Db.Connection cx, Geary.FolderPath path, bool create, out int64 parent_id, - Cancellable? cancellable = null) throws Error { - if (path.is_root()) { + + internal bool do_fetch_parent_id(Db.Connection cx, + FolderPath path, + bool create, + out int64 parent_id, + GLib.Cancellable? cancellable = null) + throws GLib.Error { + // See do_fetch_folder_id() for return semantics + bool ret = true; + + // No folder for the root is saved in the database, so + // top-levels should not have a parent. + if (path.is_top_level) { parent_id = Db.INVALID_ROWID; - - return true; + } else { + ret = do_fetch_folder_id( + cx, path.parent, create, out parent_id, cancellable + ); } - - return do_fetch_folder_id(cx, path.get_parent(), create, out parent_id, cancellable); + return ret; } - + private bool do_has_children(Db.Connection cx, int64 folder_id, Cancellable? cancellable) throws Error { Db.Statement stmt = cx.prepare("SELECT 1 FROM FolderTable WHERE parent_id = ?"); stmt.bind_rowid(0, folder_id); Db.Result result = stmt.exec(cancellable); return !result.finished; } - + // Turn the collection of folder paths into actual folder ids. As a // special case, if "folderless" or orphan emails are to be blacklisted, // set the out bool to true. @@ -1812,7 +1685,7 @@ Db.Connection cx, out bool blacklist_folderless, Cancellable? cancellable) throws Error { blacklist_folderless = false; Gee.ArrayList ids = new Gee.ArrayList(); - + if (folder_blacklist != null) { foreach (Geary.FolderPath? folder_path in folder_blacklist) { if (folder_path == null) { @@ -1825,10 +1698,10 @@ } } } - + return ids; } - + // Return a parameterless SQL statement that selects any message ids that // are in a blacklisted folder. This is used as a sub-select for the // search query to omit results from blacklisted folders. @@ -1837,7 +1710,7 @@ bool blacklist_folderless; Gee.Collection blacklisted_ids = do_get_blacklisted_folder_ids( folder_blacklist, cx, out blacklist_folderless, cancellable); - + StringBuilder sql = new StringBuilder(); if (blacklisted_ids.size > 0) { sql.append(""" @@ -1848,7 +1721,7 @@ """); sql_append_ids(sql, blacklisted_ids); sql.append(")"); - + if (blacklist_folderless) sql.append(" UNION "); } @@ -1863,187 +1736,133 @@ ) """); } - + return sql.str; } - + // For a message row id, return a set of all folders it's in, or null if // it's not in any folders. - private static Gee.Set? do_find_email_folders(Db.Connection cx, int64 message_id, - bool include_removed, Cancellable? cancellable) throws Error { + private Gee.Set? + do_find_email_folders(Db.Connection cx, + int64 message_id, + bool include_removed, + GLib.Cancellable? cancellable) + throws GLib.Error { string sql = "SELECT folder_id FROM MessageLocationTable WHERE message_id=?"; if (!include_removed) sql += " AND remove_marker=0"; Db.Statement stmt = cx.prepare(sql); stmt.bind_int64(0, message_id); Db.Result result = stmt.exec(cancellable); - + if (result.finished) return null; - + Gee.HashSet folder_paths = new Gee.HashSet(); while (!result.finished) { int64 folder_id = result.int64_at(0); Geary.FolderPath? path = do_find_folder_path(cx, folder_id, cancellable); if (path != null) folder_paths.add(path); - + result.next(cancellable); } - + return (folder_paths.size == 0 ? null : folder_paths); } - + // For a folder row id, return the folder path (constructed with default // separator and case sensitivity) of that folder, or null in the event // it's not found. - private static Geary.FolderPath? do_find_folder_path(Db.Connection cx, int64 folder_id, - Cancellable? cancellable) throws Error { - Db.Statement stmt = cx.prepare("SELECT parent_id, name FROM FolderTable WHERE id=?"); + private Geary.FolderPath? do_find_folder_path(Db.Connection cx, + int64 folder_id, + GLib.Cancellable? cancellable) + throws GLib.Error { + Db.Statement stmt = cx.prepare( + "SELECT parent_id, name FROM FolderTable WHERE id=?" + ); stmt.bind_int64(0, folder_id); Db.Result result = stmt.exec(cancellable); - + if (result.finished) return null; - + int64 parent_id = result.int64_at(0); string name = result.nonnull_string_at(1); - + // Here too, one level of loop detection is better than nothing. if (folder_id == parent_id) { warning("Loop found in database: parent of %s is %s in FolderTable", folder_id.to_string(), parent_id.to_string()); return null; } - - if (parent_id <= 0) - return new Imap.FolderRoot(name); - - Geary.FolderPath? parent_path = do_find_folder_path(cx, parent_id, cancellable); - return (parent_path == null ? null : parent_path.get_child(name)); - } - - // For SELECT/EXAMINE responses, not STATUS responses - private void do_update_last_seen_select_examine_total(Db.Connection cx, int64 parent_id, string name, int total, - Cancellable? cancellable) throws Error { - do_update_total(cx, parent_id, name, "last_seen_total", total, cancellable); - } - - // For STATUS responses, not SELECT/EXAMINE responses - private void do_update_last_seen_status_total(Db.Connection cx, int64 parent_id, string name, - int total, Cancellable? cancellable) throws Error { - do_update_total(cx, parent_id, name, "last_seen_status_total", total, cancellable); - } - - private void do_update_total(Db.Connection cx, int64 parent_id, string name, string colname, - int total, Cancellable? cancellable) throws Error { - Db.Statement stmt; - if (parent_id != Db.INVALID_ROWID) { - stmt = cx.prepare( - "UPDATE FolderTable SET %s=? WHERE parent_id=? AND name=?".printf(colname)); - stmt.bind_int(0, Numeric.int_floor(total, 0)); - stmt.bind_rowid(1, parent_id); - stmt.bind_string(2, name); - } else { - stmt = cx.prepare( - "UPDATE FolderTable SET %s=? WHERE parent_id IS NULL AND name=?".printf(colname)); - stmt.bind_int(0, Numeric.int_floor(total, 0)); - stmt.bind_string(1, name); - } - - stmt.exec(cancellable); - } - - private void do_update_uid_info(Db.Connection cx, Imap.FolderProperties properties, - int64 parent_id, FolderPath path, Cancellable? cancellable) throws Error { - int64 uid_validity = (properties.uid_validity != null) ? properties.uid_validity.value - : Imap.UIDValidity.INVALID; - int64 uid_next = (properties.uid_next != null) ? properties.uid_next.value - : Imap.UID.INVALID; - - Db.Statement stmt; - if (parent_id != Db.INVALID_ROWID) { - stmt = cx.prepare( - "UPDATE FolderTable SET uid_validity=?, uid_next=? WHERE parent_id=? AND name=?"); - stmt.bind_int64(0, uid_validity); - stmt.bind_int64(1, uid_next); - stmt.bind_rowid(2, parent_id); - stmt.bind_string(3, path.basename); + + Geary.FolderPath? path = null; + if (parent_id <= 0) { + path = this.imap_folder_root.get_child(name); } else { - stmt = cx.prepare( - "UPDATE FolderTable SET uid_validity=?, uid_next=? WHERE parent_id IS NULL AND name=?"); - stmt.bind_int64(0, uid_validity); - stmt.bind_int64(1, uid_next); - stmt.bind_string(2, path.basename); + Geary.FolderPath? parent_path = do_find_folder_path( + cx, parent_id, cancellable + ); + if (parent_path != null) { + path = parent_path.get_child(name); + } } - - stmt.exec(cancellable); + return path; } - - private int do_get_email_count(Db.Connection cx, Cancellable? cancellable) - throws Error { - Db.Statement stmt = cx.prepare( - "SELECT COUNT(*) FROM MessageTable"); - - Db.Result results = stmt.exec(cancellable); - if (results.finished) - return 0; - - return results.int_at(0); - } - + private void on_unread_updated(ImapDB.Folder source, Gee.Map unread_status) { update_unread_async.begin(source, unread_status, null); } - + // Updates unread count on all folders. private async void update_unread_async(ImapDB.Folder source, Gee.Map unread_status, Cancellable? cancellable) throws Error { Gee.Map unread_change = new Gee.HashMap(); - + yield db.exec_transaction_async(Db.TransactionType.RW, (cx) => { foreach (ImapDB.EmailIdentifier id in unread_status.keys) { Gee.Set? paths = do_find_email_folders( cx, id.message_id, true, cancellable); if (paths == null) continue; - + // Remove the folder that triggered this event. paths.remove(source.get_path()); if (paths.size == 0) continue; - + foreach (Geary.FolderPath path in paths) { int current_unread = unread_change.has_key(path) ? unread_change.get(path) : 0; current_unread += unread_status.get(id) ? 1 : -1; unread_change.set(path, current_unread); } } - + // Update each folder's unread count in the database. foreach (Geary.FolderPath path in unread_change.keys) { Geary.ImapDB.Folder? folder = get_local_folder(path); if (folder == null) continue; - + folder.do_add_to_unread_count(cx, unread_change.get(path), cancellable); } - + return Db.TransactionOutcome.SUCCESS; }, cancellable); - + // Update each folder's unread count property. foreach (Geary.FolderPath path in unread_change.keys) { Geary.ImapDB.Folder? folder = get_local_folder(path); if (folder == null) continue; - + folder.get_properties().set_status_unseen(folder.get_properties().email_unread + unread_change.get(path)); } } - + // Not using a MultiMap because when traversing want to process all values at once per iteration, // not per key-value public Gee.Map>? do_get_search_matches(Db.Connection cx, @@ -2051,11 +1870,11 @@ throws Error { if (id_map.size == 0) return null; - + Gee.HashMap query_phrases = get_query_phrases(query); if (query_phrases.size == 0) return null; - + StringBuilder sql = new StringBuilder(); sql.append(""" SELECT docid, offsets(MessageSearchTable), * @@ -2064,25 +1883,25 @@ """); sql_append_ids(sql, id_map.keys); sql.append(")"); - + StringBuilder condition = new StringBuilder("AND docid IN ("); sql_append_ids(condition, id_map.keys); condition.append(")"); sql_add_query_phrases(sql, query_phrases, "UNION", "docid, offsets(MessageSearchTable), *", condition.str); - + Db.Statement stmt = cx.prepare(sql.str); sql_bind_query_phrases(stmt, 0, query_phrases); - + Gee.Map> search_matches = new Gee.HashMap< ImapDB.EmailIdentifier, Gee.Set>(); - + Db.Result result = stmt.exec(cancellable); while (!result.finished) { int64 docid = result.rowid_at(0); assert(id_map.has_key(docid)); ImapDB.EmailIdentifier id = id_map.get(docid); - + // XXX Avoid a crash when "database disk image is // malformed" error occurs. Remove this when the SQLite // bug is fixed. See b.g.o #765515 for more info. @@ -2091,36 +1910,36 @@ result.next(cancellable); continue; } - + // offsets() function returns a list of 4 strings that are ints indicating position // and length of match string in search table corpus string[] offset_array = result.nonnull_string_at(1).split(" "); - + Gee.Set matches = new Gee.HashSet(); - + int j = 0; while (true) { unowned string[] offset_string = offset_array[j:j+4]; - + int column = int.parse(offset_string[0]); int byte_offset = int.parse(offset_string[2]); int size = int.parse(offset_string[3]); - + unowned string text = result.nonnull_string_at(column + 2); matches.add(text[byte_offset : byte_offset + size].down()); - + j += 4; if (j >= offset_array.length) break; } - + if (search_matches.has_key(id)) matches.add_all(search_matches.get(id)); search_matches.set(id, matches); - + result.next(cancellable); } - + return search_matches.size > 0 ? search_matches : null; } } diff -Nru geary-0.12.4/src/engine/imap-db/imap-db-attachment.vala geary-3.32.0/src/engine/imap-db/imap-db-attachment.vala --- geary-0.12.4/src/engine/imap-db/imap-db-attachment.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-db/imap-db-attachment.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,47 +1,296 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ private class Geary.ImapDB.Attachment : Geary.Attachment { + + public const Email.Field REQUIRED_FIELDS = Email.REQUIRED_FOR_MESSAGE; - internal const string NULL_FILE_NAME = "none"; - private const string ATTACHMENTS_DIR = "attachments"; + private const string NULL_FILE_NAME = "none"; + + + internal int64 message_id { get; private set; } + + private int64 attachment_id = -1; + + + private Attachment(int64 message_id, + Mime.ContentType content_type, + string? content_id, + string? content_description, + Mime.ContentDisposition content_disposition, + string? content_filename) { + base( + content_type, + content_id, + content_description, + content_disposition, + content_filename + ); + + this.message_id = message_id; + } + + internal Attachment.from_part(int64 message_id, RFC822.Part part) + throws Error { + Mime.ContentDisposition? disposition = part.content_disposition; + if (disposition == null) { + disposition = new Mime.ContentDisposition.simple( + Geary.Mime.DispositionType.UNSPECIFIED + ); + } + + this( + message_id, + part.get_effective_content_type(), + part.content_id, + part.content_description, + disposition, + part.get_clean_filename() + ); + } + + internal Attachment.from_row(Geary.Db.Result result, File attachments_dir) + throws Error { + string? content_filename = result.string_for("filename"); + if (content_filename == ImapDB.Attachment.NULL_FILE_NAME) { + // Prior to 0.12, Geary would store the untranslated + // string "none" as the filename when none was + // specified by the MIME content disposition. Check + // for that and clean it up. + content_filename = null; + } + + Mime.ContentDisposition disposition = new Mime.ContentDisposition.simple( + Mime.DispositionType.from_int(result.int_for("disposition")) + ); + + this( + result.rowid_for("message_id"), + Mime.ContentType.deserialize(result.nonnull_string_for("mime_type")), + result.string_for("content_id"), + result.string_for("description"), + disposition, + content_filename + ); + + this.attachment_id = result.rowid_for("id"); + + set_file_info( + generate_file(attachments_dir), result.int64_for("filesize") + ); + } + + internal void save(Db.Connection cx, + RFC822.Part part, + GLib.File attachments_dir, + Cancellable? cancellable) + throws Error { + insert_db(cx, cancellable); + try { + save_file(part, attachments_dir, cancellable); + update_db(cx, cancellable); + } catch (Error err) { + // Don't honour the cancellable here, we need to delete + // it. + this.delete(cx, cancellable); + throw err; + } + } + + // This isn't async since its only callpaths are via db async + // transactions, which run in independent threads. + internal void delete(Db.Connection cx, Cancellable? cancellable) { + if (this.attachment_id >= 0) { + try { + Db.Statement remove_stmt = cx.prepare( + "DELETE FROM MessageAttachmentTable WHERE id=?"); + remove_stmt.bind_rowid(0, this.attachment_id); + + remove_stmt.exec(); + } catch (Error err) { + debug("Error attempting to remove added attachment row for %s: %s", + this.file.get_path(), err.message); + } + } + + if (this.file != null) { + try { + this.file.delete(cancellable); + } catch (Error err) { + debug("Error attempting to remove attachment file %s: %s", + this.file.get_path(), err.message); + } + } + } + + private void insert_db(Db.Connection cx, Cancellable? cancellable) + throws Error { + // Insert it into the database. + Db.Statement stmt = cx.prepare(""" + INSERT INTO MessageAttachmentTable (message_id, filename, mime_type, filesize, disposition, content_id, description) + VALUES (?, ?, ?, ?, ?, ?, ?) + """); + stmt.bind_rowid(0, this.message_id); + stmt.bind_string(1, this.content_filename); + stmt.bind_string(2, this.content_type.to_string()); + stmt.bind_int64(3, 0); // This is updated after saving the file + stmt.bind_int(4, this.content_disposition.disposition_type); + stmt.bind_string(5, this.content_id); + stmt.bind_string(6, this.content_description); - public Attachment(int64 message_id, - int64 attachment_id, - Mime.ContentType content_type, - string? content_id, - string? content_description, - Mime.ContentDisposition content_disposition, - string? content_filename, - File data_dir, - int64 filesize) { - base (generate_id(attachment_id), - content_type, - content_id, - content_description, - content_disposition, - content_filename, - generate_file(data_dir, message_id, attachment_id, content_filename), - filesize); - } - - private static string generate_id(int64 attachment_id) { - return "imap-db:%s".printf(attachment_id.to_string()); - } - - public static File generate_file(File data_dir, int64 message_id, int64 attachment_id, - string? filename) { - return get_attachments_dir(data_dir) - .get_child(message_id.to_string()) - .get_child(attachment_id.to_string()) - .get_child(filename ?? NULL_FILE_NAME); + this.attachment_id = stmt.exec_insert(cancellable); } - public static File get_attachments_dir(File data_dir) { - return data_dir.get_child(ATTACHMENTS_DIR); + // This isn't async since its only callpaths are via db async + // transactions, which run in independent threads + private void save_file(RFC822.Part part, + GLib.File attachments_dir, + Cancellable? cancellable) + throws Error { + if (this.attachment_id < 0) { + throw new IOError.NOT_FOUND("No attachment id assigned"); + } + + File target = generate_file(attachments_dir); + + // create directory, but don't throw exception if already exists + try { + target.get_parent().make_directory_with_parents(cancellable); + } catch (IOError ioe) { + // fall through if already exists + if (!(ioe is IOError.EXISTS)) + throw ioe; + } + + // Delete any existing file now since we might not be creating + // it again below. + try { + target.delete(cancellable); + } catch (IOError err) { + // All good + } + + GLib.OutputStream target_stream = target.create( + FileCreateFlags.NONE, cancellable + ); + GMime.Stream stream = new Geary.Stream.MimeOutputStream( + target_stream + ); + stream = new GMime.StreamBuffer( + stream, GMime.StreamBufferMode.BLOCK_WRITE + ); + + part.write_to_stream(stream); + + // Using the stream's length is a bit of a hack, but at + // least on one system we are getting 0 back for the file + // size if we use target.query_info(). + int64 file_size = stream.length(); + + stream.close(); + + set_file_info(target, file_size); + } + + private void update_db(Db.Connection cx, Cancellable? cancellable) + throws Error { + // Update the file size now we know what it is + Db.Statement stmt = cx.prepare(""" + UPDATE MessageAttachmentTable + SET filesize = ? + WHERE id = ? + """); + stmt.bind_int64(0, this.filesize); + stmt.bind_rowid(1, this.attachment_id); + + stmt.exec(cancellable); + } + + private GLib.File generate_file(GLib.File attachments_dir) { + return attachments_dir + .get_child(this.message_id.to_string()) + .get_child(this.attachment_id.to_string()) + .get_child(this.content_filename ?? NULL_FILE_NAME); } + + + internal static Gee.List save_attachments(Db.Connection cx, + GLib.File attachments_path, + int64 message_id, + Gee.List attachments, + Cancellable? cancellable) + throws Error { + Gee.List list = new Gee.LinkedList(); + foreach (RFC822.Part part in attachments) { + Attachment attachment = new Attachment.from_part(message_id, part); + attachment.save(cx, part, attachments_path, cancellable); + list.add(attachment); + } + return list; + } + + internal static void delete_attachments(Db.Connection cx, + GLib.File attachments_path, + int64 message_id, + Cancellable? cancellable = null) + throws Error { + Gee.List? attachments = list_attachments( + cx, attachments_path, message_id, cancellable + ); + foreach (Attachment attachment in attachments) { + attachment.delete(cx, cancellable); + } + + // Ensure they're dead, Jim. + Db.Statement stmt = new Db.Statement(cx, """ + DELETE FROM MessageAttachmentTable WHERE message_id = ? + """); + stmt.bind_rowid(0, message_id); + stmt.exec(); + } + + // XXX this really should be a member of some internal + // ImapDB.Email class. + internal static void add_attachments(Db.Connection cx, + GLib.File attachments_path, + Geary.Email email, + int64 message_id, + Cancellable? cancellable = null) + throws Error { + if (email.fields.fulfills(ImapDB.Attachment.REQUIRED_FIELDS)) { + email.add_attachments( + list_attachments( + cx, attachments_path, message_id, cancellable + ) + ); + } + } + + internal static Gee.List list_attachments(Db.Connection cx, + GLib.File attachments_path, + int64 message_id, + Cancellable? cancellable) + throws Error { + Db.Statement stmt = cx.prepare(""" + SELECT * + FROM MessageAttachmentTable + WHERE message_id = ? + ORDER BY id + """); + stmt.bind_rowid(0, message_id); + Db.Result results = stmt.exec(cancellable); + + Gee.List list = new Gee.LinkedList(); + while (!results.finished) { + list.add(new ImapDB.Attachment.from_row(results, attachments_path)); + results.next(cancellable); + } + return list; + } + } diff -Nru geary-0.12.4/src/engine/imap-db/imap-db-contact.vala geary-3.32.0/src/engine/imap-db/imap-db-contact.vala --- geary-0.12.4/src/engine/imap-db/imap-db-contact.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-db/imap-db-contact.vala 2019-03-17 13:39:29.000000000 +0000 @@ -12,11 +12,11 @@ "SELECT real_name, highest_importance, normalized_email, flags FROM ContactTable " + "WHERE email=?"); stmt.bind_string(0, email); - + Db.Result result = stmt.exec(cancellable); if (result.finished) return null; - + return new Contact(email, result.string_at(0), result.int_at(1), result.string_at(2), ContactFlags.deserialize(result.string_at(3))); } @@ -26,7 +26,7 @@ private void do_update_contact(Db.Connection connection, Contact contact, Cancellable? cancellable) throws Error { Contact? existing_contact = do_fetch_contact(connection, contact.email, cancellable); - + // If not found, insert and done if (existing_contact == null) { Db.Statement stmt = connection.prepare( @@ -37,12 +37,12 @@ stmt.bind_string(2, contact.real_name); stmt.bind_string(3, (contact.contact_flags != null) ? contact.contact_flags.serialize() : null); stmt.bind_int(4, contact.highest_importance); - + stmt.exec(cancellable); - + return; } - + // merge two flags sets together ContactFlags? merged_flags = contact.contact_flags; if (existing_contact.contact_flags != null) { @@ -51,7 +51,7 @@ else merged_flags = existing_contact.contact_flags; } - + // update remaining fields, careful not to overwrite non-null real_name with null (but // using latest real_name if supplied) ... email is not updated (it's how existing_contact was // keyed), normalized_email is inserted at the same time as email, leaving only real_name, @@ -62,7 +62,7 @@ stmt.bind_string(1, (merged_flags != null) ? merged_flags.serialize() : null); stmt.bind_int(2, int.max(contact.highest_importance, existing_contact.highest_importance)); stmt.bind_string(3, contact.email); - + stmt.exec(cancellable); } diff -Nru geary-0.12.4/src/engine/imap-db/imap-db-database.vala geary-3.32.0/src/engine/imap-db/imap-db-database.vala --- geary-0.12.4/src/engine/imap-db/imap-db-database.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-db/imap-db-database.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,4 +1,5 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. @@ -7,232 +8,235 @@ extern int sqlite3_unicodesn_register_tokenizer(Sqlite.Database db); private class Geary.ImapDB.Database : Geary.Db.VersionedDatabase { - private const string DB_FILENAME = "geary.db"; + + internal GLib.File attachments_path; + private const int OPEN_PUMP_EVENT_LOOP_MSEC = 100; - + private ProgressMonitor upgrade_monitor; private ProgressMonitor vacuum_monitor; private string account_owner_email; private bool new_db = false; + + private GC? gc = null; private Cancellable gc_cancellable = new Cancellable(); - - public Database(File db_dir, File schema_dir, ProgressMonitor upgrade_monitor, - ProgressMonitor vacuum_monitor, string account_owner_email) { - base (get_db_file(db_dir), schema_dir); - + + public Database(GLib.File db_file, + GLib.File schema_dir, + GLib.File attachments_path, + ProgressMonitor upgrade_monitor, + ProgressMonitor vacuum_monitor, + string account_owner_email) { + base.persistent(db_file, schema_dir); + this.attachments_path = attachments_path; this.upgrade_monitor = upgrade_monitor; this.vacuum_monitor = vacuum_monitor; // Update to use all addresses on the account. Bug 768779 this.account_owner_email = account_owner_email; } - - public static File get_db_file(File db_dir) { - return db_dir.get_child(DB_FILENAME); - } - + /** - * Opens the ImapDB database. - * - * This should only be done from the main thread, as it is designed to pump the event loop - * while the database is being opened and updated. + * Prepares the ImapDB database for use. */ - public async void open_async(Db.DatabaseFlags flags, Cancellable? cancellable) throws Error { - open_background(flags, on_prepare_database_connection, pump_event_loop, - OPEN_PUMP_EVENT_LOOP_MSEC, cancellable); - + public new async void open(Db.DatabaseFlags flags, Cancellable? cancellable) + throws Error { + yield base.open(flags, on_prepare_database_connection, cancellable); + // Tie user-supplied Cancellable to internal Cancellable, which is used when close() is // called if (cancellable != null) cancellable.cancelled.connect(cancel_gc); - + // Create new garbage collection object for this database - GC gc = new GC(this, Priority.LOW); - + this.gc = new GC(this, Priority.LOW); + // Get recommendations on what GC operations should be executed - GC.RecommendedOperation recommended = yield gc.should_run_async(gc_cancellable); - + GC.RecommendedOperation recommended = yield this.gc.should_run_async( + gc_cancellable + ); + // VACUUM needs to execute in the foreground with the user given a busy prompt (and cannot // be run at the same time as REAP) if ((recommended & GC.RecommendedOperation.VACUUM) != 0) { if (!vacuum_monitor.is_in_progress) vacuum_monitor.notify_start(); - + try { - yield gc.vacuum_async(gc_cancellable); + yield this.gc.vacuum_async(gc_cancellable); } catch (Error err) { - message("Vacuum of IMAP database %s failed: %s", db_file.get_path(), err.message); - + message( + "Vacuum of IMAP database %s failed: %s", this.path, err.message + ); throw err; } finally { if (vacuum_monitor.is_in_progress) vacuum_monitor.notify_finish(); } } - + // REAP can run in the background while the application is executing if ((recommended & GC.RecommendedOperation.REAP) != 0) { // run in the background and allow application to continue running - gc.reap_async.begin(gc_cancellable, on_reap_async_completed); + this.gc.reap_async.begin(gc_cancellable, on_reap_async_completed); } - + if (cancellable != null) cancellable.cancelled.disconnect(cancel_gc); } - + private void on_reap_async_completed(Object? object, AsyncResult result) { - GC gc = (GC) object; try { - gc.reap_async.end(result); + this.gc.reap_async.end(result); } catch (Error err) { - message("Garbage collection of IMAP database %s failed: %s", db_file.get_path(), err.message); + message("Garbage collection of IMAP database %s failed: %s", + this.path, err.message); } + + this.gc = null; } - + private void cancel_gc() { gc_cancellable.cancel(); gc_cancellable = new Cancellable(); } - + public override void close(Cancellable? cancellable) throws Error { + // Ensure GC shuts down before returning cancel_gc(); - + while (this.gc != null && this.gc.is_running) { + GLib.MainContext.default().iteration(false); + } + base.close(cancellable); } - - private void pump_event_loop() { - while (MainContext.default().pending()) - MainContext.default().iteration(true); - } - + protected override void starting_upgrade(int current_version, bool new_db) { this.new_db = new_db; - // can't call the ProgressMonitor directly, as it's hooked up to signals that expect to be - // called in the foreground thread, so use the Idle loop for this - Idle.add(() => { - // don't use upgrade_monitor for new databases, as the upgrade should be near- - // instantaneous. Also, there's some issue with GTK when starting the progress - // monitor while GtkDialog's are in play: - // https://bugzilla.gnome.org/show_bug.cgi?id=726269 - if (!new_db && !upgrade_monitor.is_in_progress) - upgrade_monitor.notify_start(); - - return false; - }); + + // don't use upgrade_monitor for new databases, as the upgrade should be near- + // instantaneous. Also, there's some issue with GTK when starting the progress + // monitor while GtkDialog's are in play: + // https://bugzilla.gnome.org/show_bug.cgi?id=726269 + if (!new_db && !upgrade_monitor.is_in_progress) { + upgrade_monitor.notify_start(); + } } - + protected override void completed_upgrade(int final_version) { - // see starting_upgrade() for explanation why this is done in Idle loop - Idle.add(() => { - if (!new_db && upgrade_monitor.is_in_progress) - upgrade_monitor.notify_finish(); - - return false; - }); + if (!new_db && upgrade_monitor.is_in_progress) { + upgrade_monitor.notify_finish(); + } } - - protected override void post_upgrade(int version) { + + protected async override void post_upgrade(int version, + Cancellable? cancellable) + throws Error { switch (version) { case 5: - post_upgrade_populate_autocomplete(); + yield post_upgrade_populate_autocomplete(cancellable); break; - + case 6: - post_upgrade_encode_folder_names(); + yield post_upgrade_encode_folder_names(cancellable); break; - + case 11: - post_upgrade_add_search_table(); + yield post_upgrade_add_search_table(cancellable); break; - + case 12: - post_upgrade_populate_internal_date_time_t(); + yield post_upgrade_populate_internal_date_time_t(cancellable); break; - + case 13: - post_upgrade_populate_additional_attachments(); + yield post_upgrade_populate_additional_attachments(cancellable); break; - + case 14: - post_upgrade_expand_page_size(); + yield post_upgrade_expand_page_size(cancellable); break; - + case 15: - post_upgrade_fix_localized_internaldates(); + yield post_upgrade_fix_localized_internaldates(cancellable); break; - + case 18: - post_upgrade_populate_internal_date_time_t(); + yield post_upgrade_populate_internal_date_time_t(cancellable); break; - + case 19: - post_upgrade_validate_contacts(); + yield post_upgrade_validate_contacts(cancellable); break; - + case 22: - post_upgrade_rebuild_attachments(); + yield post_upgrade_rebuild_attachments(cancellable); break; - + case 23: - post_upgrade_add_tokenizer_table(); + yield post_upgrade_add_tokenizer_table(cancellable); break; } } - + // Version 5. - private void post_upgrade_populate_autocomplete() { - try { - Db.Result result = query("SELECT sender, from_field, to_field, cc, bcc FROM MessageTable"); - while (!result.finished) { - MessageAddresses message_addresses = + private async void post_upgrade_populate_autocomplete(Cancellable? cancellable) + throws Error { + yield exec_transaction_async(Db.TransactionType.RW, (cx) => { + Db.Result result = cx.query( + "SELECT sender, from_field, to_field, cc, bcc FROM MessageTable" + ); + while (!result.finished && !cancellable.is_cancelled()) { + MessageAddresses message_addresses = new MessageAddresses.from_result(account_owner_email, result); - foreach (Contact contact in message_addresses.contacts) { - do_update_contact(get_master_connection(), contact, null); + foreach (Contact contact in message_addresses.contacts) { + do_update_contact(cx, contact, null); + } + result.next(); } - - result.next(); - } - } catch (Error err) { - debug("Error populating autocompletion table during upgrade to database schema 5"); - } + return Geary.Db.TransactionOutcome.COMMIT; + }, cancellable); } - + // Version 6. - private void post_upgrade_encode_folder_names() { - try { - Db.Result select = query("SELECT id, name FROM FolderTable"); - while (!select.finished) { - int64 id = select.int64_at(0); - string encoded_name = select.nonnull_string_at(1); - - try { - string canonical_name = Geary.ImapUtf7.imap_utf7_to_utf8(encoded_name); - - Db.Statement update = prepare("UPDATE FolderTable SET name=? WHERE id=?"); - update.bind_string(0, canonical_name); - update.bind_int64(1, id); - update.exec(); - } catch (Error e) { - debug("Error renaming folder %s to its canonical representation: %s", encoded_name, e.message); + private async void post_upgrade_encode_folder_names(Cancellable? cancellable) + throws Error { + yield exec_transaction_async(Db.TransactionType.RW, (cx) => { + Db.Result select = cx.query("SELECT id, name FROM FolderTable"); + while (!select.finished && !cancellable.is_cancelled()) { + int64 id = select.int64_at(0); + string encoded_name = select.nonnull_string_at(1); + + try { + string canonical_name = Geary.ImapUtf7.imap_utf7_to_utf8(encoded_name); + + Db.Statement update = cx.prepare( + "UPDATE FolderTable SET name=? WHERE id=?" + ); + update.bind_string(0, canonical_name); + update.bind_int64(1, id); + update.exec(); + } catch (Error e) { + debug("Error renaming folder %s to its canonical representation: %s", encoded_name, e.message); + } + + select.next(); } - - select.next(); - } - } catch (Error e) { - debug("Error decoding folder names during upgrade to database schema 6: %s", e.message); - } + return Geary.Db.TransactionOutcome.COMMIT; + }, cancellable); } - + // Version 11. - private void post_upgrade_add_search_table() { - try { - string stemmer = find_appropriate_search_stemmer(); - debug("Creating search table using %s stemmer", stemmer); - - // This can't go in the .sql file because its schema (the stemmer - // algorithm) is determined at runtime. - exec(""" - CREATE VIRTUAL TABLE MessageSearchTable USING fts4( + private async void post_upgrade_add_search_table(Cancellable? cancellable) + throws Error { + yield exec_transaction_async(Db.TransactionType.RW, (cx) => { + string stemmer = find_appropriate_search_stemmer(); + debug("Creating search table using %s stemmer", stemmer); + + // This can't go in the .sql file because its schema (the stemmer + // algorithm) is determined at runtime. + cx.exec(""" + CREATE VIRTUAL TABLE MessageSearchTable USING fts4( body, attachment, subject, @@ -240,16 +244,15 @@ receivers, cc, bcc, - + tokenize=unicodesn "stemmer=%s", prefix="2,4,6,8,10", ); - """.printf(stemmer)); - } catch (Error e) { - error("Error creating search table: %s", e.message); - } + """.printf(stemmer)); + return Geary.Db.TransactionOutcome.COMMIT; + }, cancellable); } - + private string find_appropriate_search_stemmer() { // Unfortunately, the stemmer library only accepts the full language // name for the stemming algorithm. This translates between the user's @@ -276,27 +279,30 @@ case "tr": return "turkish"; } } - + // Default to English because it seems to be on average the language // most likely to be present in emails, regardless of the user's // language setting. This is not an exact science, and search results // should be ok either way in most cases. return "english"; } - + // Versions 12 and 18. - private void post_upgrade_populate_internal_date_time_t() { - try { - exec_transaction(Db.TransactionType.RW, (cx) => { - Db.Result select = cx.query("SELECT id, internaldate FROM MessageTable"); + private async void + post_upgrade_populate_internal_date_time_t(Cancellable? cancellable) + throws Error { + yield exec_transaction_async(Db.TransactionType.RW, (cx) => { + Db.Result select = cx.query( + "SELECT id, internaldate FROM MessageTable" + ); while (!select.finished) { int64 id = select.rowid_at(0); string? internaldate = select.string_at(1); - + try { time_t as_time_t = (internaldate != null ? Geary.Imap.InternalDate.decode(internaldate).to_time_t() : -1); - + Db.Statement update = cx.prepare( "UPDATE MessageTable SET internaldate_time_t=? WHERE id=?"); update.bind_int64(0, (int64) as_time_t); @@ -306,22 +312,19 @@ debug("Error converting internaldate '%s' to time_t: %s", internaldate, e.message); } - + select.next(); } - + return Db.TransactionOutcome.COMMIT; - }); - } catch (Error e) { - debug("Error populating internaldate_time_t column during upgrade to database schema 12: %s", - e.message); - } + }, cancellable); } - + // Version 13. - private void post_upgrade_populate_additional_attachments() { - try { - exec_transaction(Db.TransactionType.RW, (cx) => { + private async void + post_upgrade_populate_additional_attachments(Cancellable? cancellable) + throws Error { + yield exec_transaction_async(Db.TransactionType.RW, (cx) => { Db.Statement stmt = cx.prepare(""" SELECT id, header, body FROM MessageTable @@ -330,96 +333,101 @@ stmt.bind_int(0, Geary.Email.REQUIRED_FOR_MESSAGE); stmt.bind_int(1, Geary.Email.REQUIRED_FOR_MESSAGE); Db.Result select = stmt.exec(); + while (!select.finished) { int64 id = select.rowid_at(0); Geary.Memory.Buffer header = select.string_buffer_at(1); Geary.Memory.Buffer body = select.string_buffer_at(2); - + try { Geary.RFC822.Message message = new Geary.RFC822.Message.from_parts( new RFC822.Header(header), new RFC822.Text(body)); Mime.DispositionType target_disposition = Mime.DispositionType.UNSPECIFIED; if (message.get_sub_messages().is_empty) target_disposition = Mime.DispositionType.INLINE; - Geary.ImapDB.Folder.do_save_attachments_db(cx, id, - message.get_attachments(target_disposition), this, null); + Attachment.save_attachments( + cx, + this.attachments_path, + id, + message.get_attachments(target_disposition), + null + ); } catch (Error e) { debug("Error fetching inline Mime parts: %s", e.message); } - + select.next(); } - + // additionally, because this schema change (and code changes as well) introduces // two new types of attachments as well as processing for all MIME text sections // of messages (not just the first one), blow away the search table and let the // search indexer start afresh cx.exec("DELETE FROM MessageSearchTable"); - + return Db.TransactionOutcome.COMMIT; - }); - } catch (Error e) { - debug("Error populating old inline attachments during upgrade to database schema 13: %s", - e.message); - } + }, cancellable); } - + // Version 14. - private void post_upgrade_expand_page_size() { - try { - // When the MessageSearchTable is first touched, SQLite seems to - // read the whole table into memory (or an awful lot of data, - // either way). This was causing slowness when Geary first started - // and checked for any messages not yet in the search table. With - // the database's page_size set to 4096, the reads seem to happen - // about 2 orders of magnitude quicker, probably because 4096 - // matches the default filesystem block size and/or Linux's default - // memory page size. With this set, the full read into memory is - // barely noticeable even on slow machines. - - // NOTE: these can't be in the .sql file itself because they must - // be back to back, outside of a transaction. - exec(""" - PRAGMA page_size = 4096; - VACUUM; - """); - } catch (Error e) { - debug("Error bumping page_size or vacuuming database; performance may be degraded: %s", - e.message); - } + private async void post_upgrade_expand_page_size(Cancellable? cancellable) + throws Error { + // When the MessageSearchTable is first touched, + // SQLite seems to read the whole table into memory + // (or an awful lot of data, either way). This was + // causing slowness when Geary first started and + // checked for any messages not yet in the search + // table. With the database's page_size set to 4096, + // the reads seem to happen about 2 orders of + // magnitude quicker, probably because 4096 matches + // the default filesystem block size and/or Linux's + // default memory page size. With this set, the full + // read into memory is barely noticeable even on slow + // machines. + + // NOTE: these can't be in the .sql file itself because + // they must be back to back, outside of a transaction + Geary.Db.Connection cx = yield open_connection(); + yield Nonblocking.Concurrent.global.schedule_async(() => { + cx.exec(""" + PRAGMA page_size = 4096; + VACUUM; + """); + }, cancellable); } - + // Version 15 - private void post_upgrade_fix_localized_internaldates() { - try { - exec_transaction(Db.TransactionType.RW, (cx) => { + private async void + post_upgrade_fix_localized_internaldates(Cancellable? cancellable) + throws Error { + yield exec_transaction_async(Db.TransactionType.RW, (cx) => { Db.Statement stmt = cx.prepare(""" SELECT id, internaldate, fields FROM MessageTable """); - + Gee.HashMap invalid_ids = new Gee.HashMap< int64?, Geary.Email.Field>(); - + Db.Result results = stmt.exec(); while (!results.finished) { string? internaldate = results.string_at(1); - + try { if (!String.is_empty(internaldate)) Imap.InternalDate.decode(internaldate); } catch (Error err) { int64 invalid_id = results.rowid_at(0); - + debug("Invalid INTERNALDATE \"%s\" found at row %s in %s: %s", internaldate != null ? internaldate : "(null)", - invalid_id.to_string(), db_file.get_path(), err.message); + invalid_id.to_string(), this.path, err.message); invalid_ids.set(invalid_id, (Geary.Email.Field) results.int_at(2)); } - + results.next(); } - + // used prepared statement for iterating over list stmt = cx.prepare(""" UPDATE MessageTable @@ -429,54 +437,47 @@ stmt.bind_null(1); stmt.bind_null(2); stmt.bind_null(3); - + foreach (int64 invalid_id in invalid_ids.keys) { stmt.bind_int(0, invalid_ids.get(invalid_id).clear(Geary.Email.Field.PROPERTIES)); stmt.bind_rowid(4, invalid_id); - + stmt.exec(); - + // reuse statment, overwrite invalid_id, fields only stmt.reset(Db.ResetScope.SAVE_BINDINGS); } - + return Db.TransactionOutcome.COMMIT; - }); - } catch (Error err) { - debug("Error fixing INTERNALDATES during upgrade to schema 15 for %s: %s", - db_file.get_path(), err.message); - } + }, cancellable); } - + // Version 19. - private void post_upgrade_validate_contacts() { - try { - exec_transaction(Db.TransactionType.RW, (cx) => { + private async void post_upgrade_validate_contacts(Cancellable? cancellable) + throws Error { + yield exec_transaction_async(Db.TransactionType.RW, (cx) => { Db.Result result = cx.query("SELECT id, email FROM ContactTable"); while (!result.finished) { string email = result.string_at(1); if (!RFC822.MailboxAddress.is_valid_address(email)) { int64 id = result.rowid_at(0); - + Db.Statement stmt = cx.prepare("DELETE FROM ContactTable WHERE id = ?"); stmt.bind_rowid(0, id); stmt.exec(); } - + result.next(); } - + return Db.TransactionOutcome.COMMIT; - }); - } catch (Error e) { - debug("Error fixing up contacts table: %s", e.message); - } + }, cancellable); } - + // Version 22 - private void post_upgrade_rebuild_attachments() { - try { - exec_transaction(Db.TransactionType.RW, (cx) => { + private async void post_upgrade_rebuild_attachments(Cancellable? cancellable) + throws Error { + yield exec_transaction_async(Db.TransactionType.RW, (cx) => { Db.Statement stmt = cx.prepare(""" SELECT id, header, body FROM MessageTable @@ -484,77 +485,79 @@ """); stmt.bind_int(0, Geary.Email.REQUIRED_FOR_MESSAGE); stmt.bind_int(1, Geary.Email.REQUIRED_FOR_MESSAGE); - + Db.Result results = stmt.exec(); if (results.finished) return Db.TransactionOutcome.ROLLBACK; - + do { int64 message_id = results.rowid_at(0); Geary.Memory.Buffer header = results.string_buffer_at(1); Geary.Memory.Buffer body = results.string_buffer_at(2); - + Geary.RFC822.Message message; try { message = new Geary.RFC822.Message.from_parts( new RFC822.Header(header), new RFC822.Text(body)); } catch (Error err) { debug("Error decoding message: %s", err.message); - continue; } - + // build a list of attachments in the message itself - Gee.List msg_attachments = message.get_attachments(); - - // delete all attachments for this message + Gee.List msg_attachments = + message.get_attachments(); + try { - Geary.ImapDB.Folder.do_delete_attachments(cx, message_id); + Attachment.delete_attachments( + cx, this.attachments_path, message_id + ); } catch (Error err) { - debug("Error deleting existing attachments: %s", err.message); - + debug("Error deleting existing attachments: %s", + err.message); continue; } - + // rebuild all try { - Geary.ImapDB.Folder.do_save_attachments_db(cx, message_id, msg_attachments, - this, null); + Attachment.save_attachments( + cx, + this.attachments_path, + message_id, + msg_attachments, + null + ); } catch (Error err) { debug("Error saving attachments: %s", err.message); - + // fallthrough } } while (results.next()); - + // rebuild search table due to potentially new attachments cx.exec("DELETE FROM MessageSearchTable"); - + return Db.TransactionOutcome.COMMIT; - }); - } catch (Error e) { - debug("Error populating old inline attachments during upgrade to database schema 13: %s", - e.message); - } + }, cancellable); } - + // Version 23 - private void post_upgrade_add_tokenizer_table() { - try { - string stemmer = find_appropriate_search_stemmer(); - debug("Creating tokenizer table using %s stemmer", stemmer); - - // These can't go in the .sql file because its schema (the stemmer - // algorithm) is determined at runtime. - exec(""" - CREATE VIRTUAL TABLE TokenizerTable USING fts3tokenize( - unicodesn, - "stemmer=%s" - ); - """.printf(stemmer)); - } catch (Error e) { - error("Error creating tokenizer table: %s", e.message); - } + private async void post_upgrade_add_tokenizer_table(Cancellable? cancellable) + throws Error { + yield exec_transaction_async(Db.TransactionType.RW, (cx) => { + string stemmer = find_appropriate_search_stemmer(); + debug("Creating tokenizer table using %s stemmer", stemmer); + + // These can't go in the .sql file because its schema (the stemmer + // algorithm) is determined at runtime. + cx.exec(""" + CREATE VIRTUAL TABLE TokenizerTable USING fts3tokenize( + unicodesn, + "stemmer=%s" + ); + """.printf(stemmer)); + return Db.TransactionOutcome.COMMIT; + }, cancellable); } /** @@ -608,5 +611,5 @@ cx.set_synchronous(Db.SynchronousMode.NORMAL); sqlite3_unicodesn_register_tokenizer(cx.db); } -} +} diff -Nru geary-0.12.4/src/engine/imap-db/imap-db-email-identifier.vala geary-3.32.0/src/engine/imap-db/imap-db-email-identifier.vala --- geary-0.12.4/src/engine/imap-db/imap-db-email-identifier.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-db/imap-db-email-identifier.vala 2019-03-17 13:39:29.000000000 +0000 @@ -7,79 +7,65 @@ private class Geary.ImapDB.EmailIdentifier : Geary.EmailIdentifier { public int64 message_id { get; private set; } public Imap.UID? uid { get; private set; } - + public EmailIdentifier(int64 message_id, Imap.UID? uid) { assert(message_id != Db.INVALID_ROWID); - + base (message_id.to_string()); - + this.message_id = message_id; this.uid = uid; } - + // Used when a new message comes off the wire and doesn't have a rowid associated with it (yet) // Requires a UID in order to find or create such an association public EmailIdentifier.no_message_id(Imap.UID uid) { base (Db.INVALID_ROWID.to_string()); - + message_id = Db.INVALID_ROWID; this.uid = uid; } - + // Used to promote an id created with no_message_id to one that has a // message id. Warning: this causes the hash value to change, so if you // have any EmailIdentifiers in a hashed data structure, this will cause // you not to be able to find them. public void promote_with_message_id(int64 message_id) { assert(this.message_id == Db.INVALID_ROWID); - + unique = message_id.to_string(); this.message_id = message_id; } - + public bool has_uid() { return (uid != null) && uid.is_valid(); } - + public override int natural_sort_comparator(Geary.EmailIdentifier o) { ImapDB.EmailIdentifier? other = o as ImapDB.EmailIdentifier; if (other == null) return 1; - + if (uid == null) return 1; - + if (other.uid == null) return -1; - + return uid.compare_to(other.uid); } - + public override string to_string() { return "[%s/%s]".printf(message_id.to_string(), (uid == null ? "null" : uid.to_string())); } - - // Email's with no UID get sorted after emails with - public static int compare_email_uid_ascending(Geary.Email a, Geary.Email b) { - Imap.UID? auid = ((ImapDB.EmailIdentifier) a.id).uid; - Imap.UID? buid = ((ImapDB.EmailIdentifier) b.id).uid; - - if (auid == null) - return (buid != null) ? 1 : 0; - - if (buid == null) - return -1; - - return auid.compare_to(buid); - } - + public static Gee.Set to_uids(Gee.Collection ids) { Gee.HashSet uids = new Gee.HashSet(); foreach (ImapDB.EmailIdentifier id in ids) { if (id.uid != null) uids.add(id.uid); } - + return uids; } } diff -Nru geary-0.12.4/src/engine/imap-db/imap-db-folder.vala geary-3.32.0/src/engine/imap-db/imap-db-folder.vala --- geary-0.12.4/src/engine/imap-db/imap-db-folder.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-db/imap-db-folder.vala 2019-03-17 13:39:29.000000000 +0000 @@ -20,7 +20,16 @@ /** * Fields required for a message to be stored in the database. */ - public const Geary.Email.Field REQUIRED_FIELDS = Geary.Email.Field.PROPERTIES|Email.Field.REFERENCES; + public const Geary.Email.Field REQUIRED_FIELDS = ( + // Required for primary duplicate detection done with properties + Email.Field.PROPERTIES | + // Required for secondary duplicate detection via UID + Email.Field.REFERENCES | + // Required to ensure the unread count is up to date and so + // that when moving a message, the new copy turns back up as + // being not deleted. + Email.Field.FLAGS + ); /** * Fields required for a message to be considered for full-text indexing. @@ -32,7 +41,7 @@ private const int LIST_EMAIL_FIELDS_CHUNK_COUNT = 500; private const int REMOVE_COMPLETE_LOCATIONS_CHUNK_COUNT = 500; private const int CREATE_MERGE_EMAIL_CHUNK_COUNT = 25; - + [Flags] public enum ListFlags { NONE = 0, @@ -41,193 +50,254 @@ INCLUDING_ID, OLDEST_TO_NEWEST, ONLY_INCOMPLETE; - + public bool is_all_set(ListFlags flags) { return (this & flags) == flags; } - - public bool is_any_set(ListFlags flags) { - return (this & flags) != 0; - } - + public bool include_marked_for_remove() { return is_all_set(INCLUDE_MARKED_FOR_REMOVE); } - + public static ListFlags from_folder_flags(Geary.Folder.ListFlags flags) { ListFlags result = NONE; - + if (flags.is_all_set(Geary.Folder.ListFlags.INCLUDING_ID)) result |= INCLUDING_ID; - + if (flags.is_all_set(Geary.Folder.ListFlags.OLDEST_TO_NEWEST)) result |= OLDEST_TO_NEWEST; - + return result; } } - + private class LocationIdentifier { public int64 message_id; public Imap.UID uid; public ImapDB.EmailIdentifier email_id; public bool marked_removed; - + public LocationIdentifier(int64 message_id, Imap.UID uid, bool marked_removed) { this.message_id = message_id; this.uid = uid; - email_id = new ImapDB.EmailIdentifier(message_id, uid); + this.email_id = new ImapDB.EmailIdentifier(message_id, uid); this.marked_removed = marked_removed; } } - + protected int manual_ref_count { get; protected set; } - - private ImapDB.Database db; + + private Geary.Db.Database db; private Geary.FolderPath path; + private GLib.File attachments_path; private ContactStore contact_store; private string account_owner_email; private int64 folder_id; private Geary.Imap.FolderProperties properties; - + /** * Fired after one or more emails have been fetched with all Fields, and * saved locally. */ public signal void email_complete(Gee.Collection email_ids); - + /** * Fired when an email's unread (aka seen) status has changed. This allows the account to * change the unread count for other folders that contain the email. */ public signal void unread_updated(Gee.Map unread_status); - - internal Folder(ImapDB.Database db, Geary.FolderPath path, ContactStore contact_store, - string account_owner_email, int64 folder_id, Geary.Imap.FolderProperties properties) { - assert(folder_id != Db.INVALID_ROWID); - + + internal Folder(Geary.Db.Database db, + Geary.FolderPath path, + GLib.File attachments_path, + ContactStore contact_store, + string account_owner_email, + int64 folder_id, + Geary.Imap.FolderProperties properties) { this.db = db; this.path = path; + this.attachments_path = attachments_path; this.contact_store = contact_store; // Update to use all addresses on the account. Bug 768779 this.account_owner_email = account_owner_email; this.folder_id = folder_id; this.properties = properties; } - + public unowned Geary.FolderPath get_path() { return path; } - + public Geary.Imap.FolderProperties get_properties() { return properties; } - + internal void set_properties(Geary.Imap.FolderProperties properties) { this.properties = properties; } - + public async int get_email_count_async(ListFlags flags, Cancellable? cancellable) throws Error { int count = 0; yield db.exec_transaction_async(Db.TransactionType.RO, (cx) => { count = do_get_email_count(cx, flags, cancellable); - + return Db.TransactionOutcome.SUCCESS; }, cancellable); - + return count; } - - // Updates both the FolderProperties and the value in the local store. - public async void update_remote_status_message_count(int count, Cancellable? cancellable) throws Error { - if (count < 0) - return; - - yield db.exec_transaction_async(Db.TransactionType.RW, (cx) => { + + /** + * Updates folder's STATUS message count, attributes, recent, and unseen. + * + * UIDVALIDITY and UIDNEXT updated when the folder is + * SELECT/EXAMINED (see update_folder_select_examine_async()) + * unless update_uid_info is true. + */ + public async void update_folder_status(Geary.Imap.FolderProperties remote_properties, + bool update_uid_info, + bool respect_marked_for_remove, + Cancellable? cancellable) + throws Error { + // adjust for marked remove, but don't write these adjustments to the database -- they're + // only reflected in memory via the properties + int adjust_unseen = 0; + int adjust_total = 0; + + yield this.db.exec_transaction_async(Db.TransactionType.RW, (cx) => { + if (respect_marked_for_remove) { + Db.Statement stmt = cx.prepare(""" + SELECT flags + FROM MessageTable + WHERE id IN ( + SELECT message_id + FROM MessageLocationTable + WHERE folder_id = ? AND remove_marker = ? + ) + """); + stmt.bind_rowid(0, folder_id); + stmt.bind_bool(1, true); + + Db.Result results = stmt.exec(cancellable); + while (!results.finished) { + adjust_total++; + + Imap.EmailFlags flags = new Imap.EmailFlags(Imap.MessageFlags.deserialize( + results.string_at(0))); + if (flags.contains(EmailFlags.UNREAD)) + adjust_unseen++; + + results.next(cancellable); + } + } + Db.Statement stmt = cx.prepare( - "UPDATE FolderTable SET last_seen_status_total=? WHERE id=?"); - stmt.bind_int(0, Numeric.int_floor(count, 0)); - stmt.bind_rowid(1, folder_id); - + "UPDATE FolderTable SET attributes=?, unread_count=? WHERE id=?"); + stmt.bind_string(0, remote_properties.attrs.serialize()); + stmt.bind_int(1, remote_properties.email_unread); + stmt.bind_rowid(2, this.folder_id); stmt.exec(cancellable); - + + if (update_uid_info) + do_update_uid_info(cx, remote_properties, cancellable); + + if (remote_properties.status_messages >= 0) { + do_update_last_seen_status_total( + cx, remote_properties.status_messages, cancellable + ); + } + + return Db.TransactionOutcome.COMMIT; + }, cancellable); + + // update appropriate local properties + this.properties.set_status_unseen( + Numeric.int_floor(remote_properties.unseen - adjust_unseen, 0) + ); + this.properties.recent = remote_properties.recent; + this.properties.attrs = remote_properties.attrs; + + if (update_uid_info) { + this.properties.uid_validity = remote_properties.uid_validity; + this.properties.uid_next = remote_properties.uid_next; + } + + // only update STATUS MESSAGES count if previously set, but use this count as the + // "authoritative" value until another SELECT/EXAMINE or MESSAGES response + if (remote_properties.status_messages >= 0) { + this.properties.set_status_message_count( + Numeric.int_floor(remote_properties.status_messages - adjust_total, 0), + true + ); + } + } + + /** + * Updates folder's SELECT/EXAMINE message count, UIDVALIDITY, UIDNEXT, unseen, and recent. + * See also update_folder_status_async(). + */ + public async void update_folder_select_examine(Geary.Imap.FolderProperties remote_properties, + Cancellable? cancellable) + throws Error { + yield this.db.exec_transaction_async(Db.TransactionType.RW, (cx) => { + do_update_uid_info(cx, remote_properties, cancellable); + + if (remote_properties.select_examine_messages >= 0) { + do_update_last_seen_select_examine_total( + cx, remote_properties.select_examine_messages, cancellable + ); + } + return Db.TransactionOutcome.COMMIT; }, cancellable); - - properties.set_status_message_count(count, false); + + // update appropriate local properties + this.properties.set_status_unseen(remote_properties.unseen); + this.properties.recent = remote_properties.recent; + this.properties.uid_validity = remote_properties.uid_validity; + this.properties.uid_next = remote_properties.uid_next; + + if (remote_properties.select_examine_messages >= 0) { + this.properties.set_select_examine_message_count( + remote_properties.select_examine_messages + ); + } } - + // Updates both the FolderProperties and the value in the local store. Must be called while // open. public async void update_remote_selected_message_count(int count, Cancellable? cancellable) throws Error { if (count < 0) return; - + yield db.exec_transaction_async(Db.TransactionType.RW, (cx) => { - Db.Statement stmt = cx.prepare( - "UPDATE FolderTable SET last_seen_total=? WHERE id=?"); - stmt.bind_int(0, Numeric.int_floor(count, 0)); - stmt.bind_rowid(1, folder_id); - - stmt.exec(cancellable); - + do_update_last_seen_select_examine_total(cx, count, cancellable); return Db.TransactionOutcome.COMMIT; }, cancellable); - + properties.set_select_examine_message_count(count); } - - public async Imap.StatusData fetch_status_data(ListFlags flags, Cancellable? cancellable) throws Error { - Imap.StatusData? status_data = null; - yield db.exec_transaction_async(Db.TransactionType.RO, (cx) => { - Db.Statement stmt = cx.prepare(""" - SELECT uid_next, uid_validity, unread_count - FROM FolderTable - WHERE id = ? - """); - stmt.bind_rowid(0, folder_id); - - Db.Result result = stmt.exec(cancellable); - if (result.finished) - return Db.TransactionOutcome.DONE; - - int messages = do_get_email_count(cx, flags, cancellable); - Imap.UID? uid_next = !result.is_null_for("uid_next") - ? new Imap.UID(result.int64_for("uid_next")) - : null; - Imap.UIDValidity? uid_validity = !result.is_null_for("uid_validity") - ? new Imap.UIDValidity(result.int64_for("uid_validity")) - : null; - - // Note that recent is not stored - status_data = new Imap.StatusData(new Imap.MailboxSpecifier.from_folder_path(path, "?"), - messages, 0, uid_next, uid_validity, result.int_for("unread_count")); - - return Db.TransactionOutcome.DONE; - }, cancellable); - - if (status_data == null) - throw new EngineError.NOT_FOUND("%s STATUS not found in database", path.to_string()); - - return status_data; - } - + // Returns a Map with the created or merged email as the key and the result of the operation // (true if created, false if merged) as the value. Note that every email // object passed in's EmailIdentifier will be fully filled out by this // function (see ImapDB.EmailIdentifier.promote_with_message_id). This // means if you've hashed the collection of EmailIdentifiers prior, you may // not be able to find them after this function. Be warned. - public async Gee.Map create_or_merge_email_async(Gee.Collection emails, - Cancellable? cancellable) throws Error { + public async Gee.Map + create_or_merge_email_async(Gee.Collection emails, + bool update_totals, + GLib.Cancellable? cancellable) + throws GLib.Error { Gee.HashMap results = new Gee.HashMap(); - + Gee.ArrayList list = traverse(emails).to_array_list(); int index = 0; while (index < list.size) { int stop = Numeric.int_ceiling(index + CREATE_MERGE_EMAIL_CHUNK_COUNT, list.size); Gee.List slice = list.slice(index, stop); - + Gee.ArrayList complete_ids = new Gee.ArrayList(); Gee.Collection updated_contacts = new Gee.ArrayList(); int total_unread_change = 0; @@ -239,53 +309,58 @@ int unread_change = 0; bool created = do_create_or_merge_email(cx, email, out pre_fields, out post_fields, out contacts_this_email, ref unread_change, cancellable); - + if (contacts_this_email != null) updated_contacts.add_all(contacts_this_email); - + results.set(email, created); - + // in essence, only fire the "email-completed" signal if the local version didn't // have all the fields but after the create/merge now does if (post_fields.is_all_set(Geary.Email.Field.ALL) && !pre_fields.is_all_set(Geary.Email.Field.ALL)) complete_ids.add(email.id); - - // Update unread count in DB. - do_add_to_unread_count(cx, unread_change, cancellable); - - total_unread_change += unread_change; + + if (update_totals) { + // Update unread count in DB. + do_add_to_unread_count(cx, unread_change, cancellable); + total_unread_change += unread_change; + } } - + return Db.TransactionOutcome.COMMIT; }, cancellable); - + if (updated_contacts.size > 0) contact_store.update_contacts(updated_contacts); - - // Update the email_unread properties. - properties.set_status_unseen((properties.email_unread + total_unread_change).clamp(0, int.MAX)); - + + if (update_totals) { + // Update the email_unread properties. + properties.set_status_unseen( + (properties.email_unread + total_unread_change).clamp(0, int.MAX) + ); + } + if (complete_ids.size > 0) email_complete(complete_ids); - + index = stop; if (index < list.size) yield Scheduler.sleep_ms_async(100); } - + return results; } - + public async Gee.List? list_email_by_id_async(ImapDB.EmailIdentifier? initial_id, int count, Geary.Email.Field required_fields, ListFlags flags, Cancellable? cancellable) throws Error { if (count <= 0) return null; - + bool including_id = flags.is_all_set(ListFlags.INCLUDING_ID); bool oldest_to_newest = flags.is_all_set(ListFlags.OLDEST_TO_NEWEST); bool only_incomplete = flags.is_all_set(ListFlags.ONLY_INCOMPLETE); - + // Break up work so all reading isn't done in single transaction that locks up the // database ... first, gather locations of all emails in database Gee.List? locations = null; @@ -299,9 +374,9 @@ ListFlags.INCLUDE_MARKED_FOR_REMOVE, cancellable); if (location == null) return Db.TransactionOutcome.DONE; - + start_uid = location.uid; - + // deal with exclusive searches if (!including_id) { if (oldest_to_newest) @@ -314,55 +389,55 @@ } else { start_uid = new Imap.UID(Imap.UID.MAX); } - + if (!start_uid.is_valid()) return Db.TransactionOutcome.DONE; - + StringBuilder sql = new StringBuilder(""" SELECT MessageLocationTable.message_id, ordering, remove_marker FROM MessageLocationTable WHERE folder_id = ? """); - + if (oldest_to_newest) sql.append("AND ordering >= ? "); else sql.append("AND ordering <= ? "); - + if (oldest_to_newest) sql.append("ORDER BY ordering ASC "); else sql.append("ORDER BY ordering DESC "); - + if (count != int.MAX) sql.append("LIMIT ? "); - + Db.Statement stmt = cx.prepare(sql.str); stmt.bind_rowid(0, folder_id); stmt.bind_int64(1, start_uid.value); if (count != int.MAX) stmt.bind_int(2, count); - + locations = do_results_to_locations(stmt.exec(cancellable), count, flags, cancellable); - + return Db.TransactionOutcome.SUCCESS; }, cancellable); - + // remove complete locations (emails with all fields downloaded) if (only_incomplete) locations = yield remove_complete_locations_in_chunks_async(locations, cancellable); - + // Next, read in email in chunks return yield list_email_in_chunks_async(locations, required_fields, flags, cancellable); } - + // ListFlags.OLDEST_TO_NEWEST is ignored. INCLUDING_ID means including *both* identifiers. // Without this flag, neither are considered as part of the range. public async Gee.List? list_email_by_range_async(ImapDB.EmailIdentifier start_id, ImapDB.EmailIdentifier end_id, Geary.Email.Field required_fields, ListFlags flags, Cancellable? cancellable) throws Error { bool including_id = flags.is_all_set(ListFlags.INCLUDING_ID); - + // Break up work so all reading isn't done in single transaction that locks up the // database ... first, gather locations of all emails in database Gee.List? locations = null; @@ -373,25 +448,25 @@ ListFlags.INCLUDE_MARKED_FOR_REMOVE, cancellable); if (start_location == null) return Db.TransactionOutcome.DONE; - + Imap.UID start_uid = start_location.uid; - + // see note above about INCLUDE_MARKED_FOR_REMOVE LocationIdentifier? end_location = do_get_location_for_id(cx, end_id, ListFlags.INCLUDE_MARKED_FOR_REMOVE, cancellable); if (end_location == null) return Db.TransactionOutcome.DONE; - + Imap.UID end_uid = end_location.uid; - + if (!including_id) { start_uid = start_uid.next(false); end_uid = end_uid.previous(false); } - + if (!start_uid.is_valid() || !end_uid.is_valid() || start_uid.compare_to(end_uid) > 0) return Db.TransactionOutcome.DONE; - + Db.Statement stmt = cx.prepare(""" SELECT message_id, ordering, remove_marker FROM MessageLocationTable @@ -400,16 +475,16 @@ stmt.bind_rowid(0, folder_id); stmt.bind_int64(1, start_uid.value); stmt.bind_int64(2, end_uid.value); - + locations = do_results_to_locations(stmt.exec(cancellable), int.MAX, flags, cancellable); - + return Db.TransactionOutcome.SUCCESS; }, cancellable); - + // Next, read in email in chunks return yield list_email_in_chunks_async(locations, required_fields, flags, cancellable); } - + // ListFlags.OLDEST_TO_NEWEST is ignored. INCLUDING_ID means including *both* identifiers. // Without this flag, neither are considered as part of the range. public async Gee.List? list_email_by_uid_range_async(Imap.UID start, @@ -417,18 +492,18 @@ throws Error { bool including_id = flags.is_all_set(ListFlags.INCLUDING_ID); bool only_incomplete = flags.is_all_set(ListFlags.ONLY_INCOMPLETE); - + Imap.UID start_uid = start; Imap.UID end_uid = end; - + if (!including_id) { start_uid = start_uid.next(false); end_uid = end_uid.previous(false); } - + if (!start_uid.is_valid() || !end_uid.is_valid() || start_uid.compare_to(end_uid) > 0) return null; - + // Break up work so all reading isn't done in single transaction that locks up the // database ... first, gather locations of all emails in database Gee.List? locations = null; @@ -437,34 +512,34 @@ SELECT MessageLocationTable.message_id, ordering, remove_marker FROM MessageLocationTable """); - + sql.append("WHERE folder_id = ? AND ordering >= ? AND ordering <= ? "); - + Db.Statement stmt = cx.prepare(sql.str); stmt.bind_rowid(0, folder_id); stmt.bind_int64(1, start_uid.value); stmt.bind_int64(2, end_uid.value); - + locations = do_results_to_locations(stmt.exec(cancellable), int.MAX, flags, cancellable); - + return Db.TransactionOutcome.SUCCESS; }, cancellable); - + // remove complete locations (emails with all fields downloaded) if (only_incomplete) locations = yield remove_complete_locations_in_chunks_async(locations, cancellable); - + // Next, read in email in chunks return yield list_email_in_chunks_async(locations, required_fields, flags, cancellable); } - + public async Gee.List? list_email_by_sparse_id_async(Gee.Collection ids, Geary.Email.Field required_fields, ListFlags flags, Cancellable? cancellable) throws Error { if (ids.size == 0) return null; - + bool only_incomplete = flags.is_all_set(ListFlags.ONLY_INCOMPLETE); - + // Break up work so all reading isn't done in single transaction that locks up the // database ... first, gather locations of all emails in database Gee.List locations = new Gee.ArrayList(); @@ -474,19 +549,19 @@ cancellable); if (locs == null || locs.size == 0) return Db.TransactionOutcome.DONE; - + StringBuilder sql = new StringBuilder(""" SELECT MessageLocationTable.message_id, ordering, remove_marker FROM MessageLocationTable """); - + if (locs.size != 1) { sql.append("WHERE ordering IN ("); bool first = true; foreach (LocationIdentifier location in locs) { if (!first) sql.append(","); - + sql.append(location.uid.to_string()); first = false; } @@ -494,92 +569,92 @@ } else { sql.append_printf("WHERE ordering = '%s' ", locs[0].uid.to_string()); } - + sql.append("AND folder_id = ? "); - + Db.Statement stmt = cx.prepare(sql.str); stmt.bind_rowid(0, folder_id); - + locations = do_results_to_locations(stmt.exec(cancellable), int.MAX, flags, cancellable); - + return Db.TransactionOutcome.SUCCESS; }, cancellable); - + // remove complete locations (emails with all fields downloaded) if (only_incomplete) locations = yield remove_complete_locations_in_chunks_async(locations, cancellable); - + // Next, read in email in chunks return yield list_email_in_chunks_async(locations, required_fields, flags, cancellable); } - + private async Gee.List? remove_complete_locations_in_chunks_async( Gee.List? locations, Cancellable? cancellable) throws Error { if (locations == null || locations.size == 0) return locations; - + Gee.List incomplete_locations = new Gee.ArrayList(); - + // remove complete locations in chunks to avoid locking the database for long periods of // time int start = 0; for (;;) { if (start >= locations.size) break; - + int end = (start + REMOVE_COMPLETE_LOCATIONS_CHUNK_COUNT).clamp(0, locations.size); Gee.List slice = locations.slice(start, end); - + yield db.exec_transaction_async(Db.TransactionType.RO, (cx) => { do_remove_complete_locations(cx, slice, cancellable); - + return Db.TransactionOutcome.SUCCESS; }, cancellable); - + incomplete_locations.add_all(slice); - + start = end; } - + return (incomplete_locations.size > 0) ? incomplete_locations : null; } - + private async Gee.List? list_email_in_chunks_async(Gee.List? ids, Geary.Email.Field required_fields, ListFlags flags, Cancellable? cancellable) throws Error { if (ids == null || ids.size == 0) return null; - + // chunk count depends on whether or not the message -- body + headers -- is being fetched int chunk_count = required_fields.requires_any(Email.Field.BODY | Email.Field.HEADER) ? LIST_EMAIL_WITH_MESSAGE_CHUNK_COUNT : LIST_EMAIL_METADATA_COUNT; - + int length_rounded_up = Numeric.int_round_up(ids.size, chunk_count); - + Gee.List results = new Gee.ArrayList(); for (int start = 0; start < length_rounded_up; start += chunk_count) { // stop is the index *after* the end of the slice int stop = Numeric.int_ceiling((start + chunk_count), ids.size); - + Gee.List? slice = ids.slice(start, stop); assert(slice != null && slice.size > 0); - + Gee.List? list = null; yield db.exec_transaction_async(Db.TransactionType.RO, (cx, cancellable) => { list = do_list_email(cx, slice, required_fields, flags, cancellable); - + return Db.TransactionOutcome.SUCCESS; }, cancellable); - + if (list != null) results.add_all(list); } - + if (results.size != ids.size) debug("list_email_in_chunks_async: Requested %d email, returned %d", ids.size, results.size); - + return (results.size > 0) ? results : null; } - + public async Geary.Email fetch_email_async(ImapDB.EmailIdentifier id, Geary.Email.Field required_fields, ListFlags flags, Cancellable? cancellable) throws Error { Geary.Email? email = null; @@ -587,20 +662,20 @@ LocationIdentifier? location = do_get_location_for_id(cx, id, flags, cancellable); if (location == null) return Db.TransactionOutcome.DONE; - + email = do_location_to_email(cx, location, required_fields, flags, cancellable); - + return Db.TransactionOutcome.DONE; }, cancellable); - + if (email == null) { throw new EngineError.NOT_FOUND("No message ID %s in folder %s", id.to_string(), to_string()); } - + return email; } - + // Note that this does INCLUDES messages marked for removal // TODO: Let the user request a SortedSet, or have them provide the Set to add to public async Gee.Set? list_uids_by_range_async(Imap.UID first_uid, Imap.UID last_uid, @@ -614,7 +689,7 @@ start = last_uid; end = first_uid; } - + Gee.Set uids = new Gee.HashSet(); yield db.exec_transaction_async(Db.TransactionType.RO, (cx) => { Db.Statement stmt = cx.prepare(""" @@ -625,25 +700,25 @@ stmt.bind_rowid(0, folder_id); stmt.bind_int64(1, start.value); stmt.bind_int64(2, end.value); - + Db.Result result = stmt.exec(cancellable); while (!result.finished) { if (include_marked_for_removal || !result.bool_at(1)) uids.add(new Imap.UID(result.int64_at(0))); - + result.next(cancellable); } - + return Db.TransactionOutcome.DONE; }, cancellable); - + return (uids.size > 0) ? uids : null; } - + // pos is 1-based. This method does not respect messages marked for removal. public async ImapDB.EmailIdentifier? get_id_at_async(int64 pos, Cancellable? cancellable) throws Error { assert(pos >= 1); - + ImapDB.EmailIdentifier? id = null; yield db.exec_transaction_async(Db.TransactionType.RO, (cx) => { Db.Statement stmt = cx.prepare(""" @@ -656,17 +731,17 @@ """); stmt.bind_rowid(0, folder_id); stmt.bind_int64(1, pos - 1); - + Db.Result results = stmt.exec(cancellable); if (!results.finished) id = new ImapDB.EmailIdentifier(results.rowid_at(0), new Imap.UID(results.int64_at(1))); - + return Db.TransactionOutcome.DONE; }, cancellable); - + return id; } - + public async Imap.UID? get_uid_async(ImapDB.EmailIdentifier id, ListFlags flags, Cancellable? cancellable) throws Error { // Always look up the UID rather than pull the one from the EmailIdentifier; it could be @@ -676,13 +751,13 @@ LocationIdentifier? location = do_get_location_for_id(cx, id, flags, cancellable); if (location != null) uid = location.uid; - + return Db.TransactionOutcome.DONE; }, cancellable); - + return uid; } - + public async Gee.Set? get_uids_async(Gee.Collection ids, ListFlags flags, Cancellable? cancellable) throws Error { // Always look up the UID rather than pull the one from the EmailIdentifier; it could be @@ -695,13 +770,13 @@ foreach (LocationIdentifier location in locs) uids.add(location.uid); } - + return Db.TransactionOutcome.DONE; }, cancellable); - + return (uids.size > 0) ? uids : null; } - + // Returns null if the UID is not found in this Folder. public async ImapDB.EmailIdentifier? get_id_async(Imap.UID uid, ListFlags flags, Cancellable? cancellable) throws Error { @@ -711,13 +786,13 @@ cancellable); if (location != null) id = location.email_id; - + return Db.TransactionOutcome.DONE; }, cancellable); - + return id; } - + public async Gee.Set? get_ids_async(Gee.Collection uids, ListFlags flags, Cancellable? cancellable) throws Error { Gee.Set ids = new Gee.HashSet(); @@ -728,23 +803,23 @@ foreach (LocationIdentifier location in locs) ids.add(location.email_id); } - + return Db.TransactionOutcome.DONE; }, cancellable); - + return (ids.size > 0) ? ids : null; } - + // This does not respect messages marked for removal. public async ImapDB.EmailIdentifier? get_earliest_id_async(Cancellable? cancellable) throws Error { return yield get_id_extremes_async(true, cancellable); } - + // This does not respect messages marked for removal. public async ImapDB.EmailIdentifier? get_latest_id_async(Cancellable? cancellable) throws Error { return yield get_id_extremes_async(false, cancellable); } - + private async ImapDB.EmailIdentifier? get_id_extremes_async(bool earliest, Cancellable? cancellable) throws Error { ImapDB.EmailIdentifier? id = null; @@ -755,18 +830,18 @@ else stmt = cx.prepare("SELECT MAX(ordering), message_id FROM MessageLocationTable WHERE folder_id=?"); stmt.bind_rowid(0, folder_id); - + Db.Result results = stmt.exec(cancellable); // MIN and MAX return NULL if the result set being examined is zero-length if (!results.finished && !results.is_null_at(0)) id = new ImapDB.EmailIdentifier(results.rowid_at(1), new Imap.UID(results.int64_at(0))); - + return Db.TransactionOutcome.DONE; }, cancellable); - + return id; } - + public async void detach_multiple_emails_async(Gee.Collection ids, Cancellable? cancellable) throws Error { int unread_count = 0; @@ -779,10 +854,10 @@ ListFlags.INCLUDE_MARKED_FOR_REMOVE, cancellable); if (locs == null || locs.size == 0) return Db.TransactionOutcome.DONE; - + unread_count = do_get_unread_count_for_ids(cx, ids, cancellable); do_add_to_unread_count(cx, -unread_count, cancellable); - + StringBuilder sql = new StringBuilder(""" DELETE FROM MessageLocationTable WHERE message_id IN ( """); @@ -793,31 +868,31 @@ sql.append(", "); } sql.append(") AND folder_id=?"); - + Db.Statement stmt = cx.prepare(sql.str); stmt.bind_rowid(0, folder_id); - + stmt.exec(cancellable); - + return Db.TransactionOutcome.COMMIT; }, cancellable); - + if (unread_count > 0) properties.set_status_unseen(properties.email_unread - unread_count); } - + public async void detach_all_emails_async(Cancellable? cancellable) throws Error { yield db.exec_transaction_async(Db.TransactionType.WO, (cx) => { Db.Statement stmt = cx.prepare( "DELETE FROM MessageLocationTable WHERE folder_id=?"); stmt.bind_rowid(0, folder_id); - + stmt.exec(cancellable); - + return Db.TransactionOutcome.COMMIT; }, cancellable); } - + public async void mark_email_async(Gee.Collection to_mark, Geary.EmailFlags? flags_to_add, Geary.EmailFlags? flags_to_remove, Cancellable? cancellable) throws Error { @@ -829,32 +904,32 @@ to_mark, cancellable); if (map == null) return Db.TransactionOutcome.COMMIT; - + // update flags according to arguments foreach (ImapDB.EmailIdentifier id in map.keys) { Geary.Imap.EmailFlags flags = ((Geary.Imap.EmailFlags) map.get(id)); - + if (flags_to_add != null) { foreach (Geary.NamedFlag flag in flags_to_add.get_all()) { if (flags.contains(flag)) continue; - + flags.add(flag); - + if (flag.equal_to(Geary.EmailFlags.UNREAD)) { unread_change++; unread_status.set(id, true); } } } - + if (flags_to_remove != null) { foreach (Geary.NamedFlag flag in flags_to_remove.get_all()) { if (!flags.contains(flag)) continue; - + flags.remove(flag); - + if (flag.equal_to(Geary.EmailFlags.UNREAD)) { unread_change--; unread_status.set(id, false); @@ -862,19 +937,19 @@ } } } - + // write them all back out do_set_email_flags(cx, map, cancellable); - + // Update unread count. do_add_to_unread_count(cx, unread_change, cancellable); - + return Db.TransactionOutcome.COMMIT; }, cancellable); - + // Update the email_unread properties. properties.set_status_unseen((properties.email_unread + unread_change).clamp(0, int.MAX)); - + // Signal changes so other folders can be updated. if (unread_status.size > 0) unread_updated(unread_status); @@ -885,30 +960,30 @@ Gee.Map? map = null; yield db.exec_transaction_async(Db.TransactionType.RO, (cx, cancellable) => { map = do_get_email_flags(cx, ids, cancellable); - + return Db.TransactionOutcome.SUCCESS; }, cancellable); - + return map; } - + public async void set_email_flags_async(Gee.Map map, Cancellable? cancellable) throws Error { Error? error = null; int unread_change = 0; // Negative means messages are read, positive means unread. - + try { yield db.exec_transaction_async(Db.TransactionType.RW, (cx, cancellable) => { // TODO get current flags, compare to ones being set Gee.Map? existing_map = do_get_email_flags(cx, map.keys, cancellable); - + if (existing_map != null) { foreach(ImapDB.EmailIdentifier id in map.keys) { Geary.EmailFlags? existing_flags = existing_map.get(id); if (existing_flags == null) continue; - + Geary.EmailFlags new_flags = map.get(id); if (!existing_flags.contains(Geary.EmailFlags.UNREAD) && new_flags.contains(Geary.EmailFlags.UNREAD)) @@ -918,19 +993,19 @@ unread_change--; } } - + do_set_email_flags(cx, map, cancellable); - + // Update unread count. do_add_to_unread_count(cx, unread_change, cancellable); - + // TODO set db unread count return Db.TransactionOutcome.COMMIT; }, cancellable); } catch (Error e) { error = e; } - + // Update the email_unread properties. if (error == null) { properties.set_status_unseen((properties.email_unread + unread_change).clamp(0, int.MAX)); @@ -938,9 +1013,9 @@ throw error; } } - - public async void detach_single_email_async(ImapDB.EmailIdentifier id, out bool is_marked, - Cancellable? cancellable) throws Error { + + public async void detach_single_email_async(ImapDB.EmailIdentifier id, Cancellable? cancellable, + out bool is_marked) throws Error { bool internal_is_marked = false; bool was_unread = false; yield db.exec_transaction_async(Db.TransactionType.RW, (cx) => { @@ -950,27 +1025,27 @@ throw new EngineError.NOT_FOUND("Message %s cannot be removed from %s: not found", id.to_string(), to_string()); } - + // Check to see if message is unread (this only affects non-marked emails.) if (do_get_unread_count_for_ids(cx, Geary.iterate(id).to_array_list(), cancellable) > 0) { do_add_to_unread_count(cx, -1, cancellable); was_unread = true; } - + internal_is_marked = location.marked_removed; - + do_remove_association_with_folder(cx, location, cancellable); - + return Db.TransactionOutcome.COMMIT; }, cancellable); - + is_marked = internal_is_marked; - + if (was_unread) properties.set_status_unseen(properties.email_unread - 1); } - + // Mark messages as removed (but not expunged) from the folder. Marked messages are skipped // on most operations unless ListFlags.INCLUDE_MARKED_REMOVED is true. Use detach_email_async() // to formally remove the messages from the folder. @@ -990,44 +1065,44 @@ locs = do_get_locations_for_ids(cx, ids, ListFlags.INCLUDE_MARKED_FOR_REMOVE, cancellable); else locs = do_get_all_locations(cx, ListFlags.INCLUDE_MARKED_FOR_REMOVE, cancellable); - + if (locs == null || locs.size == 0) return Db.TransactionOutcome.DONE; - + unread_count = do_get_unread_count_for_ids(cx, ids, cancellable); - + Gee.HashSet uids = new Gee.HashSet(); foreach (LocationIdentifier location in locs) { uids.add(location.uid); removed_ids.add(location.email_id); } - + if (uids.size > 0) do_mark_unmark_removed(cx, uids, mark_removed, cancellable); - + do_add_to_unread_count(cx, -unread_count, cancellable); - + return Db.TransactionOutcome.DONE; }, cancellable); - + if (unread_count > 0) properties.set_status_unseen(properties.email_unread - unread_count); - + return (removed_ids.size > 0) ? removed_ids : null; } - + // Returns the number of messages marked for removal in this folder public async int get_marked_for_remove_count_async(Cancellable? cancellable) throws Error { int count = 0; yield db.exec_transaction_async(Db.TransactionType.RO, (cx) => { count = do_get_marked_removed_count(cx, cancellable); - + return Db.TransactionOutcome.DONE; }, cancellable); - + return count; } - + public async Gee.Set? get_marked_ids_async(Cancellable? cancellable) throws Error { Gee.Set ids = new Gee.HashSet(); @@ -1039,20 +1114,20 @@ """); stmt.bind_rowid(0, folder_id); stmt.bind_bool(1, false); - + Db.Result results = stmt.exec(cancellable); while (!results.finished) { ids.add(new ImapDB.EmailIdentifier(results.rowid_at(0), new Imap.UID(results.int64_at(1)))); - + results.next(cancellable); } - + return Db.TransactionOutcome.DONE; }, cancellable); - + return ids.size > 0 ? ids : null; } - + // Clears all remove markers from the folder except those in the exceptions Collection public async void clear_remove_markers_async(Gee.Collection? exceptions, Cancellable? cancellable) throws Error { @@ -1063,7 +1138,7 @@ SET remove_marker=? WHERE folder_id=? AND remove_marker <> ? """); - + if (exceptions != null && exceptions.size > 0) { sql.append(""" AND message_id NOT IN ( @@ -1076,27 +1151,27 @@ } sql.append(")"); } - + Db.Statement stmt = cx.prepare(sql.str); stmt.bind_bool(0, false); stmt.bind_rowid(1, folder_id); stmt.bind_bool(2, false); - + stmt.exec(cancellable); - + return Db.TransactionOutcome.COMMIT; }, cancellable); } - + public async Gee.Map? list_email_fields_by_id_async( Gee.Collection ids, ListFlags flags, Cancellable? cancellable) throws Error { if (ids.size == 0) return null; - + Gee.HashMap map = new Gee.HashMap< ImapDB.EmailIdentifier,Geary.Email.Field>(); - + // Break up the work Gee.List list = new Gee.ArrayList(); Gee.Iterator iter = ids.iterator(); @@ -1104,71 +1179,71 @@ list.add(iter.get()); if (list.size < LIST_EMAIL_FIELDS_CHUNK_COUNT && iter.has_next()) continue; - + yield db.exec_transaction_async(Db.TransactionType.RO, (cx, cancellable) => { Gee.List? locs = do_get_locations_for_ids(cx, ids, flags, cancellable); if (locs == null || locs.size == 0) return Db.TransactionOutcome.DONE; - + Db.Statement fetch_stmt = cx.prepare( "SELECT fields FROM MessageTable WHERE id = ?"); - + // TODO: Unroll loop foreach (LocationIdentifier location in locs) { fetch_stmt.reset(Db.ResetScope.CLEAR_BINDINGS); fetch_stmt.bind_rowid(0, location.message_id); - + Db.Result results = fetch_stmt.exec(cancellable); if (!results.finished) map.set(location.email_id, (Geary.Email.Field) results.int_at(0)); } - + return Db.TransactionOutcome.SUCCESS; }, cancellable); - + list.clear(); } assert(list.size == 0); - + return (map.size > 0) ? map : null; } - + public string to_string() { return path.to_string(); } - + // // Database transaction helper methods // These should only be called from within a TransactionMethod. // - + private int do_get_email_count(Db.Connection cx, ListFlags flags, Cancellable? cancellable) throws Error { Db.Statement stmt = cx.prepare( "SELECT COUNT(*) FROM MessageLocationTable WHERE folder_id=?"); stmt.bind_rowid(0, folder_id); - + Db.Result results = stmt.exec(cancellable); if (results.finished) return 0; - + int marked = !flags.include_marked_for_remove() ? do_get_marked_removed_count(cx, cancellable) : 0; - + return Numeric.int_floor(results.int_at(0) - marked, 0); } - + private int do_get_marked_removed_count(Db.Connection cx, Cancellable? cancellable) throws Error { Db.Statement stmt = cx.prepare( "SELECT COUNT(*) FROM MessageLocationTable WHERE folder_id=? AND remove_marker <> ?"); stmt.bind_rowid(0, folder_id); stmt.bind_bool(1, false); - + Db.Result results = stmt.exec(cancellable); - + return !results.finished ? results.int_at(0) : 0; } - + // TODO: Unroll loop private void do_mark_unmark_removed(Db.Connection cx, Gee.Collection uids, bool mark_removed, Cancellable? cancellable) throws Error { @@ -1177,62 +1252,58 @@ "UPDATE MessageLocationTable SET remove_marker=? WHERE folder_id=? AND ordering=?"); stmt.bind_bool(0, mark_removed); stmt.bind_rowid(1, folder_id); - + foreach (Imap.UID uid in uids) { stmt.bind_int64(2, uid.value); - + stmt.exec(cancellable); - + // keep folder_id and mark_removed, replace UID each iteration stmt.reset(Db.ResetScope.SAVE_BINDINGS); } } - - // Returns message_id if duplicate found, associated set to true if message is already associated - // with this folder. Only call this on emails that came from the IMAP Folder. - private LocationIdentifier? do_search_for_duplicates(Db.Connection cx, Geary.Email email, - out bool associated, Cancellable? cancellable) throws Error { - associated = false; - - ImapDB.EmailIdentifier email_id = (ImapDB.EmailIdentifier) email.id; - - // This should only ever get invoked for messages that came from the - // IMAP layer, which don't have a message id, but should have a UID. - assert(email_id.message_id == Db.INVALID_ROWID); - - LocationIdentifier? location = null; - // See if it already exists; first by UID (which is only guaranteed to - // be unique in a folder, not account-wide) - if (email_id.uid != null) - location = do_get_location_for_uid(cx, email_id.uid, ListFlags.INCLUDE_MARKED_FOR_REMOVE, - cancellable); - - if (location != null) { - associated = true; - - return location; - } - + + /** + * Returns the id of any existing message matching the given. + * + * Searches for an existing message that matches `email`, based on + * its message attributes. Currently, since ImapDB only requests + * the IMAP internal date and RFC822 message size, these are the + * only attributes used. + * + * The unique, internal message ID of the first matching message + * is returned, else `-1` if no matching message was found. + * + * This should only be called on messages obtained via the IMAP + * stack. + */ + private int64 do_search_for_duplicates(Db.Connection cx, + Geary.Email email, + ImapDB.EmailIdentifier email_id, + Cancellable? cancellable) + throws Error { + int64 id = -1; // if fields not present, then no duplicate can reliably be found if (!email.fields.is_all_set(REQUIRED_FIELDS)) { - debug("Unable to detect duplicates for %s (%s available)", email.id.to_string(), - email.fields.to_list_string()); - - return null; + debug("%s: Unable to detect duplicates for %s, fields available: %s", + this.to_string(), + email.id.to_string(), + email.fields.to_list_string() + ); + return id; } - + // what's more, actually need all those fields to be available, not merely attempted, // to err on the side of safety Imap.EmailProperties? imap_properties = (Imap.EmailProperties) email.properties; string? internaldate = (imap_properties != null && imap_properties.internaldate != null) ? imap_properties.internaldate.serialize() : null; int64 rfc822_size = (imap_properties != null) ? imap_properties.rfc822_size.value : -1; - + if (String.is_empty(internaldate) || rfc822_size < 0) { debug("Unable to detect duplicates for %s (%s available but invalid)", email.id.to_string(), email.fields.to_list_string()); - - return null; + return id; } // look for duplicate in IMAP message properties @@ -1248,164 +1319,185 @@ stmt.bind_string(2, email.message_id.to_string()); Db.Result results = stmt.exec(cancellable); - // no duplicates found - if (results.finished) - return null; - - int64 message_id = results.rowid_at(0); - if (results.next(cancellable)) { - debug("Warning: multiple messages with the same internaldate (%s) and size (%s) in %s", - internaldate, rfc822_size.to_string(), to_string()); - } - - Db.Statement search_stmt = cx.prepare( - "SELECT ordering, remove_marker FROM MessageLocationTable WHERE message_id=? AND folder_id=?"); - search_stmt.bind_rowid(0, message_id); - search_stmt.bind_rowid(1, folder_id); - - Db.Result search_results = search_stmt.exec(cancellable); - if (!search_results.finished) { - associated = true; - location = new LocationIdentifier(message_id, new Imap.UID(search_results.int64_at(0)), - search_results.bool_at(1)); - } else { - assert(email_id.uid != null); - location = new LocationIdentifier(message_id, email_id.uid, false); + if (!results.finished) { + id = results.int64_at(0); } - - return location; + return id; } - - // Note: does NOT check if message is already associated with thie folder - private void do_associate_with_folder(Db.Connection cx, int64 message_id, Imap.UID uid, - Cancellable? cancellable) throws Error { - assert(message_id != Db.INVALID_ROWID); - - // insert email at supplied position + + /** + * Adds a message to the folder. + * + * Note: does NOT check if message is already associated with thie + * folder. + */ + private void do_associate_with_folder(Db.Connection cx, + int64 message_id, + Imap.UID uid, + Cancellable? cancellable) + throws Error { Db.Statement stmt = cx.prepare( "INSERT INTO MessageLocationTable (message_id, folder_id, ordering) VALUES (?, ?, ?)"); stmt.bind_rowid(0, message_id); - stmt.bind_rowid(1, folder_id); + stmt.bind_rowid(1, this.folder_id); stmt.bind_int64(2, uid.value); - + stmt.exec(cancellable); } - + private void do_remove_association_with_folder(Db.Connection cx, LocationIdentifier location, Cancellable? cancellable) throws Error { Db.Statement stmt = cx.prepare( "DELETE FROM MessageLocationTable WHERE folder_id=? AND message_id=?"); stmt.bind_rowid(0, folder_id); stmt.bind_int64(1, location.message_id); - + stmt.exec(cancellable); } - + + /** + * Adds a single message to the folder, creating or merging it. + * + * This creates the message and appends it to the folder if the + * message does not already exist, else appends and merges if the + * message exists but not in the given position in this folder, + * else it exists in the given position, so simply merges it. + * + * Returns `true` if created, else was merged and returns `false`. + */ private bool do_create_or_merge_email(Db.Connection cx, Geary.Email email, out Geary.Email.Field pre_fields, out Geary.Email.Field post_fields, out Gee.Collection updated_contacts, ref int unread_count_change, Cancellable? cancellable) throws Error { - // see if message already present in current folder, if not, search for duplicate throughout - // mailbox - bool associated; - LocationIdentifier? location = do_search_for_duplicates(cx, email, out associated, cancellable); - - // if found, merge, and associate if necessary + + // This should only ever get invoked for messages that came + // from the IMAP layer, which should not have a message id, + // but should have a UID. + ImapDB.EmailIdentifier? email_id = email.id as ImapDB.EmailIdentifier; + if (email_id == null || + email_id.message_id != Db.INVALID_ROWID || + email_id.uid == null) { + throw new EngineError.BAD_PARAMETERS( + "IMAP message with UID required" + ); + } + + int64 message_id = -1; + bool is_associated = false; + + // First, look for the same message at the same location + LocationIdentifier? location = do_get_location_for_uid( + cx, + email_id.uid, + ListFlags.INCLUDE_MARKED_FOR_REMOVE, + cancellable + ); if (location != null) { - if (!associated) - do_associate_with_folder(cx, location.message_id, location.uid, cancellable); - - // If the email came from the Imap layer, we need to fill in the id. - ImapDB.EmailIdentifier email_id = (ImapDB.EmailIdentifier) email.id; - if (email_id.message_id == Db.INVALID_ROWID) - email_id.promote_with_message_id(location.message_id); - - // special-case updating flags, which happens often and should only write to the DB - // if necessary + // Already at the specified location, so no need to create + // or associate with this folder — just merge it + message_id = location.message_id; + is_associated = true; + } else { + // Not already at the specified location, so look for the + // same message in other locations or other folders + message_id = do_search_for_duplicates( + cx, email, email_id, cancellable + ); + if (message_id >= 0) { + location = new LocationIdentifier( + message_id, email_id.uid, false + ); + } + } + + bool was_created = false; + if (location != null) { + // Found the same or a duplicate message, so merge it. We + // special-case flag-only updates, which happens often and + // will only write to the DB if necessary. if (email.fields != Geary.Email.Field.FLAGS) { do_merge_email(cx, location, email, out pre_fields, out post_fields, out updated_contacts, ref unread_count_change, cancellable); - + // Already associated with folder and flags were known. - if (associated && pre_fields.is_all_set(Geary.Email.Field.FLAGS)) + if (is_associated && pre_fields.is_all_set(Geary.Email.Field.FLAGS)) unread_count_change = 0; } else { do_merge_email_flags(cx, location, email, out pre_fields, out post_fields, out updated_contacts, ref unread_count_change, cancellable); } - - // return false to indicate a merge - return false; + } else { + // Message was not found, so create a new message for it + was_created = true; + MessageRow row = new MessageRow.from_email(email); + pre_fields = Geary.Email.Field.NONE; + post_fields = email.fields; + + Db.Statement stmt = cx.prepare( + "INSERT INTO MessageTable " + + "(fields, date_field, date_time_t, from_field, sender, reply_to, to_field, cc, bcc, " + + "message_id, in_reply_to, reference_ids, subject, header, body, preview, flags, " + + "internaldate, internaldate_time_t, rfc822_size) " + + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + stmt.bind_int(0, row.fields); + stmt.bind_string(1, row.date); + stmt.bind_int64(2, row.date_time_t); + stmt.bind_string(3, row.from); + stmt.bind_string(4, row.sender); + stmt.bind_string(5, row.reply_to); + stmt.bind_string(6, row.to); + stmt.bind_string(7, row.cc); + stmt.bind_string(8, row.bcc); + stmt.bind_string(9, row.message_id); + stmt.bind_string(10, row.in_reply_to); + stmt.bind_string(11, row.references); + stmt.bind_string(12, row.subject); + stmt.bind_string_buffer(13, row.header); + stmt.bind_string_buffer(14, row.body); + stmt.bind_string(15, row.preview); + stmt.bind_string(16, row.email_flags); + stmt.bind_string(17, row.internaldate); + stmt.bind_int64(18, row.internaldate_time_t); + stmt.bind_int64(19, row.rfc822_size); + + message_id = stmt.exec_insert(cancellable); + + // write out attachments, if any + // TODO: Because this involves saving files, it potentially means holding up access to the + // database while they're being written; may want to do this outside of transaction. + if (email.fields.fulfills(Attachment.REQUIRED_FIELDS)) { + Attachment.save_attachments( + cx, + this.attachments_path, + message_id, + email.get_message().get_attachments(), + cancellable + ); + } + + do_add_email_to_search_table(cx, message_id, email, cancellable); + + MessageAddresses message_addresses = + new MessageAddresses.from_email(account_owner_email, email); + foreach (Contact contact in message_addresses.contacts) + do_update_contact(cx, contact, cancellable); + updated_contacts = message_addresses.contacts; + + // Update unread count if our new email is unread. + if (email.email_flags != null && email.email_flags.is_unread()) + unread_count_change++; } - - // not found, so create and associate with this folder - MessageRow row = new MessageRow.from_email(email); - - ImapDB.EmailIdentifier email_id = (ImapDB.EmailIdentifier) email.id; - - // the create case *requires* a UID be present (originating from Imap.Folder) - Imap.UID? uid = email_id.uid; - assert(uid != null); - - pre_fields = Geary.Email.Field.NONE; - post_fields = email.fields; - - Db.Statement stmt = cx.prepare( - "INSERT INTO MessageTable " - + "(fields, date_field, date_time_t, from_field, sender, reply_to, to_field, cc, bcc, " - + "message_id, in_reply_to, reference_ids, subject, header, body, preview, flags, " - + "internaldate, internaldate_time_t, rfc822_size) " - + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); - stmt.bind_int(0, row.fields); - stmt.bind_string(1, row.date); - stmt.bind_int64(2, row.date_time_t); - stmt.bind_string(3, row.from); - stmt.bind_string(4, row.sender); - stmt.bind_string(5, row.reply_to); - stmt.bind_string(6, row.to); - stmt.bind_string(7, row.cc); - stmt.bind_string(8, row.bcc); - stmt.bind_string(9, row.message_id); - stmt.bind_string(10, row.in_reply_to); - stmt.bind_string(11, row.references); - stmt.bind_string(12, row.subject); - stmt.bind_string_buffer(13, row.header); - stmt.bind_string_buffer(14, row.body); - stmt.bind_string(15, row.preview); - stmt.bind_string(16, row.email_flags); - stmt.bind_string(17, row.internaldate); - stmt.bind_int64(18, row.internaldate_time_t); - stmt.bind_int64(19, row.rfc822_size); - - int64 message_id = stmt.exec_insert(cancellable); - - // Make sure the id is filled in even if it came from the Imap layer. - if (email_id.message_id == Db.INVALID_ROWID) - email_id.promote_with_message_id(message_id); - - do_associate_with_folder(cx, message_id, uid, cancellable); - - // write out attachments, if any - // TODO: Because this involves saving files, it potentially means holding up access to the - // database while they're being written; may want to do this outside of transaction. - if (email.fields.fulfills(Attachment.REQUIRED_FIELDS)) - do_save_attachments(cx, message_id, email.get_message().get_attachments(), cancellable); - - do_add_email_to_search_table(cx, message_id, email, cancellable); - - MessageAddresses message_addresses = - new MessageAddresses.from_email(account_owner_email, email); - foreach (Contact contact in message_addresses.contacts) - do_update_contact(cx, contact, cancellable); - updated_contacts = message_addresses.contacts; - - // Update unread count if our new email is unread. - if (email.email_flags != null && email.email_flags.is_unread()) - unread_count_change++; - - return true; + + // Finally, update the email's message id and add it to the + // folder, if needed + email_id.promote_with_message_id(message_id); + if (!is_associated) { + do_associate_with_folder(cx, message_id, email_id.uid, cancellable); + } + + return was_created; } - + internal static void do_add_email_to_search_table(Db.Connection cx, int64 message_id, Geary.Email email, Cancellable? cancellable) throws Error { string? body = null; @@ -1461,11 +1553,11 @@ Cancellable? cancellable) throws Error { Db.Statement stmt = cx.prepare("SELECT 'TRUE' FROM MessageSearchTable WHERE docid=?"); stmt.bind_rowid(0, message_id); - + Db.Result result = stmt.exec(cancellable); return !result.finished; } - + private Gee.List? do_list_email(Db.Connection cx, Gee.List locations, Geary.Email.Field required_fields, ListFlags flags, Cancellable? cancellable) throws Error { Gee.List emails = new Gee.ArrayList(); @@ -1482,10 +1574,10 @@ } } } - + return (emails.size > 0) ? emails : null; } - + // Throws EngineError.NOT_FOUND if message_id is invalid. Note that this does not verify that // the message is indeed in this folder. internal static MessageRow do_fetch_message_row(Db.Connection cx, int64 message_id, @@ -1494,26 +1586,26 @@ Db.Statement stmt = cx.prepare( "SELECT %s FROM MessageTable WHERE id=?".printf(fields_to_columns(requested_fields))); stmt.bind_rowid(0, message_id); - + Db.Result results = stmt.exec(cancellable); if (results.finished) throw new EngineError.NOT_FOUND("No message ID %s found in database", message_id.to_string()); - + db_fields = (Geary.Email.Field) results.int_for("fields"); return new MessageRow.from_result(requested_fields, results); } - + private Geary.Email do_location_to_email(Db.Connection cx, LocationIdentifier location, Geary.Email.Field required_fields, ListFlags flags, Cancellable? cancellable) throws Error { if (!flags.include_marked_for_remove() && location.marked_removed) { throw new EngineError.NOT_FOUND("Message %s marked as removed in %s", location.email_id.to_string(), to_string()); } - + // look for perverse case if (required_fields == Geary.Email.Field.NONE) return new Geary.Email(location.email_id); - + Geary.Email.Field db_fields; MessageRow row = do_fetch_message_row(cx, location.message_id, required_fields, out db_fields, cancellable); @@ -1522,25 +1614,14 @@ "Message %s in folder %s only fulfills %Xh fields (required: %Xh)", location.email_id.to_string(), to_string(), row.fields, required_fields); } - + Geary.Email email = row.to_email(location.email_id); - - return do_add_attachments(cx, email, location.message_id, cancellable); - } - - internal static Geary.Email do_add_attachments(Db.Connection cx, Geary.Email email, - int64 message_id, Cancellable? cancellable = null) throws Error { - // Add attachments if available - if (email.fields.fulfills(ImapDB.Attachment.REQUIRED_FIELDS)) { - Gee.List? attachments = do_list_attachments(cx, message_id, - cancellable); - if (attachments != null) - email.add_attachments(attachments); - } - + Attachment.add_attachments( + cx, this.attachments_path, email, location.message_id, cancellable + ); return email; } - + private static string fields_to_columns(Geary.Email.Field fields) { // always pull the rowid and fields of the message StringBuilder builder = new StringBuilder("id, fields"); @@ -1551,146 +1632,159 @@ case Geary.Email.Field.DATE: append = "date_field, date_time_t"; break; - + case Geary.Email.Field.ORIGINATORS: append = "from_field, sender, reply_to"; break; - + case Geary.Email.Field.RECEIVERS: append = "to_field, cc, bcc"; break; - + case Geary.Email.Field.REFERENCES: append = "message_id, in_reply_to, reference_ids"; break; - + case Geary.Email.Field.SUBJECT: append = "subject"; break; - + case Geary.Email.Field.HEADER: append = "header"; break; - + case Geary.Email.Field.BODY: append = "body"; break; - + case Geary.Email.Field.PREVIEW: append = "preview"; break; - + case Geary.Email.Field.FLAGS: append = "flags"; break; - + case Geary.Email.Field.PROPERTIES: append = "internaldate, internaldate_time_t, rfc822_size"; break; } } - + if (append != null) { builder.append(", "); builder.append(append); } } - + return builder.str; } - + private Gee.Map? do_get_email_flags(Db.Connection cx, Gee.Collection ids, Cancellable? cancellable) throws Error { Gee.List? locs = do_get_locations_for_ids(cx, ids, ListFlags.NONE, cancellable); if (locs == null || locs.size == 0) return null; - + // prepare Statement for reuse Db.Statement fetch_stmt = cx.prepare("SELECT flags FROM MessageTable WHERE id=?"); - + Gee.Map map = new Gee.HashMap< ImapDB.EmailIdentifier, Geary.EmailFlags>(); // TODO: Unroll this loop foreach (LocationIdentifier location in locs) { fetch_stmt.reset(Db.ResetScope.CLEAR_BINDINGS); fetch_stmt.bind_rowid(0, location.message_id); - + Db.Result results = fetch_stmt.exec(cancellable); if (results.finished || results.is_null_at(0)) continue; - + map.set(location.email_id, new Geary.Imap.EmailFlags(Geary.Imap.MessageFlags.deserialize(results.string_at(0)))); } - + return (map.size > 0) ? map : null; } - - private Geary.EmailFlags? do_get_email_flags_single(Db.Connection cx, int64 message_id, + + private Geary.EmailFlags? do_get_email_flags_single(Db.Connection cx, int64 message_id, Cancellable? cancellable) throws Error { Db.Statement fetch_stmt = cx.prepare("SELECT flags FROM MessageTable WHERE id=?"); fetch_stmt.bind_rowid(0, message_id); - + Db.Result results = fetch_stmt.exec(cancellable); - + if (results.finished || results.is_null_at(0)) return null; - + return new Geary.Imap.EmailFlags(Geary.Imap.MessageFlags.deserialize(results.string_at(0))); } - + // TODO: Unroll loop private void do_set_email_flags(Db.Connection cx, Gee.Map map, Cancellable? cancellable) throws Error { Db.Statement update_stmt = cx.prepare( "UPDATE MessageTable SET flags=?, fields = fields | ? WHERE id=?"); - + foreach (ImapDB.EmailIdentifier id in map.keys) { - LocationIdentifier? location = do_get_location_for_id(cx, id, ListFlags.NONE, - cancellable); - if (location == null) - continue; - - Geary.Imap.MessageFlags flags = ((Geary.Imap.EmailFlags) map.get(id)).message_flags; - + LocationIdentifier? location = do_get_location_for_id( + cx, + id, + // Could be setting a flag on a deleted message + ListFlags.INCLUDE_MARKED_FOR_REMOVE, + cancellable + ); + if (location == null) { + throw new EngineError.NOT_FOUND( + "Email not found: %s", id.to_string() + ); + } + + Geary.Imap.EmailFlags? flags = map.get(id) as Geary.Imap.EmailFlags; + if (flags == null) { + throw new EngineError.BAD_PARAMETERS( + "Email with Geary.Imap.EmailFlags required" + ); + } + update_stmt.reset(Db.ResetScope.CLEAR_BINDINGS); - update_stmt.bind_string(0, flags.serialize()); + update_stmt.bind_string(0, flags.message_flags.serialize()); update_stmt.bind_int(1, Geary.Email.Field.FLAGS); update_stmt.bind_rowid(2, id.message_id); - + update_stmt.exec(cancellable); } } - + private bool do_fetch_email_fields(Db.Connection cx, int64 message_id, out Geary.Email.Field fields, Cancellable? cancellable) throws Error { Db.Statement stmt = cx.prepare("SELECT fields FROM MessageTable WHERE id=?"); stmt.bind_rowid(0, message_id); - + Db.Result results = stmt.exec(cancellable); if (results.finished) { fields = Geary.Email.Field.NONE; - + return false; } - + fields = (Geary.Email.Field) results.int_at(0); - + return true; } - + private void do_merge_message_row(Db.Connection cx, MessageRow row, out Geary.Email.Field new_fields, out Gee.Collection updated_contacts, ref int unread_count_change, Cancellable? cancellable) throws Error { - + // Initialize to an empty list, in case we return early. updated_contacts = new Gee.LinkedList(); - + Geary.Email.Field available_fields; if (!do_fetch_email_fields(cx, row.id, out available_fields, cancellable)) throw new EngineError.NOT_FOUND("No message with ID %s found in database", row.id.to_string()); - + // This calculates the fields in the row that are not in the database already and then adds // any available mutable fields provided by the caller new_fields = (row.fields ^ available_fields) & row.fields; @@ -1699,17 +1793,17 @@ // nothing to add return; } - + if (new_fields.is_any_set(Geary.Email.Field.DATE)) { Db.Statement stmt = cx.prepare( "UPDATE MessageTable SET date_field=?, date_time_t=? WHERE id=?"); stmt.bind_string(0, row.date); stmt.bind_int64(1, row.date_time_t); stmt.bind_rowid(2, row.id); - + stmt.exec(cancellable); } - + if (new_fields.is_any_set(Geary.Email.Field.ORIGINATORS)) { Db.Statement stmt = cx.prepare( "UPDATE MessageTable SET from_field=?, sender=?, reply_to=? WHERE id=?"); @@ -1717,10 +1811,10 @@ stmt.bind_string(1, row.sender); stmt.bind_string(2, row.reply_to); stmt.bind_rowid(3, row.id); - + stmt.exec(cancellable); } - + if (new_fields.is_any_set(Geary.Email.Field.RECEIVERS)) { Db.Statement stmt = cx.prepare( "UPDATE MessageTable SET to_field=?, cc=?, bcc=? WHERE id=?"); @@ -1728,10 +1822,10 @@ stmt.bind_string(1, row.cc); stmt.bind_string(2, row.bcc); stmt.bind_rowid(3, row.id); - + stmt.exec(cancellable); } - + if (new_fields.is_any_set(Geary.Email.Field.REFERENCES)) { Db.Statement stmt = cx.prepare( "UPDATE MessageTable SET message_id=?, in_reply_to=?, reference_ids=? WHERE id=?"); @@ -1739,65 +1833,65 @@ stmt.bind_string(1, row.in_reply_to); stmt.bind_string(2, row.references); stmt.bind_rowid(3, row.id); - + stmt.exec(cancellable); } - + if (new_fields.is_any_set(Geary.Email.Field.SUBJECT)) { Db.Statement stmt = cx.prepare( "UPDATE MessageTable SET subject=? WHERE id=?"); stmt.bind_string(0, row.subject); stmt.bind_rowid(1, row.id); - + stmt.exec(cancellable); } - + if (new_fields.is_any_set(Geary.Email.Field.HEADER)) { Db.Statement stmt = cx.prepare( "UPDATE MessageTable SET header=? WHERE id=?"); stmt.bind_string_buffer(0, row.header); stmt.bind_rowid(1, row.id); - + stmt.exec(cancellable); } - + if (new_fields.is_any_set(Geary.Email.Field.BODY)) { Db.Statement stmt = cx.prepare( "UPDATE MessageTable SET body=? WHERE id=?"); stmt.bind_string_buffer(0, row.body); stmt.bind_rowid(1, row.id); - + stmt.exec(cancellable); } - + if (new_fields.is_any_set(Geary.Email.Field.PREVIEW)) { Db.Statement stmt = cx.prepare( "UPDATE MessageTable SET preview=? WHERE id=?"); stmt.bind_string(0, row.preview); stmt.bind_rowid(1, row.id); - + stmt.exec(cancellable); } - + if (new_fields.is_any_set(Geary.Email.Field.FLAGS)) { // Fetch existing flags to update unread count Geary.EmailFlags? old_flags = do_get_email_flags_single(cx, row.id, cancellable); Geary.EmailFlags new_flags = new Geary.Imap.EmailFlags( Geary.Imap.MessageFlags.deserialize(row.email_flags)); - + if (old_flags != null && (old_flags.is_unread() != new_flags.is_unread())) unread_count_change += new_flags.is_unread() ? 1 : -1; else if (new_flags.is_unread()) unread_count_change++; - + Db.Statement stmt = cx.prepare( "UPDATE MessageTable SET flags=? WHERE id=?"); stmt.bind_string(0, row.email_flags); stmt.bind_rowid(1, row.id); - + stmt.exec(cancellable); } - + if (new_fields.is_any_set(Geary.Email.Field.PROPERTIES)) { Db.Statement stmt = cx.prepare( "UPDATE MessageTable SET internaldate=?, internaldate_time_t=?, rfc822_size=? WHERE id=?"); @@ -1805,18 +1899,18 @@ stmt.bind_int64(1, row.internaldate_time_t); stmt.bind_int64(2, row.rfc822_size); stmt.bind_rowid(3, row.id); - + stmt.exec(cancellable); } - + // now merge the new fields in the row Db.Statement stmt = cx.prepare( "UPDATE MessageTable SET fields = fields | ? WHERE id=?"); stmt.bind_int(0, new_fields); stmt.bind_rowid(1, row.id); - + stmt.exec(cancellable); - + // Update the autocompletion table. MessageAddresses message_addresses = new MessageAddresses.from_row(account_owner_email, row); @@ -1910,10 +2004,10 @@ out Gee.Collection updated_contacts, ref int unread_count_change, Cancellable? cancellable) throws Error { assert(email.fields == Geary.Email.Field.FLAGS); - + // no contacts were harmed in the production of this email updated_contacts = new Gee.ArrayList(); - + // fetch MessageRow and its fields, note that the fields now include FLAGS if they didn't // already MessageRow row = do_fetch_message_row(cx, location.message_id, Geary.Email.Field.FLAGS, @@ -1931,12 +2025,20 @@ unread_count_change += email.email_flags.is_unread() ? 1 : -1; } - Gee.Map map = - new Gee.HashMap(); - map.set((ImapDB.EmailIdentifier) email.id, email.email_flags); - do_set_email_flags(cx, map, cancellable); + // do_set_email_flags requires a valid message location, + // but doesn't accept one as an arg, so despite knowing + // the location here, make sure we pass an id with a + // message_id in so it can look the location back up. + do_set_email_flags( + cx, + Collection.single_map( + (ImapDB.EmailIdentifier) row_email.id, email.email_flags + ), + cancellable + ); post_fields |= Geary.Email.Field.FLAGS; + } } @@ -1946,7 +2048,7 @@ Cancellable? cancellable) throws Error { // Default to an empty list, in case we never call do_merge_message_row. updated_contacts = new Gee.LinkedList(); - + // fetch message from database and merge in this email MessageRow row = do_fetch_message_row(cx, location.message_id, email.fields | Email.REQUIRED_FOR_MESSAGE | Attachment.REQUIRED_FIELDS, @@ -1954,32 +2056,35 @@ Geary.Email.Field fetched_fields = row.fields; post_fields = pre_fields | email.fields; row.merge_from_remote(email); - + if (email.fields == Geary.Email.Field.NONE) return; - + // Merge in any fields in the submitted email that aren't already in the database or are mutable int new_unread_count = 0; if (((fetched_fields & email.fields) != email.fields) || email.fields.is_any_set(Geary.Email.MUTABLE_FIELDS)) { // Build the combined email from the merge, which will be used to save the attachments Geary.Email combined_email = row.to_email(location.email_id); - + // Update attachments if not already in the database if (!fetched_fields.fulfills(Attachment.REQUIRED_FIELDS) && combined_email.fields.fulfills(Attachment.REQUIRED_FIELDS)) { - do_save_attachments(cx, location.message_id, combined_email.get_message().get_attachments(), - cancellable); + combined_email.add_attachments( + Attachment.save_attachments( + cx, + this.attachments_path, + location.message_id, + combined_email.get_message().get_attachments(), + cancellable + ) + ); } - - // Must add attachments to the email object after they're saved to - // the database. - do_add_attachments(cx, combined_email, location.message_id, cancellable); - + Geary.Email.Field new_fields; do_merge_message_row(cx, row, out new_fields, out updated_contacts, ref new_unread_count, cancellable); - + if (do_check_for_message_search_row(cx, location.message_id, cancellable)) do_merge_email_in_search_table(cx, location.message_id, new_fields, combined_email, cancellable); else @@ -1991,204 +2096,10 @@ if (combined_flags != null && combined_flags.is_unread()) new_unread_count = 1; } - - unread_count_change += new_unread_count; - } - - private static Gee.List? do_list_attachments(Db.Connection cx, int64 message_id, - Cancellable? cancellable) throws Error { - Db.Statement stmt = cx.prepare(""" - SELECT id, filename, mime_type, filesize, disposition, content_id, description - FROM MessageAttachmentTable - WHERE message_id = ? - ORDER BY id - """); - stmt.bind_rowid(0, message_id); - - Db.Result results = stmt.exec(cancellable); - if (results.finished) - return null; - - Gee.List list = new Gee.ArrayList(); - do { - string? content_filename = results.string_at(1); - if (content_filename == ImapDB.Attachment.NULL_FILE_NAME) { - // Prior to 0.12, Geary would store the untranslated - // string "none" as the filename when none was - // specified by the MIME content disposition. Check - // for that and clean it up. - content_filename = null; - } - Mime.ContentDisposition disposition = new Mime.ContentDisposition.simple( - Mime.DispositionType.from_int(results.int_at(4))); - list.add( - new ImapDB.Attachment( - message_id, - results.rowid_at(0), - Mime.ContentType.deserialize(results.nonnull_string_at(2)), - results.string_at(5), - results.string_at(6), - disposition, - content_filename, - cx.database.db_file.get_parent(), - results.int64_at(3) - ) - ); - } while (results.next(cancellable)); - return list; + unread_count_change += new_unread_count; } - private void do_save_attachments(Db.Connection cx, int64 message_id, - Gee.List? attachments, Cancellable? cancellable) throws Error { - do_save_attachments_db(cx, message_id, attachments, db, cancellable); - } - - public static void do_save_attachments_db(Db.Connection cx, int64 message_id, - Gee.List? attachments, ImapDB.Database db, Cancellable? cancellable) throws Error { - // nothing to do if no attachments - if (attachments == null || attachments.size == 0) - return; - - foreach (GMime.Part attachment in attachments) { - GMime.ContentType? content_type = attachment.get_content_type(); - string mime_type = (content_type != null) - ? content_type.to_string() - : Mime.ContentType.DEFAULT_CONTENT_TYPE; - string? disposition = attachment.get_disposition(); - string? content_id = attachment.get_content_id(); - string? description = attachment.get_content_description(); - string? filename = RFC822.Utils.get_clean_attachment_filename(attachment); - - // Convert the attachment content into a usable ByteArray. - GMime.DataWrapper? attachment_data = attachment.get_content_object(); - ByteArray byte_array = new ByteArray(); - GMime.StreamMem stream = new GMime.StreamMem.with_byte_array(byte_array); - stream.set_owner(false); - if (attachment_data != null) - attachment_data.write_to_stream(stream); // data is null if it's 0 bytes - uint filesize = byte_array.len; - - // convert into DispositionType enum, which is stored as int - // (legacy code stored UNSPECIFIED as NULL, which is zero, which is ATTACHMENT, so preserve - // this behavior) - Mime.DispositionType disposition_type = Mime.DispositionType.deserialize(disposition, - null); - if (disposition_type == Mime.DispositionType.UNSPECIFIED) - disposition_type = Mime.DispositionType.ATTACHMENT; - - // Insert it into the database. - Db.Statement stmt = cx.prepare(""" - INSERT INTO MessageAttachmentTable (message_id, filename, mime_type, filesize, disposition, content_id, description) - VALUES (?, ?, ?, ?, ?, ?, ?) - """); - stmt.bind_rowid(0, message_id); - stmt.bind_string(1, filename); - stmt.bind_string(2, mime_type); - stmt.bind_uint(3, filesize); - stmt.bind_int(4, disposition_type); - stmt.bind_string(5, content_id); - stmt.bind_string(6, description); - - int64 attachment_id = stmt.exec_insert(cancellable); - - File saved_file = ImapDB.Attachment.generate_file(db.db_file.get_parent(), message_id, - attachment_id, filename); - - // On the off-chance this is marked for deletion, unmark it - try { - stmt = cx.prepare(""" - DELETE FROM DeleteAttachmentFileTable - WHERE filename = ? - """); - stmt.bind_string(0, saved_file.get_path()); - - stmt.exec(cancellable); - } catch (Error err) { - debug("Unable to delete from DeleteAttachmentFileTable: %s", err.message); - - // not a deal-breaker, fall through - } - - debug("Saving attachment to %s", saved_file.get_path()); - - try { - // create directory, but don't throw exception if already exists - try { - saved_file.get_parent().make_directory_with_parents(cancellable); - } catch (IOError ioe) { - // fall through if already exists - if (!(ioe is IOError.EXISTS)) - throw ioe; - } - - // REPLACE_DESTINATION doesn't seem to work as advertised all the time ... just - // play it safe here - if (saved_file.query_exists(cancellable)) - saved_file.delete(cancellable); - - // Create the file where the attachment will be saved and get the output stream. - FileOutputStream saved_stream = saved_file.create(FileCreateFlags.REPLACE_DESTINATION, - cancellable); - - // Save the data to disk and flush it. - size_t written; - if (filesize != 0) - saved_stream.write_all(byte_array.data[0:filesize], out written, cancellable); - - saved_stream.flush(cancellable); - } catch (Error error) { - // An error occurred while saving the attachment, so lets remove the attachment from - // the database and delete the file (in case it's partially written) - debug("Failed to save attachment %s: %s", saved_file.get_path(), error.message); - - try { - saved_file.delete(); - } catch (Error delete_error) { - debug("Error attempting to delete partial attachment %s: %s", saved_file.get_path(), - delete_error.message); - } - - try { - Db.Statement remove_stmt = cx.prepare( - "DELETE FROM MessageAttachmentTable WHERE id=?"); - remove_stmt.bind_rowid(0, attachment_id); - - remove_stmt.exec(); - } catch (Error remove_error) { - debug("Error attempting to remove added attachment row for %s: %s", - saved_file.get_path(), remove_error.message); - } - - throw error; - } - } - } - - public static void do_delete_attachments(Db.Connection cx, int64 message_id) - throws Error { - Gee.List? attachments = do_list_attachments(cx, message_id, null); - if (attachments == null || attachments.size == 0) - return; - - // delete all files - foreach (Geary.Attachment attachment in attachments) { - try { - attachment.file.delete(null); - } catch (Error err) { - debug("Unable to delete file %s: %s", attachment.file.get_path(), err.message); - } - } - - // remove all from attachment table - Db.Statement stmt = new Db.Statement(cx, """ - DELETE FROM MessageAttachmentTable WHERE message_id = ? - """); - stmt.bind_rowid(0, message_id); - - stmt.exec(); - } - /** * Adds a value to the unread count. If this makes the unread count negative, it will be * set to zero. @@ -2197,48 +2108,48 @@ throws Error { if (to_add == 0) return; // Nothing to do. - + Db.Statement update_stmt = cx.prepare( "UPDATE FolderTable SET unread_count = CASE WHEN unread_count + ? < 0 THEN 0 ELSE " + "unread_count + ? END WHERE id=?"); - + update_stmt.bind_int(0, to_add); update_stmt.bind_int(1, to_add); update_stmt.bind_rowid(2, folder_id); - + update_stmt.exec(cancellable); } - + // Db.Result must include columns for "message_id", "ordering", and "remove_marker" from the // MessageLocationTable private Gee.List do_results_to_locations(Db.Result results, int count, ListFlags flags, Cancellable? cancellable) throws Error { Gee.List locations = new Gee.ArrayList(); - + if (results.finished) return locations; - + do { LocationIdentifier location = new LocationIdentifier(results.rowid_for("message_id"), new Imap.UID(results.int64_for("ordering")), results.bool_for("remove_marker")); if (!flags.include_marked_for_remove() && location.marked_removed) continue; - + locations.add(location); if (locations.size >= count) break; } while (results.next(cancellable)); - + return locations; } - + // Use a separate step to strip out complete emails because original implementation (using an // INNER JOIN) was horribly slow under load private void do_remove_complete_locations(Db.Connection cx, Gee.List? locations, Cancellable? cancellable) throws Error { if (locations == null || locations.size == 0) return; - + StringBuilder sql = new StringBuilder(""" SELECT id FROM MessageTable WHERE id IN ( """); @@ -2246,37 +2157,37 @@ foreach (LocationIdentifier location_id in locations) { if (!first) sql.append(","); - + sql.append(location_id.message_id.to_string()); first = false; } sql.append(") AND fields <> ?"); - + Db.Statement stmt = cx.prepare(sql.str); stmt.bind_int(0, Geary.Email.Field.ALL); - + Db.Result results = stmt.exec(cancellable); - + Gee.HashSet incomplete_locations = new Gee.HashSet(Collection.int64_hash_func, Collection.int64_equal_func); while (!results.finished) { incomplete_locations.add(results.int64_at(0)); results.next(cancellable); } - + if (incomplete_locations.size == 0) { locations.clear(); - + return; } - + Gee.Iterator iter = locations.iterator(); while (iter.next()) { if (!incomplete_locations.contains(iter.get().message_id)) iter.remove(); } } - + private LocationIdentifier? do_get_location_for_id(Db.Connection cx, ImapDB.EmailIdentifier id, ListFlags flags, Cancellable? cancellable) throws Error { Db.Statement stmt = cx.prepare(""" @@ -2286,23 +2197,23 @@ """); stmt.bind_rowid(0, folder_id); stmt.bind_rowid(1, id.message_id); - + Db.Result result = stmt.exec(cancellable); if (result.finished) return null; - + LocationIdentifier location = new LocationIdentifier(id.message_id, new Imap.UID(result.int64_at(0)), result.bool_at(1)); - + return (!flags.include_marked_for_remove() && location.marked_removed) ? null : location; } - + private Gee.List? do_get_locations_for_ids(Db.Connection cx, Gee.Collection? ids, ListFlags flags, Cancellable? cancellable) throws Error { if (ids == null || ids.size == 0) return null; - + StringBuilder sql = new StringBuilder(""" SELECT message_id, ordering, remove_marker FROM MessageLocationTable @@ -2313,20 +2224,20 @@ if (!first) sql.append(","); sql.append_printf(id.message_id.to_string()); - + first = false; } sql.append(") AND folder_id = ?"); - + Db.Statement stmt = cx.prepare(sql.str); stmt.bind_rowid(0, folder_id); - + Gee.List locs = do_results_to_locations(stmt.exec(cancellable), int.MAX, flags, cancellable); - + return (locs.size > 0) ? locs : null; } - + private LocationIdentifier? do_get_location_for_uid(Db.Connection cx, Imap.UID uid, ListFlags flags, Cancellable? cancellable) throws Error { Db.Statement stmt = cx.prepare(""" @@ -2336,22 +2247,22 @@ """); stmt.bind_rowid(0, folder_id); stmt.bind_int64(1, uid.value); - + Db.Result result = stmt.exec(cancellable); if (result.finished) return null; - + LocationIdentifier location = new LocationIdentifier(result.rowid_at(0), uid, result.bool_at(1)); - + return (!flags.include_marked_for_remove() && location.marked_removed) ? null : location; } - + private Gee.List? do_get_locations_for_uids(Db.Connection cx, Gee.Collection? uids, ListFlags flags, Cancellable? cancellable) throws Error { if (uids == null || uids.size == 0) return null; - + StringBuilder sql = new StringBuilder(""" SELECT message_id, ordering, remove_marker FROM MessageLocationTable @@ -2362,20 +2273,20 @@ if (!first) sql.append(","); sql.append(uid.value.to_string()); - + first = false; } sql.append(") AND folder_id = ?"); - + Db.Statement stmt = cx.prepare(sql.str); stmt.bind_rowid(0, folder_id); - + Gee.List locs = do_results_to_locations(stmt.exec(cancellable), int.MAX, flags, cancellable); - + return (locs.size > 0) ? locs : null; } - + private Gee.List? do_get_all_locations(Db.Connection cx, ListFlags flags, Cancellable? cancellable) throws Error { Db.Statement stmt = cx.prepare(""" @@ -2384,18 +2295,18 @@ WHERE folder_id = ? """); stmt.bind_rowid(0, folder_id); - + Gee.List locs = do_results_to_locations(stmt.exec(cancellable), int.MAX, flags, cancellable); - + return (locs.size > 0) ? locs : null; } - + private int do_get_unread_count_for_ids(Db.Connection cx, Gee.Collection? ids, Cancellable? cancellable) throws Error { if (ids == null || ids.size == 0) return 0; - + // Fetch flags for each email and update this folder's unread count. // (Note that this only flags for emails which have NOT been marked for removal // are included.) @@ -2403,8 +2314,52 @@ ids, cancellable); if (flag_map != null) return Geary.traverse(flag_map.values).count_matching(f => f.is_unread()); - + return 0; } -} + // For SELECT/EXAMINE responses, not STATUS responses + private void do_update_last_seen_select_examine_total(Db.Connection cx, + int total, + Cancellable? cancellable) + throws Error { + Db.Statement stmt = cx.prepare( + "UPDATE FolderTable SET last_seen_total=? WHERE id=?" + ); + stmt.bind_int(0, Numeric.int_floor(total, 0)); + stmt.bind_rowid(1, this.folder_id); + stmt.exec(cancellable); + } + + // For STATUS responses, not SELECT/EXAMINE responses + private void do_update_last_seen_status_total(Db.Connection cx, + int total, + Cancellable? cancellable) + throws Error { + Db.Statement stmt = cx.prepare( + "UPDATE FolderTable SET last_seen_status_total=? WHERE id=?"); + stmt.bind_int(0, Numeric.int_floor(total, 0)); + stmt.bind_rowid(1, this.folder_id); + stmt.exec(cancellable); + } + + private void do_update_uid_info(Db.Connection cx, + Imap.FolderProperties remote_properties, + Cancellable? cancellable) + throws Error { + int64 uid_validity = (remote_properties.uid_validity != null) + ? remote_properties.uid_validity.value + : Imap.UIDValidity.INVALID; + int64 uid_next = (remote_properties.uid_next != null) + ? remote_properties.uid_next.value + : Imap.UID.INVALID; + + Db.Statement stmt = cx.prepare( + "UPDATE FolderTable SET uid_validity=?, uid_next=? WHERE id=?"); + stmt.bind_int64(0, uid_validity); + stmt.bind_int64(1, uid_next); + stmt.bind_rowid(2, this.folder_id); + stmt.exec(cancellable); + } + +} diff -Nru geary-0.12.4/src/engine/imap-db/imap-db-gc.vala geary-3.32.0/src/engine/imap-db/imap-db-gc.vala --- geary-0.12.4/src/engine/imap-db/imap-db-gc.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-db/imap-db-gc.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,4 +1,5 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. @@ -27,33 +28,33 @@ private class Geary.ImapDB.GC { // Maximum number of days between reaping runs. private const int REAP_DAYS_SPAN = 10; - + // Minimum number of days between vacuums. private const int VACUUM_DAYS_SPAN = 30; - + // Number of reaped messages since last vacuum indicating another vacuum should occur private const int VACUUM_WHEN_REAPED_REACHES = 10000; - + // Amount of disk space that must be saved to start a vacuum (500MB). private const long VACUUM_WHEN_FREE_BYTES = 500 * 1024 * 1024; - + // Days old from today an unlinked email message must be to be reaped by the garbage collector private const int UNLINKED_DAYS = 30; - + // Amount of time to sleep between various database-bound GC iterations to give other // transactions a chance private const uint SLEEP_MSEC = 15; - + // Number of database operations to perform before sleeping (obviously this is a rough // approximation, as not all operations are the same cost) private const int OPS_PER_SLEEP_CYCLE = 10; - + // Number of files to reap from the DeleteAttachmentFileTable per iteration private const int REAP_ATTACHMENT_PER = 5; - + // Number of files to enumerate per time when walking a directory's children private const int ENUM_DIR_PER = 10; - + /** * Operation(s) recommended by {@link should_run_async}. */ @@ -69,66 +70,68 @@ */ REAP, /** - * Indicates the caller should run {@link vauum_async} to consolidate disk space and reduce + * Indicates the caller should run {@link vacuum_async} to consolidate disk space and reduce * database fragmentation. */ VACUUM } - + /** * Indicates the garbage collector is running. - * - * {@link run_async} will return immediately if called while running. */ public bool is_running { get; private set; default = false; } - - private ImapDB.Database db; + + private weak ImapDB.Database db; private int priority; - private File data_dir; - + public GC(ImapDB.Database db, int priority) { this.db = db; this.priority = priority; - data_dir = db.db_file.get_parent(); } - + /** - * Returns if the GC should be executed (via {@link run_async}). + * Determines if the GC should be executed. + * + * @return a recommendation for the operation client to execute. */ public async RecommendedOperation should_run_async(Cancellable? cancellable) throws Error { DateTime? last_reap_time, last_vacuum_time; int reaped_messages_since_last_vacuum; int64 free_page_bytes; - yield fetch_gc_info_async(out last_reap_time, out last_vacuum_time, out reaped_messages_since_last_vacuum, - out free_page_bytes, cancellable); - + yield fetch_gc_info_async(cancellable, out last_reap_time, out last_vacuum_time, + out reaped_messages_since_last_vacuum, out free_page_bytes); + debug("[%s] GC state: last_reap_time=%s last_vacuum_time=%s reaped_messages_since=%d free_page_bytes=%s", to_string(), (last_reap_time != null) ? last_reap_time.to_string() : "never", (last_vacuum_time != null) ? last_vacuum_time.to_string() : "never", reaped_messages_since_last_vacuum, free_page_bytes.to_string()); - + RecommendedOperation op = RecommendedOperation.NONE; - DateTime now = new DateTime.now_local(); - + if (!yield has_message_rows(cancellable)) { + // No message rows exist, so don't bother vacuuming + return op; + } + // Reap every REAP_DAYS_SPAN unless never executed, in which case run now + DateTime now = new DateTime.now_local(); int64 days; if (last_reap_time == null) { // null means reaping has never executed debug("[%s] Recommending reaping: never completed", to_string()); - + op |= RecommendedOperation.REAP; } else if (elapsed_days(now, last_reap_time, out days) >= REAP_DAYS_SPAN) { debug("[%s] Recommending reaping: %s days since last run", to_string(), days.to_string()); - + op |= RecommendedOperation.REAP; } else { debug("[%s] Reaping last completed on %s (%s days ago)", to_string(), last_reap_time.to_string(), days.to_string()); } - + // VACUUM is not something we do regularly, but rather look for a lot of reaped messages // as indicator it's time ... to prevent doing this a lot (which is annoying), still space // it out over a minimum amount of time @@ -137,20 +140,20 @@ if (last_vacuum_time == null) { debug("[%s] Database never vacuumed (%d messages reaped)", to_string(), reaped_messages_since_last_vacuum); - + vacuum_permitted = true; } else if (elapsed_days(now, last_vacuum_time, out days) >= VACUUM_DAYS_SPAN) { debug("[%s] Database vacuuming permitted (%s days since last run, %d messages reaped since)", to_string(), days.to_string(), reaped_messages_since_last_vacuum); - + vacuum_permitted = true; } else { debug("[%s] Database vacuuming not permitted (%s days since last run, %d messages reaped since)", to_string(), days.to_string(), reaped_messages_since_last_vacuum); - + vacuum_permitted = false; } - + // VACUUM_DAYS_SPAN must have passed since last vacuum (unless no prior vacuum has occurred) // *and* a certain number of messages have been reaped (indicating fragmentation is a // possibility) or a certain amount of free space exists in the database (indicating they @@ -160,19 +163,19 @@ if (vacuum_permitted && (fragmentation_exists || too_much_free_space)) { debug("[%s] Recommending database vacuum: %d messages reaped since last vacuum %s days ago, %s free bytes in file", to_string(), reaped_messages_since_last_vacuum, days.to_string(), free_page_bytes.to_string()); - + op |= RecommendedOperation.VACUUM; } - + return op; } - + private static int64 elapsed_days(DateTime end, DateTime start, out int64 days) { days = end.difference(start) / TimeSpan.DAY; - + return days; } - + /** * Vacuum the database, reducing fragmentation and coalescing free space, to optimize access * and reduce disk usage. @@ -188,7 +191,7 @@ public async void vacuum_async(Cancellable? cancellable) throws Error { if (is_running) throw new EngineError.ALREADY_OPEN("Cannot vacuum %s: already running", to_string()); - + is_running = true; try { debug("[%s] Starting vacuum of IMAP database", to_string()); @@ -198,28 +201,29 @@ is_running = false; } } - + private async void internal_vacuum_async(Cancellable? cancellable) throws Error { DateTime? last_vacuum_time = null; - + // NOTE: VACUUM cannot happen inside a transaction, so to avoid blocking the main thread, // run a non-transacted command from a background thread + Geary.Db.Connection cx = yield db.open_connection(cancellable); yield Nonblocking.Concurrent.global.schedule_async(() => { - db.open_connection(cancellable).exec("VACUUM", cancellable); - + cx.exec("VACUUM", cancellable); + // it's a small thing, but take snapshot of time when vacuum completes, as scheduling // of the next transaction is not instantaneous last_vacuum_time = new DateTime.now_local(); }, cancellable); - + // could assert here, but these calls really need to be bulletproof if (last_vacuum_time == null) last_vacuum_time = new DateTime.now_local(); - + // update last vacuum time and reset messages reaped since last vacuum ... don't allow this // to be cancelled, really want to get this in stone so the user doesn't re-vacuum // unnecessarily - yield db.exec_transaction_async(Db.TransactionType.WO, (cx) => { + yield cx.exec_transaction_async(Db.TransactionType.WO, (cx) => { Db.Statement stmt = cx.prepare(""" UPDATE GarbageCollectionTable SET last_vacuum_time_t = ?, reaped_messages_since_last_vacuum = ? @@ -227,13 +231,13 @@ """); stmt.bind_int64(0, last_vacuum_time.to_unix()); stmt.bind_int(1, 0); - + stmt.exec(cancellable); - + return Db.TransactionOutcome.COMMIT; }, null); } - + /** * Run the garbage collector, which reaps unlinked messages from the database and deletes * their on-disk attachments. @@ -248,7 +252,7 @@ public async void reap_async(Cancellable? cancellable) throws Error { if (is_running) throw new EngineError.ALREADY_OPEN("Cannot garbage collect %s: already running", to_string()); - + is_running = true; try { debug("[%s] Starting garbage collection of IMAP database", to_string()); @@ -258,7 +262,7 @@ is_running = false; } } - + private async void internal_reap_async(Cancellable? cancellable) throws Error { // // Find all messages unlinked from the location table and older than the GC reap date ... @@ -278,14 +282,14 @@ // in the MessageTable but never downloaded. Since internaldate is the first thing // downloaded, this is rare, but can happen, and this will reap those rows. // - + DateTime reap_date = new DateTime.now_local().add_days(0 - UNLINKED_DAYS); debug("[%s] Garbage collector reaping date: %s (%s)", to_string(), reap_date.to_string(), reap_date.to_unix().to_string()); - + Gee.HashSet reap_message_ids = new Gee.HashSet(Collection.int64_hash_func, Collection.int64_equal_func); - + yield db.exec_transaction_async(Db.TransactionType.RO, (cx) => { Db.Statement stmt = cx.prepare(""" SELECT id @@ -298,19 +302,19 @@ ) """); stmt.bind_int64(0, reap_date.to_unix()); - + Db.Result result = stmt.exec(cancellable); while (!result.finished) { reap_message_ids.add(result.rowid_at(0)); - + result.next(cancellable); } - + return Db.TransactionOutcome.DONE; }, cancellable); - + message("[%s] Found %d email messages ready for reaping", to_string(), reap_message_ids.size); - + // // To prevent holding the database lock for long periods of time, reap each message one // at a time, deleting it from the message table and subsidiary tables. Although slow, we @@ -319,7 +323,7 @@ // without leaving the database in an incoherent state. gc can be resumed even if' // interrupted. // - + int count = 0; foreach (int64 reap_message_id in reap_message_ids) { try { @@ -328,59 +332,59 @@ } catch (Error err) { if (err is IOError.CANCELLED) throw err; - + message("[%s] Unable to reap message #%s: %s", to_string(), reap_message_id.to_string(), err.message); } - + if ((count % OPS_PER_SLEEP_CYCLE) == 0) yield Scheduler.sleep_ms_async(SLEEP_MSEC); - + if ((count % 5000) == 0) debug("[%s] Reaped %d messages", to_string(), count); } - + message("[%s] Reaped completed: %d messages", to_string(), count); - + // // Now reap on-disk attachment files marked for deletion. Since they're added to the // DeleteAttachmentFileTable as part of the reap_message_async() transaction, it's assured // that they're ready for deletion (and, again, means this process is resumable) // - + count = 0; for (;;) { int reaped = yield reap_attachment_files_async(REAP_ATTACHMENT_PER, cancellable); if (reaped == 0) break; - + count += reaped; - + if ((count % OPS_PER_SLEEP_CYCLE) == 0) yield Scheduler.sleep_ms_async(SLEEP_MSEC); - + if ((count % 1000) == 0) debug("[%s] Reaped %d attachment files", to_string(), count); } - + message("[%s] Completed: Reaped %d attachment files", to_string(), count); - + // // To be sure everything's clean, delete any empty directories in the attachment dir tree, // as code (here and elsewhere) only removes files. // - - count = yield delete_empty_attachment_directories_async(null, null, cancellable); - + + count = yield delete_empty_attachment_directories_async(null, cancellable, null); + message("[%s] Deleted %d empty attachment directories", to_string(), count); - + // // A full reap cycle completed -- store date for next time. By only storing when the full // cycle is completed, even if the user closes the application through the cycle it will // start the next time, assuring all reaped messages/attachments are dealt with in a timely // manner. // - + yield db.exec_transaction_async(Db.TransactionType.WO, (cx) => { Db.Statement stmt = cx.prepare(""" UPDATE GarbageCollectionTable @@ -388,13 +392,13 @@ WHERE id = 0 """); stmt.bind_int64(0, new DateTime.now_local().to_unix()); - + stmt.exec(cancellable); - + return Db.TransactionOutcome.COMMIT; }, cancellable); } - + private async void reap_message_async(int64 message_id, Cancellable? cancellable) throws Error { yield db.exec_transaction_async(Db.TransactionType.RW, (cx) => { // Since there's a window of time between locating gc-able messages and removing them, @@ -405,113 +409,98 @@ WHERE message_id = ? """); stmt.bind_rowid(0, message_id); - + // If find one, then message is no longer unlinked Db.Result result = stmt.exec(cancellable); if (!result.finished) { debug("[%s] Not reaping message #%s: found linked in MessageLocationTable", to_string(), message_id.to_string()); - + return Db.TransactionOutcome.ROLLBACK; } - + // // Fetch all on-disk attachments for this message // - - Gee.ArrayList attachment_files = new Gee.ArrayList(); - - stmt = cx.prepare(""" - SELECT id, filename - FROM MessageAttachmentTable - WHERE message_id = ? - """); - stmt.bind_rowid(0, message_id); - - result = stmt.exec(cancellable); - while (!result.finished) { - File file = Attachment.generate_file(data_dir, message_id, result.rowid_for("id"), - result.string_for("filename")); - attachment_files.add(file); - - result.next(cancellable); - } - + + Gee.List attachments = Attachment.list_attachments( + cx, this.db.attachments_path, message_id, cancellable + ); + // // Delete from search table // - + stmt = cx.prepare(""" DELETE FROM MessageSearchTable WHERE docid = ? """); stmt.bind_rowid(0, message_id); - + stmt.exec(cancellable); - + // // Delete from attachment table // - + stmt = cx.prepare(""" DELETE FROM MessageAttachmentTable WHERE message_id = ? """); stmt.bind_rowid(0, message_id); - + stmt.exec(cancellable); - + // // Delete from message table // - + stmt = cx.prepare(""" DELETE FROM MessageTable WHERE id = ? """); stmt.bind_rowid(0, message_id); - + stmt.exec(cancellable); - + // // Mark on-disk attachment files as ready for deletion (handled by // reap_attachments_files_async). This two-step process assures that this transaction // commits without error and the attachment files can be deleted without being // referenced by the database, in a way that's resumable. // - - foreach (File attachment_file in attachment_files) { + + foreach (Attachment attachment in attachments) { stmt = cx.prepare(""" INSERT INTO DeleteAttachmentFileTable (filename) VALUES (?) """); - stmt.bind_string(0, attachment_file.get_path()); - + stmt.bind_string(0, attachment.file.get_path()); stmt.exec(cancellable); } - + // // Increment the reap count since last vacuum // - + cx.exec(""" UPDATE GarbageCollectionTable SET reaped_messages_since_last_vacuum = reaped_messages_since_last_vacuum + 1 WHERE id = 0 """); - + // // Done; other than on-disk attachment files, message is now garbage collected. // - + return Db.TransactionOutcome.COMMIT; }, cancellable); } - + private async int reap_attachment_files_async(int limit, Cancellable? cancellable) throws Error { if (limit <= 0) return 0; - + int deleted = 0; yield db.exec_transaction_async(Db.TransactionType.RW, (cx) => { Db.Statement stmt = cx.prepare(""" @@ -520,61 +509,61 @@ LIMIT ? """); stmt.bind_int(0, limit); - + // build SQL for removing file from table (whether it's deleted or not -- at this point, // we're in best-attempt mode) StringBuilder sql = new StringBuilder(""" DELETE FROM DeleteAttachmentFileTable WHERE id IN ( """); - + Db.Result result = stmt.exec(cancellable); bool first = true; while (!result.finished) { int64 id = result.rowid_at(0); File file = File.new_for_path(result.string_at(1)); - + // if it deletes, great; if not, we tried try { file.delete(cancellable); } catch (Error err) { if (err is IOError.CANCELLED) throw err; - + debug("[%s] Unable to delete reaped attachment file \"%s\": %s", to_string(), file.get_path(), err.message); } - + if (!first) sql.append(", "); - + sql.append(id.to_string()); first = false; - + deleted++; - + result.next(cancellable); } - + sql.append(")"); - + // if any files were deleted, remove them from the table if (deleted > 0) cx.exec(sql.str); - + return Db.TransactionOutcome.COMMIT; }, cancellable); - + return deleted; } - - private async int delete_empty_attachment_directories_async(File? current, out bool empty, - Cancellable? cancellable) throws Error { - File current_dir = current ?? Attachment.get_attachments_dir(db.db_file.get_parent()); - + + private async int delete_empty_attachment_directories_async(File? current, Cancellable? cancellable, + out bool empty) throws Error { + File current_dir = current ?? db.attachments_path; + // directory is considered empty until file or non-deleted child directory is found empty = true; - + int deleted = 0; FileEnumerator file_enum = yield current_dir.enumerate_children_async("*", FileQueryInfoFlags.NOFOLLOW_SYMLINKS, priority, cancellable); @@ -582,25 +571,25 @@ List infos = yield file_enum.next_files_async(ENUM_DIR_PER, priority, cancellable); if (infos.length() == 0) break; - + foreach (FileInfo info in infos) { if (info.get_file_type() != FileType.DIRECTORY) { empty = false; - + continue; } - + File child = current_dir.get_child(info.get_name()); - + bool child_empty; - deleted += yield delete_empty_attachment_directories_async(child, out child_empty, - cancellable); + deleted += yield delete_empty_attachment_directories_async(child, cancellable, + out child_empty); if (!child_empty) { empty = false; - + continue; } - + string? failure = null; try { if (!yield child.delete_async(priority, cancellable)) @@ -608,29 +597,47 @@ } catch (Error err) { if (err is IOError.CANCELLED) throw err; - + failure = err.message; } - + if (failure == null) { deleted++; } else { message("[%s] Unable to delete empty attachment directory \"%s\": %s", to_string(), child.get_path(), failure); - + // since it remains, directory not empty empty = false; } } } - + yield file_enum.close_async(priority, cancellable); - + return deleted; } - - private async void fetch_gc_info_async(out DateTime? last_reap_time, out DateTime? last_vacuum_time, - out int reaped_messages_since_last_vacuum, out int64 free_page_bytes, Cancellable? cancellable) + + private async bool has_message_rows(GLib.Cancellable? cancellable) + throws GLib.Error { + bool ret = false; + yield db.exec_transaction_async(Db.TransactionType.RO, (cx) => { + Db.Result result = cx.query( + "SELECT count(*) FROM MessageTable LIMIT 1" + ); + + Db.TransactionOutcome txn_ret = FAILURE; + if (!result.finished) { + txn_ret = SUCCESS; + ret = result.int64_at(0) > 0; + } + return txn_ret; + }, cancellable); + return ret; + } + + private async void fetch_gc_info_async(Cancellable? cancellable, out DateTime? last_reap_time, + out DateTime? last_vacuum_time, out int reaped_messages_since_last_vacuum, out int64 free_page_bytes) throws Error { // dealing with out arguments for an async method inside a closure is asking for trouble int64 last_reap_time_t = -1, last_vacuum_time_t = -1, free_page_count = 0; @@ -641,29 +648,28 @@ FROM GarbageCollectionTable WHERE id = 0 """); - + if (result.finished) return Db.TransactionOutcome.FAILURE; - + // NULL indicates reaping/vacuum has not run last_reap_time_t = !result.is_null_at(0) ? result.int64_at(0) : -1; last_vacuum_time_t = !result.is_null_at(1) ? result.int64_at(1) : -1; reaped_count = result.int_at(2); - + free_page_count = cx.get_free_page_count(); page_size = cx.get_page_size(); - + return Db.TransactionOutcome.SUCCESS; }, cancellable); - + last_reap_time = (last_reap_time_t >= 0) ? new DateTime.from_unix_local(last_reap_time_t) : null; last_vacuum_time = (last_vacuum_time_t >= 0) ? new DateTime.from_unix_local(last_vacuum_time_t) : null; reaped_messages_since_last_vacuum = reaped_count; free_page_bytes = free_page_count * page_size; } - + public string to_string() { - return "GC:%s".printf(db.db_file.get_path()); + return "GC:%s".printf(db.path); } } - diff -Nru geary-0.12.4/src/engine/imap-db/imap-db-message-addresses.vala geary-3.32.0/src/engine/imap-db/imap-db-message-addresses.vala --- geary-0.12.4/src/engine/imap-db/imap-db-message-addresses.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-db/imap-db-message-addresses.vala 2019-03-17 13:39:29.000000000 +0000 @@ -7,13 +7,13 @@ private class Geary.ImapDB.MessageAddresses : BaseObject { // Read-only view. public Gee.Collection contacts { get; private set; } - + private RFC822.MailboxAddress? sender_address; private RFC822.MailboxAddresses? from_addresses; private RFC822.MailboxAddresses? to_addresses; private RFC822.MailboxAddresses? cc_addresses; private RFC822.MailboxAddresses? bcc_addresses; - + private int from_importance; private int to_importance; private int cc_importance; @@ -26,32 +26,32 @@ this.to_addresses = to_addresses; this.cc_addresses = cc_addresses; this.bcc_addresses = bcc_addresses; - + calculate_importance(account_owner_email); contacts = build_contacts(); } - + private MessageAddresses.from_strings(string account_owner_email, string? sender_field, string? from_field, string? to_field, string? cc_field, string? bcc_field) { this(account_owner_email, get_address_from_string(sender_field), get_addresses_from_string(from_field), get_addresses_from_string(to_field), get_addresses_from_string(cc_field), get_addresses_from_string(bcc_field)); } - + public MessageAddresses.from_email(string account_owner_email, Geary.Email email) { this(account_owner_email, email.sender, email.from, email.to, email.cc, email.bcc); } - + public MessageAddresses.from_row(string account_owner_email, MessageRow row) { this.from_strings(account_owner_email, row.sender, row.from, row.to, row.cc, row.bcc); } - + public MessageAddresses.from_result(string account_owner_email, Db.Result result) { this.from_strings(account_owner_email, get_string_or_null(result, "sender"), get_string_or_null(result, "from_field"), get_string_or_null(result, "to_field"), get_string_or_null(result, "cc"), get_string_or_null(result, "bcc")); } - + private static string? get_string_or_null(Db.Result result, string column) { try { return result.string_for(column); @@ -84,30 +84,30 @@ (from_addresses != null && from_addresses.contains_normalized(account_owner_email)); bool account_owner_in_to = to_addresses != null && to_addresses.contains_normalized(account_owner_email); - + // If the account owner's address does not appear in any of these fields, we assume they // were BCC'd. bool account_owner_in_cc = (cc_addresses != null && cc_addresses.contains_normalized(account_owner_email)) || (bcc_addresses != null && bcc_addresses.contains_normalized(account_owner_email)) || !(account_owner_in_from || account_owner_in_to); - + from_importance = -1; to_importance = -1; cc_importance = -1; - + if (account_owner_in_from) { from_importance = int.max(from_importance, ContactImportance.FROM_FROM); to_importance = int.max(to_importance, ContactImportance.FROM_TO); cc_importance = int.max(cc_importance, ContactImportance.FROM_CC); } - + if (account_owner_in_to) { from_importance = int.max(from_importance, ContactImportance.TO_FROM); to_importance = int.max(to_importance, ContactImportance.TO_TO); cc_importance = int.max(cc_importance, ContactImportance.TO_CC); } - + if (account_owner_in_cc) { from_importance = int.max(from_importance, ContactImportance.CC_FROM); to_importance = int.max(to_importance, ContactImportance.CC_TO); @@ -133,16 +133,16 @@ int importance) { if (addresses == null) return; - + foreach (RFC822.MailboxAddress address in addresses) add_contact(contacts_map, address, importance); } - + private void add_contact(Gee.Map contacts_map, RFC822.MailboxAddress address, int importance) { if (!address.is_valid()) return; - + Contact contact = new Contact.from_rfc822_address(address, importance); Contact? old_contact = contacts_map[contact.normalized_email]; if (old_contact == null || old_contact.highest_importance < contact.highest_importance) diff -Nru geary-0.12.4/src/engine/imap-db/imap-db-message-row.vala geary-3.32.0/src/engine/imap-db/imap-db-message-row.vala --- geary-0.12.4/src/engine/imap-db/imap-db-message-row.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-db/imap-db-message-row.vala 2019-03-17 13:39:29.000000000 +0000 @@ -7,104 +7,111 @@ private class Geary.ImapDB.MessageRow { public int64 id { get; set; default = Db.INVALID_ROWID; } public Geary.Email.Field fields { get; set; default = Geary.Email.Field.NONE; } - + public string? date { get; set; default = null; } public time_t date_time_t { get; set; default = -1; } - + public string? from { get; set; default = null; } public string? sender { get; set; default = null; } public string? reply_to { get; set; default = null; } - + public string? to { get; set; default = null; } public string? cc { get; set; default = null; } public string? bcc { get; set; default = null; } - + public string? message_id { get; set; default = null; } public string? in_reply_to { get; set; default = null; } public string? references { get; set; default = null; } - + public string? subject { get; set; default = null; } - + public Memory.Buffer? header { get; set; default = null; } - + public Memory.Buffer? body { get; set; default = null; } - + public string? preview { get; set; default = null; } - + public string? email_flags { get; set; default = null; } public string? internaldate { get; set; default = null; } public time_t internaldate_time_t { get; set; default = -1; } public int64 rfc822_size { get; set; default = -1; } - + public MessageRow() { } - + public MessageRow.from_email(Geary.Email email) { set_from_email(email); } - + // Converts the current row of the Result object into fields. It's vitally important that // the columns specified in requested_fields be present in Result. public MessageRow.from_result(Geary.Email.Field requested_fields, Db.Result results) throws Error { id = results.int64_for("id"); - + // the available fields are an intersection of what's available in the database and // what was requested fields = requested_fields & results.int_for("fields"); - + if (fields.is_all_set(Geary.Email.Field.DATE)) { date = results.string_for("date_field"); date_time_t = (time_t) results.int64_for("date_time_t"); } - + if (fields.is_all_set(Geary.Email.Field.ORIGINATORS)) { from = results.string_for("from_field"); sender = results.string_for("sender"); reply_to = results.string_for("reply_to"); } - + if (fields.is_all_set(Geary.Email.Field.RECEIVERS)) { to = results.string_for("to_field"); cc = results.string_for("cc"); bcc = results.string_for("bcc"); } - + if (fields.is_all_set(Geary.Email.Field.REFERENCES)) { message_id = results.string_for("message_id"); in_reply_to = results.string_for("in_reply_to"); references = results.string_for("reference_ids"); } - + if (fields.is_all_set(Geary.Email.Field.SUBJECT)) subject = results.string_for("subject"); - + if (fields.is_all_set(Geary.Email.Field.HEADER)) header = results.string_buffer_for("header"); - + if (fields.is_all_set(Geary.Email.Field.BODY)) body = results.string_buffer_for("body"); - + if (fields.is_all_set(Geary.Email.Field.PREVIEW)) preview = results.string_for("preview"); - + if (fields.is_all_set(Geary.Email.Field.FLAGS)) email_flags = results.string_for("flags"); - + if (fields.is_all_set(Geary.Email.Field.PROPERTIES)) { internaldate = results.string_for("internaldate"); internaldate_time_t = (time_t) results.int64_for("internaldate_time_t"); rfc822_size = results.int64_for("rfc822_size"); } } - + public Geary.Email to_email(ImapDB.EmailIdentifier id) throws Error { // Important to set something in the Email object if the field bit is set ... for example, // if the caller expects to see a DATE field, that field is set in the Email's bitmask, // even if the Date object is null Geary.Email email = new Geary.Email(id); - - if (fields.is_all_set(Geary.Email.Field.DATE)) - email.set_send_date(!String.is_empty(date) ? new RFC822.Date(date) : null); + + if (fields.is_all_set(Geary.Email.Field.DATE)) { + try { + email.set_send_date( + !String.is_empty(date) ? new RFC822.Date(date) : null + ); + } catch (GLib.Error err) { + debug("Error loading message date from db: %s", err.message); + } + } if (fields.is_all_set(Geary.Email.Field.ORIGINATORS)) { email.set_originators(unflatten_addresses(from), @@ -124,131 +131,131 @@ (in_reply_to != null) ? new RFC822.MessageIDList.from_rfc822_string(in_reply_to) : null, (references != null) ? new RFC822.MessageIDList.from_rfc822_string(references) : null); } - + if (fields.is_all_set(Geary.Email.Field.SUBJECT)) email.set_message_subject(new RFC822.Subject.decode(subject ?? "")); - + if (fields.is_all_set(Geary.Email.Field.HEADER)) email.set_message_header(new RFC822.Header(header ?? Memory.EmptyBuffer.instance)); - + if (fields.is_all_set(Geary.Email.Field.BODY)) email.set_message_body(new RFC822.Text(body ?? Memory.EmptyBuffer.instance)); - + if (fields.is_all_set(Geary.Email.Field.PREVIEW)) email.set_message_preview(new RFC822.PreviewText(new Geary.Memory.StringBuffer(preview ?? ""))); - + if (fields.is_all_set(Geary.Email.Field.FLAGS)) email.set_flags(get_generic_email_flags()); - + if (fields.is_all_set(Geary.Email.Field.PROPERTIES)) { Imap.EmailProperties? properties = get_imap_email_properties(); if (properties != null) email.set_email_properties(properties); } - + return email; } - - + + public Geary.Imap.EmailProperties? get_imap_email_properties() { if (internaldate == null || rfc822_size < 0) return null; - + Imap.InternalDate? constructed = null; try { constructed = Imap.InternalDate.decode(internaldate); } catch (Error err) { debug("Unable to construct internaldate object from \"%s\": %s", internaldate, err.message); - + return null; } - + return new Geary.Imap.EmailProperties(constructed, new RFC822.Size(rfc822_size)); } - + public Geary.EmailFlags? get_generic_email_flags() { return (email_flags != null) ? new Geary.Imap.EmailFlags(Geary.Imap.MessageFlags.deserialize(email_flags)) : null; } - + public void merge_from_remote(Geary.Email email) { set_from_email(email); } - + private void set_from_email(Geary.Email email) { // Although the fields bitmask might indicate various fields are set, they may still be // null if empty - + if (email.fields.is_all_set(Geary.Email.Field.DATE)) { date = (email.date != null) ? email.date.original : null; date_time_t = (email.date != null) ? email.date.to_time_t() : -1; - + fields = fields.set(Geary.Email.Field.DATE); } - + if (email.fields.is_all_set(Geary.Email.Field.ORIGINATORS)) { from = flatten_addresses(email.from); sender = flatten_address(email.sender); reply_to = flatten_addresses(email.reply_to); - + fields = fields.set(Geary.Email.Field.ORIGINATORS); } - + if (email.fields.is_all_set(Geary.Email.Field.RECEIVERS)) { to = flatten_addresses(email.to); cc = flatten_addresses(email.cc); bcc = flatten_addresses(email.bcc); - + fields = fields.set(Geary.Email.Field.RECEIVERS); } - + if (email.fields.is_all_set(Geary.Email.Field.REFERENCES)) { message_id = (email.message_id != null) ? email.message_id.value : null; in_reply_to = (email.in_reply_to != null) ? email.in_reply_to.to_rfc822_string() : null; references = (email.references != null) ? email.references.to_rfc822_string() : null; - + fields = fields.set(Geary.Email.Field.REFERENCES); } - + if (email.fields.is_all_set(Geary.Email.Field.SUBJECT)) { subject = (email.subject != null) ? email.subject.original : null; - + fields = fields.set(Geary.Email.Field.SUBJECT); } - + if (email.fields.is_all_set(Geary.Email.Field.HEADER)) { header = (email.header != null) ? email.header.buffer : null; - + fields = fields.set(Geary.Email.Field.HEADER); } - + if (email.fields.is_all_set(Geary.Email.Field.BODY)) { body = (email.body != null) ? email.body.buffer : null; - + fields = fields.set(Geary.Email.Field.BODY); } - + if (email.fields.is_all_set(Geary.Email.Field.PREVIEW)) { preview = (email.preview != null) ? email.preview.buffer.to_string() : null; - + fields = fields.set(Geary.Email.Field.PREVIEW); } - + if (email.fields.is_all_set(Geary.Email.Field.FLAGS)) { Geary.Imap.EmailFlags? imap_flags = (Geary.Imap.EmailFlags) email.email_flags; email_flags = (imap_flags != null) ? imap_flags.message_flags.serialize() : null; - + fields = fields.set(Geary.Email.Field.FLAGS); } - + if (email.fields.is_all_set(Geary.Email.Field.PROPERTIES)) { Geary.Imap.EmailProperties? imap_properties = (Geary.Imap.EmailProperties) email.properties; internaldate = (imap_properties != null) ? imap_properties.internaldate.serialize() : null; internaldate_time_t = (imap_properties != null) ? imap_properties.internaldate.to_time_t() : -1; rfc822_size = (imap_properties != null) ? imap_properties.rfc822_size.value : -1; - + fields = fields.set(Geary.Email.Field.PROPERTIES); } } @@ -262,27 +269,7 @@ } private static string? flatten_addresses(RFC822.MailboxAddresses? addrs) { - if (addrs == null) - return null; - - switch (addrs.size) { - case 0: - return null; - - case 1: - return addrs[0].to_rfc822_string(); - - default: - StringBuilder builder = new StringBuilder(); - foreach (RFC822.MailboxAddress addr in addrs) { - if (!String.is_empty(builder.str)) - builder.append(", "); - - builder.append(addr.to_rfc822_string()); - } - - return builder.str; - } + return (addrs == null || addrs.size == 0) ? null : addrs.to_rfc822_string(); } private RFC822.MailboxAddress? unflatten_address(string? str) { diff -Nru geary-0.12.4/src/engine/imap-db/outbox/smtp-outbox-email-identifier.vala geary-3.32.0/src/engine/imap-db/outbox/smtp-outbox-email-identifier.vala --- geary-0.12.4/src/engine/imap-db/outbox/smtp-outbox-email-identifier.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-db/outbox/smtp-outbox-email-identifier.vala 1970-01-01 00:00:00.000000000 +0000 @@ -1,24 +0,0 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. - * - * This software is licensed under the GNU Lesser General Public License - * (version 2.1 or later). See the COPYING file in this distribution. - */ - -private class Geary.SmtpOutboxEmailIdentifier : Geary.EmailIdentifier { - public int64 ordering { get; private set; } - - public SmtpOutboxEmailIdentifier(int64 message_id, int64 ordering) { - base ("SmtpOutboxEmailIdentifer:%s".printf(message_id.to_string())); - - this.ordering = ordering; - } - - public override int natural_sort_comparator(Geary.EmailIdentifier o) { - SmtpOutboxEmailIdentifier? other = o as SmtpOutboxEmailIdentifier; - if (other == null) - return 1; - - return (int) (ordering - other.ordering).clamp(-1, 1); - } -} - diff -Nru geary-0.12.4/src/engine/imap-db/outbox/smtp-outbox-email-properties.vala geary-3.32.0/src/engine/imap-db/outbox/smtp-outbox-email-properties.vala --- geary-0.12.4/src/engine/imap-db/outbox/smtp-outbox-email-properties.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-db/outbox/smtp-outbox-email-properties.vala 1970-01-01 00:00:00.000000000 +0000 @@ -1,16 +0,0 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. - * - * This software is licensed under the GNU Lesser General Public License - * (version 2.1 or later). See the COPYING file in this distribution. - */ - -private class Geary.SmtpOutboxEmailProperties : Geary.EmailProperties { - public SmtpOutboxEmailProperties(DateTime date_received, long total_bytes) { - base(date_received, total_bytes); - } - - public override string to_string() { - return "SmtpOutboxProperties"; - } -} - diff -Nru geary-0.12.4/src/engine/imap-db/outbox/smtp-outbox-folder-properties.vala geary-3.32.0/src/engine/imap-db/outbox/smtp-outbox-folder-properties.vala --- geary-0.12.4/src/engine/imap-db/outbox/smtp-outbox-folder-properties.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-db/outbox/smtp-outbox-folder-properties.vala 1970-01-01 00:00:00.000000000 +0000 @@ -1,16 +0,0 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. - * - * This software is licensed under the GNU Lesser General Public License - * (version 2.1 or later). See the COPYING file in this distribution. - */ - -private class Geary.SmtpOutboxFolderProperties : Geary.FolderProperties { - public SmtpOutboxFolderProperties(int total, int unread) { - base (total, unread, Trillian.FALSE, Trillian.FALSE, Trillian.TRUE, true, false, false); - } - - public void set_total(int total) { - this.email_total = total; - } -} - diff -Nru geary-0.12.4/src/engine/imap-db/outbox/smtp-outbox-folder-root.vala geary-3.32.0/src/engine/imap-db/outbox/smtp-outbox-folder-root.vala --- geary-0.12.4/src/engine/imap-db/outbox/smtp-outbox-folder-root.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-db/outbox/smtp-outbox-folder-root.vala 1970-01-01 00:00:00.000000000 +0000 @@ -1,14 +0,0 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. - * - * This software is licensed under the GNU Lesser General Public License - * (version 2.1 or later). See the COPYING file in this distribution. - */ - -private class Geary.SmtpOutboxFolderRoot : Geary.FolderRoot { - public const string MAGIC_BASENAME = "$GearyOutbox$"; - - public SmtpOutboxFolderRoot() { - base(MAGIC_BASENAME, false, false); - } -} - diff -Nru geary-0.12.4/src/engine/imap-db/outbox/smtp-outbox-folder.vala geary-3.32.0/src/engine/imap-db/outbox/smtp-outbox-folder.vala --- geary-0.12.4/src/engine/imap-db/outbox/smtp-outbox-folder.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-db/outbox/smtp-outbox-folder.vala 1970-01-01 00:00:00.000000000 +0000 @@ -1,796 +0,0 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. - * - * This software is licensed under the GNU Lesser General Public License - * (version 2.1 or later). See the COPYING file in this distribution. - */ - -// Special type of folder that runs an asynchronous send queue. Messages are -// saved to the database, then queued up for sending. -// -// The Outbox table is not currently maintained in its own database, so it must piggy-back -// on the ImapDB.Database. SmtpOutboxFolder assumes the database is opened before it's passed in -// to the constructor -- it does not open or close the database itself and will start using it -// immediately. -private class Geary.SmtpOutboxFolder : Geary.AbstractLocalFolder, Geary.FolderSupport.Remove, - Geary.FolderSupport.Create { - private class OutboxRow { - public int64 id; - public int position; - public int64 ordering; - public bool sent; - public Memory.Buffer? message; - public SmtpOutboxEmailIdentifier outbox_id; - - public OutboxRow(int64 id, int position, int64 ordering, bool sent, Memory.Buffer? message, - SmtpOutboxFolderRoot root) { - assert(position >= 1); - - this.id = id; - this.position = position; - this.ordering = ordering; - this.message = message; - this.sent = sent; - - outbox_id = new SmtpOutboxEmailIdentifier(id, ordering); - } - } - - public override Account account { get { return _account; } } - - public override FolderProperties properties { get { return _properties; } } - - private SmtpOutboxFolderRoot _path = new SmtpOutboxFolderRoot(); - public override FolderPath path { - get { - return _path; - } - } - - public override SpecialFolderType special_folder_type { - get { - return Geary.SpecialFolderType.OUTBOX; - } - } - - // Min and max times between attempting to re-send after a connection failure. - private const uint MIN_SEND_RETRY_INTERVAL_SEC = 4; - private const uint MAX_SEND_RETRY_INTERVAL_SEC = 64; - - private ImapDB.Database db; - private weak Account _account; - private Geary.ProgressMonitor sending_monitor; - private Geary.Smtp.ClientSession smtp; - private Nonblocking.Mailbox outbox_queue = new Nonblocking.Mailbox(); - private SmtpOutboxFolderProperties _properties = new SmtpOutboxFolderProperties(0, 0); - private int64 next_ordering = 0; - - public signal void report_problem(Geary.Account.Problem problem, Error? err); - public signal void email_sent(Geary.RFC822.Message rfc822); - - // Requires the Database from the get-go because it runs a background task that access it - // whether open or not - public SmtpOutboxFolder(ImapDB.Database db, Account account, Geary.ProgressMonitor sending_monitor) { - base(); - - this.db = db; - _account = account; - _account.opened.connect(() => { this.fill_outbox_queue.begin(); }); - this.sending_monitor = sending_monitor; - - smtp = new Geary.Smtp.ClientSession(_account.information.get_smtp_endpoint()); - - do_postman_async.begin(); - } - - public override async void find_boundaries_async(Gee.Collection ids, - out Geary.EmailIdentifier? low, out Geary.EmailIdentifier? high, - Cancellable? cancellable = null) throws Error { - SmtpOutboxEmailIdentifier? outbox_low = null; - SmtpOutboxEmailIdentifier? outbox_high = null; - yield db.exec_transaction_async(Db.TransactionType.RO, (cx) => { - foreach (Geary.EmailIdentifier id in ids) { - SmtpOutboxEmailIdentifier? outbox_id = id as SmtpOutboxEmailIdentifier; - if (outbox_id == null) - continue; - - OutboxRow? row = do_fetch_row_by_ordering(cx, outbox_id.ordering, cancellable); - if (row == null) - continue; - - if (outbox_low == null || outbox_id.ordering < outbox_low.ordering) - outbox_low = outbox_id; - if (outbox_high == null || outbox_id.ordering > outbox_high.ordering) - outbox_high = outbox_id; - } - - return Db.TransactionOutcome.DONE; - }, cancellable); - - low = outbox_low; - high = outbox_high; - } - - public async void add_to_containing_folders_async(Gee.Collection ids, - Gee.HashMultiMap map, Cancellable? cancellable) throws Error { - yield db.exec_transaction_async(Db.TransactionType.RO, (cx, cancellable) => { - foreach (Geary.EmailIdentifier id in ids) { - SmtpOutboxEmailIdentifier? outbox_id = id as SmtpOutboxEmailIdentifier; - if (outbox_id == null) - continue; - - OutboxRow? row = do_fetch_row_by_ordering(cx, outbox_id.ordering, cancellable); - if (row == null) - continue; - - map.set(id, path); - } - - return Db.TransactionOutcome.DONE; - }, cancellable); - } - - // Used solely for debugging, hence "(no subject)" not marked for translation - private static string message_subject(RFC822.Message message) { - return (message.subject != null && !String.is_empty(message.subject.to_string())) - ? message.subject.to_string() : "(no subject)"; - } - - - // Fill the send queue with existing mail (if any) - private async void fill_outbox_queue() { - debug("Filling outbox queue"); - try { - Gee.ArrayList list = new Gee.ArrayList(); - yield db.exec_transaction_async(Db.TransactionType.RO, (cx, cancellable) => { - Db.Statement stmt = cx.prepare(""" - SELECT id, ordering, message - FROM SmtpOutboxTable - ORDER BY ordering - """); - - Db.Result results = stmt.exec(cancellable); - int position = 1; - while (!results.finished) { - list.add(new OutboxRow(results.rowid_at(0), position++, results.int64_at(1), - false, results.string_buffer_at(2), _path)); - results.next(cancellable); - } - - return Db.TransactionOutcome.DONE; - }, null); - - if (list.size > 0) { - // set properties now (can't do yield in ctor) - _properties.set_total(list.size); - - debug("Priming outbox postman with %d stored messages", list.size); - foreach (OutboxRow row in list) - outbox_queue.send(row); - } - } catch (Error prime_err) { - warning("Error priming outbox: %s", prime_err.message); - } - } - - - // TODO: Use Cancellable to shut down outbox processor when closing account - private async void do_postman_async() { - debug("Starting outbox postman"); - uint send_retry_seconds = MIN_SEND_RETRY_INTERVAL_SEC; - - // Start the send queue. - for (;;) { - // yield until a message is ready - OutboxRow row; - bool mail_sent; - try { - row = yield outbox_queue.recv_async(); - mail_sent = !yield is_unsent_async(row.ordering, null); - } catch (Error wait_err) { - debug("Outbox postman queue error: %s", wait_err.message); - - break; - } - - // Convert row into RFC822 message suitable for sending or framing - RFC822.Message message; - try { - message = new RFC822.Message.from_buffer(row.message); - } catch (RFC822Error msg_err) { - // TODO: This needs to be reported to the user - debug("Outbox postman message error: %s", msg_err.message); - - continue; - } - - if (!mail_sent) { - // Get SMTP password if we haven't loaded it yet and the account needs credentials. - // If the account needs a password but it's not set or incorrect in the keyring, we'll - // prompt below after getting an AUTHENTICATION_FAILED error. - if (_account.information.smtp_credentials != null && - !_account.information.smtp_credentials.is_complete()) { - try { - yield _account.information.get_passwords_async(ServiceFlag.SMTP); - } catch (Error e) { - debug("SMTP password fetch error: %s", e.message); - } - } - - // Send the message, but only remove from database once sent - bool should_nap = false; - try { - // only try if (a) no TLS issues or (b) user has acknowledged them and says to - // continue - if (_account.information.get_smtp_endpoint().is_trusted_or_never_connected) { - debug("Outbox postman: Sending \"%s\" (ID:%s)...", message_subject(message), - row.outbox_id.to_string()); - yield send_email_async(message, null); - mail_sent = true; - } else { - // user was warned via Geary.Engine signal, need to wait for that to be cleared - // befor sending - outbox_queue.send(row); - should_nap = true; - } - } catch (Error send_err) { - debug("Outbox postman send error, retrying: %s", send_err.message); - - should_nap = true; - - if (send_err is SmtpError.AUTHENTICATION_FAILED) { - bool report = true; - - // Retry immediately (bug 6387) - should_nap = false; - - // At this point we may already have a password in memory -- but it's incorrect. - // Delete the current password, prompt the user for a new one, and try again. - try { - if (yield _account.information.fetch_passwords_async(ServiceFlag.SMTP, true)) - report = false; - } catch (Error e) { - debug("Error prompting for SMTP password: %s", e.message); - } - - if (report) - report_problem(Geary.Account.Problem.SEND_EMAIL_LOGIN_FAILED, send_err); - } else if (send_err is TlsError) { - // up to application to be aware of problem via Geary.Engine, but do nap and - // try later - debug("TLS connection warnings connecting to %s, user must confirm connection to continue", - _account.information.get_smtp_endpoint().to_string()); - } else { - report_problem(Geary.Account.Problem.EMAIL_DELIVERY_FAILURE, send_err); - } - } - - if (should_nap) { - debug("Outbox napping for %u seconds...", send_retry_seconds); - - // Take a brief nap before continuing to allow connection problems to resolve. - yield Geary.Scheduler.sleep_async(send_retry_seconds); - send_retry_seconds = Geary.Numeric.uint_ceiling(send_retry_seconds * 2, MAX_SEND_RETRY_INTERVAL_SEC); - } - - // If we got this far the send was successful, so reset the send retry interval. - send_retry_seconds = MIN_SEND_RETRY_INTERVAL_SEC; - - // Mark as sent, so if there's a problem pushing up to Sent Mail, - // we don't retry sending. - if (mail_sent) { - try { - debug("Outbox postman: Marking %s as sent", row.outbox_id.to_string()); - yield mark_email_as_sent_async(row.outbox_id, null); - } catch (Error e) { - debug("Outbox postman: Unable to mark row as sent: %s", e.message); - } - } - } - - if (!mail_sent) { - // try again later - outbox_queue.send(row); - continue; - } - - bool mail_saved = true; - if (_account.information.allow_save_sent_mail() && _account.information.save_sent_mail) { - mail_saved = false; - try { - debug("Outbox postman: Saving %s to sent mail", row.outbox_id.to_string()); - yield save_sent_mail_async(message, null); - mail_saved = true; - } catch (Error e) { - debug("Outbox postman: Error saving sent mail: %s", e.message); - report_problem(Geary.Account.Problem.SAVE_SENT_MAIL_FAILED, e); - } - } - - if (!mail_saved) { - // try again later - outbox_queue.send(row); - continue; - } - - // Remove from database ... can't use remove_email_async() because this runs even if - // the outbox is closed as a Geary.Folder. - try { - debug("Outbox postman: Deleting row %s", row.outbox_id.to_string()); - Gee.ArrayList list = new Gee.ArrayList(); - list.add(row.outbox_id); - yield internal_remove_email_async(list, null); - } catch (Error e) { - debug("Outbox postman: Unable to delete row: %s", e.message); - } - } - - debug("Exiting outbox postman"); - } - - public override Geary.Folder.OpenState get_open_state() { - return is_open() ? Geary.Folder.OpenState.LOCAL : Geary.Folder.OpenState.CLOSED; - } - - private async int get_email_count_async(Cancellable? cancellable) throws Error { - int count = 0; - yield db.exec_transaction_async(Db.TransactionType.RO, (cx) => { - count = do_get_email_count(cx, cancellable); - - return Db.TransactionOutcome.DONE; - }, cancellable); - - return count; - } - - // create_email_async() requires the Outbox be open according to contract, but enqueuing emails - // for background delivery can happen at any time, so this is the mechanism to do so. - // email_count is the number of emails in the Outbox after enqueueing the message. - public async SmtpOutboxEmailIdentifier enqueue_email_async(Geary.RFC822.Message rfc822, - Cancellable? cancellable) throws Error { - int email_count = 0; - OutboxRow? row = null; - yield db.exec_transaction_async(Db.TransactionType.WR, (cx) => { - int64 ordering = do_get_next_ordering(cx, cancellable); - - // save in database ready for SMTP, but without dot-stuffing - Db.Statement stmt = cx.prepare( - "INSERT INTO SmtpOutboxTable (message, ordering) VALUES (?, ?)"); - stmt.bind_string_buffer(0, rfc822.get_network_buffer(false)); - stmt.bind_int64(1, ordering); - - int64 id = stmt.exec_insert(cancellable); - - stmt = cx.prepare("SELECT message FROM SmtpOutboxTable WHERE id=?"); - stmt.bind_rowid(0, id); - - // This has got to work; Db should throw an exception if the INSERT failed - Db.Result results = stmt.exec(cancellable); - assert(!results.finished); - - Memory.Buffer message = results.string_buffer_at(0); - - int position = do_get_position_by_ordering(cx, ordering, cancellable); - - row = new OutboxRow(id, position, ordering, false, message, _path); - email_count = do_get_email_count(cx, cancellable); - - return Db.TransactionOutcome.COMMIT; - }, cancellable); - - // should have thrown an error if this failed - assert(row != null); - - // update properties - _properties.set_total(yield get_email_count_async(cancellable)); - - // immediately add to outbox queue for delivery - outbox_queue.send(row); - - Gee.List list = new Gee.ArrayList(); - list.add(row.outbox_id); - - notify_email_appended(list); - notify_email_locally_appended(list); - notify_email_count_changed(email_count, CountChangeReason.APPENDED); - - return row.outbox_id; - } - - public virtual async Geary.EmailIdentifier? create_email_async(Geary.RFC822.Message rfc822, EmailFlags? flags, - DateTime? date_received, Geary.EmailIdentifier? id = null, Cancellable? cancellable = null) throws Error { - check_open(); - - return yield enqueue_email_async(rfc822, cancellable); - } - - public override async Gee.List? list_email_by_id_async( - Geary.EmailIdentifier? _initial_id, int count, Geary.Email.Field required_fields, - Geary.Folder.ListFlags flags, Cancellable? cancellable = null) throws Error { - check_open(); - - SmtpOutboxEmailIdentifier? initial_id = _initial_id as SmtpOutboxEmailIdentifier; - if (_initial_id != null && initial_id == null) { - throw new EngineError.BAD_PARAMETERS("EmailIdentifier %s not for Outbox", - initial_id.to_string()); - } - - if (count <= 0) - return null; - - Gee.List? list = null; - yield db.exec_transaction_async(Db.TransactionType.RO, (cx) => { - string dir = flags.is_newest_to_oldest() ? "DESC" : "ASC"; - - Db.Statement stmt; - if (initial_id != null) { - stmt = cx.prepare(""" - SELECT id, ordering, message, sent - FROM SmtpOutboxTable - WHERE ordering >= ? - ORDER BY ordering %s - LIMIT ? - """.printf(dir)); - stmt.bind_int64(0, - flags.is_including_id() ? initial_id.ordering : initial_id.ordering + 1); - stmt.bind_int(1, count); - } else { - stmt = cx.prepare(""" - SELECT id, ordering, message, sent - FROM SmtpOutboxTable - ORDER BY ordering %s - LIMIT ? - """.printf(dir)); - stmt.bind_int(0, count); - } - - Db.Result results = stmt.exec(cancellable); - if (results.finished) - return Db.TransactionOutcome.DONE; - - list = new Gee.ArrayList(); - int position = -1; - do { - int64 ordering = results.int64_at(1); - if (position == -1) { - position = do_get_position_by_ordering(cx, ordering, cancellable); - assert(position >= 1); - } - - list.add(row_to_email(new OutboxRow(results.rowid_at(0), position, ordering, - results.bool_at(3), results.string_buffer_at(2), _path))); - position += flags.is_newest_to_oldest() ? -1 : 1; - assert(position >= 1); - } while (results.next()); - - return Db.TransactionOutcome.DONE; - }, cancellable); - - return list; - } - - public override async Gee.List? list_email_by_sparse_id_async( - Gee.Collection ids, Geary.Email.Field required_fields, - Geary.Folder.ListFlags flags, Cancellable? cancellable = null) throws Error { - check_open(); - - Gee.List list = new Gee.ArrayList(); - yield db.exec_transaction_async(Db.TransactionType.RO, (cx) => { - foreach (Geary.EmailIdentifier id in ids) { - SmtpOutboxEmailIdentifier? outbox_id = id as SmtpOutboxEmailIdentifier; - if (outbox_id == null) - throw new EngineError.BAD_PARAMETERS("%s is not outbox EmailIdentifier", id.to_string()); - - OutboxRow? row = do_fetch_row_by_ordering(cx, outbox_id.ordering, cancellable); - if (row == null) - continue; - - list.add(row_to_email(row)); - } - - return Db.TransactionOutcome.DONE; - }, cancellable); - - return (list.size > 0) ? list : null; - } - - public override async Gee.Map? - list_local_email_fields_async(Gee.Collection ids, - Cancellable? cancellable = null) throws Error { - check_open(); - - Gee.Map map = new Gee.HashMap< - Geary.EmailIdentifier, Geary.Email.Field>(); - yield db.exec_transaction_async(Db.TransactionType.RO, (cx) => { - Db.Statement stmt = cx.prepare( - "SELECT id FROM SmtpOutboxTable WHERE ordering=?"); - foreach (Geary.EmailIdentifier id in ids) { - SmtpOutboxEmailIdentifier? outbox_id = id as SmtpOutboxEmailIdentifier; - if (outbox_id == null) - throw new EngineError.BAD_PARAMETERS("%s is not outbox EmailIdentifier", id.to_string()); - - stmt.reset(Db.ResetScope.CLEAR_BINDINGS); - stmt.bind_int64(0, outbox_id.ordering); - - // merely checking for presence, all emails in outbox have same fields - Db.Result results = stmt.exec(cancellable); - if (!results.finished) - map.set(outbox_id, Geary.Email.Field.ALL); - } - - return Db.TransactionOutcome.DONE; - }, cancellable); - - return (map.size > 0) ? map : null; - } - - public override async Geary.Email fetch_email_async(Geary.EmailIdentifier id, - Geary.Email.Field required_fields, Geary.Folder.ListFlags flags, - Cancellable? cancellable = null) throws Error { - check_open(); - - SmtpOutboxEmailIdentifier? outbox_id = id as SmtpOutboxEmailIdentifier; - if (outbox_id == null) - throw new EngineError.BAD_PARAMETERS("%s is not outbox EmailIdentifier", id.to_string()); - - OutboxRow? row = null; - yield db.exec_transaction_async(Db.TransactionType.RO, (cx) => { - row = do_fetch_row_by_ordering(cx, outbox_id.ordering, cancellable); - - return Db.TransactionOutcome.DONE; - }, cancellable); - - if (row == null) - throw new EngineError.NOT_FOUND("No message with ID %s in outbox", id.to_string()); - - return row_to_email(row); - } - - private async void mark_email_as_sent_async(SmtpOutboxEmailIdentifier outbox_id, - Cancellable? cancellable = null) throws Error { - yield db.exec_transaction_async(Db.TransactionType.WR, (cx) => { - do_mark_email_as_sent(cx, outbox_id, cancellable); - - return Db.TransactionOutcome.COMMIT; - }, cancellable); - - Geary.EmailFlags flags = new Geary.EmailFlags(); - flags.add(Geary.EmailFlags.OUTBOX_SENT); - - Gee.HashMap changed_map - = new Gee.HashMap(); - changed_map.set(outbox_id, flags); - notify_email_flags_changed(changed_map); - } - - public virtual async void remove_email_async(Gee.List email_ids, - Cancellable? cancellable = null) throws Error { - check_open(); - - yield internal_remove_email_async(email_ids, cancellable); - } - - // Like remove_email_async(), but can be called even when the folder isn't open - private async bool internal_remove_email_async(Gee.List email_ids, - Cancellable? cancellable) throws Error { - Gee.List removed = new Gee.ArrayList(); - int final_count = 0; - yield db.exec_transaction_async(Db.TransactionType.WR, (cx) => { - foreach (Geary.EmailIdentifier id in email_ids) { - // ignore anything not belonging to the outbox, but also don't report it as removed - // either - SmtpOutboxEmailIdentifier? outbox_id = id as SmtpOutboxEmailIdentifier; - if (outbox_id == null) - continue; - - // Even though we discard the new value here, this check must - // occur before any insert/delete on the table, to ensure we - // never reuse an ordering value while Geary is running. - do_get_next_ordering(cx, cancellable); - - if (do_remove_email(cx, outbox_id, cancellable)) - removed.add(outbox_id); - } - - final_count = do_get_email_count(cx, cancellable); - - return Db.TransactionOutcome.COMMIT; - }, cancellable); - - if (removed.size == 0) - return false; - - _properties.set_total(final_count); - - notify_email_removed(removed); - notify_email_count_changed(final_count, CountChangeReason.REMOVED); - - return true; - } - - public virtual async void remove_single_email_async(Geary.EmailIdentifier id, - Cancellable? cancellable = null) throws Error { - Gee.List list = new Gee.ArrayList(); - list.add(id); - - yield remove_email_async(list, cancellable); - } - - // Utility for getting an email object back from an outbox row. - private Geary.Email row_to_email(OutboxRow row) throws Error { - RFC822.Message message = new RFC822.Message.from_buffer(row.message); - - Geary.Email email = message.get_email(row.outbox_id); - // TODO: Determine message's total size (header + body) to store in Properties. - email.set_email_properties(new SmtpOutboxEmailProperties(new DateTime.now_local(), -1)); - Geary.EmailFlags flags = new Geary.EmailFlags(); - if (row.sent) - flags.add(Geary.EmailFlags.OUTBOX_SENT); - email.set_flags(flags); - - return email; - } - - private async void send_email_async(Geary.RFC822.Message rfc822, Cancellable? cancellable) - throws Error { - sending_monitor.notify_start(); - - Error? smtp_err = null; - try { - yield smtp.login_async(_account.information.smtp_credentials, cancellable); - } catch (Error login_err) { - debug("SMTP login error: %s", login_err.message); - smtp_err = login_err; - } - - if (smtp_err == null) { - try { - yield smtp.send_email_async(_account.information.primary_mailbox, - rfc822, cancellable); - } catch (Error send_err) { - debug("SMTP send mail error: %s", send_err.message); - smtp_err = send_err; - } - } - - // always logout - try { - yield smtp.logout_async(false, cancellable); - } catch (Error err) { - debug("Unable to disconnect from SMTP server %s: %s", smtp.to_string(), err.message); - } - - sending_monitor.notify_finish(); - - if (smtp_err != null) - throw smtp_err; - - email_sent(rfc822); - } - - private async void save_sent_mail_async(Geary.RFC822.Message rfc822, Cancellable? cancellable) - throws Error { - Geary.FolderSupport.Create? create = (yield _account.get_required_special_folder_async( - Geary.SpecialFolderType.SENT, cancellable)) as Geary.FolderSupport.Create; - if (create == null) - throw new EngineError.NOT_FOUND("Save sent mail enabled, but no writable sent mail folder"); - - bool open = false; - try { - yield create.open_async(Geary.Folder.OpenFlags.FAST_OPEN, cancellable); - open = true; - - yield create.create_email_async(rfc822, null, null, null, cancellable); - - yield create.close_async(cancellable); - open = false; - } catch (Error e) { - if (open) { - try { - yield create.close_async(cancellable); - open = false; - } catch (Error e) { - debug("Error closing folder %s: %s", create.to_string(), e.message); - } - } - throw e; - } - } - - private async bool is_unsent_async(int64 ordering, Cancellable? cancellable) throws Error { - bool exists = false; - yield db.exec_transaction_async(Db.TransactionType.RO, (cx) => { - Db.Statement stmt = cx.prepare( - "SELECT 1 FROM SmtpOutboxTable WHERE ordering=? AND sent = 0"); - stmt.bind_int64(0, ordering); - - exists = !stmt.exec(cancellable).finished; - - return Db.TransactionOutcome.DONE; - }, cancellable); - - return exists; - } - - // - // Transaction helper methods - // - - private int64 do_get_next_ordering(Db.Connection cx, Cancellable? cancellable) throws Error { - lock (next_ordering) { - if (next_ordering == 0) { - Db.Statement stmt = cx.prepare("SELECT COALESCE(MAX(ordering), 0) + 1 FROM SmtpOutboxTable"); - - Db.Result result = stmt.exec(cancellable); - if (!result.finished) - next_ordering = result.int64_at(0); - - assert(next_ordering > 0); - } - - return next_ordering++; - } - } - - private int do_get_email_count(Db.Connection cx, Cancellable? cancellable) throws Error { - Db.Statement stmt = cx.prepare("SELECT COUNT(*) FROM SmtpOutboxTable"); - - Db.Result results = stmt.exec(cancellable); - - return (!results.finished) ? results.int_at(0) : 0; - } - - private int do_get_position_by_ordering(Db.Connection cx, int64 ordering, Cancellable? cancellable) - throws Error { - Db.Statement stmt = cx.prepare( - "SELECT COUNT(*), MAX(ordering) FROM SmtpOutboxTable WHERE ordering <= ? ORDER BY ordering ASC"); - stmt.bind_int64(0, ordering); - - Db.Result results = stmt.exec(cancellable); - if (results.finished) - return -1; - - // without the MAX it's possible to overshoot, so the MAX(ordering) *must* match the argument - if (results.int64_at(1) != ordering) - return -1; - - return results.int_at(0) + 1; - } - - private OutboxRow? do_fetch_row_by_ordering(Db.Connection cx, int64 ordering, Cancellable? cancellable) - throws Error { - Db.Statement stmt = cx.prepare(""" - SELECT id, message, sent - FROM SmtpOutboxTable - WHERE ordering=? - """); - stmt.bind_int64(0, ordering); - - Db.Result results = stmt.exec(cancellable); - if (results.finished) - return null; - - int position = do_get_position_by_ordering(cx, ordering, cancellable); - if (position < 1) - return null; - - return new OutboxRow(results.rowid_at(0), position, ordering, results.bool_at(2), - results.string_buffer_at(1), _path); - } - - private void do_mark_email_as_sent(Db.Connection cx, SmtpOutboxEmailIdentifier id, Cancellable? cancellable) - throws Error { - Db.Statement stmt = cx.prepare("UPDATE SmtpOutboxTable SET sent = 1 WHERE ordering = ?"); - stmt.bind_int64(0, id.ordering); - - stmt.exec(cancellable); - } - - private bool do_remove_email(Db.Connection cx, SmtpOutboxEmailIdentifier id, Cancellable? cancellable) - throws Error { - Db.Statement stmt = cx.prepare("DELETE FROM SmtpOutboxTable WHERE ordering=?"); - stmt.bind_int64(0, id.ordering); - - return stmt.exec_get_modified(cancellable) > 0; - } -} - diff -Nru geary-0.12.4/src/engine/imap-db/search/imap-db-search-email-identifier.vala geary-3.32.0/src/engine/imap-db/search/imap-db-search-email-identifier.vala --- geary-0.12.4/src/engine/imap-db/search/imap-db-search-email-identifier.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-db/search/imap-db-search-email-identifier.vala 2019-03-17 13:39:29.000000000 +0000 @@ -7,33 +7,33 @@ private class Geary.ImapDB.SearchEmailIdentifier : ImapDB.EmailIdentifier, Gee.Comparable { public DateTime? date_received { get; private set; } - + public SearchEmailIdentifier(int64 message_id, DateTime? date_received) { base(message_id, null); - + this.date_received = date_received; } - + public static int compare_descending(SearchEmailIdentifier a, SearchEmailIdentifier b) { return b.compare_to(a); } - + public static Gee.ArrayList array_list_from_results( Gee.Collection? results) { Gee.ArrayList r = new Gee.ArrayList(); - + if (results != null) { foreach (Geary.EmailIdentifier id in results) { SearchEmailIdentifier? search_id = id as SearchEmailIdentifier; - + assert(search_id != null); r.add(search_id); } } - + return r; } - + // Searches for a generic EmailIdentifier in a collection of SearchEmailIdentifiers. public static SearchEmailIdentifier? collection_get_email_identifier( Gee.Collection collection, Geary.EmailIdentifier id) { @@ -43,31 +43,31 @@ } return null; } - + public override int natural_sort_comparator(Geary.EmailIdentifier o) { ImapDB.SearchEmailIdentifier? other = o as ImapDB.SearchEmailIdentifier; if (other == null) return 1; - + return compare_to(other); } - + public virtual int compare_to(SearchEmailIdentifier other) { // if both have date received, compare on that, using stable sort if the same if (date_received != null && other.date_received != null) { int compare = date_received.compare(other.date_received); - + return (compare != 0) ? compare : stable_sort_comparator(other); } - + // if neither have date received, fall back on stable sort if (date_received == null && other.date_received == null) return stable_sort_comparator(other); - + // put identifiers with no date ahead of those with return (date_received == null ? -1 : 1); } - + public override string to_string() { return "[%s/null/%s]".printf(message_id.to_string(), (date_received == null ? "null" : date_received.to_string())); diff -Nru geary-0.12.4/src/engine/imap-db/search/imap-db-search-folder-properties.vala geary-3.32.0/src/engine/imap-db/search/imap-db-search-folder-properties.vala --- geary-0.12.4/src/engine/imap-db/search/imap-db-search-folder-properties.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-db/search/imap-db-search-folder-properties.vala 2019-03-17 13:39:29.000000000 +0000 @@ -8,7 +8,7 @@ public SearchFolderProperties(int total, int unread) { base(total, unread, Trillian.FALSE, Trillian.FALSE, Trillian.TRUE, true, true, false); } - + public void set_total(int total) { this.email_total = total; } diff -Nru geary-0.12.4/src/engine/imap-db/search/imap-db-search-folder-root.vala geary-3.32.0/src/engine/imap-db/search/imap-db-search-folder-root.vala --- geary-0.12.4/src/engine/imap-db/search/imap-db-search-folder-root.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-db/search/imap-db-search-folder-root.vala 1970-01-01 00:00:00.000000000 +0000 @@ -1,14 +0,0 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. - * - * This software is licensed under the GNU Lesser General Public License - * (version 2.1 or later). See the COPYING file in this distribution. - */ - -private class Geary.ImapDB.SearchFolderRoot : Geary.FolderRoot { - public const string MAGIC_BASENAME = "$GearySearchFolder$"; - - public SearchFolderRoot() { - base(MAGIC_BASENAME, false, false); - } -} - diff -Nru geary-0.12.4/src/engine/imap-db/search/imap-db-search-folder.vala geary-3.32.0/src/engine/imap-db/search/imap-db-search-folder.vala --- geary-0.12.4/src/engine/imap-db/search/imap-db-search-folder.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-db/search/imap-db-search-folder.vala 2019-03-17 13:39:29.000000000 +0000 @@ -5,40 +5,51 @@ */ private class Geary.ImapDB.SearchFolder : Geary.SearchFolder, Geary.FolderSupport.Remove { - // Max number of emails that can ever be in the folder. + + + /** Max number of emails that can ever be in the folder. */ public const int MAX_RESULT_EMAILS = 1000; - + + /** The canonical name of the search folder. */ + public const string MAGIC_BASENAME = "$GearySearchFolder$"; + private const Geary.SpecialFolderType[] exclude_types = { Geary.SpecialFolderType.SPAM, Geary.SpecialFolderType.TRASH, Geary.SpecialFolderType.DRAFTS, // Orphan emails (without a folder) are also excluded; see ctor. }; - + + private Gee.HashSet exclude_folders = new Gee.HashSet(); private Gee.TreeSet search_results; private Geary.Nonblocking.Mutex result_mutex = new Geary.Nonblocking.Mutex(); - - public SearchFolder(Geary.Account account) { - base (account, new SearchFolderProperties(0, 0), new SearchFolderRoot()); - + + + public SearchFolder(Geary.Account account, FolderRoot root) { + base( + account, + new SearchFolderProperties(0, 0), + root.get_child(MAGIC_BASENAME, Trillian.TRUE) + ); + account.folders_available_unavailable.connect(on_folders_available_unavailable); account.email_locally_complete.connect(on_email_locally_complete); account.email_removed.connect(on_account_email_removed); - + clear_search_results(); - + // We always want to exclude emails that don't live anywhere from // search results. exclude_orphan_emails(); } - + ~SearchFolder() { account.folders_available_unavailable.disconnect(on_folders_available_unavailable); account.email_locally_complete.disconnect(on_email_locally_complete); account.email_removed.disconnect(on_account_email_removed); } - + private void on_folders_available_unavailable(Gee.Collection? available, Gee.Collection? unavailable) { if (available != null) { @@ -48,42 +59,24 @@ exclude_folder(folder); } } - - public override async void find_boundaries_async(Gee.Collection ids, - out Geary.EmailIdentifier? low, out Geary.EmailIdentifier? high, - Cancellable? cancellable = null) throws Error { - low = null; - high = null; - - // This shouldn't require a result_mutex lock since there's no yield. - Gee.TreeSet in_folder = Geary.traverse(ids) - .cast_object() - .filter(id => id in search_results) - .to_tree_set(); - - if (in_folder.size > 0) { - low = in_folder.first(); - high = in_folder.last(); - } - } - + private async void append_new_email_async(Geary.SearchQuery query, Geary.Folder folder, Gee.Collection ids, Cancellable? cancellable) throws Error { int result_mutex_token = yield result_mutex.claim_async(); - + Error? error = null; try { yield do_search_async(query, ids, null, cancellable); } catch(Error e) { error = e; } - + result_mutex.release(ref result_mutex_token); - + if (error != null) throw error; } - + private void on_append_new_email_complete(Object? source, AsyncResult result) { try { append_new_email_async.end(result); @@ -91,17 +84,17 @@ debug("Error appending new email to search results: %s", e.message); } } - + private void on_email_locally_complete(Geary.Folder folder, Gee.Collection ids) { if (search_query != null) append_new_email_async.begin(search_query, folder, ids, null, on_append_new_email_complete); } - + private async void handle_removed_email_async(Geary.SearchQuery query, Geary.Folder folder, Gee.Collection ids, Cancellable? cancellable) throws Error { int result_mutex_token = yield result_mutex.claim_async(); - + Error? error = null; try { Gee.ArrayList relevant_ids @@ -109,19 +102,19 @@ .map_nonnull( id => ImapDB.SearchEmailIdentifier.collection_get_email_identifier(search_results, id)) .to_array_list(); - + if (relevant_ids.size > 0) yield do_search_async(query, null, relevant_ids, cancellable); } catch(Error e) { error = e; } - + result_mutex.release(ref result_mutex_token); - + if (error != null) throw error; } - + private void on_handle_removed_email_complete(Object? source, AsyncResult result) { try { handle_removed_email_async.end(result); @@ -129,13 +122,13 @@ debug("Error removing removed email from search results: %s", e.message); } } - + private void on_account_email_removed(Geary.Folder folder, Gee.Collection ids) { if (search_query != null) handle_removed_email_async.begin(search_query, folder, ids, null, on_handle_removed_email_complete); } - + /** * Clears the search query and results. */ @@ -144,20 +137,20 @@ clear_search_results(); notify_email_removed(local_results); notify_email_count_changed(0, Geary.Folder.CountChangeReason.REMOVED); - + if (search_query != null) { search_query = null; notify_search_query_changed(null); } } - + /** * Sets the keyword string for this search. */ public override void search(string query, Geary.SearchQuery.Strategy strategy, Cancellable? cancellable = null) { set_search_query_async.begin(query, strategy, cancellable, on_set_search_query_complete); } - + private void on_set_search_query_complete(Object? source, AsyncResult result) { try { set_search_query_async.end(result); @@ -165,29 +158,29 @@ debug("Search error: %s", e.message); } } - + private async void set_search_query_async(string query, Geary.SearchQuery.Strategy strategy, Cancellable? cancellable) throws Error { Geary.SearchQuery search_query = account.open_search(query, strategy); - + int result_mutex_token = yield result_mutex.claim_async(); - + Error? error = null; try { yield do_search_async(search_query, null, null, cancellable); } catch(Error e) { error = e; } - + result_mutex.release(ref result_mutex_token); - + this.search_query = search_query; notify_search_query_changed(search_query); - + if (error != null) throw error; } - + // NOTE: you must call this ONLY after locking result_mutex_token. // If both *_ids parameters are null, the results of this search are // considered to be the full new set. If non-null, the results are @@ -201,7 +194,7 @@ // remove_ids is null, and 3) remove from result set, where just // add_ids is null. We can't add and remove at the same time. assert(add_ids == null || remove_ids == null); - + // TODO: don't limit this to MAX_RESULT_EMAILS. Instead, we could be // smarter about only fetching the search results in list_email_async() // etc., but this leads to some more complications when redoing the @@ -209,12 +202,12 @@ Gee.ArrayList results = ImapDB.SearchEmailIdentifier.array_list_from_results(yield account.local_search_async( query, MAX_RESULT_EMAILS, 0, exclude_folders, add_ids ?? remove_ids, cancellable)); - + Gee.List added = Gee.List.empty(); Gee.List removed = Gee.List.empty(); - + if (remove_ids == null) { added = Geary.traverse(results) .filter(id => !(id in search_results)) @@ -225,17 +218,17 @@ .filter(id => !(id in results)) .to_array_list(); } - + search_results.remove_all(removed); search_results.add_all(added); - + ((ImapDB.SearchFolderProperties) properties).set_total(search_results.size); - + // Note that we probably shouldn't be firing these signals from inside // our mutex lock. Keep an eye on it, and if there's ever a case where // it might cause problems, it shouldn't be too hard to move the // firings outside. - + Geary.Folder.CountChangeReason reason = CountChangeReason.NONE; if (added.size > 0) { // TODO: we'd like to be able to use APPENDED here when applicable, @@ -252,16 +245,16 @@ if (reason != CountChangeReason.NONE) notify_email_count_changed(search_results.size, reason); } - + public override async Gee.List? list_email_by_id_async(Geary.EmailIdentifier? initial_id, int count, Geary.Email.Field required_fields, Geary.Folder.ListFlags flags, Cancellable? cancellable = null) throws Error { if (count <= 0) return null; - + // TODO: as above, this is incomplete and inefficient. int result_mutex_token = yield result_mutex.claim_async(); - + Geary.EmailIdentifier[] ids = new Geary.EmailIdentifier[search_results.size]; int initial_index = 0; int i = 0; @@ -270,10 +263,10 @@ initial_index = i; ids[i++] = id; } - + if (initial_id == null && flags.is_all_set(Geary.Folder.ListFlags.OLDEST_TO_NEWEST)) initial_index = ids.length - 1; - + Gee.List results = new Gee.ArrayList(); Error? fetch_err = null; if (initial_index >= 0) { @@ -282,7 +275,7 @@ if (!flags.is_including_id() && initial_id != null) i += increment; int end = i + (count * increment); - + for (; i >= 0 && i < search_results.size && i != end; i += increment) { try { results.add(yield fetch_email_async(ids[i], required_fields, flags, cancellable)); @@ -291,21 +284,21 @@ // different symantics from fetch if (!(err is EngineError.NOT_FOUND) && !(err is EngineError.INCOMPLETE_MESSAGE)) { fetch_err = err; - + break; } } } } - + result_mutex.release(ref result_mutex_token); - + if (fetch_err != null) throw fetch_err; - + return (results.size == 0 ? null : results); } - + public override async Gee.List? list_email_by_sparse_id_async( Gee.Collection ids, Geary.Email.Field required_fields, Geary.Folder.ListFlags flags, Cancellable? cancellable = null) throws Error { @@ -313,61 +306,58 @@ Gee.List result = new Gee.ArrayList(); foreach(Geary.EmailIdentifier id in ids) result.add(yield fetch_email_async(id, required_fields, flags, cancellable)); - + return (result.size == 0 ? null : result); } - + public override async Gee.Map? list_local_email_fields_async( Gee.Collection ids, Cancellable? cancellable = null) throws Error { // TODO: This method is not currently called, but is required by the interface. Before completing - // this feature, it should either be implemented either here or in AbstractLocalFolder. + // this feature, it should either be implemented either here or in AbstractLocalFolder. error("Search folder does not implement list_local_email_fields_async"); } - + public override async Geary.Email fetch_email_async(Geary.EmailIdentifier id, Geary.Email.Field required_fields, Geary.Folder.ListFlags flags, Cancellable? cancellable = null) throws Error { return yield account.local_fetch_email_async(id, required_fields, cancellable); } - - public virtual async void remove_email_async(Gee.List email_ids, - Cancellable? cancellable = null) throws Error { + + public virtual async void + remove_email_async(Gee.Collection email_ids, + GLib.Cancellable? cancellable = null) + throws GLib.Error { Gee.MultiMap? ids_to_folders = yield account.get_containing_folders_async(email_ids, cancellable); if (ids_to_folders == null) return; - + Gee.MultiMap folders_to_ids = Geary.Collection.reverse_multi_map(ids_to_folders); - + foreach (Geary.FolderPath path in folders_to_ids.get_keys()) { Geary.Folder folder = yield account.fetch_folder_async(path, cancellable); Geary.FolderSupport.Remove? remove = folder as Geary.FolderSupport.Remove; if (remove == null) continue; - + Gee.Collection ids = folders_to_ids.get(path); assert(ids.size > 0); - + debug("Search folder removing %d emails from %s", ids.size, folder.to_string()); - + bool open = false; try { - yield folder.open_async(Geary.Folder.OpenFlags.FAST_OPEN, cancellable); + yield folder.open_async(Geary.Folder.OpenFlags.NONE, cancellable); open = true; - yield remove.remove_email_async( - Geary.Collection.to_array_list(ids), cancellable); - - yield folder.close_async(cancellable); - open = false; - } catch (Error e) { - debug("Error removing messages in %s: %s", folder.to_string(), e.message); - + Geary.Collection.to_array_list(ids), + cancellable + ); + } finally { if (open) { try { - yield folder.close_async(cancellable); - open = false; + yield folder.close_async(); } catch (Error e) { debug("Error closing folder %s: %s", folder.to_string(), e.message); } @@ -375,7 +365,7 @@ } } } - + /** * Given a list of mail IDs, returns a set of casefolded words that match for the current * search query. @@ -384,18 +374,18 @@ Gee.Collection ids, Cancellable? cancellable = null) throws Error { if (search_query == null) return null; - + return yield account.get_search_matches_async(search_query, ids, cancellable); } - + private void exclude_folder(Geary.Folder folder) { exclude_folders.add(folder.path); } - + private void exclude_orphan_emails() { exclude_folders.add(null); } - + private void clear_search_results() { search_results = new Gee.TreeSet( ImapDB.SearchEmailIdentifier.compare_descending); diff -Nru geary-0.12.4/src/engine/imap-db/search/imap-db-search-query.vala geary-3.32.0/src/engine/imap-db/search/imap-db-search-query.vala --- geary-0.12.4/src/engine/imap-db/search/imap-db-search-query.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-db/search/imap-db-search-query.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,4 +1,5 @@ /* Copyright 2016 Software Freedom Conservancy Inc. + * Copyright 2016 Software Freedom Conservancy Inc. * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. @@ -7,42 +8,49 @@ /** * Internal implementation of {@link Geary.SearchQuery}. */ - private class Geary.ImapDB.SearchQuery : Geary.SearchQuery { + /** * Associated {@link ImapDB.Account}. */ public weak ImapDB.Account account { get; private set; } - + /** - * Whether or not the query has been parsed and processed prior to search submission. + * Whether or not the query has been parsed and processed prior to + * search submission. */ public bool parsed { get; set; default = false; } - + /** - * Determined by {@link strategy}. + * Returns whether stemming may be used when exerting the search. + * + * Determined by {@link Geary.SearchQuery.Strategy} passed to the + * constructor. */ public bool allow_stemming { get; private set; } - + /** * Minimum length of the term before stemming is allowed. * * This prevents short words that might be stemmed from being stemmed. * - * Overridden by {@link allow_stemming}. Determined by {@link strategy}. + * Overridden by {@link allow_stemming}. Determined by the {@link + * Geary.SearchQuery.Strategy} passed to the constructor. */ public int min_term_length_for_stemming { get; private set; } - + + /** * Maximum difference in lengths between term and stemmed variant. * - * This prevents long words from being stemmed to much shorter words (which creates - * opportunities for greedy matching). + * This prevents long words from being stemmed to much shorter + * words (which creates opportunities for greedy matching). * - * Overridden by {@link allow_stemming}. Determined by {@link strategy}. + * Overridden by {@link allow_stemming}. Determined by the {@link + * Geary.SearchQuery.Strategy} passed to the constructor. */ public int max_difference_term_stem_lengths { get; private set; } - + /** * Maximum difference in lengths between a matched word and the stemmed variant it matched * against. @@ -50,7 +58,8 @@ * This prevents long words being matched to short stem variants (which creates opportunities * for greedy matching). * - * Overridden by {@link allow_stemming}. Determined by {@link strategy}. + * Overridden by {@link allow_stemming}. Determined by the {@link + * Geary.SearchQuery.Strategy} passed to the constructor. */ public int max_difference_match_stem_lengths { get; private set; } @@ -66,9 +75,9 @@ public SearchQuery(ImapDB.Account account, string query, Geary.SearchQuery.Strategy strategy) { base (query, strategy); - + this.account = account; - + switch (strategy) { case Strategy.EXACT: allow_stemming = false; @@ -76,49 +85,49 @@ max_difference_term_stem_lengths = 0; max_difference_match_stem_lengths = 0; break; - + case Strategy.CONSERVATIVE: allow_stemming = true; min_term_length_for_stemming = 6; max_difference_term_stem_lengths = 2; max_difference_match_stem_lengths = 2; break; - + case Strategy.AGGRESSIVE: allow_stemming = true; min_term_length_for_stemming = 4; max_difference_term_stem_lengths = 4; max_difference_match_stem_lengths = 3; break; - + case Strategy.HORIZON: allow_stemming = true; min_term_length_for_stemming = 0; max_difference_term_stem_lengths = int.MAX; max_difference_match_stem_lengths = int.MAX; break; - + default: assert_not_reached(); } } - + public void add_search_term(string? field, SearchTerm term) { if (!field_map.has_key(field)) field_map.set(field, new Gee.ArrayList()); - + field_map.get(field).add(term); all.add(term); } - + public Gee.Collection get_fields() { return field_map.keys; } - + public Gee.List? get_search_terms(string? field) { return field_map.has_key(field) ? field_map.get(field) : null; } - + public Gee.List? get_all_terms() { return all; } diff -Nru geary-0.12.4/src/engine/imap-db/search/imap-db-search-term.vala geary-3.32.0/src/engine/imap-db/search/imap-db-search-term.vala --- geary-0.12.4/src/engine/imap-db/search/imap-db-search-term.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-db/search/imap-db-search-term.vala 2019-03-17 13:39:29.000000000 +0000 @@ -15,14 +15,14 @@ * For example, punctuation might be removed, but no casefolding has occurred. */ public string original { get; private set; } - + /** * The parsed tokenized search term. * * Casefolding and other normalizing text operations have been performed. */ public string parsed { get; private set; } - + /** * The stemmed search term. * @@ -30,7 +30,7 @@ * term. */ public string? stemmed { get; private set; } - + /** * A list of terms ready for binding to an SQLite statement. * @@ -38,23 +38,23 @@ * are guaranteed not to be null or empty strings. */ public Gee.List sql { get; private set; default = new Gee.ArrayList(); } - + /** * Returns true if the {@link parsed} term is exact-match only (i.e. starts with quotes) and * there is no {@link stemmed} variant. */ public bool is_exact { get { return parsed.has_prefix("\"") && stemmed == null; } } - + public SearchTerm(string original, string parsed, string? stemmed, string? sql_parsed, string? sql_stemmed) { this.original = original; this.parsed = parsed; this.stemmed = stemmed; - + // for now, only two variations: the parsed string and the stemmed; since stem is usually // shorter (and will be first in the OR statement), include it first if (!String.is_empty(sql_stemmed)) sql.add(sql_stemmed); - + if (!String.is_empty(sql_parsed)) sql.add(sql_parsed); } diff -Nru geary-0.12.4/src/engine/imap-engine/gmail/imap-engine-gmail-account.vala geary-3.32.0/src/engine/imap-engine/gmail/imap-engine-gmail-account.vala --- geary-0.12.4/src/engine/imap-engine/gmail/imap-engine-gmail-account.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-engine/gmail/imap-engine-gmail-account.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,7 +1,9 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. + * Copyright 2019 Michael Gratton * * This software is licensed under the GNU Lesser General Public License - * (version 2.1 or later). See the COPYING file in this distribution. + * (version 2.1 or later). See the COPYING file in this distribution. */ private class Geary.ImapEngine.GmailAccount : Geary.ImapEngine.GenericAccount { @@ -14,60 +16,70 @@ Geary.SpecialFolderType.TRASH, }; - public static Geary.Endpoint generate_imap_endpoint() { - return new Geary.Endpoint( - "imap.gmail.com", - Imap.ClientConnection.DEFAULT_PORT_SSL, - Geary.Endpoint.Flags.SSL, - Imap.ClientConnection.RECOMMENDED_TIMEOUT_SEC); - } - - public static Geary.Endpoint generate_smtp_endpoint() { - return new Geary.Endpoint( - "smtp.gmail.com", - Smtp.ClientConnection.DEFAULT_PORT_SSL, - Geary.Endpoint.Flags.SSL, - Smtp.ClientConnection.DEFAULT_TIMEOUT_SEC); - } - - public GmailAccount(string name, Geary.AccountInformation account_information, - Imap.Account remote, ImapDB.Account local) { - base (name, account_information, remote, local); + + public static void setup_account(AccountInformation account) { + account.save_sent = false; + } + + public static void setup_service(ServiceInformation service) { + switch (service.protocol) { + case Protocol.IMAP: + service.host = "imap.gmail.com"; + service.port = Imap.IMAP_TLS_PORT; + service.transport_security = TlsNegotiationMethod.TRANSPORT; + break; + + case Protocol.SMTP: + service.host = "smtp.gmail.com"; + service.port = Smtp.SUBMISSION_TLS_PORT; + service.transport_security = TlsNegotiationMethod.TRANSPORT; + break; + } + } + + + public GmailAccount(Geary.AccountInformation config, + ImapDB.Account local, + Endpoint incoming_remote, + Endpoint outgoing_remote) { + base(config, local, incoming_remote, outgoing_remote); } protected override Geary.SpecialFolderType[] get_supported_special_folders() { return SUPPORTED_SPECIAL_FOLDERS; } - protected override MinimalFolder new_folder(Geary.FolderPath path, Imap.Account remote_account, - ImapDB.Account local_account, ImapDB.Folder local_folder) { - SpecialFolderType special_folder_type; - if (Imap.MailboxSpecifier.folder_path_is_inbox(path)) - special_folder_type = SpecialFolderType.INBOX; - else - special_folder_type = local_folder.get_properties().attrs.get_special_folder_type(); - - switch (special_folder_type) { + protected override MinimalFolder new_folder(ImapDB.Folder local_folder) { + FolderPath path = local_folder.get_path(); + SpecialFolderType type; + if (Imap.MailboxSpecifier.folder_path_is_inbox(path)) { + type = SpecialFolderType.INBOX; + } else { + type = local_folder.get_properties().attrs.get_special_folder_type(); + // There can be only one Inbox + if (type == SpecialFolderType.INBOX) { + type = SpecialFolderType.NONE; + } + } + + switch (type) { case SpecialFolderType.ALL_MAIL: - return new GmailAllMailFolder(this, remote_account, local_account, local_folder, - special_folder_type); - + return new GmailAllMailFolder(this, local_folder, type); + case SpecialFolderType.DRAFTS: - return new GmailDraftsFolder(this, remote_account, local_account, local_folder, - special_folder_type); - + return new GmailDraftsFolder(this, local_folder, type); + case SpecialFolderType.SPAM: case SpecialFolderType.TRASH: - return new GmailSpamTrashFolder(this, remote_account, local_account, local_folder, - special_folder_type); - + return new GmailSpamTrashFolder(this, local_folder, type); + default: - return new GmailFolder(this, remote_account, local_account, local_folder, special_folder_type); + return new GmailFolder(this, local_folder, type); } } protected override SearchFolder new_search_folder() { - return new GmailSearchFolder(this); + return new GmailSearchFolder(this, this.local_folder_root); } -} +} diff -Nru geary-0.12.4/src/engine/imap-engine/gmail/imap-engine-gmail-all-mail-folder.vala geary-3.32.0/src/engine/imap-engine/gmail/imap-engine-gmail-all-mail-folder.vala --- geary-0.12.4/src/engine/imap-engine/gmail/imap-engine-gmail-all-mail-folder.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-engine/gmail/imap-engine-gmail-all-mail-folder.vala 2019-03-17 13:39:29.000000000 +0000 @@ -9,13 +9,16 @@ */ private class Geary.ImapEngine.GmailAllMailFolder : MinimalFolder, FolderSupport.Remove { - public GmailAllMailFolder(GmailAccount account, Imap.Account remote, ImapDB.Account local, - ImapDB.Folder local_folder, SpecialFolderType special_folder_type) { - base (account, remote, local, local_folder, special_folder_type); + public GmailAllMailFolder(GmailAccount account, + ImapDB.Folder local_folder, + SpecialFolderType special_folder_type) { + base(account, local_folder, special_folder_type); } - - public async void remove_email_async(Gee.List email_ids, - Cancellable? cancellable = null) throws Error { + + public async void + remove_email_async(Gee.Collection email_ids, + GLib.Cancellable? cancellable = null) + throws GLib.Error { yield GmailFolder.true_remove_email_async(this, email_ids, cancellable); } } diff -Nru geary-0.12.4/src/engine/imap-engine/gmail/imap-engine-gmail-drafts-folder.vala geary-3.32.0/src/engine/imap-engine/gmail/imap-engine-gmail-drafts-folder.vala --- geary-0.12.4/src/engine/imap-engine/gmail/imap-engine-gmail-drafts-folder.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-engine/gmail/imap-engine-gmail-drafts-folder.vala 2019-03-17 13:39:29.000000000 +0000 @@ -11,19 +11,22 @@ private class Geary.ImapEngine.GmailDraftsFolder : MinimalFolder, FolderSupport.Create, FolderSupport.Remove { - public GmailDraftsFolder(GmailAccount account, Imap.Account remote, ImapDB.Account local, - ImapDB.Folder local_folder, SpecialFolderType special_folder_type) { - base (account, remote, local, local_folder, special_folder_type); + public GmailDraftsFolder(GmailAccount account, + ImapDB.Folder local_folder, + SpecialFolderType special_folder_type) { + base (account, local_folder, special_folder_type); } - + public new async Geary.EmailIdentifier? create_email_async( RFC822.Message rfc822, Geary.EmailFlags? flags, DateTime? date_received, Geary.EmailIdentifier? id, Cancellable? cancellable = null) throws Error { return yield base.create_email_async(rfc822, flags, date_received, id, cancellable); } - - public async void remove_email_async(Gee.List email_ids, - Cancellable? cancellable = null) throws Error { + + public async void remove_email_async( + Gee.Collection email_ids, + GLib.Cancellable? cancellable = null) + throws GLib.Error { yield GmailFolder.true_remove_email_async(this, email_ids, cancellable); } } diff -Nru geary-0.12.4/src/engine/imap-engine/gmail/imap-engine-gmail-folder.vala geary-3.32.0/src/engine/imap-engine/gmail/imap-engine-gmail-folder.vala --- geary-0.12.4/src/engine/imap-engine/gmail/imap-engine-gmail-folder.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-engine/gmail/imap-engine-gmail-folder.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,4 +1,5 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. @@ -6,39 +7,44 @@ private class Geary.ImapEngine.GmailFolder : MinimalFolder, FolderSupport.Archive, FolderSupport.Create, FolderSupport.Remove { - public GmailFolder(GmailAccount account, Imap.Account remote, ImapDB.Account local, - ImapDB.Folder local_folder, SpecialFolderType special_folder_type) { - base (account, remote, local, local_folder, special_folder_type); + public GmailFolder(GmailAccount account, + ImapDB.Folder local_folder, + SpecialFolderType special_folder_type) { + base (account, local_folder, special_folder_type); } - + public new async Geary.EmailIdentifier? create_email_async( RFC822.Message rfc822, Geary.EmailFlags? flags, DateTime? date_received, Geary.EmailIdentifier? id, Cancellable? cancellable = null) throws Error { return yield base.create_email_async(rfc822, flags, date_received, id, cancellable); } - - public async Geary.Revokable? archive_email_async(Gee.List email_ids, - Cancellable? cancellable = null) throws Error { + + public async Geary.Revokable? + archive_email_async(Gee.Collection email_ids, + GLib.Cancellable? cancellable = null) + throws GLib.Error { // Use move_email_async("All Mail") here; Gmail will do the right thing and report // it was copied with the pre-existing All Mail UID (in other words, no actual copy is // performed). This allows for undoing an archive with the same code path as a move. Geary.Folder? all_mail = account.get_special_folder(Geary.SpecialFolderType.ALL_MAIL); if (all_mail != null) return yield move_email_async(email_ids, all_mail.path, cancellable); - + // although this shouldn't happen, fall back on our traditional archive, which is simply // to remove the message from this label message("%s: Unable to perform revokable archive: All Mail not found", to_string()); yield expunge_email_async(email_ids, cancellable); - + return null; } - - public async void remove_email_async(Gee.List email_ids, - Cancellable? cancellable = null) throws Error { + + public async void + remove_email_async(Gee.Collection email_ids, + GLib.Cancellable? cancellable = null) + throws GLib.Error { yield true_remove_email_async(this, email_ids, cancellable); } - + /** * Truly removes an email from Gmail by moving it to the Trash and then deleting it from the * Trash. @@ -48,43 +54,40 @@ * no connection (or the connection dies) there's no record that Geary needs to perform the * final remove when a connection is reestablished. */ - public static async void true_remove_email_async(MinimalFolder folder, - Gee.List email_ids, Cancellable? cancellable) throws Error { + public static async void + true_remove_email_async(MinimalFolder folder, + Gee.Collection email_ids, + GLib.Cancellable? cancellable) + throws GLib.Error { // Get path to Trash folder Geary.Folder? trash = folder.account.get_special_folder(SpecialFolderType.TRASH); if (trash == null) throw new EngineError.NOT_FOUND("%s: Trash folder not found for removal", folder.to_string()); - + // Copy to Trash, collect UIDs (note that copying to Trash is like a move; the copied // messages are removed from all labels) Gee.Set? uids = yield folder.copy_email_uids_async(email_ids, trash.path, cancellable); if (uids == null || uids.size == 0) { debug("%s: Can't true-remove %d emails, no COPYUIDs returned", folder.to_string(), email_ids.size); - + return; } - - // For speed reasons, use a detached Imap.Folder object to delete moved emails; this is a + + // For speed reasons, use a standalone Imap.Folder object to delete moved emails; this is a // separate connection and is not synchronized with the database, but also avoids a full // folder normalization, which can be a heavyweight operation - Imap.Folder imap_trash = yield ((GenericAccount) folder.account).fetch_detached_folder_async( - trash.path, cancellable); - - yield imap_trash.open_async(cancellable); + GenericAccount account = (GenericAccount) folder.account; + Imap.FolderSession imap_trash = yield account.claim_folder_session( + trash.path, cancellable + ); try { yield imap_trash.remove_email_async(Imap.MessageSet.uid_sparse(uids), cancellable); } finally { - try { - // don't use cancellable, need to close this connection no matter what - yield imap_trash.close_async(null); - } catch (Error err) { - // ignored - } + yield account.release_folder_session(imap_trash); } - + debug("%s: Successfully true-removed %d/%d emails", folder.to_string(), uids.size, email_ids.size); } } - diff -Nru geary-0.12.4/src/engine/imap-engine/gmail/imap-engine-gmail-search-folder.vala geary-3.32.0/src/engine/imap-engine/gmail/imap-engine-gmail-search-folder.vala --- geary-0.12.4/src/engine/imap-engine/gmail/imap-engine-gmail-search-folder.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-engine/gmail/imap-engine-gmail-search-folder.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,4 +1,5 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. @@ -8,31 +9,38 @@ * Gmail-specific SearchFolder implementation. */ private class Geary.ImapEngine.GmailSearchFolder : ImapDB.SearchFolder { + private Geary.App.EmailStore email_store; - - public GmailSearchFolder(Geary.Account account) { - base (account); - - email_store = new Geary.App.EmailStore(account); - + + public GmailSearchFolder(Geary.Account account, FolderRoot root) { + base (account, root); + + this.email_store = new Geary.App.EmailStore(account); } - - public override async void remove_email_async(Gee.List email_ids, - Cancellable? cancellable = null) throws Error { + + public override async void + remove_email_async(Gee.Collection email_ids, + GLib.Cancellable? cancellable = null) + throws GLib.Error { Geary.Folder? trash_folder = null; try { - trash_folder = yield account.get_required_special_folder_async(Geary.SpecialFolderType.TRASH, cancellable); + trash_folder = yield account.get_required_special_folder_async( + Geary.SpecialFolderType.TRASH, cancellable + ); } catch (Error e) { - debug("Error looking up trash folder in %s: %s", account.to_string(), e.message); + debug("Error looking up trash folder in %s: %s", + account.to_string(), e.message); } - + if (trash_folder == null) { debug("Can't remove email from search folder because no trash folder was found in %s", - account.to_string()); + account.to_string()); } else { // Copying to trash from one folder is all that's required in Gmail // to fully trash the message. - yield email_store.copy_email_async(email_ids, trash_folder.path, cancellable); + yield this.email_store.copy_email_async( + email_ids, trash_folder.path, cancellable + ); } } } diff -Nru geary-0.12.4/src/engine/imap-engine/gmail/imap-engine-gmail-spam-trash-folder.vala geary-3.32.0/src/engine/imap-engine/gmail/imap-engine-gmail-spam-trash-folder.vala --- geary-0.12.4/src/engine/imap-engine/gmail/imap-engine-gmail-spam-trash-folder.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-engine/gmail/imap-engine-gmail-spam-trash-folder.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,28 +1,35 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ /** - * Gmail's Spam and Trash folders support basic operations and removing messages with a traditional - * IMAP STORE/EXPUNGE operation. + * Gmail's Spam and Trash folders support basic operations and + * removing messages with a traditional IMAP STORE/EXPUNGE operation. */ - -private class Geary.ImapEngine.GmailSpamTrashFolder : MinimalFolder, FolderSupport.Remove, +private class Geary.ImapEngine.GmailSpamTrashFolder : + MinimalFolder, + FolderSupport.Remove, FolderSupport.Empty { - public GmailSpamTrashFolder(GmailAccount account, Imap.Account remote, ImapDB.Account local, - ImapDB.Folder local_folder, SpecialFolderType special_folder_type) { - base (account, remote, local, local_folder, special_folder_type); + + public GmailSpamTrashFolder(GmailAccount account, + ImapDB.Folder local_folder, + SpecialFolderType special_folder_type) { + base(account, local_folder, special_folder_type); } - - public async void remove_email_async(Gee.List email_ids, - Cancellable? cancellable = null) throws Error { + + public async void + remove_email_async(Gee.Collection email_ids, + GLib.Cancellable? cancellable = null) + throws GLib.Error { yield expunge_email_async(email_ids, cancellable); } - - public async void empty_folder_async(Cancellable? cancellable = null) throws Error { + + public async void empty_folder_async(Cancellable? cancellable = null) + throws Error { yield expunge_all_async(cancellable); } -} +} diff -Nru geary-0.12.4/src/engine/imap-engine/imap-engine-account-operation.vala geary-3.32.0/src/engine/imap-engine/imap-engine-account-operation.vala --- geary-0.12.4/src/engine/imap-engine/imap-engine-account-operation.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/src/engine/imap-engine/imap-engine-account-operation.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,169 @@ +/* + * Copyright 2017 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +/** + * A unit of work to be executed by {@link GenericAccount}. + * + * It is important that account operations are idempotent in that they + * can be safely re-executed multiple times, and perform the same task + * each time. This means that in practice instance properties should + * only be used to store state passed to the operation via its + * constructor (e.g. a target folder to be updated) and this state + * should not be modified when the operation is executed (e.g. the + * target folder should not be changed or set to `null` during or + * after execution), any state needed to be maintained when executing + * should be passed as arguments to internal methods (e.g. the list of + * messages to be checked in the target folder should be passed around + * as arguments), and the operation should perform any needed sanity + * checks before proceeding (e.g. check the target folder sill exists + * before updating it). + * + * To queue an operation for execution, pass an instance to {@link + * GenericAccount.queue_operation} after the account has been + * opened. It will added to the accounts queue and executed + * asynchronously when it reaches the front. + * + * Execution of the operation is managed by {@link + * AccountProcessor}. Since the processor will not en-queue duplicate + * operations, implementations of this class may override the {@link + * equal_to} method to specify which instances are considered to be + * duplicates. + */ +public abstract class Geary.ImapEngine.AccountOperation : Geary.BaseObject { + + + /** The account this operation applies to. */ + protected weak Geary.Account account { get; private set; } + + + /** + * Constructs a new account operation. + * + * The passed in `account` will be the account the operation will + * apply to. + */ + protected AccountOperation(Geary.Account account) { + this.account = account; + } + + /** + * Fired by after processing when the operation has completed. + * + * This is fired regardless of if an error was thrown after {@link + * execute} is called. It is always fired after either {@link + * succeeded} or {@link failed} is fired. + * + * Implementations should not fire this themselves, the + * processor will do it for them. + */ + public signal void completed(); + + /** + * Fired by the processor if the operation completes successfully. + * + * This is fired only after {@link execute} was called and did + * not raise an error. + * + * Implementations should not fire this themselves, the + * processor will do it for them. + */ + public signal void succeeded(); + + /** + * Fired by the processor if the operation throws an error. + * + * This is fired only after {@link execute} was called and + * threw an error. The argument is the error that was thrown. + * + * Implementations should not fire this themselves, the + * processor will do it for them. + */ + public signal void failed(Error err); + + + /** + * Called by the processor to execute this operation. + */ + public abstract async void execute(Cancellable cancellable) throws Error; + + /** + * Determines if this operation is equal to another. + * + * By default assumes that the same instance or two different + * instances of the exact same type are equal. Implementations + * should override it if they wish to guard against different + * instances of the same high-level operation from being executed + * twice. + */ + public virtual bool equal_to(AccountOperation op) { + return (op != null && (this == op || this.get_type() == op.get_type())); + } + + /** + * Provides a representation of this operation for debugging. + * + * By default simply returns the name of the class. + */ + public virtual string to_string() { + return this.get_type().name(); + } + +} + + +/** + * An account operation that applies to a specific folder. + * + * By default, instances of this class require that another operation + * applies to the same folder as well as having the same type to be + * considered equal, for the purpose of not en-queuing duplicate + * operations. + */ +public abstract class Geary.ImapEngine.FolderOperation : AccountOperation { + + + /** The folder this operation applies to. */ + protected weak Geary.Folder folder { get; private set; } + + + /** + * Constructs a new folder operation. + * + * The passed in `folder` and `account` will be the objects the + * operation will apply to. + */ + protected FolderOperation(Geary.Account account, Geary.Folder folder) { + base(account); + this.folder = folder; + } + + /** + * Determines if another operation is equal to this. + * + * This method compares both chain's up to {@link + * AccountOperation.equal_to} and if equal, compares the paths of + * both operation's folders to determine if `op` is equal to this + * operation. + */ + public override bool equal_to(AccountOperation op) { + return ( + base.equal_to(op) && + this.folder.path.equal_to(((FolderOperation) op).folder.path) + ); + } + + /** + * Provides a representation of this operation for debugging. + * + * The return value will include its folder's path and the name of + * the class. + */ + public override string to_string() { + return "%s(%s)".printf(base.to_string(), folder.path.to_string()); + } + +} diff -Nru geary-0.12.4/src/engine/imap-engine/imap-engine-account-processor.vala geary-3.32.0/src/engine/imap-engine/imap-engine-account-processor.vala --- geary-0.12.4/src/engine/imap-engine/imap-engine-account-processor.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/src/engine/imap-engine/imap-engine-account-processor.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,116 @@ +/* + * Copyright 2017-2018 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +/** + * Queues and asynchronously executes {@link AccountOperation} instances. + * + * Operations that are equal to any currently executing or currently + * in the queue will not be re-queued. + * + * Errors thrown are reported to the user via the account's + * `problem_report` signal. Normally if an operation throws an error + * it will not be re-queued, however if a network connection error + * occurs the error will be suppressed and it will be re-attempted + * once, to allow for the network dropping out mid-execution. + */ +internal class Geary.ImapEngine.AccountProcessor : Geary.BaseObject { + + + // Retry ops after network failures at least once before giving up + private const int MAX_NETWORK_ERRORS = 1; + + + private static bool op_equal(AccountOperation a, AccountOperation b) { + return a.equal_to(b); + } + + /** Determines an operation is currently being executed. */ + public bool is_executing { get { return this.current_op != null; } } + + /** Returns the number of operations currently waiting in the queue. */ + public uint waiting { get { return this.queue.size; } } + + + /** Fired when an error occurs processing an operation. */ + public signal void operation_error(AccountOperation op, Error error); + + + private string id; + + private Nonblocking.Queue queue = + new Nonblocking.Queue.fifo(op_equal); + + private AccountOperation? current_op = null; + + private Cancellable cancellable = new Cancellable(); + + + public AccountProcessor(string id) { + this.id = id; + this.queue.allow_duplicates = false; + this.run.begin(); + } + + public void enqueue(AccountOperation op) { + if (this.current_op == null || !op.equal_to(this.current_op)) { + this.queue.send(op); + } + } + + public void stop() { + this.cancellable.cancel(); + this.queue.clear(); + } + + private async void run() { + while (!this.cancellable.is_cancelled()) { + AccountOperation? op = null; + try { + op = yield this.queue.receive(this.cancellable); + } catch (Error err) { + // we've been cancelled, so bail out + return; + } + + if (op != null) { + debug("%s: Executing operation: %s", id, op.to_string()); + this.current_op = op; + + Error? op_error = null; + int network_errors = 0; + while (op_error == null) { + try { + yield op.execute(this.cancellable); + op.succeeded(); + break; + } catch (ImapError err) { + if (err is ImapError.NOT_CONNECTED && + ++network_errors <= MAX_NETWORK_ERRORS) { + debug( + "Retrying operation due to network error: %s", + err.message + ); + } else { + op_error = err; + } + } catch (Error err) { + op_error = err; + } + } + + if (op_error != null) { + op.failed(op_error); + operation_error(op, op_error); + } + op.completed(); + + this.current_op = null; + } + } + } + +} diff -Nru geary-0.12.4/src/engine/imap-engine/imap-engine-account-synchronizer.vala geary-3.32.0/src/engine/imap-engine/imap-engine-account-synchronizer.vala --- geary-0.12.4/src/engine/imap-engine/imap-engine-account-synchronizer.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-engine/imap-engine-account-synchronizer.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,453 +1,373 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. + * Copyright 2017 Michael Gratton * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ private class Geary.ImapEngine.AccountSynchronizer : Geary.BaseObject { - private const int FETCH_DATE_RECEIVED_CHUNK_COUNT = 25; - private const int SYNC_DELAY_SEC = 10; - private const int RETRY_SYNC_DELAY_SEC = 60; - - public GenericAccount account { get; private set; } - - private Nonblocking.Mailbox bg_queue = new Nonblocking.Mailbox(bg_queue_comparator); - private Gee.HashSet made_available = new Gee.HashSet(); - private MinimalFolder? current_folder = null; - private Cancellable? bg_cancellable = null; - private Nonblocking.Semaphore stopped = new Nonblocking.Semaphore(); - private Gee.HashSet unavailable_paths = new Gee.HashSet(); - private DateTime max_epoch = new DateTime(new TimeZone.local(), 2000, 1, 1, 0, 0, 0.0); - + + + private weak GenericAccount account { get; private set; } + + private TimeoutManager prefetch_timer; + private DateTime max_epoch = new DateTime( + new TimeZone.local(), 2000, 1, 1, 0, 0, 0.0 + ); + + public AccountSynchronizer(GenericAccount account) { this.account = account; - - // don't allow duplicates because it's possible for a Folder to change several times - // before finally opened and synchronized, which we only want to do once - bg_queue.allow_duplicates = false; - bg_queue.requeue_duplicate = false; - - account.opened.connect(on_account_opened); - account.closed.connect(on_account_closed); - account.folders_available_unavailable.connect(on_folders_available_unavailable); - account.folders_contents_altered.connect(on_folders_contents_altered); - account.email_sent.connect(on_email_sent); - } - - ~AccountSynchronizer() { - account.opened.disconnect(on_account_opened); - account.closed.disconnect(on_account_closed); - account.folders_available_unavailable.disconnect(on_folders_available_unavailable); - account.folders_contents_altered.disconnect(on_folders_contents_altered); - account.email_sent.disconnect(on_email_sent); - } - - public async void stop_async() { - bg_cancellable.cancel(); - - try { - yield stopped.wait_async(); - } catch (Error err) { - debug("Error waiting for AccountSynchronizer background task for %s to complete: %s", - account.to_string(), err.message); + this.prefetch_timer = new TimeoutManager.seconds( + 10, do_prefetch_changed + ); + + this.account.information.notify["prefetch-period-days"].connect(on_account_prefetch_changed); + this.account.folders_available_unavailable.connect(on_folders_updated); + this.account.folders_contents_altered.connect(on_folders_contents_altered); + } + + private void send_all(Gee.Collection folders, bool became_available) { + foreach (Folder folder in folders) { + // Only sync folders that: + // 1. Can actually be opened (i.e. are selectable) + // 2. Are remote backed + // and 3. if considering a folder not because it's + // contents changed (i.e. didn't just become available, + // only sync if closed, otherwise he folder will keep + // track of changes as they occur + // + // All this implies the folder must be a MinimalFolder and + // we do require that for syncing at the moment anyway, + // but keep the tests in for that one glorious day where + // we can just use a generic folder. + MinimalFolder? imap_folder = folder as MinimalFolder; + if (imap_folder != null && + folder.properties.is_openable.is_possible() && + !folder.properties.is_local_only && + !folder.properties.is_virtual && + (became_available || + imap_folder.get_open_state() == Folder.OpenState.CLOSED)) { + + AccountOperation op = became_available + ? new CheckFolderSync( + this.account, imap_folder, this.max_epoch + ) + : new RefreshFolderSync(this.account, imap_folder); + + try { + this.account.queue_operation(op); + } catch (Error err) { + debug("Failed to queue sync operation: %s", err.message); + } + } } } - - private void on_account_opened() { - if (stopped.is_passed()) - return; - - account.information.notify["prefetch-period-days"].connect(on_account_prefetch_changed); - - bg_queue.allow_duplicates = false; - bg_queue.requeue_duplicate = false; - bg_cancellable = new Cancellable(); - unavailable_paths.clear(); - - // immediately start processing folders as they are announced as available - process_queue_async.begin(); - } - - private void on_account_closed() { - account.information.notify["prefetch-period-days"].disconnect(on_account_prefetch_changed); - - bg_cancellable.cancel(); - bg_queue.clear(); - unavailable_paths.clear(); + + private void do_prefetch_changed() { + // treat as an availability check (i.e. as if the account had + // just opened) because just because this value has changed + // doesn't mean the contents in the folders have changed + if (this.account.is_open()) { + try { + send_all(this.account.list_folders(), true); + } catch (Error err) { + debug("Failed to list account folders for sync: %s", err.message); + } + } } - + private void on_account_prefetch_changed() { - try { - // treat as an availability check (i.e. as if the account had just opened) because - // just because this value has changed doesn't mean the contents in the folders - // have changed - delayed_send_all(account.list_folders(), true, SYNC_DELAY_SEC); - } catch (Error err) { - debug("Unable to schedule re-sync for %s due to prefetch time changing: %s", - account.to_string(), err.message); - } + this.prefetch_timer.start(); } - - private void on_folders_available_unavailable(Gee.Collection? available, - Gee.Collection? unavailable) { - if (stopped.is_passed()) - return; - + + private void on_folders_updated(Gee.Collection? available, + Gee.Collection? unavailable) { if (available != null) { - foreach (Folder folder in available) - unavailable_paths.remove(folder.path); - - delayed_send_all(available, true, SYNC_DELAY_SEC); - } - - if (unavailable != null) { - foreach (Folder folder in unavailable) - unavailable_paths.add(folder.path); - - revoke_all(unavailable); + send_all(available, true); } } - + private void on_folders_contents_altered(Gee.Collection altered) { - delayed_send_all(altered, false, SYNC_DELAY_SEC); + send_all(altered, false); } - - private void on_email_sent() { - try { - Folder? sent_mail = account.get_special_folder(SpecialFolderType.SENT); - if (sent_mail != null) - send_all(iterate(sent_mail).to_array_list(), false); - } catch (Error err) { - debug("Unable to retrieve Sent Mail from %s: %s", account.to_string(), err.message); - } - } - - private void delayed_send_all(Gee.Collection folders, bool reason_available, int sec) { - Timeout.add_seconds(sec, () => { - // remove any unavailable folders - Gee.ArrayList trimmed_folders = new Gee.ArrayList(); - foreach (Folder folder in folders) { - if (!unavailable_paths.contains(folder.path)) - trimmed_folders.add(folder); - } - - send_all(trimmed_folders, reason_available); - - return false; - }); - } - - private void send_all(Gee.Collection folders, bool reason_available) { - foreach (Folder folder in folders) { - MinimalFolder? imap_folder = folder as MinimalFolder; - - // only deal with ImapEngine.MinimalFolder - if (imap_folder == null) - continue; - - // if considering folder not because it's available (i.e. because its contents changed), - // and the folder is open, don't process it; MinimalFolder will take care of changes as - // they occur, in order to remain synchronized - if (!reason_available && - imap_folder.get_open_state() != Folder.OpenState.CLOSED) { - continue; - } - // don't requeue the currently processing folder - if (imap_folder != current_folder) - bg_queue.send(imap_folder); - - // If adding because now available, make sure it's flagged as such, since there's an - // additional check for available folders ... if not, remove from the map so it's - // not treated as such, in case both of these come in back-to-back - if (reason_available && imap_folder != current_folder) - made_available.add(imap_folder); - else - made_available.remove(imap_folder); - } - } - - private void revoke_all(Gee.Collection folders) { - foreach (Folder folder in folders) { - MinimalFolder? generic_folder = folder as MinimalFolder; - if (generic_folder != null) { - bg_queue.revoke(generic_folder); - made_available.remove(generic_folder); - } - } +} + +/** + * Synchronises a folder after its contents have changed. + * + * This synchronisation process simply opens the remote folder, waits + * for it to finish opening for normalisation and pre-fetching to + * complete, then closes it again. + */ +private class Geary.ImapEngine.RefreshFolderSync : FolderOperation { + + internal RefreshFolderSync(GenericAccount account, + MinimalFolder folder) { + base(account, folder); } - - // This is used to ensure that certain special folders get prioritized over others, so folders - // important to the user (i.e. Inbox) go first while less-used folders (Spam) are fetched last - private static int bg_queue_comparator(MinimalFolder a, MinimalFolder b) { - if (a == b) - return 0; - - int cmp = score_folder(a) - score_folder(b); - if (cmp != 0) - return cmp; - - // sort by path to stabilize the sort - return a.path.compare_to(b.path); - } - - // Lower the score, the higher the importance. - // - // Some explanation is due here. It may seem odd to place TRASH, SENT, and DRAFTS so high, but - // there's a method to the madness. In particular, because Geary can produce a lot of drafts - // during composition, it's important to synchronize with Trash so discarded drafts don't wind - // up included in conversations until, eventually, the Trash is synchronized. (Recall that - // Spam and Trash are blacklisted in conversations and searching.) Since Drafts is open while - // writing them, it's not vital to keep it absolutely high, but Trash is usually not open, - // so it should be. - // - // All Mail is important, but synchronizing with it can be hard on the system because of the - // sheer amount of messages, and so it's placed lower to put it off until the more active - // folders are finished. - private static int score_folder(Folder a) { - switch (a.special_folder_type) { - case SpecialFolderType.INBOX: - return -70; - - case SpecialFolderType.TRASH: - return -60; - - case SpecialFolderType.SENT: - return -50; - - case SpecialFolderType.DRAFTS: - return -40; - - case SpecialFolderType.FLAGGED: - return -30; - - case SpecialFolderType.IMPORTANT: - return -20; - - case SpecialFolderType.ALL_MAIL: - case SpecialFolderType.ARCHIVE: - return -10; - - case SpecialFolderType.SPAM: - return 10; - - default: - return 0; + + public override async void execute(GLib.Cancellable cancellable) + throws GLib.Error { + bool was_opened = false; + MinimalFolder minimal = (MinimalFolder) this.folder; + try { + // Open the folder on no delay since there's no point just + // waiting around for it. Then claim a remote session so + // we know that a remote connection has been made and the + // folder has had a chance to normalise itself. + yield minimal.open_async(Folder.OpenFlags.NO_DELAY, cancellable); + yield minimal.claim_remote_session(cancellable); + was_opened = true; + debug("Synchronising %s", minimal.to_string()); + yield sync_folder(cancellable); + } catch (GLib.IOError.CANCELLED err) { + // All good + } catch (EngineError.ALREADY_CLOSED err) { + // Failed to open the folder, which could be because the + // network went away, or because the remote folder went + // away. Either way don't bother reporting it. + debug( + "Folder failed to open %s: %s", + minimal.to_string(), + err.message + ); + } catch (GLib.Error err) { + this.account.report_problem( + new ServiceProblemReport( + ProblemType.GENERIC_ERROR, + this.account.information, + this.account.information.outgoing, + err + ) + ); } - } - - private async void process_queue_async() { - for (;;) { - MinimalFolder folder; + + if (was_opened) { try { - folder = yield bg_queue.recv_async(bg_cancellable); + // don't pass in the Cancellable; really need this + // to complete in all cases + if (yield this.folder.close_async(null)) { + // The folder was actually closing, so wait + // for it here to completely close so that its + // session has a chance to exit IMAP Selected + // state when released, allowing the next sync + // op to reuse the same session. Here we + // definitely want to use the cancellable so + // the wait can be interrupted. + yield this.folder.wait_for_close_async(cancellable); + } } catch (Error err) { - if (!(err is IOError.CANCELLED)) - debug("Failed to receive next folder for background sync: %s", err.message); - - break; + debug( + "%s: Error closing folder %s: %s", + this.account.to_string(), + this.folder.to_string(), + err.message + ); } - - // mark as current folder to prevent requeues while processing - current_folder = folder; - - // generate the current epoch for synchronization (could cache this value, obviously, but - // doesn't seem like this biggest win in this class) - DateTime epoch; - if (account.information.prefetch_period_days >= 0) { - epoch = new DateTime.now_local(); - epoch = epoch.add_days(0 - account.information.prefetch_period_days); - } else { - epoch = max_epoch; - } - - bool ok = yield process_folder_async(folder, made_available.remove(folder), epoch); - - // clear current folder in every event - current_folder = null; - - if (!ok) - break; } - - // clear queue of any remaining folders so references aren't held - bg_queue.clear(); - - // same with made_available table - made_available.clear(); - - // flag as stopped for any waiting tasks - stopped.blind_notify(); - } - - // Returns false if IOError.CANCELLED received - private async bool process_folder_async(MinimalFolder folder, bool availability_check, DateTime epoch) { - // get oldest local email and its time, as well as number of messages in local store - DateTime? oldest_local = null; - Geary.EmailIdentifier? oldest_local_id = null; - int local_count = 0; + } + + protected virtual async void sync_folder(Cancellable cancellable) + throws Error { + yield wait_for_prefetcher(cancellable); + } + + protected async void wait_for_prefetcher(Cancellable cancellable) + throws Error { + MinimalFolder minimal = (MinimalFolder) this.folder; try { - Gee.List? list = yield folder.local_folder.list_email_by_id_async(null, 1, - Email.Field.PROPERTIES, ImapDB.Folder.ListFlags.NONE | ImapDB.Folder.ListFlags.OLDEST_TO_NEWEST, - bg_cancellable); - if (list != null && list.size > 0) { - oldest_local = list[0].properties.date_received; - oldest_local_id = list[0].id; - } - - local_count = yield folder.local_folder.get_email_count_async(ImapDB.Folder.ListFlags.NONE, - bg_cancellable); + yield minimal.email_prefetcher.active_sem.wait_async(cancellable); } catch (Error err) { - debug("Unable to fetch oldest local email for %s: %s", folder.to_string(), err.message); + Logging.debug( + Logging.Flag.PERIODIC, + "Error waiting for email prefetcher to complete %s: %s", + folder.to_string(), + err.message + ); } - - if (availability_check) { - // Compare the oldest mail in the local store and see if it is before the epoch; if so, no - // need to synchronize simply because this Folder is available; wait for its contents to - // change instead - if (oldest_local != null) { - if (oldest_local.compare(epoch) < 0) { - // Oldest local email before epoch, don't sync from network - return true; - } else if (folder.properties.email_total == local_count) { - // Local earliest email is after epoch, but there's nothing before it - return true; - } else { - debug("Oldest local email in %s not old enough (%s vs. %s), email_total=%d vs. local_count=%d, synchronizing...", - folder.to_string(), oldest_local.to_string(), epoch.to_string(), - folder.properties.email_total, local_count); - } - } else if (folder.properties.email_total == 0) { - // no local messages, no remote messages -- this is as good as having everything up - // to the epoch - return true; - } else { - debug("No oldest message found for %s, synchronizing...", folder.to_string()); - } + } + +} + +/** + * Synchronises a folder after first checking if it needs to be sync'ed. + * + * This synchronisation process performs the same work as its base + * class, but also ensures enough mail has been fetched to satisfy the + * account's prefetch period, by checking the earliest mail in the + * folder and if later than the maximum prefetch epoch, expands the + * folder's vector until it does. + */ +private class Geary.ImapEngine.CheckFolderSync : RefreshFolderSync { + + + private DateTime sync_max_epoch; + + + internal CheckFolderSync(GenericAccount account, + MinimalFolder folder, + DateTime sync_max_epoch) { + base(account, folder); + this.sync_max_epoch = sync_max_epoch; + } + + protected override async void sync_folder(Cancellable cancellable) + throws Error { + // Determine the earliest date we should be synchronising back to + DateTime prefetch_max_epoch; + if (this.account.information.prefetch_period_days >= 0) { + prefetch_max_epoch = new DateTime.now_local(); + prefetch_max_epoch = prefetch_max_epoch.add_days( + 0 - account.information.prefetch_period_days + ); } else { - debug("Folder %s changed, synchronizing...", folder.to_string()); - } - - try { - yield folder.open_async(Folder.OpenFlags.FAST_OPEN, bg_cancellable); - } catch (Error err) { - // don't need to close folder; if either calls throws an error, the folder is not open - if (err is IOError.CANCELLED) - return false; - - debug("Unable to open %s: %s", folder.to_string(), err.message); - - // retry later - delayed_send_all(iterate(folder).to_array_list(), availability_check, RETRY_SYNC_DELAY_SEC); - - return true; + prefetch_max_epoch = this.sync_max_epoch; } - - bool not_cancelled = true; - try { - yield sync_folder_async(folder, epoch, oldest_local, oldest_local_id); - } catch (Error err) { - if (err is IOError.CANCELLED) { - not_cancelled = false; - } else { - debug("Error background syncing folder %s: %s", folder.to_string(), err.message); - - // retry later - delayed_send_all(iterate(folder).to_array_list(), availability_check, RETRY_SYNC_DELAY_SEC); - } - - // fallthrough and close + + // get oldest local email and its time, as well as number + // of messages in local store + ImapDB.Folder local_folder = ((MinimalFolder) this.folder).local_folder; + Gee.List? list = yield local_folder.list_email_by_id_async( + null, + 1, + Email.Field.PROPERTIES, + ImapDB.Folder.ListFlags.OLDEST_TO_NEWEST, + cancellable + ); + + Geary.Email? current_oldest = null; + if (list != null && list.size > 0) { + current_oldest = list[0]; } - - try { - // don't pass Cancellable; really need this to complete in all cases - yield folder.close_async(); - } catch (Error err) { - debug("Error closing %s: %s", folder.to_string(), err.message); + + DateTime? oldest_date = (current_oldest != null) + ? current_oldest.properties.date_received : null; + if (oldest_date == null) { + oldest_date = new DateTime.now_local(); } - - return not_cancelled; - } - - private async void sync_folder_async(MinimalFolder folder, DateTime epoch, DateTime? oldest_local, - Geary.EmailIdentifier? oldest_local_id) throws Error { - debug("Background sync'ing %s", folder.to_string()); - - // wait for the folder to be fully opened to be sure we have all the most current - // information - yield folder.wait_for_open_async(bg_cancellable); - - // only perform vector expansion if oldest isn't old enough - if (oldest_local == null || oldest_local.compare(epoch) > 0) { - // go back three months at a time to the epoch, performing a little vector expansion at a - // time rather than all at once (which will stall the replay queue) - DateTime current_epoch = (oldest_local != null) ? oldest_local : new DateTime.now_local(); - do { - // look for complete synchronization of UIDs (i.e. complete vector normalization) - // no need to keep searching once this happens - int local_count = yield folder.local_folder.get_email_count_async(ImapDB.Folder.ListFlags.NONE, - bg_cancellable); - if (local_count >= folder.properties.email_total) { - debug("Total vector normalization for %s: %d/%d emails", folder.to_string(), local_count, - folder.properties.email_total); - - break; - } - - current_epoch = current_epoch.add_months(-1); - - // if past max_epoch, then just pull in everything and be done with it - if (current_epoch.compare(max_epoch) < 0) { - debug("Background sync reached max epoch of %s, fetching all mail from %s (already got %d of %d emails)", - max_epoch.to_string(), folder.to_string(), local_count, folder.properties.email_total); - yield folder.list_email_by_id_async(null, 1, Geary.Email.Field.NONE, - Geary.Folder.ListFlags.OLDEST_TO_NEWEST, bg_cancellable); - } else { - // don't go past proscribed epoch - if (current_epoch.compare(epoch) < 0) - current_epoch = epoch; - - debug("Background sync'ing %s to %s (already got %d of %d emails)", - folder.to_string(), current_epoch.to_string(), local_count, folder.properties.email_total); - Geary.EmailIdentifier? earliest_span_id = yield folder.find_earliest_email_async(current_epoch, - oldest_local_id, bg_cancellable); - if (earliest_span_id == null && current_epoch.compare(epoch) <= 0) { - debug("Unable to locate epoch messages on remote folder %s%s, fetching one past oldest...", - folder.to_string(), - (oldest_local_id != null) ? " earlier than oldest local" : ""); - - // if there's nothing between the oldest local and the epoch, that means the - // mail just prior to our local oldest is oldest than the epoch; rather than - // continually thrashing looking for something that's just out of reach, add it - // to the folder and be done with it ... note that this even works if oldest_local_id - // is null, as that means the local folder is empty and so we should at least - // pull the first one to get a marker of age - yield folder.list_email_by_id_async(oldest_local_id, 1, Geary.Email.Field.NONE, - Geary.Folder.ListFlags.NONE, bg_cancellable); - } else if (earliest_span_id != null) { - // use earliest email from that span for the next round - oldest_local_id = earliest_span_id; + DateTime? next_epoch = oldest_date; + while (next_epoch.compare(prefetch_max_epoch) > 0) { + int local_count = yield local_folder.get_email_count_async( + ImapDB.Folder.ListFlags.NONE, cancellable + ); + + next_epoch = next_epoch.add_months(-3); + if (next_epoch.compare(prefetch_max_epoch) < 0) { + next_epoch = prefetch_max_epoch; + } + + debug( + "Synchronising %s to: %s", + folder.to_string(), + next_epoch.to_string() + ); + + if (local_count < this.folder.properties.email_total && + next_epoch.compare(prefetch_max_epoch) >= 0) { + if (next_epoch.compare(this.sync_max_epoch) > 0) { + current_oldest = yield expand_vector( + next_epoch, current_oldest, cancellable + ); + if (current_oldest == null && + next_epoch.equal(prefetch_max_epoch)) { + yield expand_to_previous( + current_oldest, cancellable + ); + // Exit next time around + next_epoch = prefetch_max_epoch.add_days(-1); } + } else { + yield expand_complete_vector(cancellable); + // Exit next time around + next_epoch = prefetch_max_epoch.add_days(-1); } - - yield Scheduler.sleep_ms_async(200); - } while (current_epoch.compare(epoch) > 0); - } else { - debug("No expansion necessary for %s, oldest local (%s) is before epoch (%s)", - folder.to_string(), oldest_local.to_string(), epoch.to_string()); - } - - // always give email prefetcher time to finish its work - debug("Waiting for email prefetcher to complete %s...", folder.to_string()); - try { - yield folder.email_prefetcher.active_sem.wait_async(bg_cancellable); - } catch (Error err) { - debug("Error waiting for email prefetcher to complete %s: %s", folder.to_string(), - err.message); + } else { + // Exit next time around + next_epoch = prefetch_max_epoch.add_days(-1); + } + + // let the prefetcher catch up + yield wait_for_prefetcher(cancellable); } - - debug("Done background sync'ing %s", folder.to_string()); } -} + private async Geary.Email? expand_vector(DateTime next_epoch, + Geary.Email? current_oldest, + Cancellable cancellable) + throws Error { + // Expand the vector up until the given epoch + Logging.debug( + Logging.Flag.PERIODIC, + "Synchronizing %s:%s to %s", + this.account.to_string(), + this.folder.to_string(), + next_epoch.to_string() + ); + return yield ((MinimalFolder) this.folder).find_earliest_email_async( + next_epoch, + (current_oldest != null) ? current_oldest.id : null, + cancellable + ); + } + + private async void expand_to_previous(Geary.Email? current_oldest, + Cancellable cancellable) + throws Error { + // there's nothing between the oldest local and the epoch, + // which means the mail just prior to our local oldest is + // oldest than the epoch; rather than continually thrashing + // looking for something that's just out of reach, add it to + // the folder and be done with it ... note that this even + // works if id is null, as that means the local folder is + // empty and so we should at least pull the first one to get a + // marker of age + Geary.EmailIdentifier? id = + (current_oldest != null) ? current_oldest.id : null; + Logging.debug( + Logging.Flag.PERIODIC, + "Unable to locate epoch messages on remote folder %s:%s%s, fetching one past oldest...", + this.account.to_string(), + this.folder.to_string(), + (id != null) ? " earlier than oldest local" : "" + ); + yield this.folder.list_email_by_id_async( + id, + 1, + Geary.Email.Field.NONE, + Geary.Folder.ListFlags.NONE, + cancellable + ); + } + + private async void expand_complete_vector(Cancellable cancellable) + throws Error { + // past max_epoch, so just pull in everything and be done with it + Logging.debug( + Logging.Flag.PERIODIC, + "Synchronization reached max epoch of %s, fetching all mail from %s:%s", + this.sync_max_epoch.to_string(), + this.account.to_string(), + this.folder.to_string() + ); + + // Per the contract for list_email_by_id_async, we need to + // specify int.MAX count and ensure that + // ListFlags.OLDEST_TO_NEWEST is *not* specified to get all + // messages listed. + // + // XXX This is expensive, but should only usually happen once + // per folder - at the end of a full sync. + yield this.folder.list_email_by_id_async( + null, + int.MAX, + Geary.Email.Field.NONE, + Geary.Folder.ListFlags.NONE, + cancellable + ); + } + +} diff -Nru geary-0.12.4/src/engine/imap-engine/imap-engine-batch-operations.vala geary-3.32.0/src/engine/imap-engine/imap-engine-batch-operations.vala --- geary-0.12.4/src/engine/imap-engine/imap-engine-batch-operations.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-engine/imap-engine-batch-operations.vala 1970-01-01 00:00:00.000000000 +0000 @@ -1,79 +0,0 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. - * - * This software is licensed under the GNU Lesser General Public License - * (version 2.1 or later). See the COPYING file in this distribution. - */ - -/** - * CreateLocalEmailOperation is a common Geary.NonblockingBatchOperation that can be used with - * Geary.NonblockingBatch. - * - * Note that this operation always returns null. The result of Geary.Sqlite.Folder.create_email_async() - * is stored in the created property. - */ - -private class Geary.ImapEngine.CreateLocalEmailOperation : Geary.Nonblocking.BatchOperation { - public ImapDB.Folder folder { get; private set; } - public Gee.Collection emails { get; private set; } - public Geary.Email.Field required_fields { get; private set; } - // Returns the result of ImapDB.Folder.create_or_merge_email_async() - // Will be non-null after successful execution - public Gee.Map? created { get; private set; default = null; } - // Map of the created/merged email with one fulfilling all required_fields - // Will be non-null after successful execution - public Gee.Map? merged { get; private set; default = null; } - - public CreateLocalEmailOperation(ImapDB.Folder folder, Gee.Collection emails, - Geary.Email.Field required_fields) { - this.folder = folder; - this.emails = emails; - this.required_fields = required_fields; - } - - public override async Object? execute_async(Cancellable? cancellable) throws Error { - created = yield folder.create_or_merge_email_async(emails, cancellable); - - merged = new Gee.HashMap(); - foreach (Geary.Email email in emails) { - if (email.fields.fulfills(required_fields)) { - merged.set(email, email); - } else { - try { - Geary.Email merged_email = yield folder.fetch_email_async( - (ImapDB.EmailIdentifier) email.id, required_fields, - ImapDB.Folder.ListFlags.NONE, cancellable); - merged.set(email, merged_email); - } catch (Error err) { - debug("Unable to fetch merged email for %s: %s", email.id.to_string(), err.message); - } - } - } - - return null; - } -} - -/** - * RemoveLocalEmailOperation is a common NonblockingBatchOperation that can be used with - * NonblockingBatch. - * - * Note that this operation always returns null, as Geary.Sqlite.Folder.remove_email_async() has no - * returned value. - */ - -private class Geary.ImapEngine.RemoveLocalEmailOperation : Geary.Nonblocking.BatchOperation { - public ImapDB.Folder folder { get; private set; } - public Gee.Collection email_ids { get; private set; } - - public RemoveLocalEmailOperation(ImapDB.Folder folder, Gee.Collection email_ids) { - this.folder = folder; - this.email_ids = email_ids; - } - - public override async Object? execute_async(Cancellable? cancellable) throws Error { - yield folder.detach_multiple_emails_async((Gee.Collection) email_ids, cancellable); - - return null; - } -} - diff -Nru geary-0.12.4/src/engine/imap-engine/imap-engine-contact-store.vala geary-3.32.0/src/engine/imap-engine/imap-engine-contact-store.vala --- geary-0.12.4/src/engine/imap-engine/imap-engine-contact-store.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-engine/imap-engine-contact-store.vala 2019-03-17 13:39:29.000000000 +0000 @@ -3,26 +3,26 @@ * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ - + internal class Geary.ImapEngine.ContactStore : Geary.ContactStore { private weak ImapDB.Account account; - + internal ContactStore(ImapDB.Account account) { this.account = account; } - + public override async void mark_contacts_async(Gee.Collection contacts, ContactFlags? to_add, ContactFlags? to_remove) throws Error{ foreach (Contact contact in contacts) { if (contact.contact_flags == null) contact.contact_flags = new Geary.ContactFlags(); - + if (to_add != null) contact.contact_flags.add_all(to_add); - + if (to_remove != null) contact.contact_flags.remove_all(to_remove); - + yield account.update_contact_flags_async(contact, null); } } diff -Nru geary-0.12.4/src/engine/imap-engine/imap-engine-email-flag-watcher.vala geary-3.32.0/src/engine/imap-engine/imap-engine-email-flag-watcher.vala --- geary-0.12.4/src/engine/imap-engine/imap-engine-email-flag-watcher.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-engine/imap-engine-email-flag-watcher.vala 1970-01-01 00:00:00.000000000 +0000 @@ -1,158 +0,0 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. - * - * This software is licensed under the GNU Lesser General Public License - * (version 2.1 or later). See the COPYING file in this distribution. - */ - -/** - * Monitor an open {@link ImapEngine.GenericFolder} for changes to {@link EmailFlags}. - * - * Because IMAP doesn't offer a standard mechanism for server notifications of email flags changing, - * have to poll for changes. This class performs this task by monitoring the supplied - * folder for its "opened" and "closed" signals and periodically polling for changes. - * - * Note that EmailFlagWatcher doesn't maintain a reference to the Geary.Folder it's watching. - */ - -private class Geary.ImapEngine.EmailFlagWatcher : BaseObject { - public const int DEFAULT_FLAG_WATCH_SEC = 3 * 60; - - private const int PULL_CHUNK_COUNT = 100; - - private unowned Geary.Folder folder; - private int seconds; - private uint watch_id = 0; - private bool in_flag_watch = false; - private Cancellable cancellable = new Cancellable(); - - public signal void email_flags_changed(Gee.Map changed); - - public EmailFlagWatcher(Geary.Folder folder, int seconds = DEFAULT_FLAG_WATCH_SEC) { - assert(seconds > 0); - - this.folder = folder; - this.seconds = seconds; - - folder.opened.connect(on_opened); - folder.closed.connect(on_closed); - } - - ~EmailFlagWatcher() { - if (watch_id != 0) - message("Warning: Geary.FlagWatcher destroyed before folder closed"); - - folder.opened.disconnect(on_opened); - folder.closed.disconnect(on_closed); - } - - private void on_opened(Geary.Folder.OpenState open_state) { - if (open_state != Geary.Folder.OpenState.BOTH) - return; - - cancellable = new Cancellable(); - if (watch_id == 0) - watch_id = Idle.add(on_opened_update_flags); - } - - private void on_closed(Geary.Folder.CloseReason close_reason) { - if (close_reason != Geary.Folder.CloseReason.FOLDER_CLOSED) - return; - - cancellable.cancel(); - - if (watch_id != 0) - Source.remove(watch_id); - - watch_id = 0; - } - - private bool on_opened_update_flags() { - flag_watch_async.begin(); - - // this callback was immediately called due to open, schedule next ones for here on out - // on a timer - watch_id = Timeout.add_seconds(seconds, on_flag_watch); - - return false; - } - - private bool on_flag_watch() { - flag_watch_async.begin(); - - watch_id = 0; - - // return false and reschedule when finished - return false; - } - - private async void flag_watch_async() { - // prevent reentrancy and don't run if folder is closed - if (!in_flag_watch && !cancellable.is_cancelled()) { - in_flag_watch = true; - try { - yield do_flag_watch_async(); - } catch (Error err) { - if (!(err is IOError.CANCELLED)) - debug("%s flag watch error: %s", folder.to_string(), err.message); - else - debug("%s flag watch cancelled", folder.to_string()); - } - in_flag_watch = false; - } - - // reschedule if not already, and if not cancelled (folder is closed) - if (watch_id == 0 && !cancellable.is_cancelled()) - watch_id = Timeout.add_seconds(seconds, on_flag_watch); - } - - private async void do_flag_watch_async() throws Error { - Logging.debug(Logging.Flag.PERIODIC, "do_flag_watch_async begin %s", folder.to_string()); - - Geary.EmailIdentifier? lowest = null; - int total = 0; - for (;;) { - Gee.List? list_local = yield folder.list_email_by_id_async(lowest, - PULL_CHUNK_COUNT, Geary.Email.Field.FLAGS, Geary.Folder.ListFlags.LOCAL_ONLY, cancellable); - if (list_local == null || list_local.is_empty) - break; - - total += list_local.size; - - // find the lowest for the next iteration - lowest = Geary.EmailIdentifier.sort_emails(list_local).first().id; - - // Get all email identifiers in the local folder mapped to their EmailFlags - Gee.HashMap local_map = new Gee.HashMap< - Geary.EmailIdentifier, Geary.EmailFlags>(); - foreach (Geary.Email e in list_local) - local_map.set(e.id, e.email_flags); - - // Fetch e-mail from folder using force update, which will cause the cache to be bypassed - // and the latest to be gotten from the server (updating the cache in the process) - Logging.debug(Logging.Flag.PERIODIC, "do_flag_watch_async: fetching %d flags for %s", - local_map.keys.size, folder.to_string()); - Gee.List? list_remote = yield folder.list_email_by_sparse_id_async(local_map.keys, - Email.Field.FLAGS, Geary.Folder.ListFlags.FORCE_UPDATE, cancellable); - if (list_remote == null || list_remote.is_empty) - break; - - // Build map of emails that have changed. - Gee.HashMap changed_map = - new Gee.HashMap(); - foreach (Geary.Email e in list_remote) { - if (!local_map.has_key(e.id)) - continue; - - if (!local_map.get(e.id).equal_to(e.email_flags)) - changed_map.set(e.id, e.email_flags); - } - - if (!cancellable.is_cancelled() && changed_map.size > 0) - email_flags_changed(changed_map); - } - - Logging.debug(Logging.Flag.PERIODIC, "do_flag_watch_async: completed %s, %d messages updates", - folder.to_string(), total); - } -} - diff -Nru geary-0.12.4/src/engine/imap-engine/imap-engine-email-prefetcher.vala geary-3.32.0/src/engine/imap-engine/imap-engine-email-prefetcher.vala --- geary-0.12.4/src/engine/imap-engine/imap-engine-email-prefetcher.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-engine/imap-engine-email-prefetcher.vala 2019-03-17 13:39:29.000000000 +0000 @@ -13,135 +13,126 @@ */ private class Geary.ImapEngine.EmailPrefetcher : Geary.BaseObject { public const int PREFETCH_DELAY_SEC = 1; - + private const Geary.Email.Field PREFETCH_FIELDS = Geary.Email.Field.ALL; private const int PREFETCH_CHUNK_BYTES = 32 * 1024; - + public Nonblocking.CountingSemaphore active_sem { get; private set; default = new Nonblocking.CountingSemaphore(null); } - - private unowned ImapEngine.MinimalFolder folder; - private int start_delay_sec; + + private weak ImapEngine.MinimalFolder folder; private Nonblocking.Mutex mutex = new Nonblocking.Mutex(); private Gee.TreeSet prefetch_emails = new Gee.TreeSet( Email.compare_recv_date_descending); - private uint schedule_id = 0; - private Cancellable cancellable = new Cancellable(); - + private TimeoutManager prefetch_timer; + private Cancellable? cancellable = null; + + public EmailPrefetcher(ImapEngine.MinimalFolder folder, int start_delay_sec = PREFETCH_DELAY_SEC) { - assert(start_delay_sec > 0); - this.folder = folder; - this.start_delay_sec = start_delay_sec; - - folder.opened.connect(on_opened); - folder.closed.connect(on_closed); - folder.email_appended.connect(on_local_expansion); - folder.email_inserted.connect(on_local_expansion); - } - - ~EmailPrefetcher() { - if (schedule_id != 0) - message("Warning: Geary.EmailPrefetcher destroyed before folder closed"); - - folder.opened.disconnect(on_opened); - folder.closed.disconnect(on_closed); - folder.email_appended.disconnect(on_local_expansion); - folder.email_inserted.disconnect(on_local_expansion); - } - - private void on_opened(Geary.Folder.OpenState open_state) { - if (open_state != Geary.Folder.OpenState.BOTH) - return; - - cancellable = new Cancellable(); - + + if (start_delay_sec <= 0) { + start_delay_sec = PREFETCH_DELAY_SEC; + } + + this.prefetch_timer = new TimeoutManager.seconds( + start_delay_sec, () => { do_prefetch_async.begin(); } + ); + } + + public void open() { + this.cancellable = new Cancellable(); + + this.folder.email_locally_appended.connect(on_local_expansion); + this.folder.email_locally_inserted.connect(on_local_expansion); + // acquire here since .begin() only schedules for later - active_sem.acquire(); - do_prepare_all_local_async.begin(); + this.active_sem.acquire(); + this.do_prepare_all_local_async.begin(); } - - private void on_closed(Geary.Folder.CloseReason close_reason) { - // cancel for any reason ... this will be called multiple times, but the following operations - // can be executed any number of times and still get the desired results - cancellable.cancel(); - - if (schedule_id != 0) { - Source.remove(schedule_id); - schedule_id = 0; - - // since an acquire was done when scheduled, need to notify when cancelled - active_sem.blind_notify(); + + public void close() { + if (this.prefetch_timer.is_running) { + this.prefetch_timer.reset(); + // since an acquire was done when scheduled, need to + // notify when cancelled + this.active_sem.blind_notify(); } + + this.folder.email_locally_appended.disconnect(on_local_expansion); + this.folder.email_locally_inserted.disconnect(on_local_expansion); + this.cancellable = null; } - + private void on_local_expansion(Gee.Collection ids) { // it's possible to be notified of an append prior to remote open; don't prefetch until // that occurs - if (folder.get_open_state() != Geary.Folder.OpenState.BOTH) + if (folder.get_open_state() != Geary.Folder.OpenState.REMOTE) return; - + // acquire here since .begin() only schedules for later active_sem.acquire(); do_prepare_new_async.begin(ids); } - + // emails should include PROPERTIES private void schedule_prefetch(Gee.Collection? emails) { - if (emails == null || emails.size == 0) - return; - - debug("%s: scheduling %d emails for prefetching", folder.to_string(), emails.size); - - prefetch_emails.add_all(emails); - - // only increment active state if not rescheduling - if (schedule_id != 0) - Source.remove(schedule_id); - else - active_sem.acquire(); - - schedule_id = Timeout.add_seconds(start_delay_sec, () => { - schedule_id = 0; - do_prefetch_async.begin(); - - return false; - }); + if (emails != null && emails.size > 0) { + this.prefetch_emails.add_all(emails); + + // only increment active state if not rescheduling + if (!this.prefetch_timer.is_running) { + this.active_sem.acquire(); + } + + this.prefetch_timer.start(); + } } - + private async void do_prepare_all_local_async() { Gee.List? list = null; try { - debug("Listing all emails needing prefetching in %s...", folder.to_string()); - list = yield folder.local_folder.list_email_by_id_async(null, int.MAX, - Geary.Email.Field.PROPERTIES, ImapDB.Folder.ListFlags.ONLY_INCOMPLETE, cancellable); - debug("Listed all emails needing prefetching in %s", folder.to_string()); - } catch (Error err) { - debug("Error while list local emails for %s: %s", folder.to_string(), err.message); + list = yield this.folder.local_folder.list_email_by_id_async( + null, int.MAX, + Geary.Email.Field.PROPERTIES, + ImapDB.Folder.ListFlags.ONLY_INCOMPLETE, + this.cancellable + ); + } catch (GLib.IOError.CANCELLED err) { + // all good + } catch (GLib.Error err) { + warning("%s: Error listing email on open: %s", + folder.to_string(), err.message); } - + + debug("%s: Scheduling %d messages on open for prefetching", + this.folder.to_string(), list != null ? list.size : 0); schedule_prefetch(list); - - active_sem.blind_notify(); + this.active_sem.blind_notify(); } - + private async void do_prepare_new_async(Gee.Collection ids) { Gee.List? list = null; try { - debug("Listing new %d emails needing prefetching in %s...", ids.size, folder.to_string()); - list = yield folder.local_folder.list_email_by_sparse_id_async( + list = yield this.folder.local_folder.list_email_by_sparse_id_async( (Gee.Collection) ids, - Geary.Email.Field.PROPERTIES, ImapDB.Folder.ListFlags.ONLY_INCOMPLETE, cancellable); - debug("Listed new emails needing prefetching in %s", folder.to_string()); - } catch (Error err) { - debug("Error while list local emails for %s: %s", folder.to_string(), err.message); + Geary.Email.Field.PROPERTIES, + ImapDB.Folder.ListFlags.ONLY_INCOMPLETE, + this.cancellable + ); + } catch (GLib.IOError.CANCELLED err) { + // all good + } catch (GLib.Error err) { + warning("%s: Error listing email on open: %s", + folder.to_string(), err.message); } - + + debug("%s: Scheduling %d new emails for prefetching", + this.folder.to_string(), list != null ? list.size : 0); schedule_prefetch(list); - - active_sem.blind_notify(); + this.active_sem.blind_notify(); } - + private async void do_prefetch_async() { int token = Nonblocking.Mutex.INVALID_TOKEN; try { @@ -151,10 +142,10 @@ if (!(err is IOError.CANCELLED)) debug("Error while prefetching emails for %s: %s", folder.to_string(), err.message); } - + // this round is done active_sem.blind_notify(); - + if (token != Nonblocking.Mutex.INVALID_TOKEN) { try { mutex.release(ref token); @@ -163,15 +154,15 @@ } } } - + private async void do_prefetch_batch_async() throws Error { // snarf up all requested Emails for this round Gee.TreeSet emails = prefetch_emails; prefetch_emails = new Gee.TreeSet(Email.compare_recv_date_descending); - + if (emails.size == 0) return; - + debug("do_prefetch_batch_async %s start_total=%d", folder.to_string(), emails.size); // Big TODO: The engine needs to be able to synthesize @@ -186,52 +177,52 @@ Gee.HashSet ids = new Gee.HashSet(); int64 chunk_bytes = 0; int count = 0; - + while (emails.size > 0) { // dequeue emails by date received, newest to oldest Geary.Email email = emails.first(); - + // only add to this chunk if the email is smaller than one chunk or there's nothing // in this chunk so far ... this means an oversized email will be pulled all by itself // in the next round if there's stuff already ahead of it if (email.properties.total_bytes < PREFETCH_CHUNK_BYTES || ids.size == 0) { bool removed = emails.remove(email); assert(removed); - + ids.add(email.id); chunk_bytes += email.properties.total_bytes; count++; - + // if not enough stuff is in this chunk, keep going if (chunk_bytes < PREFETCH_CHUNK_BYTES) continue; } - + bool keep_going = yield do_prefetch_email_async(ids, chunk_bytes); - + // clear out for next chunk ... this also prevents the final prefetch_async() from trying // to pull twice if !keep_going ids.clear(); chunk_bytes = 0; - + if (!keep_going) break; - + yield Scheduler.sleep_ms_async(200); } - + // get any remaining if (ids.size > 0) yield do_prefetch_email_async(ids, chunk_bytes); - + debug("finished do_prefetch_batch_async %s end_total=%d", folder.to_string(), count); } - + // Return true to continue, false to stop prefetching (cancelled or not open) private async bool do_prefetch_email_async(Gee.Collection ids, int64 chunk_bytes) { debug("do_prefetch_email_async: %s prefetching %d emails (%sb)", folder.to_string(), ids.size, chunk_bytes.to_string()); - + try { yield folder.list_email_by_sparse_id_async(ids, PREFETCH_FIELDS, Folder.ListFlags.NONE, cancellable); @@ -245,7 +236,7 @@ return false; } } - + return true; } } diff -Nru geary-0.12.4/src/engine/imap-engine/imap-engine-generic-account.vala geary-3.32.0/src/engine/imap-engine/imap-engine-generic-account.vala --- geary-0.12.4/src/engine/imap-engine/imap-engine-generic-account.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-engine/imap-engine-generic-account.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,4 +1,6 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. + * Copyright 2017-2019 Michael Gratton . * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. @@ -6,8 +8,15 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.Account { - private const int REFRESH_FOLDER_LIST_SEC = 2 * 60; - private const int REFRESH_UNSEEN_SEC = 1; + + /** Default IMAP session pool size. */ + private const int IMAP_MIN_POOL_SIZE = 2; + + // This is high since it's an expensive operation, and we'll go + // looking changes caused by local operations as they happen, so + // we don't need to double check. + private const int REFRESH_FOLDER_LIST_SEC = 15 * 60; + private const Geary.SpecialFolderType[] SUPPORTED_SPECIAL_FOLDERS = { Geary.SpecialFolderType.DRAFTS, Geary.SpecialFolderType.SENT, @@ -16,125 +25,114 @@ Geary.SpecialFolderType.ARCHIVE, }; - private static Geary.FolderPath? outbox_path = null; - private static Geary.FolderPath? search_path = null; + /** Service for incoming IMAP connections. */ + public Imap.ClientService imap { get; private set; } + + /** Service for outgoing SMTP connections. */ + public Smtp.ClientService smtp { get; private set; } + + /** Local database for the account. */ + public ImapDB.Account local { get; private set; } + + /** + * The root path for all local folders. + * + * No folder exists for this path, it merely exists to provide a + * common root for the paths of all local folders. + */ + protected FolderRoot local_folder_root = new Geary.FolderRoot(true); - private Imap.Account remote; - private ImapDB.Account local; private bool open = false; + private Cancellable? open_cancellable = null; + private Nonblocking.Semaphore? remote_ready_lock = null; + + private Geary.SearchFolder? search_folder { get; private set; default = null; } + private Gee.HashMap folder_map = new Gee.HashMap< FolderPath, MinimalFolder>(); private Gee.HashMap local_only = new Gee.HashMap(); - private Gee.HashMap refresh_unseen_timeout_ids - = new Gee.HashMap(); - private Gee.HashSet in_refresh_unseen = new Gee.HashSet(); - private uint refresh_folder_timeout_id = 0; - private bool in_refresh_enumerate = false; - private Cancellable refresh_cancellable = new Cancellable(); - private bool awaiting_credentials = false; + + private AccountProcessor? processor; + private AccountSynchronizer sync; + private TimeoutManager refresh_folder_timer; private Gee.Map> special_search_names = new Gee.HashMap>(); - public GenericAccount(string name, Geary.AccountInformation information, - Imap.Account remote, ImapDB.Account local) { - base (name, information); - - this.remote = remote; + + protected GenericAccount(AccountInformation config, + ImapDB.Account local, + Endpoint incoming_remote, + Endpoint outgoing_remote) { + Imap.ClientService imap = new Imap.ClientService( + config, + config.incoming, + incoming_remote + ); + Smtp.ClientService smtp = new Smtp.ClientService( + config, + config.outgoing, + outgoing_remote + ); + + base(config, imap, smtp); + this.local = local; - - this.remote.login_failed.connect(on_login_failed); - this.local.email_sent.connect(on_email_sent); - - search_upgrade_monitor = local.search_index_monitor; - db_upgrade_monitor = local.upgrade_monitor; - db_vacuum_monitor = local.vacuum_monitor; - opening_monitor = new Geary.ReentrantProgressMonitor(Geary.ProgressType.ACTIVITY); - sending_monitor = local.sending_monitor; - - if (outbox_path == null) { - outbox_path = new SmtpOutboxFolderRoot(); - } - - if (search_path == null) { - search_path = new ImapDB.SearchFolderRoot(); - } + this.local.contacts_loaded.connect(() => { contacts_loaded(); }); + + imap.min_pool_size = IMAP_MIN_POOL_SIZE; + imap.notify["current-status"].connect( + on_imap_status_notify + ); + this.imap = imap; + + smtp.outbox = new Outbox.Folder(this, local_folder_root, local); + smtp.email_sent.connect(on_email_sent); + smtp.report_problem.connect(notify_report_problem); + this.smtp = smtp; + + this.sync = new AccountSynchronizer(this); + + this.refresh_folder_timer = new TimeoutManager.seconds( + REFRESH_FOLDER_LIST_SEC, + () => { this.update_remote_folders(); } + ); + + this.opening_monitor = new ReentrantProgressMonitor(Geary.ProgressType.ACTIVITY); + this.sending_monitor = this.smtp.sending_monitor; + this.search_upgrade_monitor = local.search_index_monitor; + this.db_upgrade_monitor = local.upgrade_monitor; + this.db_vacuum_monitor = local.vacuum_monitor; compile_special_search_names(); } - protected override void notify_folders_available_unavailable(Gee.List? available, - Gee.List? unavailable) { - base.notify_folders_available_unavailable(available, unavailable); - if (available != null) { - foreach (Geary.Folder folder in available) { - folder.email_appended.connect(notify_email_appended); - folder.email_inserted.connect(notify_email_inserted); - folder.email_removed.connect(notify_email_removed); - folder.email_locally_complete.connect(notify_email_locally_complete); - folder.email_flags_changed.connect(notify_email_flags_changed); - } - } - if (unavailable != null) { - foreach (Geary.Folder folder in unavailable) { - folder.email_appended.disconnect(notify_email_appended); - folder.email_inserted.disconnect(notify_email_inserted); - folder.email_removed.disconnect(notify_email_removed); - folder.email_locally_complete.disconnect(notify_email_locally_complete); - folder.email_flags_changed.disconnect(notify_email_flags_changed); - } - } - } - - protected override void notify_email_appended(Geary.Folder folder, Gee.Collection ids) { - base.notify_email_appended(folder, ids); - reschedule_unseen_update(folder); - } - - protected override void notify_email_inserted(Geary.Folder folder, Gee.Collection ids) { - base.notify_email_inserted(folder, ids); - reschedule_unseen_update(folder); - } - - protected override void notify_email_removed(Geary.Folder folder, Gee.Collection ids) { - base.notify_email_removed(folder, ids); - reschedule_unseen_update(folder); - } - - protected override void notify_email_flags_changed(Geary.Folder folder, - Gee.Map flag_map) { - base.notify_email_flags_changed(folder, flag_map); - reschedule_unseen_update(folder); - } - - private void check_open() throws EngineError { - if (!open) - throw new EngineError.OPEN_REQUIRED("Account %s not opened", to_string()); - } - + /** {@inheritDoc} */ public override async void open_async(Cancellable? cancellable = null) throws Error { if (open) throw new EngineError.ALREADY_OPEN("Account %s already opened", to_string()); - + opening_monitor.notify_start(); - - Error? throw_err = null; try { yield internal_open_async(cancellable); - } catch (Error err) { - throw_err = err; + } finally { + opening_monitor.notify_finish(); } - - opening_monitor.notify_finish(); - - if (throw_err != null) - throw throw_err; } - + private async void internal_open_async(Cancellable? cancellable) throws Error { + this.open_cancellable = new Cancellable(); + this.remote_ready_lock = new Nonblocking.Semaphore(this.open_cancellable); + + this.processor = new AccountProcessor(this.to_string()); + this.processor.operation_error.connect(on_operation_error); + try { - yield local.open_async(information.data_dir, Engine.instance.resource_dir.get_child("sql"), - cancellable); + yield this.local.open_async( + information.data_dir, + Engine.instance.resource_dir.get_child("sql"), + cancellable + ); } catch (Error err) { // convert database-open errors if (err is DatabaseError.CORRUPT) @@ -146,160 +144,279 @@ else throw err; } - - // outbox is now available - local.outbox.report_problem.connect(notify_report_problem); - local_only.set(outbox_path, local.outbox); - - // Search folder. - local_only.set(search_path, local.search_folder); - - // To prevent spurious connection failures, we make sure we have the - // IMAP password before attempting a connection. This might have to be - // reworked when we allow passwordless logins. - if (!information.imap_credentials.is_complete()) - yield information.fetch_passwords_async(ServiceFlag.IMAP); - - // need to back out local.open_async() if remote fails - try { - yield remote.open_async(cancellable); - } catch (Error err) { - // back out - try { - yield local.close_async(cancellable); - } catch (Error close_err) { - // ignored - } - - throw err; - } - - open = true; - - notify_opened(); + // Create/load local folders + + local_only.set(this.smtp.outbox.path, this.smtp.outbox); + + this.search_folder = new_search_folder(); + local_only.set(this.search_folder.path, this.search_folder); + + this.open = true; + notify_opened(); notify_folders_available_unavailable(sort_by_path(local_only.values), null); - - // schedule an immediate sweep of the folders; once this is finished, folders will be - // regularly enumerated - reschedule_folder_refresh(true); + + this.queue_operation( + new LoadFolders(this, this.local, get_supported_special_folders()) + ); + + // Start the mail services. Start incoming directly, but queue + // outgoing so local folders can be loaded first in case + // queued mail gets sent and needs to get saved somewhere. + yield this.imap.start(cancellable); + this.queue_operation(new StartPostie(this)); + } - + public override async void close_async(Cancellable? cancellable = null) throws Error { if (!open) return; - notify_folders_available_unavailable(null, sort_by_path(local_only.values)); - notify_folders_available_unavailable(null, sort_by_path(folder_map.values)); - - local.outbox.report_problem.disconnect(notify_report_problem); - - // attempt to close both regardless of errors - Error? local_err = null; + // Stop attempting to send any outgoing messages try { - yield local.close_async(cancellable); - } catch (Error lclose_err) { - local_err = lclose_err; + yield this.smtp.stop(); + } catch (Error err) { + debug( + "%s: Error stopping SMTP service: %s", to_string(), err.message + ); } - - Error? remote_err = null; + + // Block obtaining and reusing IMAP connections + this.remote_ready_lock.reset(); + this.imap.discard_returned_sessions = true; + + // Halt internal tasks early so they stop using local and + // remote connections. + this.refresh_folder_timer.reset(); + this.open_cancellable.cancel(); + this.processor.stop(); + + // Close folders and ensure they do in fact close + + Gee.BidirSortedSet locals = sort_by_path(this.local_only.values); + Gee.BidirSortedSet remotes = sort_by_path(this.folder_map.values); + + this.local_only.clear(); + this.folder_map.clear(); + + notify_folders_available_unavailable(null, locals); + notify_folders_available_unavailable(null, remotes); + + foreach (Geary.Folder folder in locals) { + debug("%s: Waiting for local to close: %s", to_string(), folder.to_string()); + yield folder.wait_for_close_async(); + } + foreach (Geary.Folder folder in remotes) { + debug("%s: Waiting for remote to close: %s", to_string(), folder.to_string()); + yield folder.wait_for_close_async(); + } + + // Close IMAP service manager + + try { + yield this.imap.stop(); + } catch (Error err) { + debug( + "%s: Error stopping IMAP service: %s", to_string(), err.message + ); + } + this.remote_ready_lock = null; + + // Close local infrastructure + + this.search_folder = null; try { - yield remote.close_async(cancellable); - } catch (Error rclose_err) { - remote_err = rclose_err; - } - - folder_map.clear(); - local_only.clear(); - open = false; - - if (local_err != null) - throw local_err; - - if (remote_err != null) - throw remote_err; + yield local.close_async(cancellable); + } finally { + this.open = false; + notify_closed(); + } } - + + /** {@inheritDoc} */ public override bool is_open() { return open; } - + public override async void rebuild_async(Cancellable? cancellable = null) throws Error { if (open) throw new EngineError.ALREADY_OPEN("Account cannot be open during rebuild"); - + message("%s: Rebuilding account local data", to_string()); - + // get all the storage locations associated with this Account File db_file; File attachments_dir; ImapDB.Account.get_imap_db_storage_locations(information.data_dir, out db_file, out attachments_dir); - + if (yield Files.query_exists_async(db_file, cancellable)) { - message("%s: Deleting database file %s...", to_string(), db_file.get_path()); - yield db_file.delete_async(Priority.DEFAULT, cancellable); + message( + "%s: Deleting database file %s...", + to_string(), db_file.get_path() + ); + yield db_file.delete_async(GLib.Priority.DEFAULT, cancellable); } - + if (yield Files.query_exists_async(attachments_dir, cancellable)) { - message("%s: Deleting attachments directory %s...", to_string(), attachments_dir.get_path()); - yield Files.recursive_delete_async(attachments_dir, cancellable); + message( + "%s: Deleting attachments directory %s...", + to_string(), attachments_dir.get_path() + ); + yield Files.recursive_delete_async( + attachments_dir, GLib.Priority.DEFAULT, cancellable + ); } - + message("%s: Rebuild complete", to_string()); } - - // Subclasses should implement this to return their flavor of a MinimalFolder with the - // appropriate interfaces attached. The returned folder should have its SpecialFolderType - // set using either the properties from the local folder or its path. - // - // This won't be called to build the Outbox or search folder, but for all others (including Inbox) it will. - protected abstract MinimalFolder new_folder(Geary.FolderPath path, Imap.Account remote_account, - ImapDB.Account local_account, ImapDB.Folder local_folder); - - // Subclasses with specific SearchFolder implementations should override - // this to return the correct subclass. - internal virtual SearchFolder new_search_folder() { - return new ImapDB.SearchFolder(this); - } - - private MinimalFolder build_folder(ImapDB.Folder local_folder) { - return Geary.Collection.get_first(build_folders( - Geary.iterate(local_folder).to_array_list())); - } - - private Gee.Collection build_folders(Gee.Collection local_folders) { - Gee.ArrayList folders_to_build = new Gee.ArrayList(); - Gee.ArrayList built_folders = new Gee.ArrayList(); - Gee.ArrayList return_folders = new Gee.ArrayList(); - - foreach(ImapDB.Folder local_folder in local_folders) { - if (folder_map.has_key(local_folder.get_path())) - return_folders.add(folder_map.get(local_folder.get_path())); - else - folders_to_build.add(local_folder); + + /** + * Queues an operation for execution by this account. + * + * The operation will added to the account's {@link + * AccountProcessor} and executed asynchronously by that when it + * reaches the front. + */ + public void queue_operation(AccountOperation op) + throws EngineError { + check_open(); + debug("%s: Enqueuing operation: %s", this.to_string(), op.to_string()); + this.processor.enqueue(op); + } + + /** + * Claims a new IMAP account session from the pool. + * + * A new IMAP client session will be retrieved from the pool, + * connecting if needed, and used for a new account session. This + * call will wait until the pool is ready to provide sessions. The + * session must be returned via {@link release_account_session} + * after use. + * + * The account must have been opened before calling this method. + */ + public async Imap.AccountSession claim_account_session(Cancellable? cancellable = null) + throws Error { + check_open(); + debug("%s: Acquiring account session", this.to_string()); + yield this.remote_ready_lock.wait_async(cancellable); + Imap.ClientSession client = + yield this.imap.claim_authorized_session_async(cancellable); + return new Imap.AccountSession( + this.information.id, this.local.imap_folder_root, client + ); + } + + /** + * Returns an IMAP account session to the pool for re-use. + */ + public void release_account_session(Imap.AccountSession session) { + debug("%s: Releasing account session", this.to_string()); + Imap.ClientSession? old_session = session.close(); + if (old_session != null) { + this.imap.release_session_async.begin( + old_session, + (obj, res) => { + try { + this.imap.release_session_async.end(res); + } catch (Error err) { + debug("%s: Error releasing account session: %s", + to_string(), + err.message); + } + } + ); + } + } + + /** + * Claims a new IMAP folder session from the pool. + * + * A new IMAP client session will be retrieved from the pool, + * connecting if needed, and used for a new folder session. This + * call will wait until the pool is ready to provide sessions. The + * session must be returned via {@link release_folder_session} + * after use. + * + * The account must have been opened before calling this method. + */ + public async Imap.FolderSession claim_folder_session(Geary.FolderPath path, + Cancellable cancellable) + throws Error { + check_open(); + debug("%s: Acquiring folder session", this.to_string()); + yield this.remote_ready_lock.wait_async(cancellable); + + // We manually construct an account session here and then + // reuse it for the folder session so we only need to claim as + // single session from the pool, not two. + + Imap.ClientSession? client = + yield this.imap.claim_authorized_session_async(cancellable); + Imap.AccountSession account = new Imap.AccountSession( + this.information.id, this.local.imap_folder_root, client + ); + + Imap.Folder? folder = null; + GLib.Error? folder_err = null; + try { + folder = yield account.fetch_folder_async(path, cancellable); + } catch (Error err) { + folder_err = err; + } + + account.close(); + + Imap.FolderSession? folder_session = null; + if (folder_err == null) { + try { + folder_session = yield new Imap.FolderSession( + this.information.id, client, folder, cancellable + ); + } catch (Error err) { + folder_err = err; + } } - - foreach(ImapDB.Folder folder_to_build in folders_to_build) { - MinimalFolder folder = new_folder(folder_to_build.get_path(), remote, local, folder_to_build); - folder_map.set(folder.path, folder); - built_folders.add(folder); - return_folders.add(folder); - } - - if (built_folders.size > 0) - notify_folders_available_unavailable(sort_by_path(built_folders), null); - - return return_folders; + + if (folder_err != null) { + try { + yield this.imap.release_session_async(client); + } catch (Error release_err) { + debug("Error releasing folder session: %s", release_err.message); + } + + throw folder_err; + } + + return folder_session; } - + + /** + * Returns an IMAP folder session to the pool for cleanup and re-use. + */ + public async void release_folder_session(Imap.FolderSession session) { + debug("%s: Releasing folder session", this.to_string()); + Imap.ClientSession? old_session = session.close(); + if (old_session != null) { + try { + yield this.imap.release_session_async(old_session); + } catch (Error err) { + debug("%s: Error releasing %s session: %s", + to_string(), + session.folder.path.to_string(), + err.message); + } + } + } + public override Gee.Collection list_matching_folders(Geary.FolderPath? parent) throws Error { check_open(); - + return Geary.traverse(folder_map.keys) .filter(p => { - FolderPath? path_parent = p.get_parent(); + FolderPath? path_parent = p.parent; return ((parent == null && path_parent == null) || (parent != null && path_parent != null && path_parent.equal_to(parent))); }) @@ -312,276 +429,468 @@ Gee.HashSet all_folders = new Gee.HashSet(); all_folders.add_all(folder_map.values); all_folders.add_all(local_only.values); - + return all_folders; } - - private void reschedule_unseen_update(Geary.Folder folder) { - if (!folder_map.has_key(folder.path)) - return; - - if (refresh_unseen_timeout_ids.get(folder.path) != 0) - Source.remove(refresh_unseen_timeout_ids.get(folder.path)); - - refresh_unseen_timeout_ids.set(folder.path, - Timeout.add_seconds(REFRESH_UNSEEN_SEC, () => on_refresh_unseen(folder))); - } - - private bool on_refresh_unseen(Geary.Folder folder) { - // If we're in the process already, reschedule for later. - if (in_refresh_unseen.contains(folder)) - return true; - - // add here, remove in completed callback - in_refresh_unseen.add(folder); - - refresh_unseen_async.begin(folder, null, on_refresh_unseen_completed); - - refresh_unseen_timeout_ids.unset(folder.path); - return false; + + public override Geary.ContactStore get_contact_store() { + return local.contact_store; + } + + /** {@inheritDoc} */ + public override async bool folder_exists_async(Geary.FolderPath path, + Cancellable? cancellable = null) + throws Error { + check_open(); + return this.local_only.has_key(path) || this.folder_map.has_key(path); + } + + /** {@inheritDoc} */ + public override async Geary.Folder fetch_folder_async(Geary.FolderPath path, + Cancellable? cancellable = null) + throws Error { + check_open(); + + Geary.Folder? folder = this.local_only.get(path); + if (folder == null) { + folder = this.folder_map.get(path); + + if (folder == null) { + throw new EngineError.NOT_FOUND( + "Folder not found: %s", path.to_string() + ); + } + } + return folder; + } + + public override async Geary.Folder get_required_special_folder_async(Geary.SpecialFolderType special, + Cancellable? cancellable) throws Error { + if (!(special in get_supported_special_folders())) { + throw new EngineError.BAD_PARAMETERS( + "Invalid special folder type %s passed to get_required_special_folder_async", + special.to_string()); + } + check_open(); + + Geary.Folder? folder = get_special_folder(special); + if (folder == null) { + Imap.AccountSession account = yield claim_account_session(); + try { + folder = yield ensure_special_folder_async(account, special, cancellable); + } finally { + release_account_session(account); + } + } + return folder; + } + + public override async void send_email_async(Geary.ComposedEmail composed, + GLib.Cancellable? cancellable = null) + throws GLib.Error { + check_open(); + + // XXX work out what our public IP adddress is somehow and use + // that in preference to the sender's domain + string domain = composed.sender != null + ? composed.sender.domain + : this.information.primary_mailbox.domain; + Geary.RFC822.Message rfc822 = new Geary.RFC822.Message.from_composed_email( + composed, GMime.utils_generate_message_id(domain) + ); + + yield this.smtp.queue_email(rfc822, cancellable); + } + + private void on_email_sent(Geary.RFC822.Message rfc822) { + notify_email_sent(rfc822); + } + + private ImapDB.EmailIdentifier check_id(Geary.EmailIdentifier id) throws EngineError { + ImapDB.EmailIdentifier? imapdb_id = id as ImapDB.EmailIdentifier; + if (imapdb_id == null) + throw new EngineError.BAD_PARAMETERS("EmailIdentifier %s not from ImapDB folder", id.to_string()); + + return imapdb_id; + } + + private Gee.Collection check_ids(Gee.Collection ids) + throws EngineError { + foreach (Geary.EmailIdentifier id in ids) { + if (!(id is ImapDB.EmailIdentifier)) + throw new EngineError.BAD_PARAMETERS("EmailIdentifier %s not from ImapDB folder", id.to_string()); + } + + return (Gee.Collection) ids; + } + + public override async Gee.MultiMap? local_search_message_id_async( + Geary.RFC822.MessageID message_id, Geary.Email.Field requested_fields, bool partial_ok, + Gee.Collection? folder_blacklist, Geary.EmailFlags? flag_blacklist, + Cancellable? cancellable = null) throws Error { + return yield local.search_message_id_async( + message_id, requested_fields, partial_ok, folder_blacklist, flag_blacklist, cancellable); + } + + public override async Geary.Email local_fetch_email_async(Geary.EmailIdentifier email_id, + Geary.Email.Field required_fields, Cancellable? cancellable = null) throws Error { + return yield local.fetch_email_async(check_id(email_id), required_fields, cancellable); + } + + public override Geary.SearchQuery open_search(string query, SearchQuery.Strategy strategy) { + return new ImapDB.SearchQuery(local, query, strategy); + } + + public override async Gee.Collection? local_search_async(Geary.SearchQuery query, + int limit = 100, int offset = 0, Gee.Collection? folder_blacklist = null, + Gee.Collection? search_ids = null, Cancellable? cancellable = null) throws Error { + if (offset < 0) + throw new EngineError.BAD_PARAMETERS("Offset must not be negative"); + + return yield local.search_async(query, limit, offset, folder_blacklist, search_ids, cancellable); + } + + public override async Gee.Set? get_search_matches_async(Geary.SearchQuery query, + Gee.Collection ids, Cancellable? cancellable = null) throws Error { + return yield local.get_search_matches_async(query, check_ids(ids), cancellable); + } + + public override async Gee.MultiMap? + get_containing_folders_async(Gee.Collection ids, + GLib.Cancellable? cancellable) + throws GLib.Error { + Gee.MultiMap map = + new Gee.HashMultiMap(); + yield this.local.get_containing_folders_async(ids, map, cancellable); + yield this.smtp.outbox.add_to_containing_folders_async(ids, map, cancellable); + return (map.size == 0) ? null : map; + } + + /** + * Constructs a set of folders and adds them to the account. + * + * This constructs a high-level folder representation for each + * folder, adds them to this account object, fires the appropriate + * signals, then returns them. Both the local and remote folder + * equivalents need to exist beforehand — they are not created. + * + * If `are_existing` is true, the folders are assumed to have been + * seen before and the {@link Geary.Account.folders_created} signal is + * not fired. + */ + internal Gee.Collection add_folders(Gee.Collection db_folders, + bool are_existing) { + Gee.TreeSet built_folders = new Gee.TreeSet( + Account.folder_path_comparator + ); + foreach(ImapDB.Folder db_folder in db_folders) { + if (!this.folder_map.has_key(db_folder.get_path())) { + MinimalFolder folder = new_folder(db_folder); + folder.report_problem.connect(notify_report_problem); + built_folders.add(folder); + this.folder_map.set(folder.path, folder); + } + } + + if (!built_folders.is_empty) { + notify_folders_available_unavailable(built_folders, null); + if (!are_existing) { + notify_folders_created(built_folders); + } + } + + return built_folders; + } + + /** + * Fires appropriate signals for a single altered folder. + * + * This is functionally equivalent to {@link update_folders}. + */ + internal void update_folder(Geary.Folder folder) { + Gee.Collection folders = + new Gee.LinkedList(); + folders.add(folder); + debug("Contents altered!"); + notify_folders_contents_altered(folders); + } + + /** + * Fires appropriate signals for folders have been altered. + * + * This is functionally equivalent to {@link update_folder}. + */ + internal void update_folders(Gee.Collection folders) { + if (!folders.is_empty) { + notify_folders_contents_altered(sort_by_path(folders)); + } + } + + /** + * Marks a folder as a specific special folder type. + */ + internal void promote_folders(Gee.Map specials) { + Gee.Set changed = new Gee.HashSet(); + foreach (Geary.SpecialFolderType special in specials.keys) { + MinimalFolder? minimal = specials.get(special) as MinimalFolder; + if (minimal.special_folder_type != special) { + debug("%s: Promoting %s to %s", + to_string(), minimal.to_string(), special.to_string()); + minimal.set_special_folder_type(special); + changed.add(minimal); + + MinimalFolder? existing = null; + try { + existing = get_special_folder(special) as MinimalFolder; + } catch (Error err) { + debug("%s: Error getting special folder: %s", + to_string(), err.message); + } + + if (existing != null && existing != minimal) { + existing.set_special_folder_type(SpecialFolderType.NONE); + changed.add(existing); + } + } + } + + if (!changed.is_empty) { + folders_special_type(changed); + } + } + + /** + * Removes a set of folders from the account. + * + * This removes the high-level folder representations from this + * account object, and fires the appropriate signals. Deletion of + * both the local and remote folder equivalents must be handled + * before, then after calling this method. + * + * A collection of folders that was actually removed is returned. + */ + internal Gee.BidirSortedSet + remove_folders(Gee.Collection folders) { + Gee.TreeSet removed = new Gee.TreeSet( + Account.folder_path_comparator + ); + foreach(Geary.Folder folder in folders) { + MinimalFolder? impl = this.folder_map.get(folder.path); + if (impl != null) { + this.folder_map.unset(folder.path); + removed.add(impl); + } + } + + if (!removed.is_empty) { + notify_folders_available_unavailable(null, removed); + notify_folders_deleted(removed); + } + + return removed; + } + + /** + * Locates a special folder, creating it if needed. + */ + internal async Folder + ensure_special_folder_async(Imap.AccountSession remote, + SpecialFolderType type, + GLib.Cancellable? cancellable) + throws GLib.Error { + Folder? special = get_special_folder(type); + if (special == null) { + FolderPath? path = information.get_special_folder_path(type); + if (path != null && !remote.is_folder_path_valid(path)) { + debug("%s: Ignoring bad special folder path '%s' for type %s", + to_string(), + path.to_string(), + type.to_string()); + path = null; + } + if (path == null) { + FolderPath root = + yield remote.get_default_personal_namespace(cancellable); + Gee.List search_names = special_search_names.get(type); + foreach (string search_name in search_names) { + FolderPath search_path = root.get_child(search_name); + foreach (FolderPath test_path in folder_map.keys) { + if (test_path.compare_normalized_ci(search_path) == 0) { + path = search_path; + break; + } + } + if (path != null) + break; + } + + if (path == null) { + path = root.get_child(search_names[0]); + } + + debug("%s: Guessed folder \'%s\' for special_path %s", + to_string(), path.to_string(), type.to_string() + ); + information.set_special_folder_path(type, path); + } + + if (!this.folder_map.has_key(path)) { + debug("%s: Creating \"%s\" to use as special folder %s", + to_string(), path.to_string(), type.to_string()); + + GLib.Error? created_err = null; + try { + yield remote.create_folder_async(path, type, cancellable); + } catch (GLib.Error err) { + // Hang on to the error since the folder might exist + // on the remote, so try fetching it anyway. + created_err = err; + } + + Imap.Folder? remote_folder = null; + try { + remote_folder = yield remote.fetch_folder_async( + path, cancellable + ); + } catch (GLib.Error err) { + // If we couldn't fetch it after also failing to + // create it, it's probably due to the problem + // creating it, so throw that error instead. + if (created_err != null) { + throw created_err; + } else { + throw err; + } + } + + ImapDB.Folder local_folder = + yield this.local.clone_folder_async( + remote_folder, cancellable + ); + add_folders( + Collection.single(local_folder), created_err != null + ); + } + + special= this.folder_map.get(path); + promote_folders( + Collection.single_map( + type, special + ) + ); + } + + return special; + } + + /** + * Constructs a concrete folder implementation. + * + * Subclasses should implement this to return their flavor of a + * MinimalFolder with the appropriate interfaces attached. The + * returned folder should have its SpecialFolderType set using + * either the properties from the local folder or its path. + * + * This won't be called to build the Outbox or search folder, but + * for all others (including Inbox) it will. + */ + protected abstract MinimalFolder new_folder(ImapDB.Folder local_folder); + + /** + * Constructs a concrete search folder implementation. + * + * Subclasses with specific SearchFolder implementations should + * override this to return the correct subclass. + */ + protected virtual SearchFolder new_search_folder() { + return new ImapDB.SearchFolder(this, this.local_folder_root); + } + + /** {@inheritDoc} */ + protected override void + notify_folders_available_unavailable(Gee.BidirSortedSet? available, + Gee.BidirSortedSet? unavailable) { + base.notify_folders_available_unavailable(available, unavailable); + if (available != null) { + foreach (Geary.Folder folder in available) { + folder.email_appended.connect(notify_email_appended); + folder.email_inserted.connect(notify_email_inserted); + folder.email_removed.connect(notify_email_removed); + folder.email_locally_complete.connect(notify_email_locally_complete); + folder.email_flags_changed.connect(notify_email_flags_changed); + } + } + if (unavailable != null) { + foreach (Geary.Folder folder in unavailable) { + folder.email_appended.disconnect(notify_email_appended); + folder.email_inserted.disconnect(notify_email_inserted); + folder.email_removed.disconnect(notify_email_removed); + folder.email_locally_complete.disconnect(notify_email_locally_complete); + folder.email_flags_changed.disconnect(notify_email_flags_changed); + } + } + } + + /** {@inheritDoc} */ + protected override void notify_email_appended(Geary.Folder folder, Gee.Collection ids) { + base.notify_email_appended(folder, ids); + schedule_unseen_update(folder); } - - private void on_refresh_unseen_completed(Object? source, AsyncResult result) { - try { - refresh_unseen_async.end(result); - } catch (Error e) { - debug("Error refreshing unseen counts: %s", e.message); - } + + /** {@inheritDoc} */ + protected override void notify_email_inserted(Geary.Folder folder, Gee.Collection ids) { + base.notify_email_inserted(folder, ids); + schedule_unseen_update(folder); } - - private async void refresh_unseen_async(Geary.Folder folder, Cancellable? cancellable) throws Error { - debug("Refreshing unseen counts for %s", folder.to_string()); - - try { - bool folder_created; - Imap.Folder remote_folder = yield remote.fetch_folder_async(folder.path, - out folder_created, null, cancellable); - - // if created, don't need to fetch count because it was fetched when it was created - int unseen, total; - if (!folder_created) { - yield remote.fetch_counts_async(folder.path, out unseen, out total, cancellable); - remote_folder.properties.set_status_unseen(unseen); - remote_folder.properties.set_status_message_count(total, false); - } else { - unseen = remote_folder.properties.unseen; - total = remote_folder.properties.email_total; - } - - yield local.update_folder_status_async(remote_folder, false, true, cancellable); - } finally { - // added when call scheduled (above) - in_refresh_unseen.remove(folder); - } + + /** {@inheritDoc} */ + protected override void notify_email_removed(Geary.Folder folder, Gee.Collection ids) { + base.notify_email_removed(folder, ids); + schedule_unseen_update(folder); } - - private void reschedule_folder_refresh(bool immediate) { - if (in_refresh_enumerate) - return; - - cancel_folder_refresh(); - - refresh_folder_timeout_id = immediate - ? Idle.add(on_refresh_folders) - : Timeout.add_seconds(REFRESH_FOLDER_LIST_SEC, on_refresh_folders); - } - - private void cancel_folder_refresh() { - if (refresh_folder_timeout_id != 0) { - Source.remove(refresh_folder_timeout_id); - refresh_folder_timeout_id = 0; - } - } - - private bool on_refresh_folders() { - in_refresh_enumerate = true; - enumerate_folders_async.begin(refresh_cancellable, on_refresh_completed); - - refresh_folder_timeout_id = 0; - - return false; + + /** {@inheritDoc} */ + protected override void notify_email_flags_changed(Geary.Folder folder, + Gee.Map flag_map) { + base.notify_email_flags_changed(folder, flag_map); + schedule_unseen_update(folder); } - - private void on_refresh_completed(Object? source, AsyncResult result) { + + /** + * Hooks up and queues an {@link UpdateRemoteFolders} operation. + */ + private void update_remote_folders() { + this.refresh_folder_timer.reset(); + + UpdateRemoteFolders op = new UpdateRemoteFolders( + this, + this.local_only.keys, + get_supported_special_folders() + ); + op.completed.connect(() => { + this.refresh_folder_timer.start(); + }); try { - enumerate_folders_async.end(result); + queue_operation(op); } catch (Error err) { - if (!(err is IOError.CANCELLED)) - debug("Refresh of account %s folders did not complete: %s", to_string(), err.message); + // oh well } - - in_refresh_enumerate = false; - reschedule_folder_refresh(false); } - - private async void enumerate_folders_async(Cancellable? cancellable) throws Error { - check_open(); - - // enumerate local folders first - Gee.HashMap local_folders = yield enumerate_local_folders_async( - null, cancellable); - - // convert to a list of Geary.Folder ... build_folder() also reports new folders, so this - // gets the word out quickly (local_only folders have already been reported) - Gee.Collection existing_list = new Gee.ArrayList(); - existing_list.add_all(build_folders(local_folders.values)); - existing_list.add_all(local_only.values); - - // build a map of all existing folders - Gee.HashMap existing_folders - = Geary.traverse(existing_list).to_hash_map(f => f.path); - - // now that all local have been enumerated and reported (this is important to assist - // startup of the UI), enumerate the remote folders - bool remote_folders_suspect; - Gee.HashMap? remote_folders = yield enumerate_remote_folders_async( - null, out remote_folders_suspect, cancellable); - - // pair the local and remote folders and make sure everything is up-to-date - yield update_folders_async(existing_folders, remote_folders, remote_folders_suspect, cancellable); - } - - private async Gee.HashMap enumerate_local_folders_async( - Geary.FolderPath? parent, Cancellable? cancellable) throws Error { - check_open(); - - Gee.Collection? local_children = null; - try { - local_children = yield local.list_folders_async(parent, cancellable); - } catch (EngineError err) { - // don't pass on NOT_FOUND's, that means we need to go to the server for more info - if (!(err is EngineError.NOT_FOUND)) - throw err; - } - - Gee.HashMap result = new Gee.HashMap(); - if (local_children != null) { - foreach (ImapDB.Folder local_child in local_children) { - result.set(local_child.get_path(), local_child); - Collection.map_set_all(result, - yield enumerate_local_folders_async(local_child.get_path(), cancellable)); - } - } - - return result; - } - - private async Gee.HashMap enumerate_remote_folders_async( - Geary.FolderPath? parent, out bool results_suspect, Cancellable? cancellable) throws Error { - results_suspect = false; - check_open(); - - Gee.List? remote_children = null; - try { - remote_children = yield remote.list_child_folders_async(parent, cancellable); - } catch (Error err) { - // ignore everything but I/O and IMAP errors (cancellation is an IOError) - if (err is IOError || err is ImapError) - throw err; - debug("Ignoring error listing child folders of %s: %s", - (parent != null ? parent.to_string() : "root"), err.message); - results_suspect = true; - } - - Gee.HashMap result = new Gee.HashMap(); - if (remote_children != null) { - foreach (Imap.Folder remote_child in remote_children) { - result.set(remote_child.path, remote_child); - if (remote_child.properties.has_children.is_possible()) { - bool recursive_results_suspect; - Collection.map_set_all(result, - yield enumerate_remote_folders_async( - remote_child.path, out recursive_results_suspect, cancellable)); - if (recursive_results_suspect) - results_suspect = true; - } - } + + /** + * Hooks up and queues an {@link RefreshFolderUnseen} operation. + */ + private void schedule_unseen_update(Geary.Folder folder) { + MinimalFolder? impl = folder as MinimalFolder; + if (impl != null) { + impl.refresh_unseen(); } - - return result; - } - - public override Geary.ContactStore get_contact_store() { - return local.contact_store; } protected virtual Geary.SpecialFolderType[] get_supported_special_folders() { return SUPPORTED_SPECIAL_FOLDERS; } - public override async bool folder_exists_async(Geary.FolderPath path, - Cancellable? cancellable = null) throws Error { - check_open(); - - if (yield local.folder_exists_async(path, cancellable)) - return true; - - return yield remote.folder_exists_async(path, cancellable); - } - - // TODO: This needs to be made into a single transaction - public override async Geary.Folder fetch_folder_async(Geary.FolderPath path, - Cancellable? cancellable = null) throws Error { - check_open(); - - if (local_only.has_key(path)) - return local_only.get(path); - - try { - return build_folder((ImapDB.Folder) yield local.fetch_folder_async(path, cancellable)); - } catch (EngineError err) { - // don't thrown NOT_FOUND's, that means we need to fall through and clone from the - // server - if (!(err is EngineError.NOT_FOUND)) - throw err; - } - - // clone the entire path - int length = path.get_path_length(); - for (int ctr = 0; ctr < length; ctr++) { - Geary.FolderPath folder = path.get_folder_at(ctr); - - if (yield local.folder_exists_async(folder)) - continue; - - Imap.Folder remote_folder = (Imap.Folder) yield remote.fetch_folder_async(folder, - null, null, cancellable); - - yield local.clone_folder_async(remote_folder, cancellable); - } - - // Fetch the local account's version of the folder for the MinimalFolder - return build_folder((ImapDB.Folder) yield local.fetch_folder_async(path, cancellable)); - } - - /** - * Returns an Imap.Folder that is not connected (is detached) to a MinimalFolder or any other - * ImapEngine container. - * - * This is useful for one-shot operations that need to bypass the heavyweight synchronization - * routines inside MinimalFolder. This also means that operations performed on this Folder will - * not be reflected in the local database unless there's a separate connection to the server - * that is notified or detects these changes. - * - * The returned Folder must be opened prior to use and closed once completed. ''Leaving a - * Folder open will cause a connection leak.'' - * - * It is not recommended this object be held open long-term, or that its status or notifications - * be directly written to the database unless you know exactly what you're doing. ''Caveat - * implementor.'' - */ - public async Imap.Folder fetch_detached_folder_async(Geary.FolderPath path, Cancellable? cancellable) - throws Error { - check_open(); - - if (local_only.has_key(path)) { - throw new EngineError.NOT_FOUND("%s: path %s points to local-only folder, not IMAP", - to_string(), path.to_string()); - } - - return yield remote.fetch_unrecycled_folder_async(path, cancellable); - } - private void compile_special_search_names() { /* * Compiles the list of names used to search for special @@ -682,187 +991,351 @@ return loc_names; } - private async Geary.Folder ensure_special_folder_async(Geary.SpecialFolderType special, - Cancellable? cancellable) throws Error { - Geary.Folder? folder = get_special_folder(special); - if (folder != null) - return folder; + private void check_open() throws EngineError { + if (!open) + throw new EngineError.OPEN_REQUIRED("Account %s not opened", to_string()); + } - MinimalFolder? minimal_folder = null; - Geary.FolderPath? path = information.get_special_folder_path(special); - if (path != null) { - debug("Previously used %s for special folder %s", path.to_string(), special.to_string()); + private void on_operation_error(AccountOperation op, Error error) { + if (error is ImapError) { + notify_service_problem( + ProblemType.SERVER_ERROR, this.information.incoming, error + ); + } else if (error is IOError) { + // IOErrors could be network related or disk related, need + // to work out the difference and send a service problem + // if definitely network related + notify_account_problem(ProblemType.for_ioerror((IOError) error), error); } else { - // This is the first time we're turning a non-special folder into a special one. - // After we do this, we'll record which one we picked in the account info. + notify_account_problem(ProblemType.GENERIC_ERROR, error); + } + } - Gee.List search_names = special_search_names.get(special); - foreach (string search_name in search_names) { - Geary.FolderPath search_path = new Imap.FolderRoot(search_name); - foreach (Geary.FolderPath test_path in folder_map.keys) { - if (test_path.compare_normalized_ci(search_path) == 0) { - path = search_path; - break; - } - } - if (path != null) - break; + private void on_imap_status_notify() { + if (this.open) { + if (this.imap.current_status == CONNECTED) { + this.remote_ready_lock.blind_notify(); + update_remote_folders(); + } else { + this.remote_ready_lock.reset(); + this.refresh_folder_timer.reset(); } - if (path == null) { - foreach (string search_name in search_names) { - Geary.FolderPath search_path = new Imap.FolderRoot( - Imap.MailboxSpecifier.CANONICAL_INBOX_NAME).get_child(search_name); - foreach (Geary.FolderPath test_path in folder_map.keys) { - if (test_path.compare_normalized_ci(search_path) == 0) { - path = search_path; - break; - } + } + } + +} + + +/** + * Account operation for loading local folders from the database. + */ +internal class Geary.ImapEngine.LoadFolders : AccountOperation { + + + private weak ImapDB.Account local; + private Geary.SpecialFolderType[] specials; + + + internal LoadFolders(GenericAccount account, + ImapDB.Account local, + Geary.SpecialFolderType[] specials) { + base(account); + this.local = local; + this.specials = specials; + } + + public override async void execute(Cancellable cancellable) throws Error { + GenericAccount generic = (GenericAccount) this.account; + Gee.List folders = new Gee.LinkedList(); + + yield enumerate_local_folders_async( + folders, generic.local.imap_folder_root, cancellable + ); + generic.add_folders(folders, true); + if (!folders.is_empty) { + // If we have some folders to load, then this isn't the + // first run, and hence the special folders should already + // exist + yield check_special_folders(cancellable); + } + } + + private async void enumerate_local_folders_async(Gee.List folders, + Geary.FolderPath parent, + Cancellable? cancellable) + throws Error { + Gee.Collection? children = null; + try { + children = yield this.local.list_folders_async(parent, cancellable); + } catch (EngineError err) { + // don't pass on NOT_FOUND's, that means we need to go to + // the server for more info + if (!(err is EngineError.NOT_FOUND)) + throw err; + } + + if (children != null) { + foreach (ImapDB.Folder child in children) { + folders.add(child); + yield enumerate_local_folders_async( + folders, child.get_path(), cancellable + ); + } + } + } + + private async void check_special_folders(GLib.Cancellable cancellable) + throws GLib.Error { + // Local folders loaded that have the SPECIAL-USE flags set + // will have been promoted already via derived account type's + // new_child overrides or some other means. However for those + // that do not have the flag, check here against the local + // config and promote ASAP. + // + // Can't just use ensure_special_folder_async however since + // that will attempt to create the folders if missing, which + // is bad if offline. + GenericAccount generic = (GenericAccount) this.account; + Gee.Map added_specials = + new Gee.HashMap(); + foreach (Geary.SpecialFolderType type in this.specials) { + if (generic.get_special_folder(type) == null) { + Geary.FolderPath? path = + generic.information.get_special_folder_path(type); + if (path != null) { + try { + Geary.Folder target = yield generic.fetch_folder_async( + path, cancellable + ); + added_specials.set(type, target); + } catch (Error err) { + debug( + "%s: Previously used special folder %s not loaded: %s", + generic.information.id, + type.to_string(), + err.message + ); } - if (path != null) - break; } } - - if (path == null) - path = new Imap.FolderRoot(search_names[0]); - - information.set_special_folder_path(special, path); - yield information.store_async(cancellable); - } - - if (path in folder_map.keys) { - debug("Promoting %s to special folder %s", path.to_string(), special.to_string()); - - minimal_folder = folder_map.get(path); - } else { - debug("Creating %s to use as special folder %s", path.to_string(), special.to_string()); - - // TODO: ignore error due to already existing. - yield remote.create_folder_async(path, cancellable); - minimal_folder = (MinimalFolder) yield fetch_folder_async(path, cancellable); - } - - minimal_folder.set_special_folder_type(special); - return minimal_folder; - } - - public override async Geary.Folder get_required_special_folder_async(Geary.SpecialFolderType special, - Cancellable? cancellable) throws Error { - if (!(special in get_supported_special_folders())) { - throw new EngineError.BAD_PARAMETERS( - "Invalid special folder type %s passed to get_required_special_folder_async", - special.to_string()); } - check_open(); - return yield ensure_special_folder_async(special, cancellable); + generic.promote_folders(added_specials); + } + +} + + +/** + * Account operation for starting the outgoing service. + */ +internal class Geary.ImapEngine.StartPostie : AccountOperation { + + + internal StartPostie(Account account) { + base(account); + } + + public override async void execute(GLib.Cancellable cancellable) + throws GLib.Error { + yield this.account.outgoing.start(cancellable); + } + +} + + +/** + * Account operation that updates folders from the remote. + */ +internal class Geary.ImapEngine.UpdateRemoteFolders : AccountOperation { + + + private weak GenericAccount generic_account; + private Gee.Collection local_folders; + private Geary.SpecialFolderType[] specials; + + + internal UpdateRemoteFolders(GenericAccount account, + Gee.Collection local_folders, + Geary.SpecialFolderType[] specials) { + base(account); + this.generic_account = account; + this.local_folders = local_folders; + this.specials = specials; + } + + public override async void execute(Cancellable cancellable) throws Error { + Gee.Map existing_folders = + Geary.traverse(this.account.list_folders()) + .to_hash_map(f => f.path); + Gee.Map remote_folders = + new Gee.HashMap(); + + GenericAccount account = (GenericAccount) this.account; + Imap.AccountSession remote = yield account.claim_account_session( + cancellable + ); + try { + bool is_suspect = yield enumerate_remote_folders_async( + remote, + remote_folders, + account.local.imap_folder_root, + cancellable + ); + + debug("Existing folders:"); + foreach (FolderPath path in existing_folders.keys) { + debug(" - %s (%u)", path.to_string(), path.hash()); + } + debug("Remote folders:"); + foreach (FolderPath path in remote_folders.keys) { + debug(" - %s (%u)", path.to_string(), path.hash()); + } + + // pair the local and remote folders and make sure + // everything is up-to-date + yield update_folders_async( + remote, existing_folders, remote_folders, is_suspect, cancellable + ); + } finally { + account.release_account_session(remote); + } } - private async void ensure_special_folders_async(Cancellable? cancellable) throws Error { - foreach (Geary.SpecialFolderType special in get_supported_special_folders()) - yield ensure_special_folder_async(special, cancellable); + private async bool enumerate_remote_folders_async(Imap.AccountSession remote, + Gee.Map folders, + Geary.FolderPath? parent, + Cancellable? cancellable) + throws Error { + bool results_suspect = false; + + Gee.List? children = null; + try { + children = yield remote.fetch_child_folders_async(parent, cancellable); + } catch (Error err) { + // ignore everything but I/O and IMAP errors (cancellation is an IOError) + if (err is IOError || err is ImapError) + throw err; + debug("Ignoring error listing child folders of %s: %s", + (parent != null ? parent.to_string() : "root"), err.message); + results_suspect = true; + } + + if (children != null) { + foreach (Imap.Folder child in children) { + FolderPath path = child.path; + folders.set(path, child); + if (child.properties.has_children.is_possible() && + yield enumerate_remote_folders_async( + remote, folders, path, cancellable)) { + results_suspect = true; + } + } + } + + return results_suspect; } - private async void update_folders_async(Gee.Map existing_folders, - Gee.Map remote_folders, bool remote_folders_suspect, Cancellable? cancellable) { - // update all remote folders properties in the local store and active in the system + private async void update_folders_async(Imap.AccountSession remote, + Gee.Map existing_folders, + Gee.Map remote_folders, + bool remote_folders_suspect, + Cancellable? cancellable) { + // update all remote folders properties in the local store and + // active in the system Gee.HashSet altered_paths = new Gee.HashSet(); foreach (Imap.Folder remote_folder in remote_folders.values) { MinimalFolder? minimal_folder = existing_folders.get(remote_folder.path) as MinimalFolder; if (minimal_folder == null) continue; - + // only worry about alterations if the remote is openable if (remote_folder.properties.is_openable.is_possible()) { ImapDB.Folder local_folder = minimal_folder.local_folder; - + if (remote_folder.properties.have_contents_changed(local_folder.get_properties(), minimal_folder.to_string())) { altered_paths.add(remote_folder.path); } } - + // always update, openable or not; have the folder update the UID info the next time // it's opened try { - yield local.update_folder_status_async(remote_folder, false, false, cancellable); + yield minimal_folder.local_folder.update_folder_status( + remote_folder.properties, false, false, cancellable + ); } catch (Error update_error) { debug("Unable to update local folder %s with remote properties: %s", - remote_folder.to_string(), update_error.message); + remote_folder.path.to_string(), update_error.message); } - - // set the engine folder's special type - // (but only promote, not demote, since getting the special folder type via its - // properties relies on the optional XLIST extension) - // use this iteration to add discovered properties to map + + // set the engine folder's special type (but only promote, + // not demote, since getting the special folder type via + // its properties relies on the optional SPECIAL-USE or + // XLIST extensions) use this iteration to add discovered + // properties to map if (minimal_folder.special_folder_type == SpecialFolderType.NONE) minimal_folder.set_special_folder_type(remote_folder.properties.attrs.get_special_folder_type()); } - + // If path in remote but not local, need to add it Gee.ArrayList to_add = Geary.traverse(remote_folders.values) .filter(f => !existing_folders.has_key(f.path)) .to_array_list(); - + // If path in local but not remote (and isn't local-only, i.e. the Outbox), need to remove it Gee.ArrayList to_remove = Geary.traverse>(existing_folders) - .filter(e => !remote_folders.has_key(e.key) && !local_only.has_key(e.key)) + .filter(e => !remote_folders.has_key(e.key) && !this.local_folders.contains(e.key)) .map(e => (Geary.Folder) e.value) .to_array_list(); - - // For folders to add, clone them and their properties locally + + // For folders to add, clone them and their properties + // locally, then add to the account + ImapDB.Account local = ((GenericAccount) this.account).local; + Gee.ArrayList to_build = new Gee.ArrayList(); foreach (Geary.Imap.Folder remote_folder in to_add) { try { - yield local.clone_folder_async(remote_folder, cancellable); + to_build.add( + yield local.clone_folder_async(remote_folder, cancellable) + ); } catch (Error err) { - debug("Unable to add/remove folder %s to local store: %s", remote_folder.path.to_string(), - err.message); - } - } - - // Create Geary.Folder objects for all added folders - Gee.ArrayList folders_to_build = new Gee.ArrayList(); - foreach (Geary.Imap.Folder remote_folder in to_add) { - try { - folders_to_build.add(yield local.fetch_folder_async(remote_folder.path, cancellable)); - } catch (Error convert_err) { - // This isn't fatal, but irksome ... in the future, when local folders are - // removed, it's possible for one to disappear between cloning it and fetching - // it - debug("Unable to fetch local folder after cloning: %s", convert_err.message); + debug("Unable to clone folder %s in local store: %s", + remote_folder.path.to_string(), + err.message); } } - Gee.Collection engine_added = new Gee.ArrayList(); - engine_added.add_all(build_folders(folders_to_build)); - - Gee.ArrayList engine_removed = new Gee.ArrayList(); + this.generic_account.add_folders(to_build, false); + if (remote_folders_suspect) { debug("Skipping removing folders due to prior errors"); } else { - notify_folders_available_unavailable(null, to_remove); - - // Sort by path length descending, so we always remove children first. - to_remove.sort((a, b) => b.path.get_path_length() - a.path.get_path_length()); - foreach (Geary.Folder folder in to_remove) { + Gee.BidirSortedSet removed = + this.generic_account.remove_folders(to_remove); + + Gee.BidirIterator removed_iterator = + removed.bidir_iterator(); + removed_iterator.last(); + while (removed_iterator.previous()) { + MinimalFolder folder = removed_iterator.get(); + try { debug("Locally deleting removed folder %s", folder.to_string()); - - yield local.delete_folder_async(folder, cancellable); - engine_removed.add(folder); + yield local.delete_folder_async(folder.path, cancellable); } catch (Error e) { debug("Unable to locally delete removed folder %s: %s", folder.to_string(), e.message); } } + + // Let the remote know as well + remote.folders_removed( + Geary.traverse(removed) + .map(f => f.path).to_array_list() + ); } - - if (engine_added.size > 0 || engine_removed.size > 0) - notify_folders_added_removed(sort_by_path(engine_added), sort_by_path(engine_removed)); - - remote.folders_removed(Geary.traverse(engine_removed) - .map(f => f.path).to_array_list()); - + // report all altered folders if (altered_paths.size > 0) { Gee.ArrayList altered = new Gee.ArrayList(); @@ -872,123 +1345,70 @@ else debug("Unable to report %s altered: no local representation", altered_path.to_string()); } - - if (altered.size > 0) - notify_folders_contents_altered(altered); - } - - try { - yield ensure_special_folders_async(cancellable); - } catch (Error e) { - warning("Unable to ensure special folders: %s", e.message); + this.generic_account.update_folders(altered); } - } - - public override async void send_email_async(Geary.ComposedEmail composed, - Cancellable? cancellable = null) throws Error { - check_open(); - - // TODO: we should probably not use someone else's FQDN in something - // that's supposed to be globally unique... - Geary.RFC822.Message rfc822 = new Geary.RFC822.Message.from_composed_email( - composed, GMime.utils_generate_message_id(information.get_smtp_endpoint().remote_address.hostname)); - - // don't use create_email_async() as that requires the folder be open to use - yield local.outbox.enqueue_email_async(rfc822, cancellable); - } - private void on_email_sent(Geary.RFC822.Message rfc822) { - notify_email_sent(rfc822); - } - - private ImapDB.EmailIdentifier check_id(Geary.EmailIdentifier id) throws EngineError { - ImapDB.EmailIdentifier? imapdb_id = id as ImapDB.EmailIdentifier; - if (imapdb_id == null) - throw new EngineError.BAD_PARAMETERS("EmailIdentifier %s not from ImapDB folder", id.to_string()); - - return imapdb_id; - } - - private Gee.Collection check_ids(Gee.Collection ids) - throws EngineError { - foreach (Geary.EmailIdentifier id in ids) { - if (!(id is ImapDB.EmailIdentifier)) - throw new EngineError.BAD_PARAMETERS("EmailIdentifier %s not from ImapDB folder", id.to_string()); + // Ensure each of the important special folders we need already exist + foreach (Geary.SpecialFolderType special in this.specials) { + try { + yield this.generic_account.ensure_special_folder_async( + remote, special, cancellable + ); + } catch (Error e) { + warning("Unable to ensure special folder %s: %s", special.to_string(), e.message); + } } - - return (Gee.Collection) ids; - } - - public override async Gee.MultiMap? local_search_message_id_async( - Geary.RFC822.MessageID message_id, Geary.Email.Field requested_fields, bool partial_ok, - Gee.Collection? folder_blacklist, Geary.EmailFlags? flag_blacklist, - Cancellable? cancellable = null) throws Error { - return yield local.search_message_id_async( - message_id, requested_fields, partial_ok, folder_blacklist, flag_blacklist, cancellable); - } - - public override async Geary.Email local_fetch_email_async(Geary.EmailIdentifier email_id, - Geary.Email.Field required_fields, Cancellable? cancellable = null) throws Error { - return yield local.fetch_email_async(check_id(email_id), required_fields, cancellable); - } - - public override Geary.SearchQuery open_search(string query, SearchQuery.Strategy strategy) { - return new ImapDB.SearchQuery(local, query, strategy); - } - - public override async Gee.Collection? local_search_async(Geary.SearchQuery query, - int limit = 100, int offset = 0, Gee.Collection? folder_blacklist = null, - Gee.Collection? search_ids = null, Cancellable? cancellable = null) throws Error { - if (offset < 0) - throw new EngineError.BAD_PARAMETERS("Offset must not be negative"); - - return yield local.search_async(query, limit, offset, folder_blacklist, search_ids, cancellable); - } - - public override async Gee.Set? get_search_matches_async(Geary.SearchQuery query, - Gee.Collection ids, Cancellable? cancellable = null) throws Error { - return yield local.get_search_matches_async(query, check_ids(ids), cancellable); } - - public override async Gee.MultiMap? get_containing_folders_async( - Gee.Collection ids, Cancellable? cancellable) throws Error { - return yield local.get_containing_folders_async(ids, cancellable); - } - - private void on_login_failed(Geary.Credentials? credentials, Geary.Imap.StatusResponse? response) { - if (awaiting_credentials) - return; // We're already asking for the password. - - awaiting_credentials = true; - do_login_failed_async.begin(credentials, response, () => { awaiting_credentials = false; }); + +} + +/** + * Account operation that updates a folder's unseen message count. + * + * This performs a IMAP STATUS on the folder, but only if it is not + * open - if it is open it is already maintaining its unseen count. + */ +internal class Geary.ImapEngine.RefreshFolderUnseen : FolderOperation { + + + internal RefreshFolderUnseen(MinimalFolder folder, + GenericAccount account) { + base(account, folder); } - private async void do_login_failed_async(Geary.Credentials? credentials, Geary.Imap.StatusResponse? response) { - bool reask_password = true; - try { - reask_password = ( - response == null || - response.response_code == null || - response.response_code.get_response_code_type().value != Geary.Imap.ResponseCodeType.UNAVAILABLE - ); - } catch (ImapError ierr) { - debug("Unable to parse ResponseCode %s: %s", response.response_code.to_string(), - ierr.message); - } - // login can fail due to an invalid password hence we should re-ask it - // but it can also fail due to server inaccessibility, for instance "[UNAVAILABLE] / - // Maximum number of connections from user+IP exceeded". In that case, resetting password seems unneeded. - if (reask_password) { + public override async void execute(Cancellable cancellable) throws Error { + GenericAccount account = (GenericAccount) this.account; + if (this.folder.get_open_state() == Geary.Folder.OpenState.CLOSED) { + Imap.AccountSession? remote = yield account.claim_account_session( + cancellable + ); try { - if (yield information.fetch_passwords_async(ServiceFlag.IMAP, true)) - return; - } catch (Error e) { - debug("Error prompting for IMAP password: %s", e.message); + Imap.Folder remote_folder = yield remote.fetch_folder_async( + folder.path, + cancellable + ); + + // Implementation-specific hack: Although this is called + // when the MinimalFolder is closed, we can safely use + // local_folder since we are only using its properties, + // and the properties were loaded when the folder was + // first instantiated. + ImapDB.Folder local_folder = ((MinimalFolder) this.folder).local_folder; + + if (remote_folder.properties.have_contents_changed( + local_folder.get_properties(), + this.folder.to_string())) { + + yield local_folder.update_folder_status( + remote_folder.properties, false, true, cancellable + ); + + ((GenericAccount) this.account).update_folder(this.folder); + } + } finally { + account.release_account_session(remote); } - notify_report_problem(Geary.Account.Problem.RECV_EMAIL_LOGIN_FAILED, null); - } else { - notify_report_problem(Geary.Account.Problem.CONNECTION_FAILURE, null); } } -} +} diff -Nru geary-0.12.4/src/engine/imap-engine/imap-engine-generic-folder.vala geary-3.32.0/src/engine/imap-engine/imap-engine-generic-folder.vala --- geary-0.12.4/src/engine/imap-engine/imap-engine-generic-folder.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-engine/imap-engine-generic-folder.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,4 +1,5 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. @@ -10,13 +11,16 @@ Geary.FolderSupport.Create, Geary.FolderSupport.Empty { - public GenericFolder(GenericAccount account, Imap.Account remote, ImapDB.Account local, - ImapDB.Folder local_folder, SpecialFolderType special_folder_type) { - base (account, remote, local, local_folder, special_folder_type); + public GenericFolder(GenericAccount account, + ImapDB.Folder local_folder, + SpecialFolderType special_folder_type) { + base (account, local_folder, special_folder_type); } - public async Geary.Revokable? archive_email_async(Gee.List email_ids, - Cancellable? cancellable = null) throws Error { + public async Geary.Revokable? + archive_email_async(Gee.Collection email_ids, + GLib.Cancellable? cancellable = null) + throws GLib.Error { Geary.Folder? archive_folder = null; try { archive_folder = yield account.get_required_special_folder_async(Geary.SpecialFolderType.ARCHIVE, cancellable); @@ -33,19 +37,20 @@ return null; } - public async void remove_email_async(Gee.List email_ids, - Cancellable? cancellable = null) throws Error { + public async void + remove_email_async(Gee.Collection email_ids, + GLib.Cancellable? cancellable = null) + throws GLib.Error { yield expunge_email_async(email_ids, cancellable); } - + public async void empty_folder_async(Cancellable? cancellable = null) throws Error { yield expunge_all_async(cancellable); } - + public new async Geary.EmailIdentifier? create_email_async(RFC822.Message rfc822, Geary.EmailFlags? flags, DateTime? date_received, Geary.EmailIdentifier? id, Cancellable? cancellable = null) throws Error { return yield base.create_email_async(rfc822, flags, date_received, id, cancellable); } } - diff -Nru geary-0.12.4/src/engine/imap-engine/imap-engine-minimal-folder.vala geary-3.32.0/src/engine/imap-engine/imap-engine-minimal-folder.vala --- geary-0.12.4/src/engine/imap-engine/imap-engine-minimal-folder.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-engine/imap-engine-minimal-folder.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,58 +1,93 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. + * Copyright 2018 Michael Gratton * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ +/** + * Base implementation of {@link Geary.Folder}. + * + * This class maintains both a local database and a remote IMAP + * session and mediates between the two using the replay queue. Events + * generated locally (message move, folder close, etc) are placed in + * the local replay queue for execution, IMAP server messages (new + * message delivered, etc) are placed in the remote replay queue. The + * queue ensures that message state changes caused by server messages + * are interleaved correctly with local operations. + * + * The remote folder connection is not always automatically + * established, depending on flags passed to `open_async`. In any case + * the remote connection may go away if the network changes while the + * folder is still held open. In this case, the folder's remote + * connection is reestablished when the a `ready` signal is received + * from the IMAP stack, i.e. when connectivity to the server has been + * restored. + */ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport.Copy, Geary.FolderSupport.Mark, Geary.FolderSupport.Move { + + + private const int FLAG_UPDATE_TIMEOUT_SEC = 2; + private const int FLAG_UPDATE_START_CHUNK = 20; + private const int FLAG_UPDATE_MAX_CHUNK = 100; private const int FORCE_OPEN_REMOTE_TIMEOUT_SEC = 10; - private const int DEFAULT_REESTABLISH_DELAY_MSEC = 500; - private const int MAX_REESTABLISH_DELAY_MSEC = 60 * 1000; - + private const int REFRESH_UNSEEN_TIMEOUT_SEC = 1; + + public override Account account { get { return _account; } } - + public override FolderProperties properties { get { return _properties; } } - + public override FolderPath path { get { return local_folder.get_path(); } } - + private SpecialFolderType _special_folder_type; public override SpecialFolderType special_folder_type { get { return _special_folder_type; } } - - private ProgressMonitor _opening_monitor = new Geary.ReentrantProgressMonitor(Geary.ProgressType.ACTIVITY); - public override Geary.ProgressMonitor opening_monitor { get { return _opening_monitor; } } - - internal ImapDB.Folder local_folder { get; protected set; } - internal Imap.Folder? remote_folder { get; protected set; default = null; } + + private ProgressMonitor _opening_monitor = + new Geary.ReentrantProgressMonitor(Geary.ProgressType.ACTIVITY); + public override Geary.ProgressMonitor opening_monitor { + get { + return _opening_monitor; + } + } + + /** The IMAP database representation of the folder. */ + internal ImapDB.Folder local_folder { get; private set; } + + internal ReplayQueue? replay_queue { get; private set; default = null; } internal EmailPrefetcher email_prefetcher { get; private set; } - internal EmailFlagWatcher email_flag_watcher; - + private weak GenericAccount _account; - private Geary.AggregatedFolderProperties _properties = new Geary.AggregatedFolderProperties( - false, false); - private Imap.Account remote; - private ImapDB.Account local; - private Folder.OpenFlags open_flags = OpenFlags.NONE; + private Geary.AggregatedFolderProperties _properties = + new Geary.AggregatedFolderProperties(false, false); + private int open_count = 0; - private bool remote_opened = false; - private Nonblocking.ReportingSemaphore remote_semaphore = - new Nonblocking.ReportingSemaphore(false); + private Folder.OpenFlags open_flags = OpenFlags.NONE; + private Cancellable? open_cancellable = null; + private Nonblocking.Mutex lifecycle_mutex = new Nonblocking.Mutex(); private Nonblocking.Semaphore closed_semaphore = new Nonblocking.Semaphore(); - private ReplayQueue replay_queue; - private int remote_count = -1; - private uint open_remote_timer_id = 0; - private int reestablish_delay_msec = DEFAULT_REESTABLISH_DELAY_MSEC; - private Nonblocking.Mutex open_mutex = new Nonblocking.Mutex(); - private Nonblocking.Mutex close_mutex = new Nonblocking.Mutex(); - + + private Imap.FolderSession? remote_session = null; + private Nonblocking.Mutex remote_mutex = new Nonblocking.Mutex(); + private Nonblocking.ReportingSemaphore remote_wait_semaphore = + new Nonblocking.ReportingSemaphore(false); + private TimeoutManager remote_open_timer; + + private TimeoutManager update_flags_timer; + + private TimeoutManager refresh_unseen_timer; + + /** * Called when the folder is closing (and not reestablishing a connection) and will be flushing * the replay queue. Subscribers may add ReplayOperations to the list, which will be enqueued @@ -61,7 +96,7 @@ * Note that this is ''not'' fired if the queue is not being flushed. */ public signal void closing(Gee.List final_ops); - + /** * Fired when an {@link EmailIdentifier} that was marked for removal is actually reported as * removed (expunged) from the server. @@ -74,101 +109,179 @@ * operation. They need to know if the message is removed by another client, however. */ public signal void marked_email_removed(Gee.Collection removed); - - public MinimalFolder(GenericAccount account, Imap.Account remote, ImapDB.Account local, - ImapDB.Folder local_folder, SpecialFolderType special_folder_type) { - _account = account; - this.remote = remote; - this.local = local; + + /** Emitted to notify the account that some problem has occurred. */ + internal signal void report_problem(Geary.ProblemReport problem); + + + public MinimalFolder(GenericAccount account, + ImapDB.Folder local_folder, + SpecialFolderType special_folder_type) { + this._account = account; this.local_folder = local_folder; - _special_folder_type = special_folder_type; - _properties.add(local_folder.get_properties()); - replay_queue = new ReplayQueue(this); - - email_flag_watcher = new EmailFlagWatcher(this); - email_flag_watcher.email_flags_changed.connect(on_email_flags_changed); - - email_prefetcher = new EmailPrefetcher(this); - - local_folder.email_complete.connect(on_email_complete); + this.local_folder.email_complete.connect(on_email_complete); + + this._special_folder_type = special_folder_type; + this._properties.add(local_folder.get_properties()); + this.email_prefetcher = new EmailPrefetcher(this); + + this.remote_open_timer = new TimeoutManager.seconds( + FORCE_OPEN_REMOTE_TIMEOUT_SEC, () => { this.open_remote_session.begin(); } + ); + + this.update_flags_timer = new TimeoutManager.seconds( + FLAG_UPDATE_TIMEOUT_SEC, on_update_flags + ); + + this.refresh_unseen_timer = new TimeoutManager.seconds( + REFRESH_UNSEEN_TIMEOUT_SEC, on_refresh_unseen + ); + + // Notify now to ensure that wait_for_close_async does not + // block if never opened. + this.closed_semaphore.blind_notify(); } - + ~MinimalFolder() { if (open_count > 0) warning("Folder %s destroyed without closing", to_string()); - - cancel_remote_open_timer(); - - local_folder.email_complete.disconnect(on_email_complete); } - + protected virtual void notify_closing(Gee.List final_ops) { closing(final_ops); } - + /* * These signal notifiers are marked public (note this is a private class) so the various * ReplayOperations can directly fire the associated signals while within the queue. */ - + public void replay_notify_email_inserted(Gee.Collection ids) { notify_email_inserted(ids); } - + public void replay_notify_email_locally_inserted(Gee.Collection ids) { notify_email_locally_inserted(ids); } - + public void replay_notify_email_removed(Gee.Collection ids) { notify_email_removed(ids); } - + public void replay_notify_email_count_changed(int new_count, Folder.CountChangeReason reason) { notify_email_count_changed(new_count, reason); } - + public void replay_notify_email_flags_changed(Gee.Map flag_map) { notify_email_flags_changed(flag_map); } - + public void set_special_folder_type(SpecialFolderType new_type) { SpecialFolderType old_type = _special_folder_type; _special_folder_type = new_type; if (old_type != new_type) notify_special_folder_type_changed(old_type, new_type); } - + + /** {@inheritDoc} */ public override Geary.Folder.OpenState get_open_state() { - if (open_count == 0) + if (this.open_count == 0) return Geary.Folder.OpenState.CLOSED; - - return (remote_folder != null) ? Geary.Folder.OpenState.BOTH : Geary.Folder.OpenState.LOCAL; + + return (this.remote_session != null) + ? Geary.Folder.OpenState.REMOTE + : Geary.Folder.OpenState.LOCAL; } - - // Returns the synchronized remote count (-1 if not opened) and the last seen remote count (stored - // locally, -1 if not available) - // - // Return value is the remote_count, unless the remote is unopened, in which case it's the - // last_seen_remote_count (which may be -1). - // - // remote_count, last_seen_remote_count, and returned value do not reflect any notion of - // messages marked for removal - internal int get_remote_counts(out int remote_count, out int last_seen_remote_count) { - remote_count = this.remote_count; - last_seen_remote_count = local_folder.get_properties().select_examine_messages; - if (last_seen_remote_count < 0) - last_seen_remote_count = local_folder.get_properties().status_messages; - - return (remote_count >= 0) ? remote_count : last_seen_remote_count; + + /** {@inheritDoc} */ + public override async bool open_async(Folder.OpenFlags open_flags, + Cancellable? cancellable = null) + throws Error { + // Claim the lifecycle_mutex here so we don't try to re-open when + // the folder is in the middle of being closed. + bool opening = false; + Error? open_err = null; + try { + int token = yield this.lifecycle_mutex.claim_async(cancellable); + try { + opening = yield open_locked(open_flags, cancellable); + } catch (Error err) { + open_err = err; + } + this.lifecycle_mutex.release(ref token); + } catch (Error err) { + // oh well + } + + if (open_err != null) { + throw open_err; + } + + return opening; } - + + /** + * Returns a valid IMAP folder session when one is available. + * + * Implementations may use this to acquire an IMAP session for + * performing folder-related work. The call will wait until a + * connection is established then return the session. + * + * The session returned is guaranteed to be open upon return, + * however may close afterwards due to this folder closing, or the + * network connection going away. + * + * The folder must have been opened before calling this method. + */ + public async Imap.FolderSession claim_remote_session(Cancellable? cancellable = null) + throws Error { + check_open("claim_remote_session"); + debug("%s: Claiming folder session", this.to_string()); + + + // If remote has not yet been opened and we are not in the + // process of closing the folder, open a session right away. + if (this.remote_session == null && !this.open_cancellable.is_cancelled()) { + this.open_remote_session.begin(); + } + + if (!yield this.remote_wait_semaphore.wait_for_result_async(cancellable)) + throw new EngineError.ALREADY_CLOSED("%s failed to open", to_string()); + + return this.remote_session; + } + + /** {@inheritDoc} */ + public override async bool close_async(Cancellable? cancellable = null) + throws Error { + check_open("close_async"); + debug("%s: Scheduling folder close", this.to_string()); + // Although it's inefficient in the case of just decrementing + // the open count, pass all requests to close via the replay + // queue so that other operations queued are interleaved in an + // expected way, the same code path can be used to both test + // and decrement the open count, and that the decrement can be + // used under the same lock as actually closing the folder, + // making it essentially an atomic operation. + UserClose op = new UserClose(this, cancellable); + this.replay_queue.schedule(op); + yield op.wait_for_ready_async(cancellable); + return op.is_closing.is_certain(); + } + + /** {@inheritDoc} */ + public override async void wait_for_close_async(Cancellable? cancellable = null) + throws Error { + yield this.closed_semaphore.wait_async(cancellable); + } + // used by normalize_folders() during the normalization process; should not be used elsewhere private async void detach_all_emails_async(Cancellable? cancellable) throws Error { Gee.List? all = yield local_folder.list_email_by_id_async(null, -1, Geary.Email.Field.NONE, ImapDB.Folder.ListFlags.NONE, cancellable); - + yield local_folder.detach_all_emails_async(cancellable); - + if (all != null && all.size > 0) { Gee.List ids = traverse(all).map((email) => email.id).to_array_list(); @@ -176,32 +289,48 @@ notify_email_count_changed(0, Folder.CountChangeReason.REMOVED); } } - - private async bool normalize_folders(Geary.Imap.Folder remote_folder, Cancellable? cancellable) + + /** + * Synchronises the remote and local folders on session established. + * + * See [[https://tools.ietf.org/html/rfc4549|RFC 4549]] for an + * overview of the process + */ + private async void normalize_folders(Geary.Imap.FolderSession session, + Cancellable cancellable) throws Error { debug("%s: Begin normalizing remote and local folders", to_string()); - - Geary.Imap.FolderProperties local_properties = local_folder.get_properties(); - Geary.Imap.FolderProperties remote_properties = remote_folder.properties; - - // and both must have their next UID's (it's possible they don't if it's a non-selectable - // folder) + + Geary.Imap.FolderProperties local_properties = this.local_folder.get_properties(); + Geary.Imap.FolderProperties remote_properties = session.folder.properties; + + /* + * Step 1: Check UID validity. If there are any problems, we + * can't continue, so either bail out completely or clear all + * local messages and let the client start fetching them all + * again. + */ + + // Both must have their next UID's - it's possible they don't + // if it's a non-selectable folder. if (local_properties.uid_next == null || local_properties.uid_validity == null) { - debug("%s: Unable to verify UIDs: missing local UIDNEXT (%s) and/or UIDVALIDITY (%s)", - to_string(), (local_properties.uid_next == null).to_string(), - (local_properties.uid_validity == null).to_string()); - - return false; + throw new ImapError.NOT_SUPPORTED( + "%s: Unable to verify UIDs: missing local UIDNEXT (%s) and/or UIDVALIDITY (%s)", + to_string(), + (local_properties.uid_next == null).to_string(), + (local_properties.uid_validity == null).to_string() + ); } - + if (remote_properties.uid_next == null || remote_properties.uid_validity == null) { - debug("%s: Unable to verify UIDs: missing remote UIDNEXT (%s) and/or UIDVALIDITY (%s)", - to_string(), (remote_properties.uid_next == null).to_string(), - (remote_properties.uid_validity == null).to_string()); - - return false; + throw new ImapError.NOT_SUPPORTED( + "%s: Unable to verify UIDs: missing remote UIDNEXT (%s) and/or UIDVALIDITY (%s)", + to_string(), + (remote_properties.uid_next == null).to_string(), + (remote_properties.uid_validity == null).to_string() + ); } - + // If UIDVALIDITY changes, all email in the folder must be removed as the UIDs are now // invalid ... we merely detach the emails (leaving their contents behind) so duplicate // detection can fix them up. But once all UIDs are removed, it's much like the next @@ -212,84 +341,85 @@ debug("%s: UID validity changed, detaching all email: %s -> %s", to_string(), local_properties.uid_validity.value.to_string(), remote_properties.uid_validity.value.to_string()); - yield detach_all_emails_async(cancellable); - - return true; + return; } - + + /* + * Step 2: Check the local folder. It may be empty, in which + * case the client can just start fetching messages normally, + * or it may be corrupt in which also clear it out and do + * same. + */ + // fetch email from earliest email to last to (a) remove any deletions and (b) update // any flags that may have changed ImapDB.EmailIdentifier? local_earliest_id = yield local_folder.get_earliest_id_async(cancellable); ImapDB.EmailIdentifier? local_latest_id = yield local_folder.get_latest_id_async(cancellable); - + // verify still open; this is required throughout after each yield, as a close_async() can // come in ay any time since this does not run in the context of open_async() check_open("normalize_folders (local earliest/latest UID)"); - + // if no earliest UID, that means no messages in local store, so nothing to update if (local_earliest_id == null || local_latest_id == null) { debug("%s: local store empty, nothing to normalize", to_string()); - - return true; + return; } - - assert(local_earliest_id.has_uid()); - assert(local_latest_id.has_uid()); - - // if any messages are still marked for removal from last time, that means the EXPUNGE - // never arrived from the server, in which case the folder is "dirty" and needs a full - // normalization + + // If any messages are still marked for removal from last + // time, that means the EXPUNGE never arrived from the server, + // in which case the folder is "dirty" and needs a full + // normalization. However, there may be enqueued + // ReplayOperations waiting to remove messages on the server + // that marked some or all of those messages, Don't consider + // those already marked as "already marked" if they were not + // leftover from the last open of this folder Gee.Set? already_marked_ids = yield local_folder.get_marked_ids_async( cancellable); - - // however, there may be enqueue ReplayOperations waiting to remove messages on the server - // that marked some or all of those messages Gee.HashSet to_be_removed = new Gee.HashSet(); replay_queue.get_ids_to_be_remote_removed(to_be_removed); - - // don't consider those already marked as "already marked" if they were not leftover from - // the last open of this folder if (already_marked_ids != null) already_marked_ids.remove_all(to_be_removed); - + bool is_dirty = (already_marked_ids != null && already_marked_ids.size > 0); - if (is_dirty) debug("%s: %d remove markers found, folder is dirty", to_string(), already_marked_ids.size); - + // a full normalize works from the highest possible UID on the remote and work down to the lowest UID on // the local; this covers all messages appended since last seen as well as any removed Imap.UID last_uid = remote_properties.uid_next.previous(true); - + // if either local UID is out of range of the current highest UID, then something very wrong // has occurred; the only recourse is to wipe all associations and start over if (local_earliest_id.uid.compare_to(last_uid) > 0 || local_latest_id.uid.compare_to(last_uid) > 0) { debug("%s: Local UID(s) higher than remote UIDNEXT, detaching all email: %s/%s remote=%s", to_string(), local_earliest_id.uid.to_string(), local_latest_id.uid.to_string(), last_uid.to_string()); - yield detach_all_emails_async(cancellable); - - return true; + return; } - + + /* + * Step 3: Check remote folder, work out what has changed. + */ + // if UIDNEXT has changed, that indicates messages have been appended (and possibly removed) int64 uidnext_diff = remote_properties.uid_next.value - local_properties.uid_next.value; - + int local_message_count = (local_properties.select_examine_messages >= 0) ? local_properties.select_examine_messages : 0; int remote_message_count = (remote_properties.select_examine_messages >= 0) ? remote_properties.select_examine_messages : 0; - - // if UIDNEXT is the same as last time AND the total count of email is the same, then - // nothing has been added or removed + + // if UIDNEXT is the same as last time AND the total count of + // email is the same, then nothing has been added or removed, + // and we're done. if (!is_dirty && uidnext_diff == 0 && local_message_count == remote_message_count) { debug("%s: No messages added/removed since last opened, normalization completed", to_string()); - - return true; + return; } - + // if the difference in UIDNEXT values equals the difference in message count, then only // an append could have happened, so only pull in the new messages ... note that this is not foolproof, // as UIDs are not guaranteed to increase by 1; however, this is a standard implementation practice, @@ -300,44 +430,44 @@ Imap.UID first_uid; if (!is_dirty && uidnext_diff == (remote_message_count - local_message_count)) { first_uid = local_latest_id.uid.next(true); - + debug("%s: Messages only appended (local/remote UIDNEXT=%s/%s total=%d/%d diff=%s), gathering mail UIDs %s:%s", to_string(), local_properties.uid_next.to_string(), remote_properties.uid_next.to_string(), local_properties.select_examine_messages, remote_properties.select_examine_messages, uidnext_diff.to_string(), first_uid.to_string(), last_uid.to_string()); } else { first_uid = local_earliest_id.uid; - + debug("%s: Messages appended/removed (local/remote UIDNEXT=%s/%s total=%d/%d diff=%s), gathering mail UIDs %s:%s", to_string(), local_properties.uid_next.to_string(), remote_properties.uid_next.to_string(), local_properties.select_examine_messages, remote_properties.select_examine_messages, uidnext_diff.to_string(), first_uid.to_string(), last_uid.to_string()); } - + // get all the UIDs in said range from the local store, sorted; convert to non-null // for ease of use later Gee.Set? local_uids = yield local_folder.list_uids_by_range_async( first_uid, last_uid, true, cancellable); if (local_uids == null) local_uids = new Gee.HashSet(); - + check_open("normalize_folders (list local)"); - + // Do the same on the remote ... make non-null for ease of use later - Gee.Set? remote_uids = yield remote_folder.list_uids_async( + Gee.Set? remote_uids = yield session.list_uids_async( new Imap.MessageSet.uid_range(first_uid, last_uid), cancellable); if (remote_uids == null) remote_uids = new Gee.HashSet(); - + check_open("normalize_folders (list remote)"); - + debug("%s: Loaded local (%d) and remote (%d) UIDs, normalizing...", to_string(), local_uids.size, remote_uids.size); - + Gee.HashSet removed_uids = new Gee.HashSet(); Gee.HashSet appended_uids = new Gee.HashSet(); Gee.HashSet inserted_uids = new Gee.HashSet(); - + // Because the number of UIDs being processed can be immense in large folders, process // in a background thread yield Nonblocking.Concurrent.global.schedule_async(() => { @@ -348,7 +478,7 @@ if (!remote_uids.remove(local_uid)) removed_uids.add(local_uid); } - + // everything remaining in remote has been added since folder last seen ... whether they're // discovered (inserted) or appended depends on the highest local UID foreach (Imap.UID remote_uid in remote_uids) { @@ -357,22 +487,26 @@ else inserted_uids.add(remote_uid); } - + // the UIDs marked for removal are going to be re-inserted into the vector once they're // cleared, so add them here as well if (already_marked_ids != null) { foreach (ImapDB.EmailIdentifier id in already_marked_ids) { assert(id.has_uid()); - + if (!appended_uids.contains(id.uid)) inserted_uids.add(id.uid); } } }, cancellable); - + debug("%s: changes since last seen: removed=%d appended=%d inserted=%d", to_string(), removed_uids.size, appended_uids.size, inserted_uids.size); - + + /* + * Step 4: Synchronise local folder with remote + */ + // fetch from the server the local store's required flags for all appended/inserted messages // (which is simply equal to all remaining remote UIDs) Gee.List to_create = new Gee.ArrayList(); @@ -381,53 +515,58 @@ // detection) Gee.List msg_sets = Imap.MessageSet.uid_sparse(remote_uids); foreach (Imap.MessageSet msg_set in msg_sets) { - Gee.List? list = yield remote_folder.list_email_async(msg_set, + Gee.List? list = yield session.list_email_async(msg_set, ImapDB.Folder.REQUIRED_FIELDS, cancellable); if (list != null && list.size > 0) to_create.add_all(list); } } - + check_open("normalize_folders (list remote appended/inserted required fields)"); - + // store new messages and add IDs to the appended/discovered EmailIdentifier buckets Gee.Set appended_ids = new Gee.HashSet(); Gee.Set locally_appended_ids = new Gee.HashSet(); Gee.Set inserted_ids = new Gee.HashSet(); Gee.Set locally_inserted_ids = new Gee.HashSet(); if (to_create.size > 0) { - Gee.Map? created_or_merged = yield local_folder.create_or_merge_email_async( - to_create, cancellable); + // Don't update the unread count here, since it'll get + // updated once normalisation has finished anyway. See + // also Issue #213. + Gee.Map? created_or_merged = + yield local_folder.create_or_merge_email_async( + to_create, false, cancellable + ); assert(created_or_merged != null); - + // it's possible a large number of messages have come in, so process them in the // background yield Nonblocking.Concurrent.global.schedule_async(() => { foreach (Email email in created_or_merged.keys) { ImapDB.EmailIdentifier id = (ImapDB.EmailIdentifier) email.id; bool created = created_or_merged.get(email); - + // report all appended email, but separate out email never seen before (created) // as locally-appended if (appended_uids.contains(id.uid)) { appended_ids.add(id); - + if (created) locally_appended_ids.add(id); } else if (inserted_uids.contains(id.uid)) { inserted_ids.add(id); - + if (created) locally_inserted_ids.add(id); } } }, cancellable); - + debug("%s: Finished creating/merging %d emails", to_string(), created_or_merged.size); } - + check_open("normalize_folders (created/merged appended/inserted emails)"); - + // Convert removed UIDs into EmailIdentifiers and detach immediately Gee.Set? removed_ids = null; if (removed_uids.size > 0) { @@ -437,858 +576,563 @@ yield local_folder.detach_multiple_emails_async(removed_ids, cancellable); } } - + check_open("normalize_folders (removed emails)"); - + // remove any extant remove markers, as everything is accounted for now, except for those // waiting to be removed in the queue yield local_folder.clear_remove_markers_async(to_be_removed, cancellable); - - check_open("normalize_folders (clear remove markers)"); - - // - // now normalized - // notify subscribers of changes - // - + + if (cancellable.is_cancelled()) { + return; + } + + + /* + * Step 5: Notify subscribers of what has happened. + */ + Folder.CountChangeReason count_change_reason = Folder.CountChangeReason.NONE; - + if (removed_ids != null && removed_ids.size > 0) { // there may be operations pending on the remote queue for these removed emails; notify // operations that the email has shuffled off this mortal coil replay_queue.notify_remote_removed_ids(removed_ids); - + // notify subscribers about emails that have been removed debug("%s: Notifying of %d removed emails since last opened", to_string(), removed_ids.size); notify_email_removed(removed_ids); - + count_change_reason |= Folder.CountChangeReason.REMOVED; } - + // notify inserted (new email located somewhere inside the local vector) if (inserted_ids.size > 0) { debug("%s: Notifying of %d inserted emails since last opened", to_string(), inserted_ids.size); notify_email_inserted(inserted_ids); - + count_change_reason |= Folder.CountChangeReason.INSERTED; } - + // notify inserted (new email located somewhere inside the local vector that had to be // created, i.e. no portion was stored locally) if (locally_inserted_ids.size > 0) { debug("%s: Notifying of %d locally inserted emails since last opened", to_string(), locally_inserted_ids.size); notify_email_locally_inserted(locally_inserted_ids); - + count_change_reason |= Folder.CountChangeReason.INSERTED; } - + // notify appended (new email added since the folder was last opened) if (appended_ids.size > 0) { debug("%s: Notifying of %d appended emails since last opened", to_string(), appended_ids.size); notify_email_appended(appended_ids); - + count_change_reason |= Folder.CountChangeReason.APPENDED; } - + // notify locally appended (new email never seen before added since the folder was last // opened) if (locally_appended_ids.size > 0) { debug("%s: Notifying of %d locally appended emails since last opened", to_string(), locally_appended_ids.size); notify_email_locally_appended(locally_appended_ids); - + count_change_reason |= Folder.CountChangeReason.APPENDED; } - + if (count_change_reason != Folder.CountChangeReason.NONE) { debug("%s: Notifying of %Xh count change reason (%d remote messages)", to_string(), count_change_reason, remote_message_count); notify_email_count_changed(remote_message_count, count_change_reason); } - + debug("%s: Completed normalize_folder", to_string()); - - return true; } - - public override async void wait_for_open_async(Cancellable? cancellable = null) throws Error { - if (open_count == 0) - throw new EngineError.OPEN_REQUIRED("wait_for_open_async() can only be called after open_async()"); - - // if remote has not yet been opened, do it now ... this bool can go true only once after - // an open_async, it's reset at close time - if (!remote_opened) { - debug("wait_for_open_async %s: opening remote on demand...", to_string()); - - start_remote_open_now(); + + /** + * Closes the folder and the remote session. + * + * This should only be called from the replay queue. + */ + internal async bool close_internal(Folder.CloseReason local_reason, + Folder.CloseReason remote_reason, + Cancellable? cancellable) { + bool is_closing = false; + try { + int token = yield this.lifecycle_mutex.claim_async(cancellable); + // Don't ever decrement to zero here, + // close_internal_locked will do that later when it's + // appropriate to do so, after having flushed the replay + // queue. For the same reason, if we're actually going to + // do the close here, we need to hold the lock until it's + // done so that it's not possible to re-open half way + // through. + if (this.open_count == 1) { + is_closing = true; + this.close_internal_locked.begin( + local_reason, remote_reason, cancellable, + (obj, res) => { + this.close_internal_locked.end(res); + try { + this.lifecycle_mutex.release(ref token); + } catch (Error err) { + // oh well + } + } + ); + } else { + if (this.open_count > 1) { + this.open_count -= 1; + } else { + is_closing = true; + } + this.lifecycle_mutex.release(ref token); + } + } catch (Error err) { + // oh well } - - if (!yield remote_semaphore.wait_for_result_async(cancellable)) - throw new EngineError.ALREADY_CLOSED("%s failed to open", to_string()); + return is_closing; } - - public override async bool open_async(Geary.Folder.OpenFlags open_flags, Cancellable? cancellable = null) + + /** + * Unhooks the IMAP folder session and returns it to the account. + */ + private async void close_remote_session(Folder.CloseReason remote_reason) { + // Since the remote session has is/has gone away, we need to + // let waiters know. In the case of the folder being closed, + // notify that no more remotes will ever come back, otherwise + // reset the semaphore to keep them waiting, in case it does. + // + // We use open_cancellable to determine if the folder is open + // since that is cancelled before the replay queue is flushed, + // and open_count is only set to zero only afterwards. This is + // important since we need to let replay queue ops that are + // being flushed know if the session goes away so they wake + // up. + if (this.open_cancellable.is_cancelled()) { + notify_remote_waiters(false); + } else { + this.remote_wait_semaphore.reset(); + } + + Imap.FolderSession session = this.remote_session; + this.remote_session = null; + if (session != null) { + session.appended.disconnect(on_remote_appended); + session.updated.disconnect(on_remote_updated); + session.removed.disconnect(on_remote_removed); + session.disconnected.disconnect(on_remote_disconnected); + this._properties.remove(session.folder.properties); + yield this._account.release_folder_session(session); + + notify_closed(remote_reason); + } + } + + // Must be called when lifecycle_mutex is locked, i.e. from + // open_async(). + private async bool open_locked(Folder.OpenFlags open_flags, + Cancellable cancellable) throws Error { - if (open_count++ > 0) { - // even if opened or opening, or if forcing a re-open, respect the NO_DELAY flag + if (this.open_count++ > 0) { + // even if opened or opening, or if forcing a re-open, + // respect the NO_DELAY flag if (open_flags.is_all_set(OpenFlags.NO_DELAY)) { // add NO_DELAY flag if it forces an open - if (!remote_opened) + if (this.remote_session == null) this.open_flags |= OpenFlags.NO_DELAY; - - start_remote_open_now(); + + this.open_remote_session.begin(); } - - debug("Not opening %s: already open", to_string()); - return false; } - + // first open gets to name the flags, but see note above this.open_flags = open_flags; - - // reset to force waiting in wait_for_open_async() - remote_semaphore.reset(); - + // reset to force waiting in wait_for_close_async() - closed_semaphore.reset(); - - // Unless NO_DELAY is set, do NOT open the remote side here; wait for the ReplayQueue to - // require a remote connection or wait_for_open_async() to be called ... this allows for - // fast local-only operations to occur, local-only either because (a) the folder has all - // the information required (for a list or fetch operation), or (b) the operation was de - // facto local-only. In particular, EmailStore will open and close lots of folders, - // causing a lot of connection setup and teardown + this.closed_semaphore.reset(); + + // reset unseen count refresh since it will be updated when + // the remote opens + this.refresh_unseen_timer.reset(); + + // Construct objects needed when open + this.open_cancellable = new Cancellable(); + this.replay_queue = new ReplayQueue(this); + + // Notify the email prefetcher + this.email_prefetcher.open(); + + // notify about the local open + notify_opened( + Geary.Folder.OpenState.LOCAL, + this.local_folder.get_properties().email_total + ); + + // Unless NO_DELAY is set, do NOT open the remote side here; + // wait for a folder session to be claimed ... this allows for + // fast local-only operations to occur, local-only either + // because (a) the folder has all the information required + // (for a list or fetch operation), or (b) the operation was + // de facto local-only. In particular, EmailStore will open + // and close lots of folders, causing a lot of connection + // setup and teardown // - // However, want to eventually open, otherwise if there's no user interaction (i.e. a - // second account Inbox they don't manipulate), no remote connection will ever be made, - // meaning that folder normalization never happens and unsolicited notifications never - // arrive - if (open_flags.is_all_set(OpenFlags.NO_DELAY)) - start_remote_open_now(); - else - start_remote_open_timer(); - + // However, we want to eventually open, otherwise if there's + // no user interaction (i.e. a second account Inbox they don't + // manipulate), no remote connection will ever be made, + // meaning that folder normalization never happens and + // unsolicited notifications never arrive + this._account.imap.notify["current-status"].connect( + on_remote_status_notify + ); + if (open_flags.is_all_set(OpenFlags.NO_DELAY)) { + this.open_remote_session.begin(); + } else { + this.remote_open_timer.start(); + } + + debug("%s: Folder opened", to_string()); return true; } - private void start_remote_open_timer() { - if (this.open_remote_timer_id != 0) - Source.remove(this.open_remote_timer_id); - - this.open_remote_timer_id = Timeout.add_seconds(FORCE_OPEN_REMOTE_TIMEOUT_SEC, () => { - start_remote_open_now(); - this.open_remote_timer_id = 0; - return false; - }); + /** + * Closes the folder regardless of the open count. + * + * This only useful when an unrecoverable error has occurred. No + * cancellable argument is provided since the close must complete. + */ + private async void force_close(Folder.CloseReason local_reason, + Folder.CloseReason remote_reason) { + try { + int token = yield this.lifecycle_mutex.claim_async(null); + // Check we actually need to do the close in case the + // folder was in the process of closing anyway + if (this.open_count > 0) { + yield close_internal_locked(local_reason, remote_reason, null); + } + this.lifecycle_mutex.release(ref token); + } catch (Error err) { + // oh well + } } - private void start_remote_open_now() { - if (remote_opened) - return; - - cancel_remote_open_timer(); - remote_opened = true; - - open_remote_async.begin(null); - } + // Must be called when lifecycle_mutex is locked, i.e. from + // close_internal() or force_close(). + private async void close_internal_locked(Folder.CloseReason local_reason, + Folder.CloseReason remote_reason, + Cancellable? cancellable) { + debug("%s: Folder closing", to_string()); - private void cancel_remote_open_timer() { - if (this.open_remote_timer_id == 0) - return; + // Ensure we don't attempt to start opening a remote while + // closing + this._account.imap.notify["current-status"].disconnect( + on_remote_status_notify + ); + this.remote_open_timer.reset(); - Source.remove(this.open_remote_timer_id); - this.open_remote_timer_id = 0; - } + // Stop any internal tasks from running + this.open_cancellable.cancel(); + this.email_prefetcher.close(); + this.update_flags_timer.reset(); - // Open the remote connection using a Mutex to prevent concurrency. - // - // start_remote_open_now() *should* prevent more than one open from occurring at the same time, - // but it's still wise to use a nonblocking primitive to prevent it if that does occur to at - // least keep Folder state cogent. - private async void open_remote_async(Cancellable? cancellable) { - int token; - try { - token = yield open_mutex.claim_async(cancellable); - } catch (Error err) { - return; + // Once we get to this point, either there will be a remote + // session open already, or none will ever get opened - no + // more attempts to open a session will be made. We can't + // block access to any remote session here however since + // pending replay operations may need it still. Instead, rely + // on the replay queue itself to reject any new operations + // being queued once we close it. + + // Only flush pending operations if the remote is open (if + // closed they will deadlock waiting for one to open), and if + // this is a "clean" close, that is not forced due to error. + bool flush_pending = ( + this.remote_session != null && + !local_reason.is_error() && + !remote_reason.is_error() + ); + + if (flush_pending) { + // Since we are flushing the queue, gather operations + // from Revokables to give them a chance to schedule their + // commit operations before going down + Gee.List final_ops = new Gee.ArrayList(); + notify_closing(final_ops); + foreach (ReplayOperation op in final_ops) + replay_queue.schedule(op); } - - yield open_remote_locked_async(cancellable); - + + // Close the replay queues; if a "clean" close, flush pending + // operations so everything gets a chance to run; if forced + // close, drop everything outstanding + debug("Closing replay queue for %s (flush_pending=%s): %s", + to_string(), flush_pending.to_string(), this.replay_queue.to_string()); try { - open_mutex.release(ref token); + yield this.replay_queue.close_async(flush_pending); + debug("Closed replay queue for %s: %s", + to_string(), this.replay_queue.to_string()); } catch (Error err) { + debug("Error closing %s replay queue: %s", + to_string(), err.message); } + + // Actually close the remote folder + yield close_remote_session(remote_reason); + + // Since both the remote session and replay queue have shut + // down, we can reset the folder's internal state. + this.remote_wait_semaphore.reset(); + this.replay_queue = null; + this.open_cancellable = null; + this.open_flags = OpenFlags.NONE; + + // Officially marks the folder as closed. Beyond this point it + // may start to re-open again if open_async is called. + this.open_count = 0; + + // need to call these every time, even if remote was not fully + // opened, as some callers rely on order of signals + notify_closed(local_reason); + notify_closed(CloseReason.FOLDER_CLOSED); + + // Notify waiting tasks + this.closed_semaphore.blind_notify(); + + debug("%s: Folder closed", to_string()); } - - // Should only be called when open_mutex is locked, i.e. use open_remote_async() - private async void open_remote_locked_async(Cancellable? cancellable) { - // watch for folder closing before this call got a chance to execute - if (open_count == 0) - return; - - // to ensure this isn't running when open_remote_async() is called again (due to a connection - // reestablishment), stop this monitoring from running *before* launching close_internal_async - // ... in essence, guard against reentrancy, which is possible - opening_monitor.notify_start(); - - // following blocks of code are fairly tricky because if the remote open fails need to - // carefully back out and possibly retry - Imap.Folder? opening_folder = null; + + /** + * Establishes a new IMAP session, normalising local and remote folders. + */ + private async void open_remote_session() { try { - debug("Fetching STATUS for remote %s from local", to_string()); - Imap.StatusData local_status = yield local_folder.fetch_status_data( - ImapDB.Folder.ListFlags.NONE, cancellable); - - debug("Fetching information for remote folder %s", to_string()); - opening_folder = yield remote.fetch_folder_async(local_folder.get_path(), null, local_status, - cancellable); - - debug("Opening remote folder %s", opening_folder.to_string()); - yield opening_folder.open_async(cancellable); - - // allow subclasses to examine the opened folder and resolve any vital - // inconsistencies - if (yield normalize_folders(opening_folder, cancellable)) { - // update flags, properties, etc. - yield local.update_folder_select_examine_async(opening_folder, cancellable); - - // signals - opening_folder.appended.connect(on_remote_appended); - opening_folder.removed.connect(on_remote_removed); - opening_folder.disconnected.connect(on_remote_disconnected); - - // state - remote_count = opening_folder.properties.email_total; - - // all set; bless the remote folder as opened (don't do this until completely - // open, as other functions rely on this to determine folder-open state) - remote_folder = opening_folder; - } else { - debug("Unable to prepare remote folder %s: normalize_folders() failed", to_string()); - notify_open_failed(Geary.Folder.OpenFailed.REMOTE_FAILED, null); - - // be sure to close opening_folder, close_internal_async won't do it - try { - yield opening_folder.close_async(null); - } catch (Error err) { - debug("%s: Error closing remote folder %s: %s", to_string(), opening_folder.to_string(), - err.message); - - // fall through - } - - // stop before starting the close - opening_monitor.notify_finish(); - - // normalize_folders() returning false indicates a soft error, but hard in the sense - // that opening cannot proceed, even with a connection retry - open_count = 0; - - // schedule immediate close - close_internal_async.begin(CloseReason.LOCAL_CLOSE, CloseReason.REMOTE_CLOSE, false, - cancellable); - - return; - } - } catch (Error open_err) { - bool hard_failure; - bool is_cancellation = false; - if (open_err is ImapError || open_err is EngineError) { - // "hard" error in the sense of network conditions make connection impossible - // at the moment, "soft" error in the sense that some logical error prevented - // connect (like bad credentials) - hard_failure = is_hard_failure(open_err); - } else if (open_err is IOError.CANCELLED) { - // user cancelled open, treat like soft error - hard_failure = false; - is_cancellation = true; - } else { - // a different IOError, a hard failure - hard_failure = true; - } - - Folder.CloseReason remote_reason; - if (hard_failure) { - // hard failure, retry - debug("Hard failure opening or preparing remote folder %s, retrying: %s", to_string(), - open_err.message); - - remote_reason = CloseReason.REMOTE_ERROR; - } else { - // soft failure, treat as failure to open - debug("Soft failure opening or preparing remote folder %s, closing: %s", to_string(), - open_err.message); - notify_open_failed( - is_cancellation ? Folder.OpenFailed.CANCELLED : Folder.OpenFailed.REMOTE_FAILED, - open_err); - - remote_reason = CloseReason.REMOTE_CLOSE; - - // clear open_count to ensure that close_internal_async() doesn't attempt to - // reestablish the connection - open_count = 0; - } - - // be sure to close opening_folder if it was fetched or opened - try { - if (opening_folder != null) - yield opening_folder.close_async(null); - } catch (Error err) { - debug("%s: Error closing remote folder %s: %s", to_string(), opening_folder.to_string(), - err.message); + int token = yield this.remote_mutex.claim_async(this.open_cancellable); + + // Ensure we are open already and guard against someone + // else having called this just before we did. + if (this.open_count > 0 && + this._account.imap.current_status == CONNECTED && + this.remote_session == null) { + + this.opening_monitor.notify_start(); + yield open_remote_session_locked(this.open_cancellable); + this.opening_monitor.notify_finish(); } - - // stop before starting the close - opening_monitor.notify_finish(); - - // schedule immediate close (and possible connection reestablishment) - close_internal_async.begin(CloseReason.LOCAL_CLOSE, remote_reason, false, null); - - return; + + this.remote_mutex.release(ref token); + } catch (Error err) { + // Lock error } - - opening_monitor.notify_finish(); - - // open success, reset reestablishment delay - reestablish_delay_msec = DEFAULT_REESTABLISH_DELAY_MSEC; - - // at this point, remote_folder should be set; there's no notion of a local-only open (yet) - assert(remote_folder != null); - - // notify any threads of execution waiting for the remote folder to open that the result - // of that operation is ready - try { - remote_semaphore.notify_result(true, null); - } catch (Error notify_err) { - // This should only happen if cancelled, which can't happen without a Cancellable - warning("%s: Unable to fire semaphore notifying remote folder ready/not ready: %s", - to_string(), notify_err.message); - } - - _properties.add(remote_folder.properties); - - // notify any subscribers with similar information - notify_opened(Geary.Folder.OpenState.BOTH, remote_count); } - - public override async bool close_async(Cancellable? cancellable = null) throws Error { - // Check open_count but only decrement inside of replay queue - if (open_count <= 0) - return false; - - UserClose user_close = new UserClose(this, cancellable); - replay_queue.schedule(user_close); - - yield user_close.wait_for_ready_async(cancellable); - - return user_close.closing; - } - - public override async void wait_for_close_async(Cancellable? cancellable = null) throws Error { - yield closed_semaphore.wait_async(cancellable); - } - - internal async bool user_close_async(Cancellable? cancellable) { - // decrement open_count and, if zero, continue closing Folder - if (open_count == 0 || --open_count > 0) - return false; - - if (remote_folder != null) - _properties.remove(remote_folder.properties); - - // block anyone from wait_until_open_async(), as this is no longer open - remote_semaphore.reset(); - - // don't yield here, close_internal_async() needs to be called outside of the replay queue - // the open_count protects against this path scheduling it more than once - close_internal_async.begin(CloseReason.LOCAL_CLOSE, CloseReason.REMOTE_CLOSE, true, cancellable); - - return true; - } - - // Close the remote connection and, if open_count is zero, the Folder itself. A Mutex is used - // to prevent concurrency. - // - // This is best called using a ReplayDisconnect operation, which ensures an orderly disconnect - // by going through the ReplayQueue. There are certain situations in open_remote_async() where - // this is not possible (because the queue hasn't been started). - // - // NOTE: This bypasses open_count and forces the Folder closed, reestablishing a connection if - // open_count is greater than zero - internal async void close_internal_async(Folder.CloseReason local_reason, Folder.CloseReason remote_reason, - bool flush_pending, Cancellable? cancellable) { - // make sure no open is waiting in the wings to start; close_internal_locked_async() will - // reestablish a connection if necessary - cancel_remote_open_timer(); - - int token; + + // Should only be called when remote_mutex is locked, i.e. use open_remote_session() + private async void open_remote_session_locked(Cancellable? cancellable) { + debug("%s: Opening remote session", to_string()); + + // Note that any IOError.CANCELLED errors caught below do not + // cause any error signals to be fired and do not force + // closing the folder, because the only time opening the + // session is cancelled is when the folder is already being + // closed, which is the desired result. + + // Don't try to re-open again + this.remote_open_timer.reset(); + + // Phase 1: Acquire a new session + + Imap.FolderSession? session = null; try { - token = yield close_mutex.claim_async(cancellable); - } catch (Error err) { + session = yield this._account.claim_folder_session( + this.path, cancellable + ); + } catch (IOError.CANCELLED err) { + // Fine, just bail out + return; + } catch (EngineError.NOT_FOUND err) { + debug("Remote folder not found, forcing closed"); + yield force_close( + CloseReason.LOCAL_CLOSE, CloseReason.REMOTE_ERROR + ); + return; + } catch (ImapError.NOT_SUPPORTED err) { + debug("Remote folder not selectable, forcing closed"); + yield force_close( + CloseReason.LOCAL_CLOSE, CloseReason.REMOTE_ERROR + ); return; - } - - yield close_internal_locked_async(local_reason, remote_reason, flush_pending, cancellable); - - try { - close_mutex.release(ref token); } catch (Error err) { - } - } - - // Should only be called when close_mutex is locked, i.e. use close_internal_async() - private async void close_internal_locked_async(Folder.CloseReason local_reason, - Folder.CloseReason remote_reason, bool flush_pending, Cancellable? cancellable) { - // only flushing pending ReplayOperations if this is a "clean" close, not forced due to - // error and if specified by caller (could be a non-error close on the server, i.e. "BYE", - // but the connection is dropping, so don't flush pending) - flush_pending = flush_pending && !remote_reason.is_error(); - - // If closing due to error, notify all operations waiting for the remote that it's not - // coming available ... this wakes up any ReplayOperation blocking on wait_for_open_async(), - // necessary in order to finish ReplayQueue.close_async (i.e. to prevent deadlock); this - // is necessary because it's possible for this method to be called before the remote_folder - // has even had a chance to open. - // - // Note that we don't want to do this for a clean close, because we want to flush out - // pending operations first - Imap.Folder? closing_remote_folder = null; - if (!flush_pending) - closing_remote_folder = clear_remote_folder(); - - // That said, only flush, close, and destroy the ReplayQueue if fully closing and not - // preparing for a connection reestablishment - if (open_count <= 0) { - // if closing and flushing the queue, give Revokables a chance to schedule their - // commit operations before going down - if (flush_pending) { - Gee.List final_ops = new Gee.ArrayList(); - notify_closing(final_ops); - - foreach (ReplayOperation op in final_ops) - replay_queue.schedule(op); - } - - // Close the replay queues; if a "clean" close, flush pending operations so everything - // gets a chance to run; if forced close, drop everything outstanding - try { - // swap out the ReplayQueue while closing so, if re-opened, future commands can - // be queued on the new queue - ReplayQueue closing_replay_queue = replay_queue; - replay_queue = new ReplayQueue(this); - - debug("Closing replay queue for %s (flush_pending=%s): %s", to_string(), - flush_pending.to_string(), closing_replay_queue.to_string()); - yield closing_replay_queue.close_async(flush_pending); - debug("Closed replay queue for %s: %s", to_string(), closing_replay_queue.to_string()); - } catch (Error replay_queue_err) { - debug("Error closing %s replay queue: %s", to_string(), replay_queue_err.message); + ErrorContext context = new ErrorContext(err); + if (!is_recoverable_failure(err)) { + debug("Unrecoverable failure opening remote, forcing closed: %s", + context.format_full_error()); + yield force_close( + CloseReason.LOCAL_CLOSE, CloseReason.REMOTE_ERROR + ); + } else { + debug("Recoverable error opening remote: %s", + context.format_full_error()); + notify_open_failed(Folder.OpenFailed.REMOTE_ERROR, err); } - } - - // if a "clean" close, now go ahead and close the folder - if (flush_pending) - closing_remote_folder = clear_remote_folder(); - - // now treat remote as closed, i.e. a call to open_async() will reinitiate opening and not fall - // through (unless open_count is > 0) ... do this before close_remote_folder_async() since - // it's *possible* for it to loop back to open_async() before returning - remote_opened = false; - - // use background call to (a) close remote if necessary and/or (b) reestablish connection if - // necessary ... store reestablish condition now, before scheduling close_remote_folder_async(), - // as it touches open_count - bool reestablish = open_count > 0; - if (closing_remote_folder != null || reestablish) { - // to avoid keeping the caller waiting while the remote end closes (i.e. drops the - // connection or performs an IMAP CLOSE operation), close it in the background and - // reestablish connection there, if necessary - // - // TODO: Problem with this is that we cannot effectively signal or report a close error, - // because by the time this operation completes the folder is considered closed. That - // may not be important to most callers, however. - // - // It also means the reference to the Folder must be maintained until completely - // closed. Also not a problem, as GenericAccount does that internally. However, this - // might be an issue if GenericAccount removes this folder due to a user command or - // detection on the server, so this background op keeps a reference to the Folder - close_remote_folder_async.begin(this, closing_remote_folder); - } - - // if reestablishing in close_remote_folder_async(), go no further - if (reestablish) return; - - // forced closed one way or another, so reset state - open_flags = OpenFlags.NONE; - reestablish_delay_msec = DEFAULT_REESTABLISH_DELAY_MSEC; - - // use remote_reason even if remote_folder was null; it could be that the error occurred - // while opening and remote_folder was yet unassigned ... also, need to call this every - // time, even if remote was not fully opened, as some callers rely on order of signals - notify_closed(remote_reason); - - // see above note for why this must be called every time - notify_closed(local_reason); - - notify_closed(CloseReason.FOLDER_CLOSED); - - // If not closing in the background, do it here - if (closing_remote_folder == null) - closed_semaphore.blind_notify(); - - debug("Folder %s closed", to_string()); - } - - // Returns the remote_folder, if it was set - private Imap.Folder? clear_remote_folder() { - if (remote_folder != null) { - // disconnect signals before ripping out reference - remote_folder.appended.disconnect(on_remote_appended); - remote_folder.removed.disconnect(on_remote_removed); - remote_folder.disconnected.disconnect(on_remote_disconnected); - } - - Imap.Folder? old_remote_folder = remote_folder; - remote_folder = null; - remote_count = -1; - - // only signal waiters in wait_for_open_async() that the open failed if there is no cx - // reestablishment to occur - if (open_count <= 0) { - try { - remote_semaphore.notify_result(false, null); - } catch (Error err) { - debug("Error attempting to notify that remote folder %s is now closed: %s", to_string(), - err.message); - } } - - return old_remote_folder; - } - - // See note in close_async() for why this method is static and uses an owned ref - private static async void close_remote_folder_async(owned MinimalFolder folder, - owned Imap.Folder? remote_folder) { - // force the remote closed; if due to a remote disconnect and plan on reopening, *still* - // need to do this ... don't set remote_folder to null, as that will make some code paths - // think the folder is closing or closed when in fact it will be re-opening in a moment + + // Phase 2: Update local state based on the remote session + + // Replay signals need to be hooked up before normalisation to + // avoid there being a race between that and new messages + // arriving, being removed, etc. This is safe since + // normalisation only issues FETCH commands for messages based + // on the state of the remote right after being selected, so + // any untagged EXIST and FETCH responses will be handled + // later by their replay ops, and no untagged EXPUNGE + // responses will be received since they are forbidden to be + // issued for FETCH commands. + // + // Note we don't need to unhook from these signals if an error + // occurs below since they won't be called once the session + // has been released. + session.appended.connect(on_remote_appended); + session.updated.connect(on_remote_updated); + session.removed.connect(on_remote_removed); + try { - if (remote_folder != null) - yield remote_folder.close_async(null); + yield normalize_folders(session, cancellable); } catch (Error err) { - debug("Unable to close remote %s: %s", remote_folder.to_string(), err.message); - - // fallthrough - } - - if (folder.open_count <= 0) { - debug("Not reestablishing connection to %s: closed", folder.to_string()); - - // need to do it here if not done in close_internal_locked_async() - if (remote_folder != null) - folder.closed_semaphore.blind_notify(); - - return; - } - - // reestablish connection (which requires renormalizing the remote with the local) if - // close was in error - debug("Reestablishing broken connection to %s in %dms", folder.to_string(), - folder.reestablish_delay_msec); - - yield Scheduler.sleep_ms_async(folder.reestablish_delay_msec); - - if (folder.open_count <= 0) { - debug("Not reestablishing connection to %s: closed after delay", folder.to_string()); - + // Normalisation failed, so we have a pretty serious + // problem and should not try to use the folder further, + // unless the open was simply cancelled. So clean up, and + // force the folder closed. + yield this._account.release_folder_session(session); + if (!(err is IOError.CANCELLED)) { + Folder.CloseReason local_reason = CloseReason.LOCAL_ERROR; + Folder.CloseReason remote_reason = CloseReason.REMOTE_CLOSE; + if (!is_remote_error(err)) { + notify_open_failed(OpenFailed.LOCAL_ERROR, err); + } else { + notify_open_failed(OpenFailed.REMOTE_ERROR, err); + local_reason = CloseReason.LOCAL_CLOSE; + remote_reason = CloseReason.REMOTE_ERROR; + } + yield force_close(local_reason, remote_reason); + } return; } - + + // Update the local folder's totals and UID values after + // normalisation, so it does not mistake the remote's current + // state with our previous state try { - // double timer now, reset to init value when cleanly opened - folder.reestablish_delay_msec = (folder.reestablish_delay_msec * 2).clamp( - DEFAULT_REESTABLISH_DELAY_MSEC, MAX_REESTABLISH_DELAY_MSEC); - - // since open_async() increments open_count, artificially decrement here to - // prevent driving the value up - folder.open_count = Numeric.int_floor(folder.open_count - 1, 0); - - debug("Reestablishing broken connection to %s", folder.to_string()); - yield folder.open_async(OpenFlags.NO_DELAY, null); + yield local_folder.update_folder_select_examine( + session.folder.properties, cancellable + ); } catch (Error err) { - debug("Error reestablishing broken connection to %s: %s", folder.to_string(), err.message); - } - } - - public override async void find_boundaries_async(Gee.Collection ids, - out Geary.EmailIdentifier? low, out Geary.EmailIdentifier? high, - Cancellable? cancellable = null) throws Error { - low = null; - high = null; - - Gee.MultiMap? map - = yield account.get_containing_folders_async(ids, cancellable); - - if (map != null) { - Gee.ArrayList in_folder = new Gee.ArrayList(); - foreach (Geary.EmailIdentifier id in map.get_keys()) { - if (path in map.get(id)) - in_folder.add(id); - } - - if (in_folder.size > 0) { - Gee.SortedSet sorted = Geary.EmailIdentifier.sort(in_folder); - - low = sorted.first(); - high = sorted.last(); + // Database failed, which is also a pretty serious + // problem, so handle as per above. + yield this._account.release_folder_session(session); + if (!(err is IOError.CANCELLED)) { + notify_open_failed(Folder.OpenFailed.LOCAL_ERROR, err); + yield force_close(CloseReason.LOCAL_ERROR, CloseReason.REMOTE_CLOSE); } + return; } + + // All done, can now hook up the session to the folder + this.remote_session = session; + this._properties.add(session.folder.properties); + session.disconnected.connect(on_remote_disconnected); + + // Enable IDLE now that the local and remote folders are in + // sync. Can't do this earlier since we might get untagged + // EXPUNGE responses during normalisation, which would be + // Bad™. Do it in the background to avoid delay notifying + session.enable_idle.begin(cancellable); + + // Phase 3: Notify tasks waiting for the connection + + // notify any subscribers with similar information + notify_opened( + Geary.Folder.OpenState.REMOTE, + session.folder.properties.email_total + ); + + // notify any threads of execution waiting for the remote + // folder to open that the result of that operation is ready + notify_remote_waiters(true); + + // Update flags once the remote has opened. We will receive + // notifications of changes as long as the session remains + // open, so only need to do this once + this.update_flags_timer.start(); } - + private void on_email_complete(Gee.Collection email_ids) { notify_email_locally_complete(email_ids); } - - private void on_remote_appended(int reported_remote_count) { - debug("%s on_remote_appended: remote_count=%d reported_remote_count=%d", to_string(), remote_count, - reported_remote_count); - - if (reported_remote_count < 0) - return; - + + private void on_remote_appended(Imap.FolderSession session, int appended) { + // Use the session param rather than remote_session attr since + // it may not be available yet + int remote_count = session.folder.properties.email_total; + debug("%s on_remote_appended: remote_count=%d appended=%d", + to_string(), remote_count, appended); + // from the new remote total and the old remote total, glean the SequenceNumbers of the // new email(s) Gee.List positions = new Gee.ArrayList(); - for (int pos = remote_count + 1; pos <= reported_remote_count; pos++) + for (int pos = remote_count - appended + 1; pos <= remote_count; pos++) positions.add(new Imap.SequenceNumber(pos)); - - // store the remote count NOW, as further appended messages could arrive before the - // ReplayAppend executes - remote_count = reported_remote_count; - - if (positions.size > 0) - replay_queue.schedule_server_notification(new ReplayAppend(this, reported_remote_count, positions)); - } - - // Need to prefetch at least an EmailIdentifier (and duplicate detection fields) to create a - // normalized placeholder in the local database of the message, so all positions are - // properly relative to the end of the message list; once this is done, notify user of new - // messages. If duplicates, create_email_async() will fall through to an updated merge, - // which is exactly what we want. - // - // This MUST only be called from ReplayAppend. - internal async void do_replay_appended_messages(int reported_remote_count, - Gee.List remote_positions) { - StringBuilder positions_builder = new StringBuilder("( "); - foreach (Imap.SequenceNumber remote_position in remote_positions) - positions_builder.append_printf("%s ", remote_position.to_string()); - positions_builder.append(")"); - - debug("%s do_replay_appended_message: current remote_count=%d reported_remote_count=%d remote_positions=%s", - to_string(), remote_count, reported_remote_count, positions_builder.str); - - if (remote_positions.size == 0) - return; - - Gee.HashSet created = new Gee.HashSet(); - Gee.HashSet appended = new Gee.HashSet(); - try { - Gee.List msg_sets = Imap.MessageSet.sparse(remote_positions); - foreach (Imap.MessageSet msg_set in msg_sets) { - Gee.List? list = yield remote_folder.list_email_async(msg_set, - ImapDB.Folder.REQUIRED_FIELDS, null); - if (list != null && list.size > 0) { - debug("%s do_replay_appended_message: %d new messages in %s", to_string(), - list.size, msg_set.to_string()); - - // need to report both if it was created (not known before) and appended (which - // could mean created or simply a known email associated with this folder) - Gee.Map created_or_merged = - yield local_folder.create_or_merge_email_async(list, null); - foreach (Geary.Email email in created_or_merged.keys) { - // true means created - if (created_or_merged.get(email)) { - debug("%s do_replay_appended_message: appended email ID %s added", - to_string(), email.id.to_string()); - - created.add(email.id); - } else { - debug("%s do_replay_appended_message: appended email ID %s associated", - to_string(), email.id.to_string()); - } - - appended.add(email.id); - } - } else { - debug("%s do_replay_appended_message: no new messages in %s", to_string(), - msg_set.to_string()); - } - } - } catch (Error err) { - debug("%s do_replay_appended_message: Unable to process: %s", - to_string(), err.message); - } - - // store the reported count, *not* the current count (which is updated outside the of - // the queue) to ensure that updates happen serially and reflect committed local changes - try { - yield local_folder.update_remote_selected_message_count(reported_remote_count, null); - } catch (Error err) { - debug("%s do_replay_appended_message: Unable to save appended remote count %d: %s", - to_string(), reported_remote_count, err.message); + + if (positions.size > 0) { + // We don't pass in open_cancellable here since we want + // the op to still run when closing and flushing the queue + ReplayAppend op = new ReplayAppend(this, remote_count, positions, null); + op.email_appended.connect(notify_email_appended); + op.email_locally_appended.connect(notify_email_locally_appended); + op.email_count_changed.connect(notify_email_count_changed); + this.replay_queue.schedule_server_notification(op); } - - if (appended.size > 0) - notify_email_appended(appended); - - if (created.size > 0) - notify_email_locally_appended(created); - - notify_email_count_changed(reported_remote_count, CountChangeReason.APPENDED); - - debug("%s do_replay_appended_message: completed, current remote_count=%d reported_remote_count=%d", - to_string(), remote_count, reported_remote_count); - } - - private void on_remote_removed(Imap.SequenceNumber position, int reported_remote_count) { - debug("%s on_remote_removed: remote_count=%d position=%s reported_remote_count=%d", to_string(), - remote_count, position.to_string(), reported_remote_count); - - if (reported_remote_count < 0) - return; - + } + + private void on_remote_updated(Imap.FolderSession session, + Imap.SequenceNumber position, + Imap.FetchedData data) { + // Use the session param rather than remote_session attr since + // it may not be available yet + int remote_count = session.folder.properties.email_total; + debug("%s on_remote_updated: remote_count=%d position=%s", to_string(), + remote_count, position.to_string()); + + this.replay_queue.schedule_server_notification( + new ReplayUpdate(this, remote_count, position, data) + ); + } + + private void on_remote_removed(Imap.FolderSession session, + Imap.SequenceNumber position) { + // Use the session param rather than remote_session attr since + // it may not be available yet + int remote_count = session.folder.properties.email_total; + debug("%s on_remote_removed: remote_count=%d position=%s", + to_string(), remote_count, position.to_string()); + // notify of removal to all pending replay operations replay_queue.notify_remote_removed_position(position); - - // update remote count NOW, as further appended and removed messages can arrive before - // ReplayRemoval executes - // - // something to note at this point: the ExpungeEmail operation marks messages as removed, - // then signals they're removed and reports an adjusted count in its replay_local_async(). - // remote_count is *not* updated, which is why it's safe to do that here without worry. - // similarly, signals are only fired here if marked, so the same EmailIdentifier isn't - // reported twice - remote_count = reported_remote_count; - - replay_queue.schedule_server_notification(new ReplayRemoval(this, reported_remote_count, position)); - } - - // This MUST only be called from ReplayRemoval. - internal async void do_replay_removed_message(int reported_remote_count, Imap.SequenceNumber remote_position) { - debug("%s do_replay_removed_message: current remote_count=%d remote_position=%s reported_remote_count=%d", - to_string(), remote_count, remote_position.value.to_string(), reported_remote_count); - - if (!remote_position.is_valid()) { - debug("%s do_replay_removed_message: ignoring, invalid remote position or count", - to_string()); - - return; - } - - int local_count = -1; - int64 local_position = -1; - - ImapDB.EmailIdentifier? owned_id = null; - try { - // need total count, including those marked for removal, to accurately calculate position - // from server's point of view, not client's - local_count = yield local_folder.get_email_count_async( - ImapDB.Folder.ListFlags.INCLUDE_MARKED_FOR_REMOVE, null); - local_position = remote_position.value - (reported_remote_count + 1 - local_count); - - // zero or negative means the message exists beyond the local vector's range, so - // nothing to do there - if (local_position > 0) { - debug("%s do_replay_removed_message: local_count=%d local_position=%s", to_string(), - local_count, local_position.to_string()); - - owned_id = yield local_folder.get_id_at_async(local_position, null); - } else { - debug("%s do_replay_removed_message: message not stored locally (local_count=%d local_position=%s)", - to_string(), local_count, local_position.to_string()); - } - } catch (Error err) { - debug("%s do_replay_removed_message: unable to determine ID of removed message %s: %s", - to_string(), remote_position.to_string(), err.message); - } - - bool marked = false; - if (owned_id != null) { - debug("%s do_replay_removed_message: detaching from local store Email ID %s", to_string(), - owned_id.to_string()); - try { - // Reflect change in the local store and notify subscribers - yield local_folder.detach_single_email_async(owned_id, out marked, null); - } catch (Error err) { - debug("%s do_replay_removed_message: unable to remove message #%s: %s", to_string(), - remote_position.to_string(), err.message); - } - - // Notify queued replay operations that the email has been removed (by EmailIdentifier) - replay_queue.notify_remote_removed_ids( - Geary.iterate(owned_id).to_array_list()); - } else { - debug("%s do_replay_removed_message: remote_position=%d unknown in local store " - + "(reported_remote_count=%d local_position=%lld local_count=%d)", - to_string(), remote_position.value, reported_remote_count, local_position, local_count); - } - - // for debugging - int new_local_count = -1; - try { - new_local_count = yield local_folder.get_email_count_async( - ImapDB.Folder.ListFlags.INCLUDE_MARKED_FOR_REMOVE, null); - } catch (Error err) { - debug("%s do_replay_removed_message: error fetching new local count: %s", to_string(), - err.message); - } - - // as with on_remote_appended(), only update in local store inside a queue operation, to - // ensure serial commits - try { - yield local_folder.update_remote_selected_message_count(reported_remote_count, null); - } catch (Error err) { - debug("%s do_replay_removed_message: unable to save removed remote count: %s", to_string(), - err.message); - } - - // notify of change ... use "marked-email-removed" for marked email to allow internal code - // to be notified when a removed email is "really" removed - if (owned_id != null) { - Gee.List removed = Geary.iterate(owned_id).to_array_list(); - if (!marked) - notify_email_removed(removed); - else - marked_email_removed(removed); - } - - if (!marked) - notify_email_count_changed(reported_remote_count, CountChangeReason.REMOVED); - - debug("%s do_replay_remove_message: completed, current remote_count=%d " - + "(reported_remote_count=%d local_count=%d starting local_count=%d remote_position=%lld local_position=%lld marked=%s)", - to_string(), remote_count, reported_remote_count, new_local_count, local_count, remote_position.value, - local_position, marked.to_string()); - } - - private void on_remote_disconnected(Imap.ClientSession.DisconnectReason reason) { - debug("on_remote_disconnected: reason=%s", reason.to_string()); - - // reset remote_semaphore to indicate that callers must again wait for the remote to open... - // do this now to avoid race conditions w/ wait_for_open_async() - remote_semaphore.reset(); - - replay_queue.schedule(new ReplayDisconnect(this, reason, false, null)); + + ReplayRemoval op = new ReplayRemoval(this, remote_count, position); + op.email_removed.connect(notify_email_removed); + op.marked_email_removed.connect(notify_marked_email_removed); + op.email_count_changed.connect(notify_email_count_changed); + this.replay_queue.schedule_server_notification(op); } - + // // list email variants // - + public override async Gee.List? list_email_by_id_async(Geary.EmailIdentifier? initial_id, int count, Geary.Email.Field required_fields, Folder.ListFlags flags, Cancellable? cancellable = null) throws Error { @@ -1296,202 +1140,233 @@ check_flags("list_email_by_id_async", flags); if (initial_id != null) check_id("list_email_by_id_async", initial_id); - + if (count == 0) return null; - + // Schedule list operation and wait for completion. ListEmailByID op = new ListEmailByID(this, (ImapDB.EmailIdentifier) initial_id, count, required_fields, flags, cancellable); replay_queue.schedule(op); - + yield op.wait_for_ready_async(cancellable); - + return !op.accumulator.is_empty ? op.accumulator : null; } - + public async override Gee.List? list_email_by_sparse_id_async( Gee.Collection ids, Geary.Email.Field required_fields, Folder.ListFlags flags, Cancellable? cancellable = null) throws Error { check_open("list_email_by_sparse_id_async"); check_flags("list_email_by_sparse_id_async", flags); check_ids("list_email_by_sparse_id_async", ids); - + if (ids.size == 0) return null; - + // Schedule list operation and wait for completion. // TODO: Break up requests to avoid hogging the queue ListEmailBySparseID op = new ListEmailBySparseID(this, (Gee.Collection) ids, required_fields, flags, cancellable); replay_queue.schedule(op); - + yield op.wait_for_ready_async(cancellable); - + return !op.accumulator.is_empty ? op.accumulator : null; } - + public override async Gee.Map? list_local_email_fields_async( Gee.Collection ids, Cancellable? cancellable = null) throws Error { check_open("list_local_email_fields_async"); check_ids("list_local_email_fields_async", ids); - + return yield local_folder.list_email_fields_by_id_async( (Gee.Collection) ids, ImapDB.Folder.ListFlags.NONE, cancellable); } - + public override async Geary.Email fetch_email_async(Geary.EmailIdentifier id, Geary.Email.Field required_fields, Geary.Folder.ListFlags flags, Cancellable? cancellable = null) throws Error { check_open("fetch_email_async"); check_flags("fetch_email_async", flags); check_id("fetch_email_async", id); - - FetchEmail op = new FetchEmail(this, (ImapDB.EmailIdentifier) id, required_fields, flags, - cancellable); + + FetchEmail op = new FetchEmail( + this, + (ImapDB.EmailIdentifier) id, + required_fields, + flags, + cancellable + ); replay_queue.schedule(op); - + yield op.wait_for_ready_async(cancellable); - - if (op.email == null) { - throw new EngineError.NOT_FOUND("Email %s not found in %s", id.to_string(), to_string()); - } else if (!op.email.fields.fulfills(required_fields)) { - throw new EngineError.INCOMPLETE_MESSAGE("Email %s in %s does not fulfill required fields %Xh (has %Xh)", - id.to_string(), to_string(), required_fields, op.email.fields); - } - return op.email; } - - // Helper function for child classes dealing with the delete/archive question. This method will - // mark the message as deleted and expunge it. - protected async void expunge_email_async(Gee.List email_ids, - Cancellable? cancellable) throws Error { + + // Helper function for child classes dealing with the + // delete/archive question. This method will mark the message as + // deleted and expunge it. + protected async void + expunge_email_async(Gee.Collection email_ids, + GLib.Cancellable? cancellable) + throws GLib.Error { check_open("expunge_email_async"); check_ids("expunge_email_async", email_ids); - + RemoveEmail remove = new RemoveEmail(this, (Gee.List) email_ids, cancellable); replay_queue.schedule(remove); - + yield remove.wait_for_ready_async(cancellable); } - + protected async void expunge_all_async(Cancellable? cancellable = null) throws Error { check_open("expunge_all_async"); - + EmptyFolder empty_folder = new EmptyFolder(this, cancellable); replay_queue.schedule(empty_folder); - + yield empty_folder.wait_for_ready_async(cancellable); } - + private void check_open(string method) throws EngineError { if (open_count == 0) throw new EngineError.OPEN_REQUIRED("%s failed: folder %s is not open", method, to_string()); } - + private void check_flags(string method, Folder.ListFlags flags) throws EngineError { if (flags.is_all_set(Folder.ListFlags.LOCAL_ONLY) && flags.is_all_set(Folder.ListFlags.FORCE_UPDATE)) { throw new EngineError.BAD_PARAMETERS("%s %s failed: LOCAL_ONLY and FORCE_UPDATE are mutually exclusive", to_string(), method); } } - + private void check_id(string method, EmailIdentifier id) throws EngineError { if (!(id is ImapDB.EmailIdentifier)) throw new EngineError.BAD_PARAMETERS("Email ID %s is not IMAP Email ID", id.to_string()); } - + private void check_ids(string method, Gee.Collection ids) throws EngineError { foreach (EmailIdentifier id in ids) check_id(method, id); } - - public virtual async void mark_email_async(Gee.List to_mark, - Geary.EmailFlags? flags_to_add, Geary.EmailFlags? flags_to_remove, - Cancellable? cancellable = null) throws Error { + + public virtual async void + mark_email_async(Gee.Collection to_mark, + Geary.EmailFlags? flags_to_add, + Geary.EmailFlags? flags_to_remove, + GLib.Cancellable? cancellable = null) + throws GLib.Error { check_open("mark_email_async"); check_ids("mark_email_async", to_mark); MarkEmail mark = new MarkEmail(this, (Gee.List) to_mark, flags_to_add, flags_to_remove, cancellable); replay_queue.schedule(mark); - + yield mark.wait_for_ready_async(cancellable); } - - public virtual async void copy_email_async(Gee.List to_copy, - Geary.FolderPath destination, Cancellable? cancellable = null) throws Error { + + public virtual async void + copy_email_async(Gee.Collection to_copy, + Geary.FolderPath destination, + GLib.Cancellable? cancellable = null) + throws GLib.Error { + Geary.Folder target = yield this._account.fetch_folder_async(destination); yield copy_email_uids_async(to_copy, destination, cancellable); + this._account.update_folder(target); } - + /** * Returns the destination folder's UIDs for the copied messages. */ - public async Gee.Set? copy_email_uids_async(Gee.List to_copy, - Geary.FolderPath destination, Cancellable? cancellable = null) throws Error { + protected async Gee.Set? + copy_email_uids_async(Gee.Collection to_copy, + Geary.FolderPath destination, + GLib.Cancellable? cancellable = null) + throws GLib.Error { check_open("copy_email_uids_async"); check_ids("copy_email_uids_async", to_copy); - + // watch for copying to this folder, which is treated as a no-op if (destination.equal_to(path)) return null; - + CopyEmail copy = new CopyEmail(this, (Gee.List) to_copy, destination); replay_queue.schedule(copy); - + yield copy.wait_for_ready_async(cancellable); - + return copy.destination_uids.size > 0 ? copy.destination_uids : null; } - public virtual async Geary.Revokable? move_email_async(Gee.List to_move, - Geary.FolderPath destination, Cancellable? cancellable = null) throws Error { + public virtual async Geary.Revokable? move_email_async( + Gee.Collection to_move, + Geary.FolderPath destination, + Cancellable? cancellable = null) + throws Error { check_open("move_email_async"); check_ids("move_email_async", to_move); - + // watch for moving to this folder, which is treated as a no-op if (destination.equal_to(path)) return null; - + MoveEmailPrepare prepare = new MoveEmailPrepare(this, (Gee.List) to_move, cancellable); replay_queue.schedule(prepare); - + yield prepare.wait_for_ready_async(cancellable); - + if (prepare.prepared_for_move == null || prepare.prepared_for_move.size == 0) return null; - - return new RevokableMove(_account, this, destination, prepare.prepared_for_move); + + Geary.Folder target = yield this._account.fetch_folder_async(destination); + return new RevokableMove( + _account, this, target, prepare.prepared_for_move + ); } - + public void schedule_op(ReplayOperation op) throws Error { check_open("schedule_op"); - + replay_queue.schedule(op); } - + public async void exec_op_async(ReplayOperation op, Cancellable? cancellable) throws Error { schedule_op(op); yield op.wait_for_ready_async(cancellable); } - - private void on_email_flags_changed(Gee.Map changed) { - notify_email_flags_changed(changed); + + public override string to_string() { + return "%s (open_count=%d remote_opened=%s)".printf( + base.to_string(), open_count, (this.remote_session != null).to_string() + ); + } + + /** + * Schedules a refresh of the unseen count for the folder. + * + * This will only refresh folders that are not open, since if they + * are open or opening, they will already be updated. Hence it is safe to be called on closed folders. + */ + internal void refresh_unseen() { + if (this.open_count == 0) { + this.refresh_unseen_timer.start(); + } } - + // TODO: A proper public search mechanism; note that this always round-trips to the remote, // doesn't go through the replay queue, and doesn't deal with messages marked for deletion - internal async Geary.EmailIdentifier? find_earliest_email_async(DateTime datetime, + internal async Geary.Email? find_earliest_email_async(DateTime datetime, Geary.EmailIdentifier? before_id, Cancellable? cancellable) throws Error { check_open("find_earliest_email_async"); if (before_id != null) check_id("find_earliest_email_async", before_id); - + Imap.SearchCriteria criteria = new Imap.SearchCriteria(); criteria.is_(Imap.SearchCriterion.since_internaldate(new Imap.InternalDate.from_date_time(datetime))); - + // if before_id available, only search for messages before it if (before_id != null) { Imap.UID? before_uid = yield local_folder.get_uid_async((ImapDB.EmailIdentifier) before_id, @@ -1500,52 +1375,48 @@ throw new EngineError.NOT_FOUND("before_id %s not found in %s", before_id.to_string(), to_string()); } - + criteria.and(Imap.SearchCriterion.message_set( new Imap.MessageSet.uid_range(new Imap.UID(Imap.UID.MIN), before_uid.previous(true)))); } - - debug("%s: find_earliest_email_async: %s", to_string(), criteria.to_string()); - + ServerSearchEmail op = new ServerSearchEmail(this, criteria, Geary.Email.Field.NONE, cancellable); - + // need to check again due to the yield in the above conditional block check_open("find_earliest_email_async.schedule operation"); - + replay_queue.schedule(op); - + yield op.wait_for_ready_async(cancellable); - + // find earliest ID; because all Email comes from Folder, UID should always be present + Geary.Email? earliest = null; ImapDB.EmailIdentifier? earliest_id = null; foreach (Geary.Email email in op.accumulator) { ImapDB.EmailIdentifier email_id = (ImapDB.EmailIdentifier) email.id; - - if (earliest_id == null || email_id.uid.compare_to(earliest_id.uid) < 0) + if (earliest_id == null || email_id.uid.compare_to(earliest_id.uid) < 0) { + earliest = email; earliest_id = email_id; + } } - - debug("%s: find_earliest_email_async: found %s", to_string(), - earliest_id != null ? earliest_id.to_string() : "(null)"); - - return earliest_id; + return earliest; } - + protected async Geary.EmailIdentifier? create_email_async(RFC822.Message rfc822, Geary.EmailFlags? flags, DateTime? date_received, Geary.EmailIdentifier? id, Cancellable? cancellable = null) throws Error { check_open("create_email_async"); if (id != null) check_id("create_email_async", id); - + Error? cancel_error = null; Geary.EmailIdentifier? ret = null; try { CreateEmail create = new CreateEmail(this, rfc822, flags, date_received, cancellable); replay_queue.schedule(create); yield create.wait_for_ready_async(cancellable); - + ret = create.created_id; } catch (Error e) { if (e is IOError.CANCELLED) @@ -1553,28 +1424,156 @@ else throw e; } - + Geary.FolderSupport.Remove? remove_folder = this as Geary.FolderSupport.Remove; - + // Remove old message. if (id != null && remove_folder != null) yield remove_folder.remove_email_async(iterate(id).to_array_list()); - + // If the user cancelled the operation, throw the error here. if (cancel_error != null) throw cancel_error; - + // If the caller cancelled during the remove operation, delete the newly created message to // safely back out. if (cancellable != null && cancellable.is_cancelled() && ret != null && remove_folder != null) yield remove_folder.remove_email_async(iterate(ret).to_array_list()); - + + this._account.update_folder(this); + return ret; } - - public override string to_string() { - return "%s (open_count=%d remote_opened=%s)".printf(base.to_string(), open_count, - remote_opened.to_string()); + + /** Fires a {@link marked_email_removed} signal for this folder. */ + protected virtual void notify_marked_email_removed(Gee.Collection removed) { + marked_email_removed(removed); } -} + private inline void notify_remote_waiters(bool successful) { + try { + this.remote_wait_semaphore.notify_result(successful, null); + } catch (Error err) { + // Can't happen because semaphore has no cancellable + } + } + + /** + * Checks for changes to {@link EmailFlags} after a folder opens. + */ + private async void update_flags(Cancellable cancellable) throws Error { + // Update this to use CHANGEDSINCE FETCH when available, when + // we support IMAP CONDSTORE (Bug 713117). + int chunk_size = FLAG_UPDATE_START_CHUNK; + Geary.EmailIdentifier? lowest = null; + while (get_open_state() != Geary.Folder.OpenState.CLOSED) { + Gee.List? list_local = yield list_email_by_id_async( + lowest, chunk_size, + Geary.Email.Field.FLAGS, + Geary.Folder.ListFlags.LOCAL_ONLY, + cancellable + ); + if (list_local == null || list_local.is_empty) + break; + + // find the lowest for the next iteration + lowest = Geary.EmailIdentifier.sort_emails(list_local).first().id; + + // Get all email identifiers in the local folder mapped to their EmailFlags + Gee.HashMap local_map = + new Gee.HashMap(); + foreach (Geary.Email e in list_local) + local_map.set(e.id, e.email_flags); + + // Fetch e-mail from folder using force update, which will cause the cache to be bypassed + // and the latest to be gotten from the server (updating the cache in the process) + debug("%s: fetching %d flags", this.to_string(), local_map.keys.size); + Gee.List? list_remote = yield list_email_by_sparse_id_async( + local_map.keys, + Email.Field.FLAGS, + Folder.ListFlags.FORCE_UPDATE | + // Updating read/unread count here breaks the unread + // count, so don't do it. See issue #213. + Folder.ListFlags.NO_UNREAD_UPDATE, + cancellable + ); + if (list_remote == null || list_remote.is_empty) + break; + + // Build map of emails that have changed. + Gee.HashMap changed_map = + new Gee.HashMap(); + foreach (Geary.Email e in list_remote) { + if (!local_map.has_key(e.id)) + continue; + + if (!local_map.get(e.id).equal_to(e.email_flags)) + changed_map.set(e.id, e.email_flags); + } + + if (!cancellable.is_cancelled() && changed_map.size > 0) + notify_email_flags_changed(changed_map); + + chunk_size *= 2; + if (chunk_size > FLAG_UPDATE_MAX_CHUNK) { + chunk_size = FLAG_UPDATE_MAX_CHUNK; + } + } + } + + private void on_refresh_unseen() { + // We queue an account operation since the folder itself is + // closed and hence does not have a connection to use for it. + RefreshFolderUnseen op = new RefreshFolderUnseen(this, this._account); + try { + this._account.queue_operation(op); + } catch (Error err) { + // oh well + } + } + + private void on_update_flags() { + this.update_flags.begin( + this.open_cancellable, + (obj, res) => { + try { + this.update_flags.end(res); + } catch (IOError.CANCELLED err) { + // all good + } catch (Error err) { + debug("Error updating flags: %s", err.message); + } + } + ); + } + + private void on_remote_status_notify() { + if (this._account.imap.current_status == CONNECTED) { + this.open_remote_session.begin(); + } + } + + private void on_remote_disconnected(Imap.ClientSession.DisconnectReason reason) { + bool is_error = reason.is_error(); + + // Need to close the remote session immediately to avoid a + // race with it opening again + Geary.Folder.CloseReason remote_reason = is_error + ? Geary.Folder.CloseReason.REMOTE_ERROR + : Geary.Folder.CloseReason.REMOTE_CLOSE; + this.close_remote_session.begin( + remote_reason, + (obj, res) => { + this.close_remote_session.end(res); + // Once closed, if we are closing because an error + // occurred, but the folder is still open and so is + // the pool, try re-establishing the connection. + if (is_error && + this._account.imap.current_status == CONNECTED && + !this.open_cancellable.is_cancelled()) { + this.open_remote_session.begin(); + } + }); + } + +} diff -Nru geary-0.12.4/src/engine/imap-engine/imap-engine-replay-operation.vala geary-3.32.0/src/engine/imap-engine/imap-engine-replay-operation.vala --- geary-0.12.4/src/engine/imap-engine/imap-engine-replay-operation.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-engine/imap-engine-replay-operation.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,56 +1,66 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ +/** + * Base class for folder operations executed by {@link ReplayQueue}. + */ private abstract class Geary.ImapEngine.ReplayOperation : Geary.BaseObject, Gee.Comparable { + /** - * Scope specifies what type of operations (remote, local, or both) are needed by this operation. - * - * What methods are made on the operation depends on the returned Scope: + * Specifies the call scope (local, remote, both) of an operation. * - * LOCAL_AND_REMOTE: replay_local_async() is called. If that method returns COMPLETED, - * no further calls are made. If it returns CONTINUE, replay_remote_async() is called. - * LOCAL_ONLY: replay_local_async() only. replay_remote_async() will never be called. - * REMOTE_ONLY: replay_remote_async() only. replay_local_async() will never be called. + * The methods that are called for the operation depends on the + * returned Scope. * - * See the various replay methods for how backout_local_async() may be called depending on - * this field and those methods' return values. + * * `LOCAL_AND_REMOTE`: replay_local_async() is called. If that + * method returns COMPLETED, no further calls are made. If it + * returns CONTINUE, replay_remote_async() is called. + * * `LOCAL_ONLY`: replay_local_async() only. + * replay_remote_async() will never be called. + * * `REMOTE_ONLY`: replay_remote_async() only. + * replay_local_async() will never be called. + * + * See the various replay methods for how backout_local_async() + * may be called depending on this field and those methods' return + * values. */ public enum Scope { LOCAL_AND_REMOTE, LOCAL_ONLY, REMOTE_ONLY } - + public enum Status { COMPLETED, CONTINUE } - + public enum OnError { THROW, RETRY, - IGNORE + IGNORE_REMOTE } - + public string name { get; set; } public int64 submission_number { get; set; default = -1; } public Scope scope { get; private set; } public OnError on_remote_error { get; protected set; } public int remote_retry_count { get; set; default = 0; } public Error? err { get; private set; default = null; } - public bool notified { get { return semaphore.is_passed(); } } - + public bool notified { get { return semaphore.can_pass; } } + private Nonblocking.Semaphore semaphore = new Nonblocking.Semaphore(); - - public ReplayOperation(string name, Scope scope, OnError on_remote_error = OnError.THROW) { + + protected ReplayOperation(string name, Scope scope, OnError on_remote_error = OnError.THROW) { this.name = name; this.scope = scope; this.on_remote_error = on_remote_error; } - + /** * Notify the operation that a message has been removed by position (SequenceNumber). * @@ -62,8 +72,10 @@ * * This won't be called while replay_local_async() or replay_remote_async() are executing. */ - public abstract void notify_remote_removed_position(Imap.SequenceNumber removed); - + public virtual void notify_remote_removed_position(Imap.SequenceNumber removed) { + // noop + } + /** * Notify the operation that a message has been removed by UID (EmailIdentifier). * @@ -80,8 +92,10 @@ * * This won't be called while replay_local_async() or replay_remote_async() are executing. */ - public abstract void notify_remote_removed_ids(Gee.Collection ids); - + public virtual void notify_remote_removed_ids(Gee.Collection ids) { + // noop + } + /** * Add to the Collection EmailIdentifiers that will be removed in replay_remote_async(). * @@ -94,77 +108,99 @@ * invocation (i.e. the Folder closed before the server could notify the engine that they were * removed). */ - public abstract void get_ids_to_be_remote_removed(Gee.Collection ids); - + public virtual void get_ids_to_be_remote_removed(Gee.Collection ids) { + // noop + } + /** + * Executes the local parts of this operation, if any. + * * See Scope for conditions where this method will be called. * - * Returns: - * COMPLETED: the operation has completed and no further calls should be made. - * CONTINUE: The local operation has completed and the remote portion must be executed as - * well. This is treated as COMPLETED if get_scope() returns LOCAL_ONLY. + * If an error is thrown, {@link backout_local_async} will + * *not* be executed. * - * If Error thrown: - * backout_local_async() will *not* be executed. - */ - public abstract async Status replay_local_async() throws Error; - + * @return {@link Status.COMPLETED} if the operation has completed + * and no further calls should be made, else {@link + * Status.CONTINUE} if the local operation has completed and the + * remote portion must be executed as well. This is treated as + * `COMPLETED` if get_scope() returns {@link Scope.LOCAL_ONLY}. + */ + public virtual async Status replay_local_async() + throws GLib.Error { + if (this.scope != Scope.REMOTE_ONLY) { + throw new GLib.IOError.NOT_SUPPORTED("Local operation is not implemented"); + } + return (this.scope == Scope.LOCAL_ONLY) + ? Status.COMPLETED : Status.CONTINUE; + } + /** + * Executes the remote parts of this operation, if any. + * * See Scope for conditions where this method will be called. * - * Returns: - * COMPLETED: the operation has completed and no further calls should be made. - * CONTINUE: Treated as COMPLETED. + * Passed a folder session with the current folder selected. * - * If Error thrown: - * backout_local_async() will be executed only if scope is LOCAL_AND_REMOTE. + * If an error is thrown, {@link backout_local_async} will be + * executed only if scope is LOCAL_AND_REMOTE. */ - public abstract async Status replay_remote_async() throws Error; - + public virtual async void replay_remote_async(Imap.FolderSession remote) + throws GLib.Error { + if (this.scope != Scope.LOCAL_ONLY) { + throw new GLib.IOError.NOT_SUPPORTED("Remote operation is not implemented"); + } + } + /** - * See Scope, replay_local_async(), and replay_remote_async() for conditions for this where this - * will be called. + * Reverts any local effects of this operation. + * + * See {@link Scope}, {@link replay_local_async}, and {@link + * replay_remote_async} for conditions for this where this will be + * called. */ - public abstract async void backout_local_async() throws Error; - + public virtual async void backout_local_async() throws Error { + // noop + } + /** * Completes when the operation has completed execution. If the operation threw an error * during execution, it will be thrown here. */ public async void wait_for_ready_async(Cancellable? cancellable = null) throws Error { yield semaphore.wait_async(cancellable); - + if (err != null) throw err; } - + // Can only be called once internal void notify_ready(Error? err) { - assert(!semaphore.is_passed()); - + assert(!semaphore.can_pass); + this.err = err; - + try { semaphore.notify(); } catch (Error notify_err) { debug("Unable to notify replay operation as ready: [%s] %s", name, notify_err.message); } } - + public abstract string describe_state(); - + // The Comparable interface is merely to ensure the ReplayQueue sorts operations by their // submission order, ensuring that retry operations are retried in order of submissions public int compare_to(ReplayOperation other) { assert(submission_number >= 0); assert(other.submission_number >= 0); - + return (int) (submission_number - other.submission_number).clamp(-1, 1); } - + public string to_string() { string state = describe_state(); - + return String.is_empty(state) ? "[%s] %s remote_retry_count=%d".printf(submission_number.to_string(), name, remote_retry_count) : "[%s] %s: %s remote_retry_count=%d".printf(submission_number.to_string(), name, state, diff -Nru geary-0.12.4/src/engine/imap-engine/imap-engine-replay-queue.vala geary-3.32.0/src/engine/imap-engine/imap-engine-replay-queue.vala --- geary-0.12.4/src/engine/imap-engine/imap-engine-replay-queue.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-engine/imap-engine-replay-queue.vala 2019-03-17 13:39:29.000000000 +0000 @@ -4,125 +4,145 @@ * (version 2.1 or later). See the COPYING file in this distribution. */ +/** + * Interleaves IMAP operations to maintain consistent sequence numbering. + * + * The replay queue manages and executes operations originating both + * locally and from the server for a specific IMAP mailbox so as to + * ensure the execution of the operations maintains consistent. + */ private class Geary.ImapEngine.ReplayQueue : Geary.BaseObject { - // this value is high because delays between back-to-back unsolicited notifications have been - // see as high as 250ms + + // Maximum number of times a retry-able operation should be + // retried before failing. It's set to 1 since we only attempt to + // retry if there's some transient error, so if it doesn't work + // second time around, it probably won't work at all. + private const int MAX_OP_RETRIES = 1; + + // This value is high because delays between back-to-back + // unsolicited notifications have been see as high as 250ms private const int NOTIFICATION_QUEUE_WAIT_MSEC = 1000; - + + private enum State { OPEN, CLOSING, CLOSED } - + private class CloseReplayQueue : ReplayOperation { + + bool local_closed = false; + bool remote_closed = false; + public CloseReplayQueue() { // LOCAL_AND_REMOTE to make sure this operation is flushed all the way down the pipe - base ("CloseReplayQueue", ReplayOperation.Scope.LOCAL_AND_REMOTE, OnError.IGNORE); + base ("CloseReplayQueue", ReplayOperation.Scope.LOCAL_AND_REMOTE, OnError.IGNORE_REMOTE); } - - public override void notify_remote_removed_position(Imap.SequenceNumber removed) { - } - - public override void notify_remote_removed_ids(Gee.Collection ids) { - } - - public override void get_ids_to_be_remote_removed(Gee.Collection ids) { - } - - public override async ReplayOperation.Status replay_local_async() throws Error { + + public override async ReplayOperation.Status replay_local_async() + throws GLib.Error { + this.local_closed = true; return Status.CONTINUE; } - - public override async ReplayOperation.Status replay_remote_async() throws Error { - return Status.COMPLETED; - } - - public override async void backout_local_async() throws Error { - // nothing to backout (and should never be called, to boot) + + // This doesn't actually get executed, but it's here for + // completeness + public override async void replay_remote_async(Imap.FolderSession remote) + throws GLib.Error { + this.remote_closed = true; } - + public override string describe_state() { - return ""; + return "local_closed: %s, remote_closed: %s".printf( + this.local_closed.to_string(), this.remote_closed.to_string() + ); } } - + public int local_count { get { return local_queue.size; } } - + public int remote_count { get { return remote_queue.size; } } - + private weak MinimalFolder owner; - private Nonblocking.Mailbox local_queue = new Nonblocking.Mailbox(); - private Nonblocking.Mailbox remote_queue = new Nonblocking.Mailbox(); + private Nonblocking.Queue local_queue = + new Nonblocking.Queue.fifo(); + private Nonblocking.Queue remote_queue = + new Nonblocking.Queue.fifo(); private ReplayOperation? local_op_active = null; private ReplayOperation? remote_op_active = null; private Gee.ArrayList notification_queue = new Gee.ArrayList(); private Scheduler.Scheduled? notification_timer = null; private int64 next_submission_number = 0; private State state = State.OPEN; - + private Cancellable remote_wait_cancellable = new Cancellable(); + public virtual signal void scheduled(ReplayOperation op) { - Logging.debug(Logging.Flag.REPLAY, "[%s] ReplayQueue::scheduled: %s %s", to_string(), - op.to_string()); + Logging.debug( + Logging.Flag.REPLAY, + "[%s] ReplayQueue::scheduled: %s", + to_string(), + op.to_string() + ); } - + public virtual signal void locally_executing(ReplayOperation op) { Logging.debug(Logging.Flag.REPLAY, "[%s] ReplayQueue::locally-executing: %s", to_string(), op.to_string()); } - + public virtual signal void locally_executed(ReplayOperation op, bool continuing) { Logging.debug(Logging.Flag.REPLAY, "[%s] ReplayQueue::locally-executed: %s continuing=%s", to_string(), op.to_string(), continuing.to_string()); } - + public virtual signal void remotely_executing(ReplayOperation op) { Logging.debug(Logging.Flag.REPLAY, "[%s] ReplayQueue::remotely-executing: %s", to_string(), op.to_string()); } - + public virtual signal void remotely_executed(ReplayOperation op) { Logging.debug(Logging.Flag.REPLAY, "[%s] ReplayQueue::remotely-executed: %s", to_string(), op.to_string()); } - + public virtual signal void backing_out(ReplayOperation op, Error? err) { Logging.debug(Logging.Flag.REPLAY, "[%s] ReplayQueue::backout-out: %s err=%s", to_string(), op.to_string(), (err != null) ? err.message : "(null)"); } - + public virtual signal void backed_out(ReplayOperation op, Error? err) { Logging.debug(Logging.Flag.REPLAY, "[%s] ReplayQueue::backed-out: %s err=%s", to_string(), op.to_string(), (err != null) ? err.message : "(null)"); } - + public virtual signal void backout_failed(ReplayOperation op, Error? backout_err) { Logging.debug(Logging.Flag.REPLAY, "[%s] ReplayQueue::backout-failed: %s err=%s", to_string(), op.to_string(), (backout_err != null) ? backout_err.message : "(null)"); } - + public virtual signal void completed(ReplayOperation op) { Logging.debug(Logging.Flag.REPLAY, "[%s] ReplayQueue::completed: %s", to_string(), op.to_string()); } - + public virtual signal void failed(ReplayOperation op) { Logging.debug(Logging.Flag.REPLAY, "[%s] ReplayQueue::failed: %s", to_string(), op.to_string()); } - + public virtual signal void closing() { Logging.debug(Logging.Flag.REPLAY, "[%s] ReplayQueue::closing", to_string()); } - + public virtual signal void closed() { Logging.debug(Logging.Flag.REPLAY, "[%s] ReplayQueue::closed", to_string()); } - + /** * ReplayQueue accepts a NonblockingReportingSemaphore which, when signaled, returns * true if the remote folder is ready and open, false if not (closing or closed), and @@ -132,17 +152,17 @@ */ public ReplayQueue(MinimalFolder owner) { this.owner = owner; - + // fire off background queue processors do_replay_local_async.begin(); do_replay_remote_async.begin(); } - + ~ReplayQueue() { if (notification_timer != null) notification_timer.cancel(); } - + /** * Returns false if the operation was not schedule (queue already closed). */ @@ -151,14 +171,14 @@ if (state != State.OPEN && !(op is CloseReplayQueue)) { debug("Unable to schedule replay operation %s on %s: replay queue closed", op.to_string(), to_string()); - + return false; } - + // assign a submission number to operation ... this *must* happen before it's submitted to // any Mailbox op.submission_number = next_submission_number++; - + // note that in order for this to work (i.e. for sent and received operations to be handled // in order), it's *vital* that even REMOTE_ONLY operations go through the local queue, // only being scheduled on the remote queue *after* local operations ahead of it have @@ -166,10 +186,10 @@ bool is_scheduled = local_queue.send(op); if (is_scheduled) scheduled(op); - + return is_scheduled; } - + /** * Schedules a ReplayOperation created due to a server notification. * @@ -194,30 +214,29 @@ */ public bool schedule_server_notification(ReplayOperation op) { if (state != State.OPEN) { - debug("Unable to schedule notification operation %s on %s: replay queue closed", op.to_string(), - to_string()); - + debug("Unable to schedule notification operation %s on %s: replay queue closed", + op.to_string(), to_string()); return false; } - + notification_queue.add(op); - + // reschedule timeout every time new operation is added if (notification_timer != null) notification_timer.cancel(); - + notification_timer = Scheduler.after_msec(NOTIFICATION_QUEUE_WAIT_MSEC, on_notification_timeout); - + return true; } - + private bool on_notification_timeout() { if (notification_queue.size == 0) return false; - + debug("%s: Scheduling %d held server notification operations", owner.to_string(), notification_queue.size); - + // no new operations in timeout span, add them all to the "real" queue foreach (ReplayOperation notification_op in notification_queue) { if (!schedule(notification_op)) { @@ -225,12 +244,12 @@ to_string()); } } - + notification_queue.clear(); - + return false; } - + /** * This call gives all enqueued remote replay operations a chance to update their own state * due to a message being removed due to an unsolicited notification from the server) @@ -242,16 +261,16 @@ notify_remote_removed_position_collection(local_queue.get_all(), local_op_active, pos); notify_remote_removed_position_collection(remote_queue.get_all(), remote_op_active, pos); } - + private void notify_remote_removed_position_collection(Gee.Collection replay_ops, ReplayOperation? active, Imap.SequenceNumber pos) { foreach (ReplayOperation replay_op in replay_ops) replay_op.notify_remote_removed_position(pos); - + if (active != null) active.notify_remote_removed_position(pos); } - + /** * This call gives all enqueued remote replay operations a chance to update their own state * due to a message being removed (either during normalization or an unsolicited notification @@ -264,16 +283,16 @@ notify_remote_removed_ids_collection(local_queue.get_all(), local_op_active, ids); notify_remote_removed_ids_collection(remote_queue.get_all(), remote_op_active, ids); } - + private void notify_remote_removed_ids_collection(Gee.Collection replay_ops, ReplayOperation? active, Gee.Collection ids) { foreach (ReplayOperation replay_op in replay_ops) replay_op.notify_remote_removed_ids(ids); - + if (active != null) active.notify_remote_removed_ids(ids); } - + /** * Returns all ImapDb.EmailIdentifiers for enqueued ReplayOperations waiting for * replay_remote_async() that are planning to be removed on the server. @@ -281,11 +300,11 @@ public void get_ids_to_be_remote_removed(Gee.Collection ids) { foreach (ReplayOperation replay_op in remote_queue.get_all()) replay_op.get_ids_to_be_remote_removed(ids); - + if (remote_op_active != null) remote_op_active.get_ids_to_be_remote_removed(ids); } - + /** * Closes the {@link ReplayQueue}. * @@ -300,51 +319,54 @@ public async void close_async(bool flush_pending, Cancellable? cancellable = null) throws Error { if (state != State.OPEN) return; - + // cancel notification queue timeout if (notification_timer != null) notification_timer.cancel(); - + // piggyback on the notification timer callback to flush notification operations if (flush_pending) on_notification_timeout(); - + // mark as closed now to prevent further scheduling ... ReplayClose gets special // consideration in schedule() state = State.CLOSING; closing(); - - // if not flushing pending, clear out all waiting operations, backing out any that need to - // be backed out - if (!flush_pending) + + // if not flushing pending, stop waiting for a remote session + // and clear out all waiting operations, backing out any that + // need to be backed out + if (!flush_pending) { + this.remote_wait_cancellable.cancel(); yield clear_pending_async(cancellable); - + } + // flush a ReplayClose operation down the pipe so all working operations complete CloseReplayQueue close_op = new CloseReplayQueue(); bool is_scheduled = schedule(close_op); assert(is_scheduled); - + yield close_op.wait_for_ready_async(cancellable); - + state = State.CLOSED; closed(); } - + private async void clear_pending_async(Cancellable? cancellable) { // note that this merely clears the queue; disabling the timer is performed in close_async notification_queue.clear(); - + // clear the local queue; nothing more to do there local_queue.clear(); - + // have to backout elements that have executed locally but not remotely // clear the remote queue before backing out, otherwise the queue might proceed while // yielding Gee.List remote_list = new Gee.ArrayList(); remote_list.add_all(remote_queue.get_all()); - + remote_queue.clear(); - + foreach (ReplayOperation op in remote_list) { try { yield op.backout_local_async(); @@ -353,26 +375,26 @@ } } } - + private async void do_replay_local_async() { bool queue_running = true; while (queue_running) { ReplayOperation op; try { - op = yield local_queue.recv_async(); + op = yield local_queue.receive(); } catch (Error recv_err) { debug("Unable to receive next replay operation on local queue %s: %s", to_string(), recv_err.message); - + break; } - + local_op_active = op; - + // If this is a Close operation, shut down the queue after processing it if (op is CloseReplayQueue) queue_running = false; - + bool local_execute = false; bool remote_enqueue = false; switch (op.scope) { @@ -380,24 +402,24 @@ local_execute = true; remote_enqueue = true; break; - + case ReplayOperation.Scope.LOCAL_ONLY: local_execute = true; remote_enqueue = false; break; - + case ReplayOperation.Scope.REMOTE_ONLY: local_execute = false; remote_enqueue = true; break; - + default: assert_not_reached(); } - + if (local_execute) { locally_executing(op); - + try { switch (yield op.replay_local_async()) { case ReplayOperation.Status.COMPLETED: @@ -405,26 +427,26 @@ remote_enqueue = false; op.notify_ready(null); break; - + case ReplayOperation.Status.CONTINUE: // don't touch remote_enqueue; if already false, CONTINUE is treated as // COMPLETED. if (!remote_enqueue) op.notify_ready(null); break; - + default: assert_not_reached(); } } catch (Error replay_err) { debug("Replay local error for %s on %s: %s", op.to_string(), to_string(), replay_err.message); - + op.notify_ready(replay_err); remote_enqueue = false; } } - + if (remote_enqueue) { if (!remote_queue.send(op)) { debug("Unable to enqueue operation %s for %s remote operation", op.to_string(), @@ -435,23 +457,23 @@ // next stage assert(op.notified); } - + if (local_execute) locally_executed(op, remote_enqueue); - + if (!remote_enqueue) { if (op.err == null) completed(op); else failed(op); } - + local_op_active = null; } - + debug("ReplayQueue.do_replay_local_async %s exiting", to_string()); } - + private async void do_replay_remote_async() { bool folder_opened = true; bool queue_running = true; @@ -459,70 +481,76 @@ // wait for the next operation ... do this *before* waiting for remote ReplayOperation op; try { - op = yield remote_queue.recv_async(); + op = yield remote_queue.receive(); } catch (Error recv_err) { debug("Unable to receive next replay operation on remote queue %s: %s", to_string(), recv_err.message); - + break; } - + remote_op_active = op; - + // ReplayClose means this queue (and the folder) are closing, so handle errors a little // differently bool is_close_op = op is CloseReplayQueue; if (is_close_op) queue_running = false; - + // wait until the remote folder is opened (or throws an exception, in which case closed) + Imap.FolderSession? remote = null; try { - if (!is_close_op && folder_opened && state == State.OPEN) - yield owner.wait_for_open_async(); + if (!is_close_op && folder_opened && state == State.OPEN) { + remote = yield owner.claim_remote_session( + this.remote_wait_cancellable + ); + } } catch (Error remote_err) { debug("Folder %s closed or failed to open, remote replay queue closing: %s", - to_string(), remote_err.message); - + to_string(), remote_err.message); + // not open folder_opened = false; - + // fall through } - + remotely_executing(op); - + Error? remote_err = null; - if (folder_opened || is_close_op) { + if (remote != null) { if (op.remote_retry_count > 0) debug("Retrying op %s on %s", op.to_string(), to_string()); - + try { - yield op.replay_remote_async(); + yield op.replay_remote_async(remote); } catch (Error replay_err) { debug("Replay remote error for %s on %s: %s (%s)", op.to_string(), to_string(), replay_err.message, op.on_remote_error.to_string()); - - // If a hard failure and operation allows remote replay and not closing, - // re-schedule now - if ((op.on_remote_error == ReplayOperation.OnError.RETRY) - && is_hard_failure(replay_err) - && state == State.OPEN) { + + // If a recoverable failure and operation allows + // remote replay and not closing, re-schedule now + if (op.on_remote_error == RETRY && + op.remote_retry_count <= MAX_OP_RETRIES && + is_recoverable_failure(replay_err) && + state == State.OPEN) { debug("Schedule op retry %s on %s", op.to_string(), to_string()); - + // the Folder will disconnect and reconnect due to the hard error and // wait_for_open_async() will block this command until reconnected and // normalized op.remote_retry_count++; remote_queue.send(op); - + continue; - } else if (op.on_remote_error == ReplayOperation.OnError.IGNORE) { + } else if (op.on_remote_error == IGNORE_REMOTE && + is_remote_error(replay_err)) { // ignoring error, simply notify as completed and continue - debug("Ignoring op %s on %s", op.to_string(), to_string()); + debug("Ignoring remote error op %s on %s", op.to_string(), to_string()); } else { - debug("Throwing remote error for op %s on %s: %s", op.to_string(), to_string(), + debug("Throwing error for op %s on %s: %s", op.to_string(), to_string(), replay_err.message); - + // store for notification remote_err = replay_err; } @@ -530,37 +558,37 @@ } else if (!is_close_op) { remote_err = new EngineError.SERVER_UNAVAILABLE("Folder %s not available", owner.to_string()); } - + // COMPLETED == CONTINUE, only exception of interest here if not closing if (remote_err != null && !is_close_op) { try { backing_out(op, remote_err); - + yield op.backout_local_async(); - + backed_out(op, remote_err); } catch (Error backout_err) { backout_failed(op, backout_err); } } - + // use the remote error (not the backout error) for the operation's completion // state op.notify_ready(remote_err); - + remotely_executed(op); - + if (op.err == null) completed(op); else failed(op); - + remote_op_active = null; } - + debug("ReplayQueue.do_replay_remote_async %s exiting", to_string()); } - + public string to_string() { return "ReplayQueue:%s (notification=%d local=%d local_active=%s remote=%d remote_active=%s)".printf( owner.to_string(), notification_queue.size, local_queue.size, (local_op_active != null).to_string(), diff -Nru geary-0.12.4/src/engine/imap-engine/imap-engine-revokable-committed-move.vala geary-3.32.0/src/engine/imap-engine/imap-engine-revokable-committed-move.vala --- geary-0.12.4/src/engine/imap-engine/imap-engine-revokable-committed-move.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-engine/imap-engine-revokable-committed-move.vala 2019-03-17 13:39:29.000000000 +0000 @@ -14,7 +14,7 @@ private FolderPath source; private FolderPath destination; private Gee.Set destination_uids; - + public RevokableCommittedMove(GenericAccount account, FolderPath source, FolderPath destination, Gee.Set destination_uids) { this.account = account; @@ -22,39 +22,34 @@ this.destination = destination; this.destination_uids = destination_uids; } - + protected override async void internal_revoke_async(Cancellable? cancellable) throws Error { - Imap.Folder? detached_destination = null; + Imap.FolderSession? session = null; try { // use a detached folder to quickly open, issue command, and leave, without full // normalization that MinimalFolder requires - detached_destination = yield account.fetch_detached_folder_async(destination, cancellable); - - yield detached_destination.open_async(cancellable); - + session = yield this.account.claim_folder_session(destination, cancellable); foreach (Imap.MessageSet msg_set in Imap.MessageSet.uid_sparse(destination_uids)) { // don't use Cancellable to try to make operations atomic - yield detached_destination.copy_email_async(msg_set, source, null); - yield detached_destination.remove_email_async(msg_set.to_list(), null); - + yield session.copy_email_async(msg_set, source, null); + yield session.remove_email_async(msg_set.to_list(), null); + if (cancellable != null && cancellable.is_cancelled()) throw new IOError.CANCELLED("Revoke cancelled"); } - + notify_revoked(); + + Geary.Folder target = yield this.account.fetch_folder_async(this.destination); + this.account.update_folder(target); } finally { - if (detached_destination != null) { - try { - yield detached_destination.close_async(cancellable); - } catch (Error err) { - // ignored - } + if (session != null) { + yield this.account.release_folder_session(session); } - set_invalid(); } } - + protected override async void internal_commit_async(Cancellable? cancellable) throws Error { // pretty simple: already committed, so done notify_committed(null); diff -Nru geary-0.12.4/src/engine/imap-engine/imap-engine-revokable-move.vala geary-3.32.0/src/engine/imap-engine/imap-engine-revokable-move.vala --- geary-0.12.4/src/engine/imap-engine/imap-engine-revokable-move.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-engine/imap-engine-revokable-move.vala 2019-03-17 13:39:29.000000000 +0000 @@ -14,41 +14,41 @@ */ private class Geary.ImapEngine.RevokableMove : Revokable { - private const int COMMIT_TIMEOUT_SEC = 60; - + private const int COMMIT_TIMEOUT_SEC = 5; + private GenericAccount account; - private ImapEngine.MinimalFolder source; - private FolderPath destination; + private MinimalFolder source; + private Geary.Folder destination; private Gee.Set move_ids; - - public RevokableMove(GenericAccount account, ImapEngine.MinimalFolder source, FolderPath destination, + + public RevokableMove(GenericAccount account, MinimalFolder source, Geary.Folder destination, Gee.Set move_ids) { base (COMMIT_TIMEOUT_SEC); - + this.account = account; this.source = source; this.destination = destination; this.move_ids = move_ids; - + account.folders_available_unavailable.connect(on_folders_available_unavailable); source.email_removed.connect(on_source_email_removed); source.marked_email_removed.connect(on_source_email_removed); source.closing.connect(on_source_closing); } - + ~RevokableMove() { account.folders_available_unavailable.disconnect(on_folders_available_unavailable); source.email_removed.disconnect(on_source_email_removed); source.marked_email_removed.disconnect(on_source_email_removed); source.closing.disconnect(on_source_closing); - + // if still valid, schedule operation so its executed if (valid && source.get_open_state() != Folder.OpenState.CLOSED) { debug("Freeing revokable, scheduling move %d emails from %s to %s", move_ids.size, source.path.to_string(), destination.to_string()); - + try { - source.schedule_op(new MoveEmailCommit(source, move_ids, destination, null)); + source.schedule_op(new MoveEmailCommit(source, move_ids, destination.path, null)); } catch (Error err) { debug("Move from %s to %s failed: %s", source.path.to_string(), destination.to_string(), err.message); @@ -58,62 +58,86 @@ source.path.to_string(), source.get_open_state().to_string()); } } - + protected override async void internal_revoke_async(Cancellable? cancellable) throws Error { try { - yield source.exec_op_async(new MoveEmailRevoke(source, move_ids, cancellable), - cancellable); - + MoveEmailRevoke op = new MoveEmailRevoke( + source, move_ids, cancellable + ); + yield source.exec_op_async(op, cancellable); + // valid must still be true before firing notify_revoked(); + + yield op.wait_for_ready_async(cancellable); + this.account.update_folder(this.destination); } finally { set_invalid(); } } - + protected override async void internal_commit_async(Cancellable? cancellable) throws Error { try { - MoveEmailCommit op = new MoveEmailCommit(source, move_ids, destination, cancellable); + MoveEmailCommit op = new MoveEmailCommit(source, move_ids, destination.path, cancellable); yield source.exec_op_async(op, cancellable); - + // valid must still be true before firing - notify_committed(new RevokableCommittedMove(account, source.path, destination, op.destination_uids)); + notify_committed(new RevokableCommittedMove(account, source.path, destination.path, op.destination_uids)); + + yield op.wait_for_ready_async(cancellable); + this.account.update_folder(this.destination); } finally { set_invalid(); } } - - private void on_folders_available_unavailable(Gee.List? available, Gee.List? unavailable) { + + private void on_folders_available_unavailable(Gee.Collection? available, + Gee.Collection? unavailable) { // look for either of the folders going away if (unavailable != null) { foreach (Folder folder in unavailable) { - if (folder.path.equal_to(source.path) || folder.path.equal_to(destination)) { + if (folder.path.equal_to(source.path) || + folder.path.equal_to(destination.path)) { set_invalid(); - break; } } } } - + private void on_source_email_removed(Gee.Collection ids) { // one-way switch if (!valid) return; - + foreach (EmailIdentifier id in ids) move_ids.remove((ImapDB.EmailIdentifier) id); - + if (move_ids.size <= 0) set_invalid(); } - + private void on_source_closing(Gee.List final_ops) { if (!valid) return; - - final_ops.add(new MoveEmailCommit(source, move_ids, destination, null)); + + MoveEmailCommit op = new MoveEmailCommit( + source, move_ids, destination.path, null + ); + final_ops.add(op); set_invalid(); + + // Capture these for the closure below, since once it gets + // invoked, this instance may no longer exist. + GenericAccount account = this.account; + Geary.Folder destination = this.destination; + op.wait_for_ready_async.begin(null, (obj, res) => { + try { + op.wait_for_ready_async.end(res); + account.update_folder(destination); + } catch (Error err) { + // Oh well + } + }); } } - diff -Nru geary-0.12.4/src/engine/imap-engine/imap-engine-send-replay-operation.vala geary-3.32.0/src/engine/imap-engine/imap-engine-send-replay-operation.vala --- geary-0.12.4/src/engine/imap-engine/imap-engine-send-replay-operation.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-engine/imap-engine-send-replay-operation.vala 2019-03-17 13:39:29.000000000 +0000 @@ -4,21 +4,25 @@ * (version 2.1 or later). See the COPYING file in this distribution. */ + +/** + * A replay operation for a user-initiated operation. + */ private abstract class Geary.ImapEngine.SendReplayOperation : Geary.ImapEngine.ReplayOperation { protected SendReplayOperation(string name, ReplayOperation.OnError on_remote_error = OnError.THROW) { base (name, ReplayOperation.Scope.LOCAL_AND_REMOTE, on_remote_error); } - + protected SendReplayOperation.only_local(string name, ReplayOperation.OnError on_remote_error = OnError.THROW) { base (name, ReplayOperation.Scope.LOCAL_ONLY, on_remote_error); } - + protected SendReplayOperation.only_remote(string name, ReplayOperation.OnError on_remote_error = OnError.THROW) { base (name, ReplayOperation.Scope.REMOTE_ONLY, on_remote_error); } - + public override void notify_remote_removed_position(Imap.SequenceNumber removed) { // we've worked very hard to keep positional addressing out of the SendReplayOperations } -} +} diff -Nru geary-0.12.4/src/engine/imap-engine/imap-engine.vala geary-3.32.0/src/engine/imap-engine/imap-engine.vala --- geary-0.12.4/src/engine/imap-engine/imap-engine.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-engine/imap-engine.vala 2019-03-17 13:39:29.000000000 +0000 @@ -6,76 +6,51 @@ namespace Geary.ImapEngine { -private int init_count = 0; -private Gee.HashMap? account_synchronizers = null; - -internal void init() { - if (init_count++ != 0) - return; - - account_synchronizers = new Gee.HashMap(); - - // create a FullAccountSync object for each Account as it comes and goes - Engine.instance.account_available.connect(on_account_available); - Engine.instance.account_unavailable.connect(on_account_unavailable); -} - -private GenericAccount? get_imap_account(AccountInformation account_info) { - try { - return Engine.instance.get_account_instance(account_info) as GenericAccount; - } catch (Error err) { - debug("Unable to get account instance %s: %s", account_info.id, err.message); + /** + * Determines if retrying an operation might succeed or not. + * + * A recoverable failure is defined as one that may not occur + * again if the operation that caused it is retried, without + * needing to make some change in the mean time. For example, + * recoverable failures may occur due to transient network + * connectivity issues or server rate limiting. On the other hand, + * an unrecoverable failure is due to some problem that will not + * succeed if tried again unless some action is taken, such as + * authentication failures, protocol parsing errors, and so on. + */ + private static bool is_recoverable_failure(GLib.Error err) { + return ( + err is EngineError.SERVER_UNAVAILABLE || + err is IOError.BROKEN_PIPE || + err is IOError.BUSY || + err is IOError.CONNECTION_CLOSED || + err is IOError.NOT_CONNECTED || + err is IOError.TIMED_OUT || + err is ImapError.NOT_CONNECTED || + err is ImapError.TIMED_OUT || + err is ImapError.UNAVAILABLE + ); } - - return null; -} -private void on_account_available(AccountInformation account_info) { - GenericAccount? imap_account = get_imap_account(account_info); - if (imap_account == null) - return; - - assert(!account_synchronizers.has_key(imap_account)); - account_synchronizers.set(imap_account, new AccountSynchronizer(imap_account)); -} - -private void on_account_unavailable(AccountInformation account_info) { - GenericAccount? imap_account = get_imap_account(account_info); - if (imap_account == null) - return; - - AccountSynchronizer? account_synchronizer = account_synchronizers.get(imap_account); - assert(account_synchronizer != null); - - account_synchronizer.stop_async.begin(on_synchronizer_stopped); -} - -private void on_synchronizer_stopped(Object? source, AsyncResult result) { - AccountSynchronizer account_synchronizer = (AccountSynchronizer) source; - account_synchronizer.stop_async.end(result); - - bool removed = account_synchronizers.unset(account_synchronizer.account); - assert(removed); -} - -/** - * A hard failure is defined as one due to hardware or connectivity issues, where a soft failure - * is due to software reasons, like credential failure or protocol violation. - */ -private static bool is_hard_failure(Error err) { - // CANCELLED is not a hard error - if (err is IOError.CANCELLED) - return false; - - // Treat other errors -- most likely IOErrors -- as hard failures - if (!(err is ImapError) && !(err is EngineError)) - return true; - - return err is ImapError.NOT_CONNECTED - || err is ImapError.TIMED_OUT - || err is ImapError.SERVER_ERROR - || err is EngineError.SERVER_UNAVAILABLE; -} + /** + * Determines if an error was caused by the remote host or not. + */ + private static bool is_remote_error(GLib.Error err) { + return ( + err is EngineError.NOT_FOUND || + err is EngineError.SERVER_UNAVAILABLE || + err is IOError.CONNECTION_CLOSED || + err is IOError.CONNECTION_REFUSED || + err is IOError.HOST_UNREACHABLE || + err is IOError.MESSAGE_TOO_LARGE || + err is IOError.NETWORK_UNREACHABLE || + err is IOError.NOT_CONNECTED || + err is IOError.PROXY_AUTH_FAILED || + err is IOError.PROXY_FAILED || + err is IOError.PROXY_NEED_AUTH || + err is IOError.PROXY_NOT_ALLOWED || + err is ImapError + ); + } } - diff -Nru geary-0.12.4/src/engine/imap-engine/other/imap-engine-other-account.vala geary-3.32.0/src/engine/imap-engine/other/imap-engine-other-account.vala --- geary-0.12.4/src/engine/imap-engine/other/imap-engine-other-account.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-engine/other/imap-engine-other-account.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,24 +1,34 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. + * Copyright 2019 Michael Gratton * * This software is licensed under the GNU Lesser General Public License - * (version 2.1 or later). See the COPYING file in this distribution. + * (version 2.1 or later). See the COPYING file in this distribution. */ private class Geary.ImapEngine.OtherAccount : Geary.ImapEngine.GenericAccount { - public OtherAccount(string name, AccountInformation account_information, - Imap.Account remote, ImapDB.Account local) { - base (name, account_information, remote, local); + + public OtherAccount(AccountInformation config, + ImapDB.Account local, + Endpoint incoming_remote, + Endpoint outgoing_remote) { + base(config, local, incoming_remote, outgoing_remote); } - protected override MinimalFolder new_folder(Geary.FolderPath path, Imap.Account remote_account, - ImapDB.Account local_account, ImapDB.Folder local_folder) { + protected override MinimalFolder new_folder(ImapDB.Folder local_folder) { + FolderPath path = local_folder.get_path(); SpecialFolderType type; - if (Imap.MailboxSpecifier.folder_path_is_inbox(path)) + if (Imap.MailboxSpecifier.folder_path_is_inbox(path)) { type = SpecialFolderType.INBOX; - else + } else { type = local_folder.get_properties().attrs.get_special_folder_type(); - - return new OtherFolder(this, remote_account, local_account, local_folder, type); + // There can be only one Inbox + if (type == SpecialFolderType.INBOX) { + type = SpecialFolderType.NONE; + } + } + + return new OtherFolder(this, local_folder, type); } -} +} diff -Nru geary-0.12.4/src/engine/imap-engine/other/imap-engine-other-folder.vala geary-3.32.0/src/engine/imap-engine/other/imap-engine-other-folder.vala --- geary-0.12.4/src/engine/imap-engine/other/imap-engine-other-folder.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-engine/other/imap-engine-other-folder.vala 2019-03-17 13:39:29.000000000 +0000 @@ -5,9 +5,10 @@ */ private class Geary.ImapEngine.OtherFolder : GenericFolder { - public OtherFolder(OtherAccount account, Imap.Account remote, ImapDB.Account local, - ImapDB.Folder local_folder, SpecialFolderType special_folder_type) { - base (account, remote, local, local_folder, special_folder_type); + public OtherFolder(OtherAccount account, + ImapDB.Folder local_folder, + SpecialFolderType special_folder_type) { + base (account, local_folder, special_folder_type); } } diff -Nru geary-0.12.4/src/engine/imap-engine/outlook/imap-engine-outlook-account.vala geary-3.32.0/src/engine/imap-engine/outlook/imap-engine-outlook-account.vala --- geary-0.12.4/src/engine/imap-engine/outlook/imap-engine-outlook-account.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-engine/outlook/imap-engine-outlook-account.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,54 +1,59 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. + * Copyright 2019 Michael Gratton * * This software is licensed under the GNU Lesser General Public License - * (version 2.1 or later). See the COPYING file in this distribution. + * (version 2.1 or later). See the COPYING file in this distribution. */ private class Geary.ImapEngine.OutlookAccount : Geary.ImapEngine.GenericAccount { - public static Geary.Endpoint generate_imap_endpoint() { - Geary.Endpoint endpoint = new Geary.Endpoint( - "imap-mail.outlook.com", - Imap.ClientConnection.DEFAULT_PORT_SSL, - Geary.Endpoint.Flags.SSL, - Imap.ClientConnection.RECOMMENDED_TIMEOUT_SEC); - // As of June 2016, outlook.com's IMAP servers have a bug - // where a large number (~50) of pipelined STATUS commands on - // mailboxes with many messages will eventually cause it to - // break command parsing and return a BAD response, causing us - // to drop the connection. Limit the number of pipelined - // commands per batch to work around this. See b.g.o Bug - // 766552 - endpoint.max_pipeline_batch_size = 25; - return endpoint; + + + public static void setup_account(AccountInformation account) { + account.save_sent = false; } - public static Geary.Endpoint generate_smtp_endpoint() { - return new Geary.Endpoint( - "smtp-mail.outlook.com", - Smtp.ClientConnection.DEFAULT_PORT_STARTTLS, - Geary.Endpoint.Flags.STARTTLS, - Smtp.ClientConnection.DEFAULT_TIMEOUT_SEC); + public static void setup_service(ServiceInformation service) { + switch (service.protocol) { + case Protocol.IMAP: + service.host = "imap-mail.outlook.com"; + service.port = Imap.IMAP_TLS_PORT; + service.transport_security = TlsNegotiationMethod.TRANSPORT; + break; + + case Protocol.SMTP: + service.host = "smtp-mail.outlook.com"; + service.port = Smtp.SUBMISSION_PORT; + service.transport_security = TlsNegotiationMethod.START_TLS; + break; + } } - public OutlookAccount(string name, AccountInformation account_information, Imap.Account remote, - ImapDB.Account local) { - base (name, account_information, remote, local); + + public OutlookAccount(AccountInformation config, + ImapDB.Account local, + Endpoint incoming_remote, + Endpoint outgoing_remote) { + base(config, local, incoming_remote, outgoing_remote); } - protected override MinimalFolder new_folder(Geary.FolderPath path, Imap.Account remote_account, - ImapDB.Account local_account, ImapDB.Folder local_folder) { - // use the Folder's attributes to determine if it's a special folder type, unless it's - // INBOX; that's determined by name - SpecialFolderType special_folder_type; - if (Imap.MailboxSpecifier.folder_path_is_inbox(path)) - special_folder_type = SpecialFolderType.INBOX; - else - special_folder_type = local_folder.get_properties().attrs.get_special_folder_type(); - - if (special_folder_type == Geary.SpecialFolderType.DRAFTS) - return new OutlookDraftsFolder(this, remote_account, local_account, local_folder, special_folder_type); - - return new OutlookFolder(this, remote_account, local_account, local_folder, special_folder_type); + protected override MinimalFolder new_folder(ImapDB.Folder local_folder) { + FolderPath path = local_folder.get_path(); + SpecialFolderType type; + if (Imap.MailboxSpecifier.folder_path_is_inbox(path)) { + type = SpecialFolderType.INBOX; + } else { + type = local_folder.get_properties().attrs.get_special_folder_type(); + // There can be only one Inbox + if (type == SpecialFolderType.INBOX) { + type = SpecialFolderType.NONE; + } + } + + if (type == Geary.SpecialFolderType.DRAFTS) + return new OutlookDraftsFolder(this, local_folder, type); + + return new OutlookFolder(this, local_folder, type); } -} +} diff -Nru geary-0.12.4/src/engine/imap-engine/outlook/imap-engine-outlook-drafts-folder.vala geary-3.32.0/src/engine/imap-engine/outlook/imap-engine-outlook-drafts-folder.vala --- geary-0.12.4/src/engine/imap-engine/outlook/imap-engine-outlook-drafts-folder.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-engine/outlook/imap-engine-outlook-drafts-folder.vala 2019-03-17 13:39:29.000000000 +0000 @@ -12,8 +12,9 @@ * saved at all. */ private class Geary.ImapEngine.OutlookDraftsFolder : MinimalFolder { - public OutlookDraftsFolder(OutlookAccount account, Imap.Account remote, ImapDB.Account local, - ImapDB.Folder local_folder, SpecialFolderType special_folder_type) { - base (account, remote, local, local_folder, special_folder_type); + public OutlookDraftsFolder(OutlookAccount account, + ImapDB.Folder local_folder, + SpecialFolderType special_folder_type) { + base(account, local_folder, special_folder_type); } } diff -Nru geary-0.12.4/src/engine/imap-engine/outlook/imap-engine-outlook-folder.vala geary-3.32.0/src/engine/imap-engine/outlook/imap-engine-outlook-folder.vala --- geary-0.12.4/src/engine/imap-engine/outlook/imap-engine-outlook-folder.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-engine/outlook/imap-engine-outlook-folder.vala 2019-03-17 13:39:29.000000000 +0000 @@ -5,9 +5,9 @@ */ private class Geary.ImapEngine.OutlookFolder : GenericFolder { - public OutlookFolder(OutlookAccount account, Imap.Account remote, ImapDB.Account local, - ImapDB.Folder local_folder, SpecialFolderType special_folder_type) { - base (account, remote, local, local_folder, special_folder_type); + public OutlookFolder(OutlookAccount account, + ImapDB.Folder local_folder, + SpecialFolderType special_folder_type) { + base(account, local_folder, special_folder_type); } } - diff -Nru geary-0.12.4/src/engine/imap-engine/replay-ops/imap-engine-abstract-list-email.vala geary-3.32.0/src/engine/imap-engine/replay-ops/imap-engine-abstract-list-email.vala --- geary-0.12.4/src/engine/imap-engine/replay-ops/imap-engine-abstract-list-email.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-engine/replay-ops/imap-engine-abstract-list-email.vala 2019-03-17 13:39:29.000000000 +0000 @@ -4,88 +4,109 @@ * (version 2.1 or later). See the COPYING file in this distribution. */ +/** + * A base class for building replay operations that list messages. + */ private abstract class Geary.ImapEngine.AbstractListEmail : Geary.ImapEngine.SendReplayOperation { + private static int total_fetches_avoided = 0; - + private class RemoteBatchOperation : Nonblocking.BatchOperation { // IN - public MinimalFolder owner; + public Imap.FolderSession remote; + public ImapDB.Folder local; public Imap.MessageSet msg_set; public Geary.Email.Field unfulfilled_fields; public Geary.Email.Field required_fields; - + public bool update_unread; + // OUT public Gee.Set created_ids = new Gee.HashSet(); - - public RemoteBatchOperation(MinimalFolder owner, Imap.MessageSet msg_set, - Geary.Email.Field unfulfilled_fields, Geary.Email.Field required_fields) { - this.owner = owner; + + public RemoteBatchOperation(Imap.FolderSession remote, + ImapDB.Folder local, + Imap.MessageSet msg_set, + Geary.Email.Field unfulfilled_fields, + Geary.Email.Field required_fields, + bool update_unread) { + this.remote = remote; + this.local = local; this.msg_set = msg_set; this.unfulfilled_fields = unfulfilled_fields; this.required_fields = required_fields; + this.update_unread = update_unread; } - + public override async Object? execute_async(Cancellable? cancellable) throws Error { // fetch from remote folder - Gee.List? list = yield owner.remote_folder.list_email_async(msg_set, - unfulfilled_fields, cancellable); + Gee.List? list = yield this.remote.list_email_async( + msg_set, unfulfilled_fields, cancellable + ); if (list == null || list.size == 0) return null; - + // TODO: create_or_merge_email_async() should only write if something has changed - Gee.Map created_or_merged = yield owner.local_folder.create_or_merge_email_async( - list, cancellable); + Gee.Map created_or_merged = + yield this.local.create_or_merge_email_async( + list, + this.update_unread, + cancellable + ); for (int ctr = 0; ctr < list.size; ctr++) { Geary.Email email = list[ctr]; - + // if created, add to id pool if (created_or_merged.get(email)) created_ids.add(email.id); - + // if remote email doesn't fulfills all required fields, fetch full and return that // TODO: Need a sparse ID fetch in ImapDB.Folder to do this all at once if (!email.fields.fulfills(required_fields)) { - email = yield owner.local_folder.fetch_email_async((ImapDB.EmailIdentifier) email.id, - required_fields, ImapDB.Folder.ListFlags.NONE, cancellable); + email = yield this.local.fetch_email_async( + (ImapDB.EmailIdentifier) email.id, + required_fields, + ImapDB.Folder.ListFlags.NONE, + cancellable + ); list[ctr] = email; } } - + return list; } } - + // The accumulated Email from the list operation. Should only be accessed once the operation // has completed. public Gee.List accumulator = new Gee.ArrayList(); - + protected MinimalFolder owner; protected Geary.Email.Field required_fields; protected Cancellable? cancellable; protected Folder.ListFlags flags; - + private Gee.HashMap unfulfilled = new Gee.HashMap(); - - public AbstractListEmail(string name, MinimalFolder owner, Geary.Email.Field required_fields, + + protected AbstractListEmail(string name, MinimalFolder owner, Geary.Email.Field required_fields, Folder.ListFlags flags, Cancellable? cancellable) { - base(name, OnError.IGNORE); - + base(name, OnError.IGNORE_REMOTE); + this.owner = owner; this.required_fields = required_fields; this.cancellable = cancellable; this.flags = flags; } - + protected void add_unfulfilled_fields(Imap.UID? uid, Geary.Email.Field unfulfilled_fields) { assert(uid != null); assert(uid.is_valid()); - + if (!unfulfilled.has_key(uid)) unfulfilled.set(uid, unfulfilled_fields); else unfulfilled.set(uid, unfulfilled.get(uid) | unfulfilled_fields); } - + protected void add_many_unfulfilled_fields(Gee.Collection? uids, Geary.Email.Field unfulfilled_fields) { if (uids != null) { @@ -93,18 +114,18 @@ add_unfulfilled_fields(uid, unfulfilled_fields); } } - + protected int get_unfulfilled_count() { return unfulfilled.size; } - + public override void notify_remote_removed_ids(Gee.Collection ids) { // remove email already picked up from local store ... for email reported via the // callback, too late Collection.remove_if(accumulator, (email) => { return ids.contains((ImapDB.EmailIdentifier) email.id); }); - + // remove from unfulfilled list, as there's now nothing to fetch from the server // NOTE: Requires UID to work; this *should* always work, as the EmailIdentifier should // be originating from the database, not the Imap.Folder layer @@ -113,37 +134,35 @@ unfulfilled.unset(id.uid); } } - - public override void get_ids_to_be_remote_removed(Gee.Collection ids) { - } - + // Child class should execute its own calls *before* calling this base method - public override async ReplayOperation.Status replay_remote_async() throws Error { + public override async void replay_remote_async(Imap.FolderSession remote) + throws GLib.Error { // only deal with unfulfilled email, child class must deal with everything else if (unfulfilled.size == 0) - return ReplayOperation.Status.COMPLETED; - + return; + // since list and search commands ahead of this one in the queue may have fulfilled some of // the emails thought to be unfulfilled when first checked locally, look for them now int fetches_avoided = yield remove_fulfilled_uids_async(); if (fetches_avoided > 0) { total_fetches_avoided += fetches_avoided; - + debug("[%s] %d previously-fulfilled fetches avoided in list operation, %d total", owner.to_string(), fetches_avoided, total_fetches_avoided); - + // if all fulfilled, emails were added to accumulator in remove call, so done if (unfulfilled.size == 0) - return ReplayOperation.Status.COMPLETED; + return; } - + // convert UID -> needed fields mapping to needed fields -> UIDs, as they can be grouped // and submitted at same time Gee.HashMultiMap reverse_unfulfilled = new Gee.HashMultiMap< Geary.Email.Field, Imap.UID>(); foreach (Imap.UID uid in unfulfilled.keys) reverse_unfulfilled.set(unfulfilled.get(uid), uid); - + // schedule operations to remote for each set of email with unfulfilled fields and merge // in results, pulling out the entire email Nonblocking.Batch batch = new Nonblocking.Batch(); @@ -151,161 +170,148 @@ Gee.Collection unfulfilled_uids = reverse_unfulfilled.get(unfulfilled_fields); if (unfulfilled_uids.size == 0) continue; - + Gee.List msg_sets = Imap.MessageSet.uid_sparse(unfulfilled_uids); foreach (Imap.MessageSet msg_set in msg_sets) { - RemoteBatchOperation remote_op = new RemoteBatchOperation(owner, msg_set, - unfulfilled_fields, required_fields); + RemoteBatchOperation remote_op = new RemoteBatchOperation( + remote, + this.owner.local_folder, + msg_set, + unfulfilled_fields, + required_fields, + !this.flags.is_any_set(NO_UNREAD_UPDATE) + ); batch.add(remote_op); } } - + yield batch.execute_all_async(cancellable); batch.throw_first_exception(); - + Gee.ArrayList result_list = new Gee.ArrayList(); Gee.HashSet created_ids = new Gee.HashSet(); foreach (int batch_id in batch.get_ids()) { Gee.List? list = (Gee.List?) batch.get_result(batch_id); if (list != null && list.size > 0) { result_list.add_all(list); - + RemoteBatchOperation op = (RemoteBatchOperation) batch.get_operation(batch_id); created_ids.add_all(op.created_ids); } } - + // report merged emails if (result_list.size > 0) accumulator.add_all(result_list); - + // signal if (created_ids.size > 0) { owner.replay_notify_email_inserted(created_ids); owner.replay_notify_email_locally_inserted(created_ids); } - - return ReplayOperation.Status.COMPLETED; } - - protected async Trillian is_fully_expanded_async() throws Error { - int remote_count; - owner.get_remote_counts(out remote_count, null); - - // if unknown (unconnected), say so - if (remote_count < 0) - return Trillian.UNKNOWN; - - // include marked for removed in the count in case this is being called while a removal - // is in process, in which case don't want to expand vector this moment because the - // vector is in flux - int local_count_with_marked = yield owner.local_folder.get_email_count_async( - ImapDB.Folder.ListFlags.INCLUDE_MARKED_FOR_REMOVE, cancellable); - - return Trillian.from_boolean(local_count_with_marked >= remote_count); - } - - // Adds everything in the expansion to the unfulfilled set with ImapDB's field requirements ... - // UIDs are returned if anything else needs to be added to them - protected async Gee.Set? expand_vector_async(Imap.UID? initial_uid, int count) throws Error { - // watch out for situations where the entire folder is represented locally (i.e. no - // expansion necessary) - int remote_count = owner.get_remote_counts(null, null); - if (remote_count < 0) - return null; - + + /** + * Expands the owning folder's vector. + * + * Lists on the remote messages needed to fulfill ImapDB's + * requirements from `initial_uid` (inclusive) forward to the + * start of the vector if the OLDEST_TO_NEWEST flag is set, else + * from `initial_uid` (inclusive) back at most by `count` number + * of messages. If `initial_uid` is null, the start or end of the + * vector is used, respectively. + * + * The returned UIDs are those added to the vector, which can then + * be examined and added to the messages to be fulfilled if + * needed. + */ + protected async Gee.Set? expand_vector_async(Imap.FolderSession remote, + Imap.UID? initial_uid, + int count) + throws GLib.Error { + debug("%s: expanding vector...", owner.to_string()); + int remote_count = remote.folder.properties.email_total; + // include marked for removed in the count in case this is being called while a removal // is in process, in which case don't want to expand vector this moment because the // vector is in flux int local_count = yield owner.local_folder.get_email_count_async( ImapDB.Folder.ListFlags.INCLUDE_MARKED_FOR_REMOVE, cancellable); - + // watch out for attempts to expand vector when it's expanded as far as it will go if (local_count >= remote_count) return null; - - // determine low and high position for expansion ... default in most code paths for high - // is the SequenceNumber just below the lowest known message, unless no local messages - // are present - Imap.SequenceNumber? low_pos = null; - Imap.SequenceNumber? high_pos = null; - if (local_count > 0) - high_pos = new Imap.SequenceNumber(Numeric.int_floor(remote_count - local_count, 1)); - + + // Determine low and high position for expansion. The vector + // start position is based on the assumption that the vector + // end is the same as the remote end. + int64 vector_start = (remote_count - local_count + 1); + int64 low_pos = -1; + int64 high_pos = -1; + int64 initial_pos = -1; + + if (initial_uid != null) { + Gee.Map? map = + yield remote.uid_to_position_async( + new Imap.MessageSet.uid(initial_uid), cancellable + ); + Imap.SequenceNumber? pos = map.get(initial_uid); + if (pos != null) { + initial_pos = pos.value; + } + } + + // Determine low and high position for expansion if (flags.is_oldest_to_newest()) { - if (initial_uid == null) { - // if oldest to newest and initial-id is null, then start at the bottom - low_pos = new Imap.SequenceNumber(Imap.SequenceNumber.MIN); - } else { - Gee.Map? map = yield owner.remote_folder.uid_to_position_async( - new Imap.MessageSet.uid(initial_uid), cancellable); - if (map == null || map.size == 0 || !map.has_key(initial_uid)) { - debug("%s: Unable to expand vector for initial_uid=%s: unable to convert to position", - to_string(), initial_uid.to_string()); - - return null; - } - - low_pos = map.get(initial_uid); + low_pos = Imap.SequenceNumber.MIN; + if (initial_pos > Imap.SequenceNumber.MIN) { + low_pos = initial_pos; } + high_pos = vector_start - 1; } else { - // newest to oldest - // - // if initial_id is null or no local earliest UID, then vector expansion is simple: - // merely count backwards from the top of the locally available vector - if (initial_uid == null || local_count == 0) { - low_pos = new Imap.SequenceNumber(Numeric.int_floor((remote_count - local_count) - count, 1)); - - // don't set high_pos, leave null to use symbolic "highest" in MessageSet - high_pos = null; + // Newest to oldest. + if (initial_pos <= Imap.SequenceNumber.MIN) { + high_pos = remote_count; + low_pos = Numeric.int64_floor( + high_pos - count + 1, Imap.SequenceNumber.MIN + ); } else { - // not so simple; need to determine the *remote* position of the earliest local - // UID and count backward from that; if no UIDs present, then it's as if no initial_id - // is specified. - // - // low position: count backwards; note that it's possible this will overshoot and - // pull in more email than technically required, but without a round-trip to the - // server to determine the position number of a particular UID, this makes sense - assert(high_pos != null); - low_pos = new Imap.SequenceNumber( - Numeric.int64_floor((high_pos.value - count) + 1, 1)); + high_pos = Numeric.int64_floor( + initial_pos, vector_start - 1 + ); + low_pos = Numeric.int64_floor( + initial_pos - (count - 1), Imap.SequenceNumber.MIN + ); } } - - // low_pos must be defined by this point - assert(low_pos != null); - - if (high_pos != null && low_pos.value > high_pos.value) { - debug("%s: Aborting vector expansion, low_pos=%s > high_pos=%s", owner.to_string(), - low_pos.to_string(), high_pos.to_string()); - + + if (low_pos > high_pos) { + debug("%s: Aborting vector expansion, low_pos=%s > high_pos=%s", + owner.to_string(), low_pos.to_string(), high_pos.to_string()); return null; } - - Imap.MessageSet msg_set; - int64 actual_count = -1; - if (high_pos != null) { - msg_set = new Imap.MessageSet.range_by_first_last(low_pos, high_pos); - actual_count = (high_pos.value - low_pos.value) + 1; - } else { - msg_set = new Imap.MessageSet.range_to_highest(low_pos); - } - + + Imap.MessageSet msg_set = new Imap.MessageSet.range_by_first_last( + new Imap.SequenceNumber(low_pos), + new Imap.SequenceNumber(high_pos) + ); + int64 actual_count = (high_pos - low_pos) + 1; + debug("%s: Performing vector expansion using %s for initial_uid=%s count=%d actual_count=%s local_count=%d remote_count=%d oldest_to_newest=%s", owner.to_string(), msg_set.to_string(), (initial_uid != null) ? initial_uid.to_string() : "(null)", count, actual_count.to_string(), local_count, remote_count, flags.is_oldest_to_newest().to_string()); - - Gee.List? list = yield owner.remote_folder.list_email_async(msg_set, + + Gee.List? list = yield remote.list_email_async(msg_set, Geary.Email.Field.NONE, cancellable); - + Gee.Set uids = new Gee.HashSet(); if (list != null) { // add all the new email to the unfulfilled list, which ensures (when replay_remote_async // is called) that the fields are downloaded and added to the database foreach (Geary.Email email in list) uids.add(((ImapDB.EmailIdentifier) email.id).uid); - + // remove any already stored locally Gee.Collection? ids = yield owner.local_folder.get_ids_async(uids, ImapDB.Folder.ListFlags.INCLUDE_MARKED_FOR_REMOVE, @@ -316,42 +322,42 @@ uids.remove(id.uid); } } - + // for the remainder (not in local store), fetch the required fields add_many_unfulfilled_fields(uids, ImapDB.Folder.REQUIRED_FIELDS); } - + debug("%s: Vector expansion completed (%d new email)", owner.to_string(), (uids != null) ? uids.size : 0); - + return uids != null && uids.size > 0 ? uids : null; } - + private async int remove_fulfilled_uids_async() throws Error { // if the update is forced, don't rely on cached database, have to go to the horse's mouth if (flags.is_force_update()) return 0; - + ImapDB.Folder.ListFlags list_flags = ImapDB.Folder.ListFlags.from_folder_flags(flags); - + Gee.Set? unfulfilled_ids = yield owner.local_folder.get_ids_async( unfulfilled.keys, list_flags, cancellable); if (unfulfilled_ids == null || unfulfilled_ids.size == 0) return 0; - + Gee.Map? local_fields = yield owner.local_folder.list_email_fields_by_id_async(unfulfilled_ids, list_flags, cancellable); if (local_fields == null || local_fields.size == 0) return 0; - + // For each identifier, if now fulfilled in the database, fetch it, add it to the accumulator, // and remove it from the unfulfilled map -- one network operation saved int fetch_avoided = 0; foreach (ImapDB.EmailIdentifier id in local_fields.keys) { if (!local_fields.get(id).fulfills(required_fields)) continue; - + try { Email email = yield owner.local_folder.fetch_email_async(id, required_fields, list_flags, cancellable); @@ -359,26 +365,21 @@ } catch (Error err) { if (err is IOError.CANCELLED) throw err; - + // some problem locally, do the network round-trip continue; } - + // got it, don't fetch from remote unfulfilled.unset(id.uid); fetch_avoided++; } - + return fetch_avoided; } - - public override async void backout_local_async() throws Error { - // R/O, no backout - } - + public override string describe_state() { return "required_fields=%Xh local_only=%s force_update=%s".printf(required_fields, flags.is_local_only().to_string(), flags.is_force_update().to_string()); } } - diff -Nru geary-0.12.4/src/engine/imap-engine/replay-ops/imap-engine-copy-email.vala geary-3.32.0/src/engine/imap-engine/replay-ops/imap-engine-copy-email.vala --- geary-0.12.4/src/engine/imap-engine/replay-ops/imap-engine-copy-email.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-engine/replay-ops/imap-engine-copy-email.vala 2019-03-17 13:39:29.000000000 +0000 @@ -6,18 +6,18 @@ private class Geary.ImapEngine.CopyEmail : Geary.ImapEngine.SendReplayOperation { public Gee.Set destination_uids = new Gee.HashSet(); - + private MinimalFolder engine; private Gee.HashSet to_copy = new Gee.HashSet(); private Geary.FolderPath destination; private Cancellable? cancellable; - public CopyEmail(MinimalFolder engine, Gee.List to_copy, + public CopyEmail(MinimalFolder engine, Gee.List to_copy, Geary.FolderPath destination, Cancellable? cancellable = null) { base("CopyEmail", OnError.RETRY); - + this.engine = engine; - + this.to_copy.add_all(to_copy); this.destination = destination; this.cancellable = cancellable; @@ -26,45 +26,39 @@ public override void notify_remote_removed_ids(Gee.Collection ids) { to_copy.remove_all(ids); } - - public override void get_ids_to_be_remote_removed(Gee.Collection ids) { - } - + public override async ReplayOperation.Status replay_local_async() throws Error { if (to_copy.size == 0) return ReplayOperation.Status.COMPLETED; - + // The local DB will be updated when the remote folder is opened and we see a new message // existing there. return ReplayOperation.Status.CONTINUE; } - - public override async ReplayOperation.Status replay_remote_async() throws Error { - if (to_copy.size == 0) - return ReplayOperation.Status.COMPLETED; - - Gee.Set? uids = yield engine.local_folder.get_uids_async(to_copy, - ImapDB.Folder.ListFlags.NONE, cancellable); - - if (uids != null && uids.size > 0) { - Gee.List msg_sets = Imap.MessageSet.uid_sparse(uids); - foreach (Imap.MessageSet msg_set in msg_sets) { - Gee.Map? src_dst_uids = yield engine.remote_folder.copy_email_async( - msg_set, destination, cancellable); - if (src_dst_uids != null) - destination_uids.add_all(src_dst_uids.values); + + public override async void replay_remote_async(Imap.FolderSession remote) + throws GLib.Error { + if (to_copy.size > 0) { + Gee.Set? uids = yield engine.local_folder.get_uids_async( + to_copy, ImapDB.Folder.ListFlags.NONE, cancellable + ); + + if (uids != null && uids.size > 0) { + Gee.List msg_sets = Imap.MessageSet.uid_sparse(uids); + foreach (Imap.MessageSet msg_set in msg_sets) { + Gee.Map? src_dst_uids = + yield remote.copy_email_async( + msg_set, destination, cancellable + ); + if (src_dst_uids != null) + destination_uids.add_all(src_dst_uids.values); + } } } - - return ReplayOperation.Status.COMPLETED; - } - - public override async void backout_local_async() throws Error { - // Nothing to undo. } public override string describe_state() { return "%d email IDs to %s".printf(to_copy.size, destination.to_string()); } -} +} diff -Nru geary-0.12.4/src/engine/imap-engine/replay-ops/imap-engine-create-email.vala geary-3.32.0/src/engine/imap-engine/replay-ops/imap-engine-create-email.vala --- geary-0.12.4/src/engine/imap-engine/replay-ops/imap-engine-create-email.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-engine/replay-ops/imap-engine-create-email.vala 2019-03-17 13:39:29.000000000 +0000 @@ -6,79 +6,74 @@ private class Geary.ImapEngine.CreateEmail : Geary.ImapEngine.SendReplayOperation { public Geary.EmailIdentifier? created_id { get; private set; default = null; } - + private MinimalFolder engine; private RFC822.Message? rfc822; private Geary.EmailFlags? flags; private DateTime? date_received; private Cancellable? cancellable; - + public CreateEmail(MinimalFolder engine, RFC822.Message rfc822, Geary.EmailFlags? flags, DateTime? date_received, Cancellable? cancellable) { base.only_remote("CreateEmail", OnError.RETRY); - + this.engine = engine; - + this.rfc822 = rfc822; this.flags = flags; this.date_received = date_received; this.cancellable = cancellable; } - - public override async ReplayOperation.Status replay_local_async() throws Error { - return ReplayOperation.Status.CONTINUE; - } - - public override void notify_remote_removed_ids(Gee.Collection ids) { - } - - public override void get_ids_to_be_remote_removed(Gee.Collection ids) { - } - - public override async void backout_local_async() throws Error { - } - - public override string describe_state() { - return ""; - } - - public override async ReplayOperation.Status replay_remote_async() throws Error { + + public override async void replay_remote_async(Imap.FolderSession remote) + throws GLib.Error { // Deal with cancellable manually since create_email_async cannot be cancelled. if (cancellable.is_cancelled()) throw new IOError.CANCELLED("CreateEmail op cancelled immediately"); - + // use IMAP APPEND command on remote folders, which doesn't require opening a folder ... // if retrying after a successful create, rfc822 will be null if (rfc822 != null) - created_id = yield engine.remote_folder.create_email_async(rfc822, flags, date_received); - + created_id = yield remote.create_email_async(rfc822, flags, date_received); + // because this command retries, the create completed, remove the RFC822 message to prevent // creating it twice rfc822 = null; - + // If the user cancelled the operation, we need to wipe the new message to keep this // operation atomic. if (cancellable.is_cancelled()) { if (created_id != null) { - yield engine.remote_folder.remove_email_async( - new Imap.MessageSet.uid(((ImapDB.EmailIdentifier) created_id).uid).to_list(), null); + yield remote.remove_email_async( + new Imap.MessageSet.uid(((ImapDB.EmailIdentifier) created_id).uid).to_list(), + null + ); } - + throw new IOError.CANCELLED("CreateEmail op cancelled after create"); } - - if (created_id == null) - return ReplayOperation.Status.COMPLETED; - - // TODO: need to prevent gaps that may occur here - Geary.Email created = new Geary.Email(created_id); - Gee.Map results = yield engine.local_folder.create_or_merge_email_async( - Geary.iterate(created).to_array_list(), cancellable); - if (results.size > 0) - created_id = Collection.get_first(results.keys).id; - else - created_id = null; - - return ReplayOperation.Status.COMPLETED; + + if (created_id != null) { + // TODO: need to prevent gaps that may occur here + Geary.Email created = new Geary.Email(created_id); + Gee.Map results = + yield this.engine.local_folder.create_or_merge_email_async( + Geary.iterate(created).to_array_list(), + true, + this.cancellable + ); + if (results.size > 0) { + created_id = Collection.get_first(results.keys).id; + } else { + created_id = null; + } + } } + + public override string describe_state() { + return "created_id: %s".printf( + this.created_id != null ? this.created_id.to_string() : "none" + ); + } + } diff -Nru geary-0.12.4/src/engine/imap-engine/replay-ops/imap-engine-empty-folder.vala geary-3.32.0/src/engine/imap-engine/replay-ops/imap-engine-empty-folder.vala --- geary-0.12.4/src/engine/imap-engine/replay-ops/imap-engine-empty-folder.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-engine/replay-ops/imap-engine-empty-folder.vala 2019-03-17 13:39:29.000000000 +0000 @@ -14,64 +14,58 @@ private Cancellable? cancellable; private Gee.Set? removed_ids = null; private int original_count = 0; - + public EmptyFolder(MinimalFolder engine, Cancellable? cancellable) { base("EmptyFolder", OnError.RETRY); - + this.engine = engine; this.cancellable = cancellable; } - - public override void notify_remote_removed_ids(Gee.Collection ids) { - } - + public override async ReplayOperation.Status replay_local_async() throws Error { - original_count = engine.get_remote_counts(null, null); - + this.original_count = this.engine.properties.email_total; // because this value is only used for reporting count changes, offer best-possible service - if (original_count < 0) - original_count = 0; - + if (this.original_count < 0) + this.original_count = 0; + // mark everything in the folder as removed removed_ids = yield engine.local_folder.mark_removed_async(null, true, cancellable); - + // if local folder is not empty, report all as being removed if (removed_ids != null) { if (removed_ids.size > 0) engine.replay_notify_email_removed(removed_ids); - + int new_count = Numeric.int_floor(original_count - removed_ids.size, 0); if (new_count != original_count) engine.replay_notify_email_count_changed(new_count, Geary.Folder.CountChangeReason.REMOVED); } - + return ReplayOperation.Status.CONTINUE; } - + public override void get_ids_to_be_remote_removed(Gee.Collection ids) { if (removed_ids != null) ids.add_all(removed_ids); } - - public override async ReplayOperation.Status replay_remote_async() throws Error { + + public override async void replay_remote_async(Imap.FolderSession remote) + throws GLib.Error { // STORE and EXPUNGE using positional addressing: "1:*" Imap.MessageSet msg_set = new Imap.MessageSet.range_to_highest( new Imap.SequenceNumber(Imap.SequenceNumber.MIN)); - - yield engine.remote_folder.remove_email_async(msg_set.to_list(), cancellable); - - return ReplayOperation.Status.COMPLETED; + yield remote.remove_email_async(msg_set.to_list(), cancellable); } - + public override async void backout_local_async() throws Error { if (removed_ids != null && removed_ids.size > 0) { yield engine.local_folder.mark_removed_async(removed_ids, false, cancellable); engine.replay_notify_email_inserted(removed_ids); } - + engine.replay_notify_email_count_changed(original_count, Geary.Folder.CountChangeReason.INSERTED); } - + public override string describe_state() { return "removed_ids.size=%d".printf((removed_ids != null) ? removed_ids.size : 0); } diff -Nru geary-0.12.4/src/engine/imap-engine/replay-ops/imap-engine-fetch-email.vala geary-3.32.0/src/engine/imap-engine/replay-ops/imap-engine-fetch-email.vala --- geary-0.12.4/src/engine/imap-engine/replay-ops/imap-engine-fetch-email.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-engine/replay-ops/imap-engine-fetch-email.vala 2019-03-17 13:39:29.000000000 +0000 @@ -6,7 +6,7 @@ private class Geary.ImapEngine.FetchEmail : Geary.ImapEngine.SendReplayOperation { public Email? email = null; - + private MinimalFolder engine; private ImapDB.EmailIdentifier id; private Email.Field required_fields; @@ -15,123 +15,133 @@ private Cancellable? cancellable; private Imap.UID? uid = null; private bool remote_removed = false; - + public FetchEmail(MinimalFolder engine, ImapDB.EmailIdentifier id, Email.Field required_fields, Folder.ListFlags flags, Cancellable? cancellable) { // Unlike the list operations, fetch needs to retry remote base ("FetchEmail", OnError.RETRY); - + this.engine = engine; this.id = id; this.required_fields = required_fields; this.flags = flags; this.cancellable = cancellable; - + // always fetch the required fields unless a modified list, in which case want to do exactly // what's required, no more and no less if (!flags.is_all_set(Folder.ListFlags.LOCAL_ONLY) && !flags.is_all_set(Folder.ListFlags.FORCE_UPDATE)) this.required_fields |= ImapDB.Folder.REQUIRED_FIELDS; - + remaining_fields = required_fields; } - + public override void notify_remote_removed_ids(Gee.Collection ids) { remote_removed = ids.contains(id); } - - public override void get_ids_to_be_remote_removed(Gee.Collection ids) { - } - + public override async ReplayOperation.Status replay_local_async() throws Error { - // If forcing an update, skip local operation and go direct to replay_remote() - if (flags.is_all_set(Folder.ListFlags.FORCE_UPDATE)) - return ReplayOperation.Status.CONTINUE; - + if (flags.is_all_set(Folder.ListFlags.FORCE_UPDATE)) { + // Forcing an update, get the local UID then go direct to + // replay_remote() + this.uid = yield engine.local_folder.get_uid_async( + this.id, NONE, this.cancellable + ); + return Status.CONTINUE; + } + + bool local_only = flags.is_all_set(Folder.ListFlags.LOCAL_ONLY); + Geary.Email? email = null; try { - email = yield engine.local_folder.fetch_email_async(id, required_fields, - ImapDB.Folder.ListFlags.PARTIAL_OK, cancellable); - } catch (Error err) { - // If NOT_FOUND or INCOMPLETE_MESSAGE, then fall through, otherwise return to sender - if (!(err is Geary.EngineError.NOT_FOUND) && !(err is Geary.EngineError.INCOMPLETE_MESSAGE)) + email = yield engine.local_folder.fetch_email_async( + id, + required_fields, + ImapDB.Folder.ListFlags.PARTIAL_OK, + cancellable + ); + } catch (Geary.EngineError.NOT_FOUND err) { + if (local_only) { throw err; + } } - + // If returned in full, done - if (email != null && email.fields.fulfills(required_fields)) + if (email != null && email.fields.fulfills(required_fields)) { + this.email = email; + this.remaining_fields = Email.Field.NONE; return ReplayOperation.Status.COMPLETED; - - // If local only and not found fully in local store, throw NOT_FOUND - if (flags.is_all_set(Folder.ListFlags.LOCAL_ONLY)) { - throw new EngineError.NOT_FOUND("Email %s with fields %Xh not found in %s", id.to_string(), - required_fields, to_string()); + } else if (local_only) { + // Didn't have an email that fulfills the reqs, but the + // caller didn't want to go to the remote, so let them + // know + throw new EngineError.INCOMPLETE_MESSAGE( + "Email %s with fields %Xh locally incomplete %s", + id.to_string(), + required_fields, + to_string() + ); } - + // only fetch what's missing if (email != null) remaining_fields = required_fields.clear(email.fields); else remaining_fields = required_fields; - + assert(remaining_fields != 0); - + if (email != null) uid = ((ImapDB.EmailIdentifier) email.id).uid; else uid = yield engine.local_folder.get_uid_async(id, ImapDB.Folder.ListFlags.NONE, cancellable); - + if (uid == null) throw new EngineError.NOT_FOUND("Unable to find %s in %s", id.to_string(), engine.to_string()); - + return ReplayOperation.Status.CONTINUE; } - - public override async ReplayOperation.Status replay_remote_async() throws Error { + + public override async void replay_remote_async(Imap.FolderSession remote) + throws GLib.Error { if (remote_removed) { throw new EngineError.NOT_FOUND("Unable to fetch %s in %s (removed from remote)", id.to_string(), engine.to_string()); } - + // fetch only the remaining fields from the remote folder (if only pulling partial information, // will merge at end of this method) - Gee.List? list = yield engine.remote_folder.list_email_async( + Gee.List? list = yield remote.list_email_async( new Imap.MessageSet.uid(uid), remaining_fields, cancellable); if (list == null || list.size != 1) throw new EngineError.NOT_FOUND("Unable to fetch %s in %s", id.to_string(), engine.to_string()); - - // save to local store - email = list[0]; - assert(email != null); - + Gee.Map created_or_merged = - yield engine.local_folder.create_or_merge_email_async( - Geary.iterate(email).to_array_list(), cancellable); - - // true means created + yield this.engine.local_folder.create_or_merge_email_async( + list, true, this.cancellable + ); + + Geary.Email email = list[0]; if (created_or_merged.get(email)) { Gee.Collection ids = Geary.iterate(email.id).to_array_list(); engine.replay_notify_email_inserted(ids); engine.replay_notify_email_locally_inserted(ids); } - - // if remote_email doesn't fulfill all required, pull from local database, which should now - // be able to do all of that - if (!email.fields.fulfills(required_fields)) { - email = yield engine.local_folder.fetch_email_async(id, required_fields, - ImapDB.Folder.ListFlags.NONE, cancellable); - assert(email != null); - } - - return ReplayOperation.Status.COMPLETED; - } - - public override async void backout_local_async() throws Error { - // read-only + + // Finally, pull again from the local database, to get the + // full set of required fields, and ensure attachments are + // created, if needed. + this.email = yield this.engine.local_folder.fetch_email_async( + this.id, this.required_fields, NONE, this.cancellable + ); } - + public override string describe_state() { - return "id=%s required_fields=%Xh remaining_fields=%Xh flags=%Xh".printf(id.to_string(), - required_fields, remaining_fields, flags); + return "id=%s required_fields=%Xh remaining_fields=%Xh flags=%Xh has_email=%s".printf( + this.id.to_string(), + this.required_fields, + this.remaining_fields, + this.flags, + (this.email == null).to_string() + ); } } - diff -Nru geary-0.12.4/src/engine/imap-engine/replay-ops/imap-engine-list-email-by-id.vala geary-3.32.0/src/engine/imap-engine/replay-ops/imap-engine-list-email-by-id.vala --- geary-0.12.4/src/engine/imap-engine/replay-ops/imap-engine-list-email-by-id.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-engine/replay-ops/imap-engine-list-email-by-id.vala 2019-03-17 13:39:29.000000000 +0000 @@ -9,60 +9,68 @@ private int count; private int fulfilled_count = 0; private Imap.UID? initial_uid = null; - + public ListEmailByID(MinimalFolder owner, ImapDB.EmailIdentifier? initial_id, int count, Geary.Email.Field required_fields, Folder.ListFlags flags, Cancellable? cancellable) { base ("ListEmailByID", owner, required_fields, flags, cancellable); - + this.initial_id = initial_id; this.count = count; } - + public override async ReplayOperation.Status replay_local_async() throws Error { if (flags.is_force_update()) return ReplayOperation.Status.CONTINUE; - - // get everything from local store, even partial matches, that fit range - ImapDB.Folder.ListFlags list_flags = ImapDB.Folder.ListFlags.from_folder_flags(flags); - list_flags |= ImapDB.Folder.ListFlags.PARTIAL_OK; - Gee.List? list = yield owner.local_folder.list_email_by_id_async(initial_id, - count, required_fields, list_flags, cancellable); - - // walk list, breaking out unfulfilled items from fulfilled items + + // Fetch the initial ID to a) make sure it exists, and b) so + // its UID is available when expanding the vector + if (this.initial_id != null) { + Email email = yield owner.local_folder.fetch_email_async( + this.initial_id, + // Only need the id here + Email.Field.NONE, + ImapDB.Folder.ListFlags.NONE, + cancellable + ); + this.initial_uid = ((ImapDB.EmailIdentifier) email.id).uid; + } + + // List all locally known, desired email that fits the list + // range. Include partial matches so there's potentially less + // to fetch from the remote if not all are fulfilled. + ImapDB.Folder.ListFlags local_flags = ( + ImapDB.Folder.ListFlags.from_folder_flags(flags) | + ImapDB.Folder.ListFlags.PARTIAL_OK + ); + Gee.List? list = + yield owner.local_folder.list_email_by_id_async( + initial_id, + count, + required_fields, + local_flags, + cancellable + ); + + // Break out unfulfilled email from fulfilled ones Gee.ArrayList fulfilled = new Gee.ArrayList(); if (list != null) { foreach (Geary.Email email in list) { - Imap.UID uid = ((ImapDB.EmailIdentifier) email.id).uid; - - // if INCLUDING_ID, then find the initial UID for the initial_id (if specified) - if (flags.is_including_id()) { - if (initial_id != null && email.id.equal_to(initial_id)) - initial_uid = uid; + if (email.fields.fulfills(required_fields)) { + fulfilled.add(email); } else { - // !INCLUDING_ID, so find the earliest UID (for oldest-to-newest) or latest - // UID (newest-to-oldest) - if (flags.is_oldest_to_newest()) { - if (initial_uid == null || uid.compare_to(initial_uid) < 0) - initial_uid = uid; - } else { - // newest-to-oldest - if (initial_uid == null || uid.compare_to(initial_uid) > 0) - initial_uid = uid; - } + Imap.UID uid = ((ImapDB.EmailIdentifier) email.id).uid; + add_unfulfilled_fields( + uid, required_fields.clear(email.fields) + ); } - - if (email.fields.fulfills(required_fields)) - fulfilled.add(email); - else - add_unfulfilled_fields(uid, required_fields.clear(email.fields)); } } - + // report fulfilled items fulfilled_count = fulfilled.size; if (fulfilled_count > 0) accumulator.add_all(fulfilled); - + // determine if everything was listed bool finished = false; if (flags.is_local_only()) { @@ -74,76 +82,88 @@ // an initial_id finished = (get_unfulfilled_count() == 0 && fulfilled_count >= count); } else { - // count == int.MAX - // This sentinel means "get everything from this point", so this has different meanings - // depending on direction - if (flags.is_newest_to_oldest()) { - // only finished if the folder is entirely normalized - Trillian is_fully_expanded = yield is_fully_expanded_async(); - finished = (is_fully_expanded == Trillian.TRUE); - } else { - // for oldest-to-newest, finished if no unfulfilled items - finished = (get_unfulfilled_count() == 0); - } + // Here, count == int.MAX, but this sentinel means "get + // everything from this point", so this has different + // meanings depending on direction. If + // flags.is_newest_to_oldest(), only finished if the + // folder is entirely normalized, but we don't know here + // since we don't have a remote. Else for + // oldest-to-newest, finished if no unfulfilled items + finished = ( + !flags.is_newest_to_oldest() && get_unfulfilled_count() == 0 + ); } - - // local-only operations stop here; also, since the local store is normalized from the top - // of the vector on down, if enough items came back fulfilled, then done - if (finished) - return ReplayOperation.Status.COMPLETED; - - return ReplayOperation.Status.CONTINUE; + + return finished + ? ReplayOperation.Status.COMPLETED + : ReplayOperation.Status.CONTINUE; } - - public override async ReplayOperation.Status replay_remote_async() throws Error { + + public override async void replay_remote_async(Imap.FolderSession remote) + throws GLib.Error { bool expansion_required = false; - Trillian is_fully_expanded = yield is_fully_expanded_async(); - if (is_fully_expanded == Trillian.FALSE) { + if (!(yield is_fully_expanded_async(remote))) { if (flags.is_oldest_to_newest()) { - if (initial_id != null) { - // expand vector if not initial_id not discovered - expansion_required = (initial_uid == null); - } else { - // initial_id == null, expansion required if not fully already - expansion_required = true; - } + // Expansion is required since there are + // unfulfilled email within the vector. + expansion_required = true; } else { // newest-to-oldest if (count == int.MAX) { - // if infinite count, expansion required if not already + // Infinite count, expand to fill in all + // unfulfilled or not-yet-found email. expansion_required = true; - } else if (initial_id != null) { - // finite count, expansion required if initial not found *or* not enough - // items were pulled in - expansion_required = (initial_uid == null) || (fulfilled_count + get_unfulfilled_count() < count); } else { - // initial_id == null - // finite count, expansion required if not enough found - expansion_required = (fulfilled_count + get_unfulfilled_count() < count); + // Finite count, expansion required only if not + // enough items were pulled in, in total. + expansion_required = ( + fulfilled_count + get_unfulfilled_count() < count + ); } } } - + // If the vector is too short, expand it now if (expansion_required) { - Gee.Set? uids = yield expand_vector_async(initial_uid, count); + Gee.Set? uids = yield expand_vector_async( + remote, initial_uid, count + ); if (uids != null) { // add required_fields as well as basic required fields for new email add_many_unfulfilled_fields(uids, required_fields); } } - + // Even after expansion it's possible for the local_list_count + unfulfilled to be less // than count if the folder has fewer messages or the user is requesting a span near // either end of the vector, so don't do that kind of sanity checking here - - return yield base.replay_remote_async(); + + yield base.replay_remote_async(remote); } - + public override string describe_state() { return "%s initial_id=%s count=%u incl=%s newest_to_oldest=%s".printf(base.describe_state(), (initial_id != null) ? initial_id.to_string() : "(null)", count, flags.is_including_id().to_string(), flags.is_newest_to_oldest().to_string()); } -} + /** + * Determines if the owning folder's vector is fully expanded. + */ + private async bool is_fully_expanded_async(Imap.FolderSession remote) + throws GLib.Error { + int remote_count = remote.folder.properties.email_total; + + // include marked for removed in the count in case this is + // being called while a removal is in process, in which case + // don't want to expand vector this moment because the vector + // is in flux + int local_count_with_marked = + yield this.owner.local_folder.get_email_count_async( + ImapDB.Folder.ListFlags.INCLUDE_MARKED_FOR_REMOVE, cancellable + ); + + return local_count_with_marked >= remote_count; + } + +} diff -Nru geary-0.12.4/src/engine/imap-engine/replay-ops/imap-engine-list-email-by-sparse-id.vala geary-3.32.0/src/engine/imap-engine/replay-ops/imap-engine-list-email-by-sparse-id.vala --- geary-0.12.4/src/engine/imap-engine/replay-ops/imap-engine-list-email-by-sparse-id.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-engine/replay-ops/imap-engine-list-email-by-sparse-id.vala 2019-03-17 13:39:29.000000000 +0000 @@ -6,49 +6,49 @@ private class Geary.ImapEngine.ListEmailBySparseID : Geary.ImapEngine.AbstractListEmail { private Gee.HashSet ids = new Gee.HashSet(); - + public ListEmailBySparseID(MinimalFolder owner, Gee.Collection ids, Geary.Email.Field required_fields, Folder.ListFlags flags, Cancellable? cancellable) { base ("ListEmailBySparseID", owner, required_fields, flags, cancellable); - + this.ids.add_all(ids); } - + public override void notify_remote_removed_ids(Gee.Collection removed_ids) { ids.remove_all(removed_ids); - + base.notify_remote_removed_ids(removed_ids); } - + public override async ReplayOperation.Status replay_local_async() throws Error { if (flags.is_force_update()) { Gee.Set? uids = yield owner.local_folder.get_uids_async(ids, ImapDB.Folder.ListFlags.NONE, cancellable); add_many_unfulfilled_fields(uids, required_fields); - + return ReplayOperation.Status.CONTINUE; } - + Gee.List? local_list = yield owner.local_folder.list_email_by_sparse_id_async(ids, required_fields, ImapDB.Folder.ListFlags.PARTIAL_OK, cancellable); - + // Build list of emails fully fetched from local store and table of remaining emails by // their lack of completeness Gee.List fulfilled = new Gee.ArrayList(); if (local_list != null && local_list.size > 0) { Gee.Map? map = Email.emails_to_map(local_list); assert(map != null); - + // walk list of *requested* IDs to ensure that unknown are considering unfulfilled foreach (ImapDB.EmailIdentifier id in ids) { Geary.Email? email = map.get(id); - + // if non-null, then the local_folder should've supplied a UID; if null, then // it's simply not present in the local folder (since PARTIAL_OK is spec'd), so // we have no way of referring to it on the server if (email == null) continue; - + // if completely unknown, make sure duplicate detection fields are included; otherwise, // if known, then they were pulled down during folder normalization and during // vector expansion @@ -60,20 +60,20 @@ } } } - + if (fulfilled.size > 0) accumulator.add_all(fulfilled); - + if (flags.is_local_only() || get_unfulfilled_count() == 0) return ReplayOperation.Status.COMPLETED; - + return ReplayOperation.Status.CONTINUE; } - + public override async void backout_local_async() throws Error { // R/O, nothing to backout } - + public override string describe_state() { return "ids.size=%d required_fields=%Xh flags=%Xh".printf(ids.size, required_fields, flags); } diff -Nru geary-0.12.4/src/engine/imap-engine/replay-ops/imap-engine-mark-email.vala geary-3.32.0/src/engine/imap-engine/replay-ops/imap-engine-mark-email.vala --- geary-0.12.4/src/engine/imap-engine/replay-ops/imap-engine-mark-email.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-engine/replay-ops/imap-engine-mark-email.vala 2019-03-17 13:39:29.000000000 +0000 @@ -11,33 +11,30 @@ private Geary.EmailFlags? flags_to_remove; private Gee.Map? original_flags = null; private Cancellable? cancellable; - - public MarkEmail(MinimalFolder engine, Gee.List to_mark, - Geary.EmailFlags? flags_to_add, Geary.EmailFlags? flags_to_remove, + + public MarkEmail(MinimalFolder engine, Gee.List to_mark, + Geary.EmailFlags? flags_to_add, Geary.EmailFlags? flags_to_remove, Cancellable? cancellable = null) { base("MarkEmail", OnError.RETRY); - + this.engine = engine; - + this.to_mark.add_all(to_mark); this.flags_to_add = flags_to_add; this.flags_to_remove = flags_to_remove; this.cancellable = cancellable; } - + public override void notify_remote_removed_ids(Gee.Collection ids) { // don't bother updating on server or backing out locally if (original_flags != null) Collection.map_unset_all_keys(original_flags, ids); } - - public override void get_ids_to_be_remote_removed(Gee.Collection ids) { - } - + public override async ReplayOperation.Status replay_local_async() throws Error { if (to_mark.size == 0) return ReplayOperation.Status.COMPLETED; - + // Save original flags, then set new ones. // TODO: Make this atomic (otherwise there stands a chance backout_local_async() will // reapply the wrong flags): should get the original flags and the new flags in the same @@ -45,39 +42,38 @@ original_flags = yield engine.local_folder.get_email_flags_async(to_mark, cancellable); if (original_flags == null || original_flags.size == 0) return ReplayOperation.Status.COMPLETED; - + yield engine.local_folder.mark_email_async(original_flags.keys, flags_to_add, flags_to_remove, cancellable); - + // Notify using flags from DB. Gee.Map? map = yield engine.local_folder.get_email_flags_async( original_flags.keys, cancellable); if (map != null && map.size > 0) engine.replay_notify_email_flags_changed(map); - + return ReplayOperation.Status.CONTINUE; } - - public override async ReplayOperation.Status replay_remote_async() throws Error { + + public override async void replay_remote_async(Imap.FolderSession remote) + throws GLib.Error { // potentially empty due to writebehind operation - if (original_flags.size == 0) - return ReplayOperation.Status.COMPLETED; - - Gee.List msg_sets = Imap.MessageSet.uid_sparse( - ImapDB.EmailIdentifier.to_uids(original_flags.keys)); - yield engine.remote_folder.mark_email_async(msg_sets, flags_to_add, flags_to_remove, - cancellable); - - return ReplayOperation.Status.COMPLETED; + if (original_flags.size > 0) { + Gee.List msg_sets = Imap.MessageSet.uid_sparse( + ImapDB.EmailIdentifier.to_uids(original_flags.keys)); + yield remote.mark_email_async( + msg_sets, flags_to_add, flags_to_remove, cancellable + ); + } } - + public override async void backout_local_async() throws Error { // Restore original flags (if fetched, which may not have occurred if an error happened // during transaction) if (original_flags != null) yield engine.local_folder.set_email_flags_async(original_flags, cancellable); } - + public override string describe_state() { return "to_mark=%d flags_to_add=%s flags_to_remove=%s".printf(to_mark.size, (flags_to_add != null) ? flags_to_add.to_string() : "(none)", diff -Nru geary-0.12.4/src/engine/imap-engine/replay-ops/imap-engine-move-email-commit.vala geary-3.32.0/src/engine/imap-engine/replay-ops/imap-engine-move-email-commit.vala --- geary-0.12.4/src/engine/imap-engine/replay-ops/imap-engine-move-email-commit.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-engine/replay-ops/imap-engine-move-email-commit.vala 2019-03-17 13:39:29.000000000 +0000 @@ -10,84 +10,100 @@ private class Geary.ImapEngine.MoveEmailCommit : Geary.ImapEngine.SendReplayOperation { public Gee.Set destination_uids = new Gee.HashSet(); - + private MinimalFolder engine; private Gee.List to_move = new Gee.ArrayList(); private Geary.FolderPath destination; private Cancellable? cancellable; private Gee.List? remaining_msg_sets = null; - + public MoveEmailCommit(MinimalFolder engine, Gee.Collection to_move, Geary.FolderPath destination, Cancellable? cancellable) { base.only_remote("MoveEmailCommit", OnError.RETRY); - + this.engine = engine; - + this.to_move.add_all(to_move); this.destination = destination; this.cancellable = cancellable; } - + public override void notify_remote_removed_ids(Gee.Collection ids) { to_move.remove_all(ids); } - - public override async ReplayOperation.Status replay_local_async() throws Error { - return ReplayOperation.Status.CONTINUE; - } - + public override void get_ids_to_be_remote_removed(Gee.Collection ids) { ids.add_all(to_move); } - - public override async ReplayOperation.Status replay_remote_async() throws Error { - if (to_move.size == 0) - return ReplayOperation.Status.COMPLETED; - - // Remaining MessageSets are persisted in case of network retries - if (remaining_msg_sets == null) - remaining_msg_sets = Imap.MessageSet.uid_sparse(ImapDB.EmailIdentifier.to_uids(to_move)); - - if (remaining_msg_sets == null || remaining_msg_sets.size == 0) - return ReplayOperation.Status.COMPLETED; - - Gee.Iterator iter = remaining_msg_sets.iterator(); - while (iter.next()) { - // don't use Cancellable throughout I/O operations in order to assure transaction completes - // fully - if (cancellable != null && cancellable.is_cancelled()) - throw new IOError.CANCELLED("Move email to %s cancelled", engine.remote_folder.to_string()); - - Imap.MessageSet msg_set = iter.get(); - - Gee.Map? map = yield engine.remote_folder.copy_email_async(msg_set, - destination, null); - if (map != null) - destination_uids.add_all(map.values); - - yield engine.remote_folder.remove_email_async(msg_set.to_list(), null); - - // completed successfully, remove from list in case of retry - iter.remove(); + + public override async void replay_remote_async(Imap.FolderSession remote) + throws GLib.Error { + if (to_move.size > 0) { + // Remaining MessageSets are persisted in case of network retries + if (remaining_msg_sets == null) + remaining_msg_sets = Imap.MessageSet.uid_sparse( + ImapDB.EmailIdentifier.to_uids(to_move) + ); + + if (remaining_msg_sets == null || remaining_msg_sets.size == 0) + return; + + Gee.Iterator iter = remaining_msg_sets.iterator(); + while (iter.next()) { + // don't use Cancellable throughout I/O operations in + // order to assure transaction completes fully + if (cancellable != null && cancellable.is_cancelled()) { + throw new IOError.CANCELLED( + "Move email to %s cancelled", this.destination.to_string() + ); + } + + // The copy is implemented as follows: + // 1. Have an open connection to source folder + // 2. IMAP COPY from source to destination folder + // 3. IMAP STORE \Deleted + // 4. IMAP EXPUNGE + // + // The net result is that since the source folder will + // notify that the email has been marked as deleted + // then actually removed, the email will be flagged as + // deleted, and will need to be unflagged as such once + // it is noticed in the destination folder. This + // happens via the account synchroniser being notified + // that the destination has had its contents altered, + // so it goes off and checks. + + Imap.MessageSet msg_set = iter.get(); + + Gee.Map? map = yield remote.copy_email_async( + msg_set, destination, null + ); + if (map != null) + destination_uids.add_all(map.values); + + yield remote.remove_email_async(msg_set.to_list(), null); + + // completed successfully, remove from list in case of retry + iter.remove(); + } } - - return ReplayOperation.Status.COMPLETED; } - + public override async void backout_local_async() throws Error { if (to_move.size == 0) return; - + yield engine.local_folder.mark_removed_async(to_move, false, cancellable); - - int count = engine.get_remote_counts(null, null); - + + int count = this.engine.properties.email_total; + if (count < 0) { + count = 0; + } engine.replay_notify_email_inserted(to_move); engine.replay_notify_email_count_changed(count + to_move.size, Folder.CountChangeReason.INSERTED); } - + public override string describe_state() { return "%d email IDs to %s".printf(to_move.size, destination.to_string()); } } - diff -Nru geary-0.12.4/src/engine/imap-engine/replay-ops/imap-engine-move-email-prepare.vala geary-3.32.0/src/engine/imap-engine/replay-ops/imap-engine-move-email-prepare.vala --- geary-0.12.4/src/engine/imap-engine/replay-ops/imap-engine-move-email-prepare.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-engine/replay-ops/imap-engine-move-email-prepare.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,70 +1,61 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ /** - * Stage one of a {@link RevokableMove}: collect valid {@link ImapDB.EmailIdentifiers}, mark - * messages as removed, and update counts. + * Stage one of a {@link RevokableMove}. + * + * This operation collects valid {@link ImapDB.EmailIdentifier}s for + * messages to be removed, mark the messages as removed, and update + * counts. */ - private class Geary.ImapEngine.MoveEmailPrepare : Geary.ImapEngine.SendReplayOperation { public Gee.Set? prepared_for_move = null; - + private MinimalFolder engine; private Cancellable? cancellable; private Gee.List to_move = new Gee.ArrayList(); - + public MoveEmailPrepare(MinimalFolder engine, Gee.Collection to_move, Cancellable? cancellable) { base.only_local("MoveEmailPrepare", OnError.RETRY); - + this.engine = engine; this.to_move.add_all(to_move); this.cancellable = cancellable; } - + public override void notify_remote_removed_ids(Gee.Collection ids) { if (prepared_for_move != null) prepared_for_move.remove_all(ids); } - + public override async ReplayOperation.Status replay_local_async() throws Error { if (to_move.size <= 0) return ReplayOperation.Status.COMPLETED; - - int count = engine.get_remote_counts(null, null); - + + int count = this.engine.properties.email_total; // as this value is only used for reporting, offer best-possible service if (count < 0) count = to_move.size; - + prepared_for_move = yield engine.local_folder.mark_removed_async(to_move, true, cancellable); if (prepared_for_move == null || prepared_for_move.size == 0) return ReplayOperation.Status.COMPLETED; - + engine.replay_notify_email_removed(prepared_for_move); - + engine.replay_notify_email_count_changed( Numeric.int_floor(count - prepared_for_move.size, 0), Folder.CountChangeReason.REMOVED); - - return ReplayOperation.Status.COMPLETED; - } - - public override void get_ids_to_be_remote_removed(Gee.Collection ids) { - } - - public override async ReplayOperation.Status replay_remote_async() throws Error { + return ReplayOperation.Status.COMPLETED; } - - public override async void backout_local_async() throws Error { - } - + public override string describe_state() { return "%d email IDs".printf(prepared_for_move != null ? prepared_for_move.size : 0); } } - diff -Nru geary-0.12.4/src/engine/imap-engine/replay-ops/imap-engine-move-email-revoke.vala geary-3.32.0/src/engine/imap-engine/replay-ops/imap-engine-move-email-revoke.vala --- geary-0.12.4/src/engine/imap-engine/replay-ops/imap-engine-move-email-revoke.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-engine/replay-ops/imap-engine-move-email-revoke.vala 2019-03-17 13:39:29.000000000 +0000 @@ -12,51 +12,43 @@ private MinimalFolder engine; private Gee.List to_revoke = new Gee.ArrayList(); private Cancellable? cancellable; - + public MoveEmailRevoke(MinimalFolder engine, Gee.Collection to_revoke, Cancellable? cancellable) { base.only_local("MoveEmailRevoke", OnError.RETRY); - + this.engine = engine; - + this.to_revoke.add_all(to_revoke); this.cancellable = cancellable; } - + public override void notify_remote_removed_ids(Gee.Collection ids) { to_revoke.remove_all(ids); } - + public override async ReplayOperation.Status replay_local_async() throws Error { if (to_revoke.size == 0) return ReplayOperation.Status.COMPLETED; - + Gee.Set? revoked = yield engine.local_folder.mark_removed_async( to_revoke, false, cancellable); if (revoked == null || revoked.size == 0) return ReplayOperation.Status.COMPLETED; - - int count = engine.get_remote_counts(null, null); - + + int count = this.engine.properties.email_total; + if (count < 0) { + count = 0; + } + engine.replay_notify_email_inserted(revoked); engine.replay_notify_email_count_changed(count + revoked.size, Geary.Folder.CountChangeReason.INSERTED); - - return ReplayOperation.Status.COMPLETED; - } - - public override void get_ids_to_be_remote_removed(Gee.Collection ids) { - } - - public override async ReplayOperation.Status replay_remote_async() throws Error { + return ReplayOperation.Status.COMPLETED; } - - public override async void backout_local_async() throws Error { - } - + public override string describe_state() { return "%d email IDs".printf(to_revoke.size); } } - diff -Nru geary-0.12.4/src/engine/imap-engine/replay-ops/imap-engine-remove-email.vala geary-3.32.0/src/engine/imap-engine/replay-ops/imap-engine-remove-email.vala --- geary-0.12.4/src/engine/imap-engine/replay-ops/imap-engine-remove-email.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-engine/replay-ops/imap-engine-remove-email.vala 2019-03-17 13:39:29.000000000 +0000 @@ -10,75 +10,71 @@ private Cancellable? cancellable; private Gee.Set? removed_ids = null; private int original_count = 0; - + public RemoveEmail(MinimalFolder engine, Gee.List to_remove, Cancellable? cancellable = null) { base("RemoveEmail", OnError.RETRY); - + this.engine = engine; - + this.to_remove.add_all(to_remove); this.cancellable = cancellable; } - + public override void notify_remote_removed_ids(Gee.Collection ids) { if (removed_ids != null) removed_ids.remove_all(ids); } - + public override async ReplayOperation.Status replay_local_async() throws Error { // if performing a full expunge, need to move on to replay_remote_async() for that - if (to_remove.size <= 0) + if (this.to_remove.size <= 0) return ReplayOperation.Status.COMPLETED; - - int remote_count; - int last_seen_remote_count; - original_count = engine.get_remote_counts(out remote_count, out last_seen_remote_count); - + + this.original_count = this.engine.properties.email_total; // because this value is only used for reporting count changes, offer best-possible service - if (original_count < 0) - original_count = to_remove.size; - + if (this.original_count < 0) + this.original_count = this.to_remove.size; + removed_ids = yield engine.local_folder.mark_removed_async(to_remove, true, cancellable); if (removed_ids == null || removed_ids.size == 0) return ReplayOperation.Status.COMPLETED; - + engine.replay_notify_email_removed(removed_ids); - + engine.replay_notify_email_count_changed(Numeric.int_floor(original_count - removed_ids.size, 0), Geary.Folder.CountChangeReason.REMOVED); - + return ReplayOperation.Status.CONTINUE; } - + public override void get_ids_to_be_remote_removed(Gee.Collection ids) { if (removed_ids != null) ids.add_all(removed_ids); } - - public override async ReplayOperation.Status replay_remote_async() throws Error { - if (removed_ids.size == 0) - return ReplayOperation.Status.COMPLETED; - - // Remove from server. Note that this causes the receive replay queue to kick into - // action, removing the e-mail but *NOT* firing a signal; the "remove marker" indicates - // that the signal has already been fired. - Gee.List msg_sets = Imap.MessageSet.uid_sparse( - ImapDB.EmailIdentifier.to_uids(removed_ids)); - yield engine.remote_folder.remove_email_async(msg_sets, cancellable); - - return ReplayOperation.Status.COMPLETED; + + public override async void replay_remote_async(Imap.FolderSession remote) + throws GLib.Error { + if (removed_ids.size > 0) { + // Remove from server. Note that this causes the receive + // replay queue to kick into action, removing the e-mail + // but *NOT* firing a signal; the "remove marker" + // indicates that the signal has already been fired. + Gee.List msg_sets = Imap.MessageSet.uid_sparse( + ImapDB.EmailIdentifier.to_uids(removed_ids)); + yield remote.remove_email_async(msg_sets, cancellable); + } } - + public override async void backout_local_async() throws Error { if (removed_ids != null && removed_ids.size > 0) { yield engine.local_folder.mark_removed_async(removed_ids, false, cancellable); engine.replay_notify_email_inserted(removed_ids); } - + engine.replay_notify_email_count_changed(original_count, Geary.Folder.CountChangeReason.INSERTED); } - + public override string describe_state() { return "to_remove.size=%d removed_ids.size=%d".printf(to_remove.size, (removed_ids != null) ? removed_ids.size : 0); diff -Nru geary-0.12.4/src/engine/imap-engine/replay-ops/imap-engine-replay-append.vala geary-3.32.0/src/engine/imap-engine/replay-ops/imap-engine-replay-append.vala --- geary-0.12.4/src/engine/imap-engine/replay-ops/imap-engine-replay-append.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-engine/replay-ops/imap-engine-replay-append.vala 2019-03-17 13:39:29.000000000 +0000 @@ -5,62 +5,130 @@ */ private class Geary.ImapEngine.ReplayAppend : Geary.ImapEngine.ReplayOperation { + private MinimalFolder owner; private int remote_count; private Gee.List positions; - - public ReplayAppend(MinimalFolder owner, int remote_count, Gee.List positions) { + private Cancellable? cancellable; + + public signal void email_appended(Gee.Collection ids); + public signal void email_locally_appended(Gee.Collection ids); + public signal void email_count_changed(int count, Folder.CountChangeReason reason); + + + public ReplayAppend(MinimalFolder owner, + int remote_count, + Gee.List positions, + Cancellable? cancellable) { // IGNORE remote errors because the reconnect will re-normalize the folder, making this // append moot - base ("Append", Scope.REMOTE_ONLY, OnError.IGNORE); - + base ("Append", Scope.REMOTE_ONLY, OnError.IGNORE_REMOTE); + this.owner = owner; this.remote_count = remote_count; this.positions = positions; + this.cancellable = cancellable; } - + public override void notify_remote_removed_position(Imap.SequenceNumber removed) { Gee.List new_positions = new Gee.ArrayList(); foreach (Imap.SequenceNumber? position in positions) { Imap.SequenceNumber old_position = position; - + // adjust depending on relation to removed message position = position.shift_for_removed(removed); if (position != null) new_positions.add(position); - + debug("%s: ReplayAppend remote unsolicited remove: %s -> %s", owner.to_string(), old_position.to_string(), (position != null) ? position.to_string() : "(null)"); } - + positions = new_positions; - + // DON'T update remote_count, it is intended to report the remote count at the time the // appended messages arrived } - - public override void notify_remote_removed_ids(Gee.Collection ids) { - } - - public override void get_ids_to_be_remote_removed(Gee.Collection ids) { - } - - public override async ReplayOperation.Status replay_local_async() throws Error { - return ReplayOperation.Status.CONTINUE; - } - - public override async void backout_local_async() throws Error { - } - - public override async ReplayOperation.Status replay_remote_async() { - if (positions.size > 0) - yield owner.do_replay_appended_messages(remote_count, positions); - - return ReplayOperation.Status.COMPLETED; + + public override async void replay_remote_async(Imap.FolderSession remote) + throws GLib.Error { + if (this.positions.size > 0) { + yield do_replay_appended_messages(remote); + } } - + public override string describe_state() { return "remote_count=%d positions.size=%d".printf(remote_count, positions.size); } -} + // Need to prefetch at least an EmailIdentifier (and duplicate detection fields) to create a + // normalized placeholder in the local database of the message, so all positions are + // properly relative to the end of the message list; once this is done, notify user of new + // messages. If duplicates, create_email_async() will fall through to an updated merge, + // which is exactly what we want. + private async void do_replay_appended_messages(Imap.FolderSession remote) + throws Error { + StringBuilder positions_builder = new StringBuilder("( "); + foreach (Imap.SequenceNumber remote_position in this.positions) + positions_builder.append_printf("%s ", remote_position.to_string()); + positions_builder.append(")"); + + debug("%s do_replay_appended_message: this.remote_count=%d this.positions=%s", + to_string(), this.remote_count, positions_builder.str); + + Gee.HashSet created = new Gee.HashSet(); + Gee.HashSet appended = new Gee.HashSet(); + Gee.List msg_sets = Imap.MessageSet.sparse(this.positions); + foreach (Imap.MessageSet msg_set in msg_sets) { + Gee.List? list = yield remote.list_email_async( + msg_set, ImapDB.Folder.REQUIRED_FIELDS, this.cancellable + ); + if (list != null && list.size > 0) { + debug("%s do_replay_appended_message: %d new messages in %s", to_string(), + list.size, msg_set.to_string()); + + // need to report both if it was created (not known before) and appended (which + // could mean created or simply a known email associated with this folder) + Gee.Map created_or_merged = + yield this.owner.local_folder.create_or_merge_email_async( + list, true, this.cancellable + ); + foreach (Geary.Email email in created_or_merged.keys) { + // true means created + if (created_or_merged.get(email)) { + debug("%s do_replay_appended_message: appended email ID %s added", + to_string(), email.id.to_string()); + + created.add(email.id); + } else { + debug("%s do_replay_appended_message: appended email ID %s associated", + to_string(), email.id.to_string()); + } + + appended.add(email.id); + } + } else { + debug("%s do_replay_appended_message: no new messages in %s", to_string(), + msg_set.to_string()); + } + } + + // store the reported count, *not* the current count (which is updated outside the of + // the queue) to ensure that updates happen serially and reflect committed local changes + yield this.owner.local_folder.update_remote_selected_message_count( + this.remote_count, this.cancellable + ); + + if (appended.size > 0) + email_appended(appended); + + if (created.size > 0) + email_locally_appended(created); + + email_count_changed(this.remote_count, Folder.CountChangeReason.APPENDED); + + debug("%s do_replay_appended_message: completed, this.remote_count=%d", + to_string(), this.remote_count); + } + +} diff -Nru geary-0.12.4/src/engine/imap-engine/replay-ops/imap-engine-replay-disconnect.vala geary-3.32.0/src/engine/imap-engine/replay-ops/imap-engine-replay-disconnect.vala --- geary-0.12.4/src/engine/imap-engine/replay-ops/imap-engine-replay-disconnect.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-engine/replay-ops/imap-engine-replay-disconnect.vala 1970-01-01 00:00:00.000000000 +0000 @@ -1,65 +0,0 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. - * - * This software is licensed under the GNU Lesser General Public License - * (version 2.1 or later). See the COPYING file in this distribution. - */ - -private class Geary.ImapEngine.ReplayDisconnect : Geary.ImapEngine.ReplayOperation { - private MinimalFolder owner; - private Imap.ClientSession.DisconnectReason reason; - private bool flush_pending; - private Cancellable? cancellable; - - public ReplayDisconnect(MinimalFolder owner, Imap.ClientSession.DisconnectReason reason, - bool flush_pending, Cancellable? cancellable) { - base ("Disconnect", Scope.LOCAL_ONLY); - - this.owner = owner; - this.reason = reason; - this.flush_pending = flush_pending; - this.cancellable = cancellable; - } - - public override void notify_remote_removed_position(Imap.SequenceNumber removed) { - } - - public override void notify_remote_removed_ids(Gee.Collection ids) { - } - - public override void get_ids_to_be_remote_removed(Gee.Collection ids) { - } - - public override async ReplayOperation.Status replay_local_async() throws Error { - debug("%s ReplayDisconnect reason=%s", owner.to_string(), reason.to_string()); - - Geary.Folder.CloseReason remote_reason = reason.is_error() - ? Geary.Folder.CloseReason.REMOTE_ERROR : Geary.Folder.CloseReason.REMOTE_CLOSE; - - // because close_internal_async() may schedule a ReplayOperation before its first yield, - // that means a ReplayOperation is scheduling a ReplayOperation, which isn't something - // we want to encourage, so use the Idle queue to schedule close_internal_async - Idle.add(() => { - // ReplayDisconnect is only used when remote disconnects, so never flush pending, the - // connection is down or going down - owner.close_internal_async.begin(Geary.Folder.CloseReason.LOCAL_CLOSE, remote_reason, - flush_pending, cancellable); - - return false; - }); - - return ReplayOperation.Status.COMPLETED; - } - - public override async void backout_local_async() throws Error { - } - - public override async ReplayOperation.Status replay_remote_async() throws Error { - // should not be called - return ReplayOperation.Status.COMPLETED; - } - - public override string describe_state() { - return "reason=%s".printf(reason.to_string()); - } -} - diff -Nru geary-0.12.4/src/engine/imap-engine/replay-ops/imap-engine-replay-removal.vala geary-3.32.0/src/engine/imap-engine/replay-ops/imap-engine-replay-removal.vala --- geary-0.12.4/src/engine/imap-engine/replay-ops/imap-engine-replay-removal.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-engine/replay-ops/imap-engine-replay-removal.vala 2019-03-17 13:39:29.000000000 +0000 @@ -5,49 +5,153 @@ */ private class Geary.ImapEngine.ReplayRemoval : Geary.ImapEngine.ReplayOperation { + private MinimalFolder owner; private int remote_count; private Imap.SequenceNumber position; - + + public signal void email_removed(Gee.Collection ids); + public signal void marked_email_removed(Gee.Collection ids); + public signal void email_count_changed(int count, Folder.CountChangeReason reason); + + public ReplayRemoval(MinimalFolder owner, int remote_count, Imap.SequenceNumber position) { // remote error will cause folder to reconnect and re-normalize, making this remove moot - base ("Removal", Scope.LOCAL_AND_REMOTE, OnError.IGNORE); - + base ("Removal", Scope.LOCAL_AND_REMOTE, OnError.IGNORE_REMOTE); + this.owner = owner; this.remote_count = remote_count; this.position = position; } - + public override void notify_remote_removed_position(Imap.SequenceNumber removed) { // although using positional addressing, don't update state; EXPUNGEs that happen after // other EXPUNGEs have no affect on those ahead of it } - + public override void notify_remote_removed_ids(Gee.Collection ids) { // this operation deals only in positional addressing } - + public override void get_ids_to_be_remote_removed(Gee.Collection ids) { // this ReplayOperation doesn't do remote removes, it reacts to them } - + public override async ReplayOperation.Status replay_local_async() throws Error { // Although technically a local-only operation, must treat as remote to ensure it's // processed in-order with ReplayAppend operations return ReplayOperation.Status.CONTINUE; } - - public override async void backout_local_async() throws Error { - } - - public override async ReplayOperation.Status replay_remote_async() throws Error { - yield owner.do_replay_removed_message(remote_count, position); - - return ReplayOperation.Status.COMPLETED; + + public override async void replay_remote_async(Imap.FolderSession remote) + throws GLib.Error { + debug("%s: ReplayRemoval this.position=%s reported_remote_count=%d", + this.owner.to_string(), this.position.value.to_string(), this.remote_count); + + if (this.position.is_valid()) { + yield do_replay_removed_message(); + } else { + debug("%s do_replay_removed_message: ignoring, invalid remote position or count", + to_string()); + } } - + public override string describe_state() { return "position=%s".printf(position.to_string()); } -} + private async void do_replay_removed_message() { + int local_count = -1; + int64 local_position = -1; + + ImapDB.EmailIdentifier? owned_id = null; + try { + // need total count, including those marked for removal, + // to accurately calculate position from server's point of + // view, not client's. The extra 1 taken off is due to the + // remote count already being decremented in MinimalFolder + // when this op was queued. + local_count = yield this.owner.local_folder.get_email_count_async( + ImapDB.Folder.ListFlags.INCLUDE_MARKED_FOR_REMOVE, null); + local_position = this.position.value - (this.remote_count + 1 - local_count); + + // zero or negative means the message exists beyond the local vector's range, so + // nothing to do there + if (local_position > 0) { + debug("%s do_replay_removed_message: local_count=%d local_position=%s", to_string(), + local_count, local_position.to_string()); + + owned_id = yield this.owner.local_folder.get_id_at_async(local_position, null); + } else { + debug("%s do_replay_removed_message: message not stored locally (local_count=%d local_position=%s)", + to_string(), local_count, local_position.to_string()); + } + } catch (Error err) { + debug("%s do_replay_removed_message: unable to determine ID of removed message %s: %s", + to_string(), this.position.to_string(), err.message); + } + + bool marked = false; + if (owned_id != null) { + debug("%s do_replay_removed_message: detaching from local store Email ID %s", to_string(), + owned_id.to_string()); + try { + // Reflect change in the local store and notify subscribers + yield this.owner.local_folder.detach_single_email_async(owned_id, null, out marked); + } catch (Error err) { + debug("%s do_replay_removed_message: unable to remove message #%s: %s", to_string(), + this.position.to_string(), err.message); + } + + // Notify queued replay operations that the email has been removed (by EmailIdentifier) + this.owner.replay_queue.notify_remote_removed_ids( + Geary.iterate(owned_id).to_array_list()); + } else { + debug("%s do_replay_removed_message: this.position=%lld unknown in local store " + + "(this.remote_count=%d local_position=%lld local_count=%d)", + to_string(), this.position.value, this.remote_count, local_position, local_count); + } + + // for debugging + int new_local_count = -1; + try { + new_local_count = yield this.owner.local_folder.get_email_count_async( + ImapDB.Folder.ListFlags.INCLUDE_MARKED_FOR_REMOVE, null); + } catch (Error err) { + debug("%s do_replay_removed_message: error fetching new local count: %s", to_string(), + err.message); + } + + // as with on_remote_appended(), only update in local store inside a queue operation, to + // ensure serial commits + try { + yield this.owner.local_folder.update_remote_selected_message_count(this.remote_count, null); + } catch (Error err) { + debug("%s do_replay_removed_message: unable to save removed remote count: %s", to_string(), + err.message); + } + + // notify of change ... use "marked-email-removed" for marked email to allow internal code + // to be notified when a removed email is "really" removed + if (owned_id != null) { + Gee.List removed = Geary.iterate(owned_id).to_array_list(); + if (!marked) + email_removed(removed); + else + marked_email_removed(removed); + } + + if (!marked) { + this.owner.replay_notify_email_count_changed( + this.remote_count, Folder.CountChangeReason.REMOVED + ); + } + + debug("%s ReplayRemoval: completed, " + + "(this.remote_count=%d local_count=%d starting local_count=%d this.position=%lld local_position=%lld marked=%s)", + this.owner.to_string(), + this.remote_count, new_local_count, local_count, + this.position.value, local_position, marked.to_string()); + } + +} diff -Nru geary-0.12.4/src/engine/imap-engine/replay-ops/imap-engine-replay-update.vala geary-3.32.0/src/engine/imap-engine/replay-ops/imap-engine-replay-update.vala --- geary-0.12.4/src/engine/imap-engine/replay-ops/imap-engine-replay-update.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/src/engine/imap-engine/replay-ops/imap-engine-replay-update.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,81 @@ +/* + * Copyright 2016 Software Freedom Conservancy Inc. + * Copyright 2017 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +/** + * Updates an existing message locally after an unsolicited FETCH. + */ +private class Geary.ImapEngine.ReplayUpdate : Geary.ImapEngine.ReplayOperation { + + + private MinimalFolder owner; + private int remote_count; + private Imap.SequenceNumber position; + private Imap.FetchedData data; + + + public ReplayUpdate(MinimalFolder owner, + int remote_count, + Imap.SequenceNumber position, + Imap.FetchedData data) { + base ("Update", Scope.LOCAL_ONLY, OnError.RETRY); + + this.owner = owner; + this.remote_count = remote_count; + this.position = position; + this.data = data; + } + + public override async ReplayOperation.Status replay_local_async() + throws Error { + Imap.MessageFlags? message_flags = + this.data.data_map.get(Imap.FetchDataSpecifier.FLAGS) as Imap.MessageFlags; + if (message_flags != null) { + int local_count = -1; + int64 local_position = -1; + + // need total count, including those marked for removal, to accurately calculate position + // from server's point of view, not client's + local_count = yield this.owner.local_folder.get_email_count_async( + ImapDB.Folder.ListFlags.INCLUDE_MARKED_FOR_REMOVE, null); + local_position = this.position.value - (this.remote_count - local_count); + + ImapDB.EmailIdentifier? id = null; + if (local_position > 0) { + id = yield this.owner.local_folder.get_id_at_async( + local_position, null + ); + } + + if (id != null) { + Gee.Map changed_map = + new Gee.HashMap(); + changed_map.set(id, new Imap.EmailFlags(message_flags)); + + yield this.owner.local_folder.set_email_flags_async(changed_map, null); + + this.owner.replay_notify_email_flags_changed(changed_map); + } else { + debug("%s replay_local_async id is null!", to_string()); + } + } else { + debug("%s Don't know what to do without any FLAGS: %s", + to_string(), this.data.to_string()); + } + + return ReplayOperation.Status.COMPLETED; + } + + public override string describe_state() { + Imap.MessageData? fetch_flags = + this.data.data_map.get(Imap.FetchDataSpecifier.FLAGS); + return "position.value=%lld, flags=%s".printf( + this.position.value, + fetch_flags != null ? fetch_flags.to_string() : "null" + ); + } +} diff -Nru geary-0.12.4/src/engine/imap-engine/replay-ops/imap-engine-server-search-email.vala geary-3.32.0/src/engine/imap-engine/replay-ops/imap-engine-server-search-email.vala --- geary-0.12.4/src/engine/imap-engine/replay-ops/imap-engine-server-search-email.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-engine/replay-ops/imap-engine-server-search-email.vala 2019-03-17 13:39:29.000000000 +0000 @@ -12,35 +12,40 @@ */ private class Geary.ImapEngine.ServerSearchEmail : Geary.ImapEngine.AbstractListEmail { private Imap.SearchCriteria criteria; - + public ServerSearchEmail(MinimalFolder owner, Imap.SearchCriteria criteria, Geary.Email.Field required_fields, Cancellable? cancellable) { // OLDEST_TO_NEWEST used for vector expansion, if necessary base ("ServerSearchEmail", owner, required_fields, Geary.Folder.ListFlags.OLDEST_TO_NEWEST, cancellable); - + // unlike list, need to retry this as there's no local component to return on_remote_error = OnError.RETRY; - + this.criteria = criteria; } - - public override async ReplayOperation.Status replay_local_async() throws Error { - // accumulate nothing, nothing unfulfilled (yet) + + // XXX Shouldn't need to override this, but AbstractListEmail + // won't let us declare it as remote-only + public override async ReplayOperation.Status replay_local_async() + throws GLib.Error { return ReplayOperation.Status.CONTINUE; } - - public override async ReplayOperation.Status replay_remote_async() throws Error { - Gee.SortedSet? uids = yield owner.remote_folder.search_async(criteria, cancellable); + + public override async void replay_remote_async(Imap.FolderSession remote) + throws GLib.Error { + Gee.SortedSet? uids = yield remote.search_async( + criteria, this.cancellable + ); if (uids == null || uids.size == 0) - return ReplayOperation.Status.COMPLETED; - + return; + // if the earliest UID is not in the local store, then need to expand vector to it Geary.EmailIdentifier? first_id = yield owner.local_folder.get_id_async(uids.first(), ImapDB.Folder.ListFlags.NONE, cancellable); if (first_id == null) - yield expand_vector_async(uids.first(), 1); - + yield expand_vector_async(remote, uids.first(), 1); + // Convert UIDs into EmailIdentifiers for lookup Gee.HashSet local_ids = new Gee.HashSet(); foreach (Imap.UID uid in uids) { @@ -53,17 +58,17 @@ if (id != null) local_ids.add(id); } - + Gee.List? local_list = yield owner.local_folder.list_email_by_sparse_id_async( local_ids, required_fields, ImapDB.Folder.ListFlags.PARTIAL_OK, cancellable); - + // Build list of local email Gee.Map map = new Gee.HashMap(); if (local_list != null) { foreach (Geary.Email email in local_list) map.set((ImapDB.EmailIdentifier) email.id, email); } - + // Convert into fulfilled and unfulfilled email for the base class to complete foreach (ImapDB.EmailIdentifier id in map.keys) { Geary.Email? email = map.get(id); @@ -74,12 +79,12 @@ else accumulator.add(email); } - - // with unfufilled set and fulfilled added to accumulator, let base class do the rest of the - // work - return yield base.replay_remote_async(); + + // with unfufilled set and fulfilled added to accumulator, let + // base class do the rest of the work + yield base.replay_remote_async(remote); } - + public override string describe_state() { return "criteria=%s".printf(criteria.to_string()); } diff -Nru geary-0.12.4/src/engine/imap-engine/replay-ops/imap-engine-user-close.vala geary-3.32.0/src/engine/imap-engine/replay-ops/imap-engine-user-close.vala --- geary-0.12.4/src/engine/imap-engine/replay-ops/imap-engine-user-close.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-engine/replay-ops/imap-engine-user-close.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,47 +1,45 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. + * Copyright 2018 Michael Gratton * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ +/** + * Operation to close the folder. + * + * This is a replay queue operation to allow existing local ops to + * complete, and to ease the implementation. See comments in {@link + * MinimalFolder.close_async}. + */ private class Geary.ImapEngine.UserClose : Geary.ImapEngine.ReplayOperation { - public bool closing = false; - + + /** Determines the state of the close operation. */ + public Trillian is_closing = Trillian.UNKNOWN; + private MinimalFolder owner; private Cancellable? cancellable; - + + public UserClose(MinimalFolder owner, Cancellable? cancellable) { - base ("UserClose", Scope.LOCAL_ONLY); - + base("UserClose", Scope.LOCAL_ONLY); this.owner = owner; this.cancellable = cancellable; } - - public override void notify_remote_removed_position(Imap.SequenceNumber removed) { - } - - public override void notify_remote_removed_ids(Gee.Collection ids) { - } - - public override void get_ids_to_be_remote_removed(Gee.Collection ids) { - } - + public override async ReplayOperation.Status replay_local_async() throws Error { - closing = yield owner.user_close_async(cancellable); - - return ReplayOperation.Status.COMPLETED; - } - - public override async void backout_local_async() throws Error { - } - - public override async ReplayOperation.Status replay_remote_async() throws Error { - // should not be called + bool closing = yield this.owner.close_internal( + Folder.CloseReason.LOCAL_CLOSE, + Folder.CloseReason.REMOTE_CLOSE, + this.cancellable + ); + this.is_closing = Trillian.from_boolean(closing); return ReplayOperation.Status.COMPLETED; } - + public override string describe_state() { - return ""; + return "is_closing: %s".printf(this.is_closing.to_string()); } -} +} diff -Nru geary-0.12.4/src/engine/imap-engine/yahoo/imap-engine-yahoo-account.vala geary-3.32.0/src/engine/imap-engine/yahoo/imap-engine-yahoo-account.vala --- geary-0.12.4/src/engine/imap-engine/yahoo/imap-engine-yahoo-account.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-engine/yahoo/imap-engine-yahoo-account.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,49 +1,60 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. + * Copyright 2019 Michael Gratton * * This software is licensed under the GNU Lesser General Public License - * (version 2.1 or later). See the COPYING file in this distribution. + * (version 2.1 or later). See the COPYING file in this distribution. */ private class Geary.ImapEngine.YahooAccount : Geary.ImapEngine.GenericAccount { - public static Geary.Endpoint generate_imap_endpoint() { - return new Geary.Endpoint( - "imap.mail.yahoo.com", - Imap.ClientConnection.DEFAULT_PORT_SSL, - Geary.Endpoint.Flags.SSL, - Imap.ClientConnection.RECOMMENDED_TIMEOUT_SEC); - } - - public static Geary.Endpoint generate_smtp_endpoint() { - return new Geary.Endpoint( - "smtp.mail.yahoo.com", - Smtp.ClientConnection.DEFAULT_PORT_SSL, - Geary.Endpoint.Flags.SSL, - Smtp.ClientConnection.DEFAULT_TIMEOUT_SEC); + + + public static void setup_account(AccountInformation account) { + // noop } - - private static Gee.HashMap? special_map = null; - public YahooAccount(string name, AccountInformation account_information, - Imap.Account remote, ImapDB.Account local) { - base (name, account_information, remote, local); - - if (special_map == null) { - special_map = new Gee.HashMap(); - - special_map.set(Imap.MailboxSpecifier.inbox.to_folder_path(null, null), Geary.SpecialFolderType.INBOX); - special_map.set(new Imap.FolderRoot("Sent"), Geary.SpecialFolderType.SENT); - special_map.set(new Imap.FolderRoot("Draft"), Geary.SpecialFolderType.DRAFTS); - special_map.set(new Imap.FolderRoot("Bulk Mail"), Geary.SpecialFolderType.SPAM); - special_map.set(new Imap.FolderRoot("Trash"), Geary.SpecialFolderType.TRASH); + public static void setup_service(ServiceInformation service) { + switch (service.protocol) { + case Protocol.IMAP: + service.host = "imap.mail.yahoo.com"; + service.port = Imap.IMAP_TLS_PORT; + service.transport_security = TlsNegotiationMethod.TRANSPORT; + break; + + case Protocol.SMTP: + service.host = "smtp.mail.yahoo.com"; + service.port = Smtp.SUBMISSION_TLS_PORT; + service.transport_security = TlsNegotiationMethod.TRANSPORT; + break; } } - - protected override MinimalFolder new_folder(Geary.FolderPath path, Imap.Account remote_account, - ImapDB.Account local_account, ImapDB.Folder local_folder) { - SpecialFolderType special_folder_type = special_map.has_key(path) ? special_map.get(path) - : Geary.SpecialFolderType.NONE; - return new YahooFolder(this, remote_account, local_account, local_folder, - special_folder_type); + + + public YahooAccount(AccountInformation config, + ImapDB.Account local, + Endpoint incoming_remote, + Endpoint outgoing_remote) { + base(config, local, incoming_remote, outgoing_remote); + } + + protected override MinimalFolder new_folder(ImapDB.Folder local_folder) { + FolderPath path = local_folder.get_path(); + SpecialFolderType type; + if (Imap.MailboxSpecifier.folder_path_is_inbox(path)) { + type = SpecialFolderType.INBOX; + } else { + // Despite Yahoo not advertising that it supports + // SPECIAL-USE via its CAPABILITIES, it lists the + // appropriate attributes in LIST results anyway, so we + // can just consult that. :| + type = local_folder.get_properties().attrs.get_special_folder_type(); + // There can be only one Inbox + if (type == SpecialFolderType.INBOX) { + type = SpecialFolderType.NONE; + } + } + + return new YahooFolder(this, local_folder, type); } -} +} diff -Nru geary-0.12.4/src/engine/imap-engine/yahoo/imap-engine-yahoo-folder.vala geary-3.32.0/src/engine/imap-engine/yahoo/imap-engine-yahoo-folder.vala --- geary-0.12.4/src/engine/imap-engine/yahoo/imap-engine-yahoo-folder.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/imap-engine/yahoo/imap-engine-yahoo-folder.vala 2019-03-17 13:39:29.000000000 +0000 @@ -5,9 +5,9 @@ */ private class Geary.ImapEngine.YahooFolder : GenericFolder { - public YahooFolder(YahooAccount account, Imap.Account remote, ImapDB.Account local, - ImapDB.Folder local_folder, SpecialFolderType special_folder_type) { - base (account, remote, local, local_folder, special_folder_type); + public YahooFolder(YahooAccount account, + ImapDB.Folder local_folder, + SpecialFolderType special_folder_type) { + base (account, local_folder, special_folder_type); } } - diff -Nru geary-0.12.4/src/engine/memory/memory-buffer.vala geary-3.32.0/src/engine/memory/memory-buffer.vala --- geary-0.12.4/src/engine/memory/memory-buffer.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/memory/memory-buffer.vala 2019-03-17 13:39:29.000000000 +0000 @@ -26,12 +26,12 @@ * Returns the number of valid (usable) bytes in the buffer. */ public abstract size_t size { get; } - + /** * Returns the number of bytes allocated (usable and unusable) for the buffer. */ public abstract size_t allocated_size { get; } - + /** * Returns a Bytes object holding the buffer's contents. * @@ -39,7 +39,7 @@ * the data. */ public abstract Bytes get_bytes(); - + /** * Returns an InputStream that can read the buffer in its current entirety. * @@ -53,7 +53,7 @@ public virtual InputStream get_input_stream() { return new MemoryInputStream.from_bytes(get_bytes()); } - + /** * Returns a ByteArray storing the buffer in its entirety. * @@ -65,10 +65,10 @@ public virtual ByteArray get_byte_array() { ByteArray byte_array = new ByteArray(); byte_array.append(get_bytes().get_data()); - + return byte_array; } - + /** * Returns an array of uint8 storing the buffer in its entirety. * @@ -82,7 +82,7 @@ public virtual uint8[] get_uint8_array() { return get_bytes().get_data(); } - + /** * Returns a copy of the contents of the buffer as though it was a null terminated string. * @@ -96,10 +96,10 @@ public virtual string to_string() { uint8[] buffer = get_uint8_array(); buffer += (uint8) '\0'; - + return (string) buffer; } - + /** * Returns a copy of the contents of the buffer as though it was a UTF-8 string. * @@ -112,7 +112,7 @@ */ public virtual string get_valid_utf8() { string str = to_string(); - + return str.validate() ? str : ""; } } diff -Nru geary-0.12.4/src/engine/memory/memory-byte-buffer.vala geary-3.32.0/src/engine/memory/memory-byte-buffer.vala --- geary-0.12.4/src/engine/memory/memory-byte-buffer.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/memory/memory-byte-buffer.vala 2019-03-17 13:39:29.000000000 +0000 @@ -17,7 +17,7 @@ return bytes.length; } } - + /** * {@inheritDoc} */ @@ -26,10 +26,10 @@ return allocated_bytes; } } - + private Bytes bytes; private size_t allocated_bytes; - + /** * filled is the number of usable bytes in the supplied buffer, allocated is the total size * of the buffer. @@ -41,11 +41,11 @@ */ public ByteBuffer(uint8[] data, size_t filled) { assert(filled <= data.length); - + bytes = new Bytes(data[0:filled]); allocated_bytes = bytes.length; } - + /** * filled is the number of usable bytes in the supplied buffer, allocated is the total size * of the buffer. @@ -54,11 +54,11 @@ */ public ByteBuffer.take(owned uint8[] data, size_t filled) { assert(filled <= data.length); - + bytes = new Bytes.take(data[0:filled]); allocated_bytes = data.length; } - + /** * Takes ownership and converts a ByteArray to a {@link ByteBuffer}. * @@ -68,7 +68,7 @@ bytes = ByteArray.free_to_bytes(byte_array); allocated_bytes = bytes.length; } - + /** * Takes ownership and converts a MemoryOutputStream to a {@link ByteBuffer}. * @@ -76,18 +76,18 @@ */ public ByteBuffer.from_memory_output_stream(MemoryOutputStream mouts) { assert(mouts.is_closed()); - + bytes = mouts.steal_as_bytes(); allocated_bytes = bytes.length; } - + /** * {@inheritDoc} */ public override Bytes get_bytes() { return bytes; } - + /** * {@inheritDoc} */ diff -Nru geary-0.12.4/src/engine/memory/memory-empty-buffer.vala geary-3.32.0/src/engine/memory/memory-empty-buffer.vala --- geary-0.12.4/src/engine/memory/memory-empty-buffer.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/memory/memory-empty-buffer.vala 2019-03-17 13:39:29.000000000 +0000 @@ -18,7 +18,7 @@ return (_instance != null) ? _instance : _instance = new EmptyBuffer(); } } - + /** * {@inheritDoc} */ @@ -27,7 +27,7 @@ return 0; } } - + /** * {@inheritDoc} */ @@ -36,34 +36,34 @@ return 0; } } - + private Bytes bytes = new Bytes(new uint8[0]); private ByteArray byte_array = new ByteArray(); - + private EmptyBuffer() { } - + /** * {@inheritDoc} */ public override Bytes get_bytes() { return bytes; } - + /** * {@inheritDoc} */ public unowned uint8[] to_unowned_uint8_array() { return bytes.get_data(); } - + /** * {@inheritDoc} */ public unowned string to_unowned_string() { return ""; } - + /** * {@inheritDoc} */ diff -Nru geary-0.12.4/src/engine/memory/memory-file-buffer.vala geary-3.32.0/src/engine/memory/memory-file-buffer.vala --- geary-0.12.4/src/engine/memory/memory-file-buffer.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/memory/memory-file-buffer.vala 2019-03-17 13:39:29.000000000 +0000 @@ -13,38 +13,38 @@ public class Geary.Memory.FileBuffer : Memory.Buffer, Memory.UnownedBytesBuffer { private File file; private MappedFile mmap; - + public override size_t size { get { return mmap.get_length(); } } - + public override size_t allocated_size { get { return mmap.get_length(); } } - + /** * The File is immediately opened when this is called. */ public FileBuffer(File file, bool readonly) throws Error { if (file.get_path() == null) throw new IOError.NOT_FOUND("File for Geary.Memory.FileBuffer not found"); - + this.file = file; mmap = new MappedFile(file.get_path(), !readonly); } - + public override Bytes get_bytes() { return Bytes.new_with_owner(to_unowned_uint8_array(), mmap); } - + public unowned uint8[] to_unowned_uint8_array() { unowned uint8[] buffer = (uint8[]) mmap.get_contents(); buffer.length = (int) mmap.get_length(); - + return buffer; } } diff -Nru geary-0.12.4/src/engine/memory/memory-growable-buffer.vala geary-3.32.0/src/engine/memory/memory-growable-buffer.vala --- geary-0.12.4/src/engine/memory/memory-growable-buffer.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/memory/memory-growable-buffer.vala 2019-03-17 13:39:29.000000000 +0000 @@ -15,77 +15,77 @@ public class Geary.Memory.GrowableBuffer : Memory.Buffer, Memory.UnownedBytesBuffer, Memory.UnownedStringBuffer { private static uint8[] NUL_ARRAY = { '\0' }; - + private ByteArray? byte_array = new ByteArray(); private Bytes? bytes = null; - + public override size_t size { // account for trailing NUL, which is always kept in place for UnownedStringBuffer get { if (bytes != null) return bytes.length - 1; - + assert(byte_array != null); - + return byte_array.len - 1; } } - + public override size_t allocated_size { get { return size; } } - + public GrowableBuffer() { // add NUL for UnownedStringBuffer byte_array.append(NUL_ARRAY); } - + private Bytes to_bytes() { if (bytes != null) { assert(byte_array == null); - + return bytes; } - + assert(byte_array != null); - + bytes = ByteArray.free_to_bytes(byte_array); byte_array = null; - + return bytes; } - + private unowned uint8[] get_bytes_no_nul() { assert(bytes != null); assert(bytes.get_size() > 0); - + return bytes.get_data()[0:bytes.get_size() - 1]; } - + private ByteArray to_byte_array() { if (byte_array != null) { assert(bytes == null); - + return byte_array; } - + assert(bytes != null); - + byte_array = Bytes.unref_to_array(bytes); bytes = null; - + return byte_array; } - + private unowned uint8[] get_byte_array_no_nul() { assert(byte_array != null); assert(byte_array.len > 0); - + return byte_array.data[0:byte_array.len - 1]; } - + /** * Appends the data to the existing GrowableBuffer. * @@ -95,18 +95,18 @@ public void append(uint8[] buffer) { if (buffer.length <= 0) return; - + to_byte_array(); - + // account for existing NUL assert(byte_array.len > 0); byte_array.set_size(byte_array.len - 1); - + // append buffer and new NUL for UnownedStringBuffer byte_array.append(buffer); byte_array.append(NUL_ARRAY); } - + /** * Allocate data within the backing buffer for writing. * @@ -120,23 +120,23 @@ */ public unowned uint8[] allocate(size_t requested_bytes) { to_byte_array(); - + // existing NUL must be there already assert(byte_array.len > 0); - + uint original_bytes = byte_array.len; uint new_size = original_bytes + (uint) requested_bytes; - + byte_array.set_size(new_size); byte_array.data[new_size - 1] = String.EOS; - + // only return portion request, not including new NUL, but overwriting existing NUL unowned uint8[] buffer = byte_array.data[(original_bytes - 1):(new_size - 1)]; assert(buffer.length == requested_bytes); - + return buffer; } - + /** * Trim a previously allocated buffer. * @@ -157,29 +157,29 @@ // ByteArray assert(byte_array != null); assert(filled_bytes <= allocation.length); - + // don't need to worry about the NUL byte here (unless caller overran buffer, then we // have bigger problems) byte_array.set_size(byte_array.len - (uint) (allocation.length - filled_bytes)); } - + /** * {@inheritDoc} */ public override Bytes get_bytes() { to_bytes(); assert(bytes.get_size() > 0); - + // don't return trailing nul return new Bytes.from_bytes(bytes, 0, bytes.get_size() - 1); } - + /** * {@inheritDoc} */ public override ByteArray get_byte_array() { ByteArray copy = new ByteArray(); - + // don't copy trailing NUL if (bytes != null) { copy.append(get_bytes_no_nul()); @@ -187,10 +187,10 @@ assert(byte_array != null); copy.append(get_byte_array_no_nul()); } - + return copy; } - + /** * {@inheritDoc} */ @@ -198,7 +198,7 @@ // because returned array is not unowned, Vala will make a copy return to_unowned_uint8_array(); } - + /** * {@inheritDoc} */ @@ -206,12 +206,12 @@ // in any case, don't return trailing NUL if (bytes != null) return get_bytes_no_nul(); - + assert(byte_array != null); - + return get_byte_array_no_nul(); } - + /** * {@inheritDoc} */ @@ -219,7 +219,7 @@ // because returned string is not unowned, Vala will make a copy return to_unowned_string(); } - + /** * {@inheritDoc} */ @@ -228,9 +228,9 @@ // string without copy-and-append if (bytes != null) return (string) bytes.get_data(); - + assert(byte_array != null); - + return (string) byte_array.data; } } diff -Nru geary-0.12.4/src/engine/memory/memory-offset-buffer.vala geary-3.32.0/src/engine/memory/memory-offset-buffer.vala --- geary-0.12.4/src/engine/memory/memory-offset-buffer.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/memory/memory-offset-buffer.vala 2019-03-17 13:39:29.000000000 +0000 @@ -13,22 +13,22 @@ * {@inheritDoc} */ public override size_t size { get { return buffer.size - offset; } } - + /** * {@inheritDoc} */ public override size_t allocated_size { get { return size; } } - + private Geary.Memory.Buffer buffer; private size_t offset; private Bytes? bytes = null; - + public OffsetBuffer(Geary.Memory.Buffer buffer, size_t offset) { assert(offset < buffer.size); this.buffer = buffer; this.offset = offset; } - + /** * {@inheritDoc} */ @@ -37,7 +37,7 @@ bytes = new Bytes.from_bytes(buffer.get_bytes(), offset, buffer.size - offset); return bytes; } - + /** * {@inheritDoc} */ diff -Nru geary-0.12.4/src/engine/memory/memory-string-buffer.vala geary-3.32.0/src/engine/memory/memory-string-buffer.vala --- geary-0.12.4/src/engine/memory/memory-string-buffer.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/memory/memory-string-buffer.vala 2019-03-17 13:39:29.000000000 +0000 @@ -15,50 +15,50 @@ return length; } } - + public override size_t allocated_size { get { return length; } } - + private string str; private size_t length; private Bytes? bytes = null; - + public StringBuffer(string str) { this.str = str; length = str.data.length; } - + /** * {@inheritDoc} */ public override Bytes get_bytes() { return (bytes != null) ? bytes : bytes = new Bytes(str.data); } - + /** * {@inheritDoc} */ public override string to_string() { return str; } - + /** * {@inheritDoc} */ public override string get_valid_utf8() { return str.validate() ? str : ""; } - + /** * {@inheritDoc} */ public unowned string to_unowned_string() { return str; } - + /** * {@inheritDoc} */ diff -Nru geary-0.12.4/src/engine/memory/memory-unowned-string-buffer.vala geary-3.32.0/src/engine/memory/memory-unowned-string-buffer.vala --- geary-0.12.4/src/engine/memory/memory-unowned-string-buffer.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/memory/memory-unowned-string-buffer.vala 2019-03-17 13:39:29.000000000 +0000 @@ -21,13 +21,13 @@ * The returned string should not be modified or freed. */ public abstract unowned string to_unowned_string(); - + /** * An unowned version of {@link Memory.Buffer.get_valid_utf8}. */ public virtual unowned string get_unowned_valid_utf8() { string str = to_unowned_string(); - + return str.validate() ? str : ""; } } diff -Nru geary-0.12.4/src/engine/meson.build geary-3.32.0/src/engine/meson.build --- geary-0.12.4/src/engine/meson.build 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/src/engine/meson.build 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,408 @@ +# Geary engine +geary_engine_vala_sources = files( + 'api/geary.vala', + 'api/geary-abstract-local-folder.vala', + 'api/geary-account.vala', + 'api/geary-account-information.vala', + 'api/geary-aggregated-folder-properties.vala', + 'api/geary-attachment.vala', + 'api/geary-base-object.vala', + 'api/geary-client-service.vala', + 'api/geary-composed-email.vala', + 'api/geary-contact.vala', + 'api/geary-contact-flags.vala', + 'api/geary-contact-importance.vala', + 'api/geary-contact-store.vala', + 'api/geary-credentials.vala', + 'api/geary-credentials-mediator.vala', + 'api/geary-email-flags.vala', + 'api/geary-email-header-set.vala', + 'api/geary-email-identifier.vala', + 'api/geary-email-properties.vala', + 'api/geary-email.vala', + 'api/geary-endpoint.vala', + 'api/geary-engine-error.vala', + 'api/geary-engine.vala', + 'api/geary-folder.vala', + 'api/geary-folder-path.vala', + 'api/geary-folder-properties.vala', + 'api/geary-folder-supports-archive.vala', + 'api/geary-folder-supports-copy.vala', + 'api/geary-folder-supports-create.vala', + 'api/geary-folder-supports-empty.vala', + 'api/geary-folder-supports-mark.vala', + 'api/geary-folder-supports-move.vala', + 'api/geary-folder-supports-remove.vala', + 'api/geary-logging.vala', + 'api/geary-named-flag.vala', + 'api/geary-named-flags.vala', + 'api/geary-problem-report.vala', + 'api/geary-progress-monitor.vala', + 'api/geary-revokable.vala', + 'api/geary-search-folder.vala', + 'api/geary-search-query.vala', + 'api/geary-service-information.vala', + 'api/geary-service-provider.vala', + 'api/geary-special-folder-type.vala', + + 'app/app-conversation.vala', + 'app/app-conversation-monitor.vala', + 'app/app-draft-manager.vala', + 'app/app-email-store.vala', + + 'app/conversation-monitor/app-append-operation.vala', + 'app/conversation-monitor/app-conversation-operation-queue.vala', + 'app/conversation-monitor/app-conversation-operation.vala', + 'app/conversation-monitor/app-conversation-set.vala', + 'app/conversation-monitor/app-external-append-operation.vala', + 'app/conversation-monitor/app-fill-window-operation.vala', + 'app/conversation-monitor/app-insert-operation.vala', + 'app/conversation-monitor/app-local-search-operation.vala', + 'app/conversation-monitor/app-remove-operation.vala', + 'app/conversation-monitor/app-reseed-operation.vala', + 'app/conversation-monitor/app-terminate-operation.vala', + + 'app/email-store/app-async-folder-operation.vala', + 'app/email-store/app-copy-operation.vala', + 'app/email-store/app-fetch-operation.vala', + 'app/email-store/app-list-operation.vala', + 'app/email-store/app-mark-operation.vala', + + 'common/common-message-data.vala', + + 'db/db.vala', + 'db/db-connection.vala', + 'db/db-context.vala', + 'db/db-database.vala', + 'db/db-database-error.vala', + 'db/db-result.vala', + 'db/db-statement.vala', + 'db/db-synchronous-mode.vala', + 'db/db-transaction-async-job.vala', + 'db/db-transaction-outcome.vala', + 'db/db-transaction-type.vala', + 'db/db-versioned-database.vala', + + 'imap/imap.vala', + 'imap/imap-error.vala', + 'imap/api/imap-account-session.vala', + 'imap/api/imap-client-service.vala', + 'imap/api/imap-email-flags.vala', + 'imap/api/imap-email-properties.vala', + 'imap/api/imap-folder.vala', + 'imap/api/imap-folder-properties.vala', + 'imap/api/imap-folder-root.vala', + 'imap/api/imap-folder-session.vala', + 'imap/api/imap-session-object.vala', + 'imap/command/imap-append-command.vala', + 'imap/command/imap-authenticate-command.vala', + 'imap/command/imap-capability-command.vala', + 'imap/command/imap-close-command.vala', + 'imap/command/imap-command.vala', + 'imap/command/imap-compress-command.vala', + 'imap/command/imap-copy-command.vala', + 'imap/command/imap-create-command.vala', + 'imap/command/imap-delete-command.vala', + 'imap/command/imap-examine-command.vala', + 'imap/command/imap-expunge-command.vala', + 'imap/command/imap-fetch-command.vala', + 'imap/command/imap-id-command.vala', + 'imap/command/imap-idle-command.vala', + 'imap/command/imap-list-command.vala', + 'imap/command/imap-list-return-parameter.vala', + 'imap/command/imap-login-command.vala', + 'imap/command/imap-logout-command.vala', + 'imap/command/imap-message-set.vala', + 'imap/command/imap-namespace-command.vala', + 'imap/command/imap-noop-command.vala', + 'imap/command/imap-search-command.vala', + 'imap/command/imap-search-criteria.vala', + 'imap/command/imap-search-criterion.vala', + 'imap/command/imap-select-command.vala', + 'imap/command/imap-starttls-command.vala', + 'imap/command/imap-status-command.vala', + 'imap/command/imap-store-command.vala', + 'imap/message/imap-data-format.vala', + 'imap/message/imap-envelope.vala', + 'imap/message/imap-fetch-body-data-specifier.vala', + 'imap/message/imap-fetch-data-specifier.vala', + 'imap/message/imap-flag.vala', + 'imap/message/imap-flags.vala', + 'imap/message/imap-internal-date.vala', + 'imap/message/imap-mailbox-specifier.vala', + 'imap/message/imap-message-data.vala', + 'imap/message/imap-message-flag.vala', + 'imap/message/imap-message-flags.vala', + 'imap/message/imap-namespace.vala', + 'imap/message/imap-sequence-number.vala', + 'imap/message/imap-status-data-type.vala', + 'imap/message/imap-tag.vala', + 'imap/message/imap-uid.vala', + 'imap/message/imap-uid-validity.vala', + 'imap/parameter/imap-atom-parameter.vala', + 'imap/parameter/imap-list-parameter.vala', + 'imap/parameter/imap-literal-parameter.vala', + 'imap/parameter/imap-nil-parameter.vala', + 'imap/parameter/imap-number-parameter.vala', + 'imap/parameter/imap-parameter.vala', + 'imap/parameter/imap-quoted-string-parameter.vala', + 'imap/parameter/imap-root-parameters.vala', + 'imap/parameter/imap-string-parameter.vala', + 'imap/parameter/imap-unquoted-string-parameter.vala', + 'imap/response/imap-capabilities.vala', + 'imap/response/imap-continuation-response.vala', + 'imap/response/imap-fetch-data-decoder.vala', + 'imap/response/imap-fetched-data.vala', + 'imap/response/imap-mailbox-attribute.vala', + 'imap/response/imap-mailbox-attributes.vala', + 'imap/response/imap-mailbox-information.vala', + 'imap/response/imap-namespace-response.vala', + 'imap/response/imap-response-code.vala', + 'imap/response/imap-response-code-type.vala', + 'imap/response/imap-server-data.vala', + 'imap/response/imap-server-data-type.vala', + 'imap/response/imap-server-response.vala', + 'imap/response/imap-status.vala', + 'imap/response/imap-status-data.vala', + 'imap/response/imap-status-response.vala', + 'imap/transport/imap-client-connection.vala', + 'imap/transport/imap-client-session.vala', + 'imap/transport/imap-deserializer.vala', + 'imap/transport/imap-serializer.vala', + + 'imap-db/imap-db-account.vala', + 'imap-db/imap-db-attachment.vala', + 'imap-db/imap-db-contact.vala', + 'imap-db/imap-db-database.vala', + 'imap-db/imap-db-email-identifier.vala', + 'imap-db/imap-db-folder.vala', + 'imap-db/imap-db-gc.vala', + 'imap-db/imap-db-message-addresses.vala', + 'imap-db/imap-db-message-row.vala', + 'imap-db/search/imap-db-search-email-identifier.vala', + 'imap-db/search/imap-db-search-folder.vala', + 'imap-db/search/imap-db-search-folder-properties.vala', + 'imap-db/search/imap-db-search-query.vala', + 'imap-db/search/imap-db-search-term.vala', + + 'imap-engine/imap-engine.vala', + 'imap-engine/imap-engine-account-operation.vala', + 'imap-engine/imap-engine-account-processor.vala', + 'imap-engine/imap-engine-account-synchronizer.vala', + 'imap-engine/imap-engine-contact-store.vala', + 'imap-engine/imap-engine-email-prefetcher.vala', + 'imap-engine/imap-engine-generic-account.vala', + 'imap-engine/imap-engine-generic-folder.vala', + 'imap-engine/imap-engine-minimal-folder.vala', + 'imap-engine/imap-engine-replay-operation.vala', + 'imap-engine/imap-engine-replay-queue.vala', + 'imap-engine/imap-engine-revokable-move.vala', + 'imap-engine/imap-engine-revokable-committed-move.vala', + 'imap-engine/imap-engine-send-replay-operation.vala', + 'imap-engine/gmail/imap-engine-gmail-account.vala', + 'imap-engine/gmail/imap-engine-gmail-all-mail-folder.vala', + 'imap-engine/gmail/imap-engine-gmail-drafts-folder.vala', + 'imap-engine/gmail/imap-engine-gmail-folder.vala', + 'imap-engine/gmail/imap-engine-gmail-search-folder.vala', + 'imap-engine/gmail/imap-engine-gmail-spam-trash-folder.vala', + 'imap-engine/other/imap-engine-other-account.vala', + 'imap-engine/other/imap-engine-other-folder.vala', + 'imap-engine/outlook/imap-engine-outlook-account.vala', + 'imap-engine/outlook/imap-engine-outlook-folder.vala', + 'imap-engine/outlook/imap-engine-outlook-drafts-folder.vala', + 'imap-engine/replay-ops/imap-engine-abstract-list-email.vala', + 'imap-engine/replay-ops/imap-engine-copy-email.vala', + 'imap-engine/replay-ops/imap-engine-create-email.vala', + 'imap-engine/replay-ops/imap-engine-empty-folder.vala', + 'imap-engine/replay-ops/imap-engine-fetch-email.vala', + 'imap-engine/replay-ops/imap-engine-list-email-by-id.vala', + 'imap-engine/replay-ops/imap-engine-list-email-by-sparse-id.vala', + 'imap-engine/replay-ops/imap-engine-mark-email.vala', + 'imap-engine/replay-ops/imap-engine-move-email-commit.vala', + 'imap-engine/replay-ops/imap-engine-move-email-prepare.vala', + 'imap-engine/replay-ops/imap-engine-move-email-revoke.vala', + 'imap-engine/replay-ops/imap-engine-remove-email.vala', + 'imap-engine/replay-ops/imap-engine-replay-append.vala', + 'imap-engine/replay-ops/imap-engine-replay-removal.vala', + 'imap-engine/replay-ops/imap-engine-replay-update.vala', + 'imap-engine/replay-ops/imap-engine-server-search-email.vala', + 'imap-engine/replay-ops/imap-engine-user-close.vala', + 'imap-engine/yahoo/imap-engine-yahoo-account.vala', + 'imap-engine/yahoo/imap-engine-yahoo-folder.vala', + + 'memory/memory-buffer.vala', + 'memory/memory-byte-buffer.vala', + 'memory/memory-empty-buffer.vala', + 'memory/memory-file-buffer.vala', + 'memory/memory-growable-buffer.vala', + 'memory/memory-offset-buffer.vala', + 'memory/memory-string-buffer.vala', + 'memory/memory-unowned-byte-array-buffer.vala', + 'memory/memory-unowned-bytes-buffer.vala', + 'memory/memory-unowned-string-buffer.vala', + + 'mime/mime-content-disposition.vala', + 'mime/mime-content-parameters.vala', + 'mime/mime-content-type.vala', + 'mime/mime-data-format.vala', + 'mime/mime-disposition-type.vala', + 'mime/mime-error.vala', + 'mime/mime-multipart-subtype.vala', + + 'nonblocking/nonblocking-batch.vala', + 'nonblocking/nonblocking-concurrent.vala', + 'nonblocking/nonblocking-counting-semaphore.vala', + 'nonblocking/nonblocking-error.vala', + 'nonblocking/nonblocking-lock.vala', + 'nonblocking/nonblocking-mutex.vala', + 'nonblocking/nonblocking-queue.vala', + 'nonblocking/nonblocking-reporting-semaphore.vala', + 'nonblocking/nonblocking-variants.vala', + + 'outbox/outbox-email-identifier.vala', + 'outbox/outbox-email-properties.vala', + 'outbox/outbox-folder.vala', + 'outbox/outbox-folder-properties.vala', + + 'rfc822/rfc822.vala', + 'rfc822/rfc822-error.vala', + 'rfc822/rfc822-gmime-filter-flowed.vala', + 'rfc822/rfc822-gmime-filter-blockquotes.vala', + 'rfc822/rfc822-gmime-filter-plain.vala', + 'rfc822/rfc822-mailbox-addresses.vala', + 'rfc822/rfc822-mailbox-address.vala', + 'rfc822/rfc822-message.vala', + 'rfc822/rfc822-message-data.vala', + 'rfc822/rfc822-part.vala', + 'rfc822/rfc822-utils.vala', + + 'smtp/smtp-authenticator.vala', + 'smtp/smtp-capabilities.vala', + 'smtp/smtp-client-connection.vala', + 'smtp/smtp-client-service.vala', + 'smtp/smtp-client-session.vala', + 'smtp/smtp-command.vala', + 'smtp/smtp-data-format.vala', + 'smtp/smtp-error.vala', + 'smtp/smtp-greeting.vala', + 'smtp/smtp-login-authenticator.vala', + 'smtp/smtp-oauth2-authenticator.vala', + 'smtp/smtp-plain-authenticator.vala', + 'smtp/smtp-request.vala', + 'smtp/smtp-response.vala', + 'smtp/smtp-response-code.vala', + 'smtp/smtp-response-line.vala', + + 'state/state-machine-descriptor.vala', + 'state/state-machine.vala', + 'state/state-mapping.vala', + + 'util/util-ascii.vala', + 'util/util-collection.vala', + 'util/util-config-file.vala', + 'util/util-connectivity-manager.vala', + 'util/util-error-context.vala', + 'util/util-files.vala', + 'util/util-generic-capabilities.vala', + 'util/util-html.vala', + 'util/util-idle-manager.vala', + 'util/util-imap-utf7.vala', + 'util/util-inet.vala', + 'util/util-iterable.vala', + 'util/util-js.vala', + 'util/util-numeric.vala', + 'util/util-object.vala', + 'util/util-reference-semantics.vala', + 'util/util-scheduler.vala', + 'util/util-stream.vala', + 'util/util-string.vala', + 'util/util-synchronization.vala', + 'util/util-time.vala', + 'util/util-timeout-manager.vala', + 'util/util-trillian.vala', +) + +geary_engine_sources = [ + geary_engine_vala_sources, + geary_version_vala, +] + +geary_engine_dependencies = [ + gee, + gio, + glib, + gmime, + javascriptcoregtk, + libxml, + posix, + sqlite +] + +if libunwind_dep.found() + geary_engine_dependencies += libunwind +endif + +build_dir = meson.current_build_dir() + +# Generate internal VAPI for unit testing. See Meson issue +# https://github.com/mesonbuild/meson/issues/1781 for official +# internal VAPI support. +geary_engine_vala_options = geary_vala_options +geary_engine_vala_options += [ + '--internal-header=@0@/geary-engine-internal.h'.format(build_dir), + '--internal-vapi=@0@/geary-engine-internal.vapi'.format(build_dir) +] + +if libunwind_dep.found() + geary_engine_vala_options += [ + '-D', 'HAVE_LIBUNWIND', + ] +endif + +geary_engine_lib = static_library('geary-engine', + geary_engine_sources, + dependencies: geary_engine_dependencies, + include_directories: config_h_dir, + vala_args: geary_engine_vala_options, + c_args: geary_c_options, +) + +# Dummy target to tell Meson about the internal VAPI given the +# workaround above, and fix the VAPI header to work around +# GNOME/vala#358 +geary_engine_internal_header_fixup = custom_target( + 'geary_engine_internal_header_fixup', + output: [ + 'geary-engine-internal.h', + 'geary-engine-internal.vapi', + ], + command: [ + find_program('sed'), + '-ibak', + 's/geary-engine.h/geary-engine-internal.h/g', + '@OUTDIR@/geary-engine-internal.vapi', + ], + depends: geary_engine_lib +) + +geary_engine_dep = declare_dependency( + link_with: [ + geary_engine_lib, + sqlite3_unicodesn_lib + ], + include_directories: include_directories('.') +) + +geary_engine_internal_dep = declare_dependency( + # Can't just include geary_engine_lib in link_with since that will + # pull in the public header and we get duplicate symbol errors. + link_args: [ + '-L' + build_dir, + '-lgeary-engine' + ], + link_with: [ + sqlite3_unicodesn_lib, + ], + include_directories: include_directories('.'), + sources: geary_engine_internal_header_fixup +) diff -Nru geary-0.12.4/src/engine/mime/mime-content-disposition.vala geary-3.32.0/src/engine/mime/mime-content-disposition.vala --- geary-0.12.4/src/engine/mime/mime-content-disposition.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/mime/mime-content-disposition.vala 2019-03-17 13:39:29.000000000 +0000 @@ -17,50 +17,50 @@ * See [[https://tools.ietf.org/html/rfc2183#section-2.3]] */ public const string FILENAME = "filename"; - + /** * Creation-Date parameter name. * * See [[https://tools.ietf.org/html/rfc2183#section-2.4]] */ public const string CREATION_DATE = "creation-date"; - + /** * Modification-Date parameter name. * * See [[https://tools.ietf.org/html/rfc2183#section-2.5]] */ public const string MODIFICATION_DATE = "modification-date"; - + /** * Read-Date parameter name. * * See [[https://tools.ietf.org/html/rfc2183#section-2.6]] */ public const string READ_DATE = "read-date"; - + /** * Size parameter name. * * See [[https://tools.ietf.org/html/rfc2183#section-2.7]] */ public const string SIZE = "size"; - + /** * The {@link DispositionType}, which is {@link DispositionType.UNSPECIFIED} if not specified. */ public DispositionType disposition_type { get; private set; } - + /** * True if the original DispositionType was unknown. */ public bool is_unknown_disposition_type { get; private set; } - + /** * The original disposition type string. */ public string? original_disposition_type_string { get; private set; } - + /** * Various parameters associated with the content's disposition. * @@ -74,7 +74,7 @@ * @see SIZE */ public ContentParameters params { get; private set; } - + /** * Create a Content-Disposition representation */ @@ -85,7 +85,7 @@ original_disposition_type_string = disposition; this.params = params ?? new ContentParameters(); } - + /** * Create a simplified Content-Disposition representation. */ @@ -95,7 +95,7 @@ original_disposition_type_string = null; this.params = new ContentParameters(); } - + internal ContentDisposition.from_gmime(GMime.ContentDisposition content_disposition) { bool is_unknown; disposition_type = DispositionType.deserialize(content_disposition.get_disposition(), diff -Nru geary-0.12.4/src/engine/mime/mime-content-parameters.vala geary-3.32.0/src/engine/mime/mime-content-parameters.vala --- geary-0.12.4/src/engine/mime/mime-content-parameters.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/mime/mime-content-parameters.vala 2019-03-17 13:39:29.000000000 +0000 @@ -6,25 +6,26 @@ /** * Content parameters (for {@link ContentType} and {@link ContentDisposition}). + * + * This class is immutable. */ - public class Geary.Mime.ContentParameters : BaseObject { public int size { get { return params.size; } } - + public Gee.Collection attributes { owned get { return params.keys; } } - + // See get_parameters() for why the keys but not the values are stored case-insensitive private Gee.HashMap params = new Gee.HashMap( Ascii.stri_hash, Ascii.stri_equal); - + /** * Create a mapping of content parameters. * @@ -37,14 +38,28 @@ if (params != null && params.size > 0) Collection.map_set_all(this.params, params); } - + + /** + * Create a mapping of content parameters. + * + * Note that the given params must be a two-dimensional array, + * where each element contains a key/value pair. + */ + public ContentParameters.from_array(string[,] params) { + for (int i = 0; i < params.length[0]; i++) { + this.params.set(params[i,0], params[i,1]); + } + } + internal ContentParameters.from_gmime(GMime.Param? gmime_param) { + Gee.Map params = new Gee.HashMap(); while (gmime_param != null) { - set_parameter(gmime_param.get_name(), gmime_param.get_value()); + params.set(gmime_param.get_name(), gmime_param.get_value()); gmime_param = gmime_param.get_next(); } + this(params); } - + /** * A read-only mapping of parameter attributes (names) and values. * @@ -58,7 +73,7 @@ public Gee.Map get_parameters() { return params.read_only_view; } - + /** * Returns the parameter value for the attribute name. * @@ -67,7 +82,7 @@ public string? get_value(string attribute) { return params.get(attribute); } - + /** * Returns true if the attribute has the supplied value (case-insensitive comparison). * @@ -75,10 +90,10 @@ */ public bool has_value_ci(string attribute, string value) { string? stored = params.get(attribute); - + return (stored != null) ? Ascii.stri_equal(stored, value) : false; } - + /** * Returns true if the attribute has the supplied value (case-sensitive comparison). * @@ -86,29 +101,8 @@ */ public bool has_value_cs(string attribute, string value) { string? stored = params.get(attribute); - + return (stored != null) ? Ascii.str_equal(stored, value) : false; } - - /** - * Add or replace the parameter. - * - * Returns true if the parameter was added, false, otherwise. - */ - public bool set_parameter(string attribute, string value) { - bool added = !params.has_key(attribute); - params.set(attribute, value); - - return added; - } - - /** - * Removes the parameter. - * - * Returns true if the parameter was present. - */ - public bool remove_parameter(string attribute) { - return params.unset(attribute); - } -} +} diff -Nru geary-0.12.4/src/engine/mime/mime-content-type.vala geary-3.32.0/src/engine/mime/mime-content-type.vala --- geary-0.12.4/src/engine/mime/mime-content-type.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/mime/mime-content-type.vala 2019-03-17 13:39:29.000000000 +0000 @@ -8,8 +8,9 @@ * A representation of an RFC 2045 MIME Content-Type field. * * See [[https://tools.ietf.org/html/rfc2045#section-5]] + * + * This class is immutable. */ - public class Geary.Mime.ContentType : Geary.BaseObject { /** @@ -20,15 +21,32 @@ public const string WILDCARD = "*"; /** - * Default Content-Type for unknown or unmarked content. + * Default Content-Type for inline, displayed entities. + * + * This is as specified by RFC 2052 § 5.2. */ - public const string DEFAULT_CONTENT_TYPE = "application/octet-stream"; + public static ContentType DISPLAY_DEFAULT; + + /** + * Default Content-Type for attached entities. + * + * Although RFC 2052 § 5.2 specifies US-ASCII as the default, for + * attachments assume a binary blob so that users aren't presented + * with garbled text editor content and warnings on opening it. + */ + public static ContentType ATTACHMENT_DEFAULT; private static Gee.Map TYPES_TO_EXTENSIONS = new Gee.HashMap(); static construct { + DISPLAY_DEFAULT = new ContentType( + "text", "plain", + new ContentParameters.from_array({{"charset", "us-ascii"}}) + ); + ATTACHMENT_DEFAULT = new ContentType("application", "octet-stream", null); + // XXX We should be loading file name extension information // from /etc/mime.types and/or the XDG Shared MIME-info // Database globs2 file, usually located at @@ -54,15 +72,18 @@ throw new MimeError.PARSE("Empty MIME Content-Type"); if (!str.contains("/")) - throw new MimeError.PARSE("Invalid MIME Content-Type: %s", str); + throw new MimeError.PARSE("Invalid MIME Content-Type: %s", str); return new ContentType.from_gmime(new GMime.ContentType.from_string(str)); } /** * Attempts to guess the content type for a buffer using GIO sniffing. + * + * Returns null if it could not be guessed. */ - public static ContentType guess_type(string? file_name, Geary.Memory.Buffer? buf) throws Error { + public static ContentType? guess_type(string? file_name, Geary.Memory.Buffer? buf) + throws Error { string? mime_type = null; if (file_name != null) { @@ -89,10 +110,7 @@ mime_type = GLib.ContentType.get_mime_type(glib_type); } - if (Geary.String.is_empty(mime_type)) { - mime_type = DEFAULT_CONTENT_TYPE; - } - return deserialize(mime_type); + return !Geary.String.is_empty(mime_type) ? deserialize(mime_type) : null; } @@ -107,7 +125,7 @@ * @see has_media_type */ public string media_type { get; private set; } - + /** * The subtype (extension-token or iana-token) portion of the Content-Type field. * @@ -119,7 +137,7 @@ * @see has_media_subtype */ public string media_subtype { get; private set; } - + /** * Content parameters, if any, in the Content-Type field. * @@ -127,7 +145,7 @@ * no parameters. */ public ContentParameters params { get; private set; } - + /** * Create a MIME Content-Type representative object. */ @@ -136,7 +154,7 @@ this.media_subtype = media_subtype.strip(); this.params = params ?? new ContentParameters(); } - + internal ContentType.from_gmime(GMime.ContentType content_type) { media_type = content_type.get_media_type().strip(); media_subtype = content_type.get_media_subtype().strip(); @@ -153,7 +171,7 @@ public bool has_media_type(string media_type) { return (media_type != WILDCARD) ? Ascii.stri_equal(this.media_type, media_type) : true; } - + /** * Compares the {@link media_subtype} with the supplied subtype. * @@ -164,7 +182,7 @@ public bool has_media_subtype(string media_subtype) { return (media_subtype != WILDCARD) ? Ascii.stri_equal(this.media_subtype, media_subtype) : true; } - + /** * Returns the {@link ContentType}'s media content type (its "MIME type"). * @@ -194,7 +212,7 @@ public bool is_type(string media_type, string media_subtype) { return has_media_type(media_type) && has_media_subtype(media_subtype); } - + /** * Compares this {@link ContentType} with another instance. * @@ -206,7 +224,7 @@ public bool is_same(ContentType other) { return is_type(other.media_type, other.media_subtype); } - + /** * Compares the supplied MIME type (i.e. "image/jpeg") with this instance. * @@ -219,59 +237,52 @@ int index = mime_type.index_of_char('/'); if (index < 0) throw new MimeError.PARSE("Invalid MIME type: %s", mime_type); - + string mime_media_type = mime_type.substring(0, index).strip(); - + string mime_media_subtype = mime_type.substring(index + 1); index = mime_media_subtype.index_of_char(';'); if (index >= 0) mime_media_subtype = mime_media_subtype.substring(0, index); mime_media_subtype = mime_media_subtype.strip(); - + if (String.is_empty(mime_media_type) || String.is_empty(mime_media_subtype)) throw new MimeError.PARSE("Invalid MIME type: %s", mime_type); - - return is_type(mime_media_type, mime_media_subtype); - } - /** - * Determines if this type is the same as the default content type. - */ - public bool is_default() { - return get_mime_type() == DEFAULT_CONTENT_TYPE; + return is_type(mime_media_type, mime_media_subtype); } public string serialize() { StringBuilder builder = new StringBuilder(); builder.append_printf("%s/%s", media_type, media_subtype); - + if (params != null && params.size > 0) { foreach (string attribute in params.attributes) { string value = params.get_value(attribute); - + switch (DataFormat.get_encoding_requirement(value)) { case DataFormat.Encoding.QUOTING_OPTIONAL: builder.append_printf("; %s=%s", attribute, value); break; - + case DataFormat.Encoding.QUOTING_REQUIRED: builder.append_printf("; %s=\"%s\"", attribute, value); break; - + case DataFormat.Encoding.UNALLOWED: message("Cannot encode ContentType param value %s=\"%s\": unallowed", attribute, value); break; - + default: assert_not_reached(); } } } - + return builder.str; } - + public string to_string() { return serialize(); } diff -Nru geary-0.12.4/src/engine/mime/mime-data-format.vala geary-3.32.0/src/engine/mime/mime-data-format.vala --- geary-0.12.4/src/engine/mime/mime-data-format.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/mime/mime-data-format.vala 2019-03-17 13:39:29.000000000 +0000 @@ -23,22 +23,22 @@ public Encoding get_encoding_requirement(string str) { if (String.is_empty(str)) return Encoding.QUOTING_REQUIRED; - + Encoding encoding = Encoding.QUOTING_OPTIONAL; int index = 0; for (;;) { char ch = str[index++]; if (ch == String.EOS) break; - + if (ch.iscntrl()) return Encoding.UNALLOWED; - + // don't return immediately, it's possible unallowed characters may still be ahead if (ch.isspace() || ch in CONTENT_TYPE_TOKEN_SPECIALS) encoding = Encoding.QUOTING_REQUIRED; } - + return encoding; } diff -Nru geary-0.12.4/src/engine/mime/mime-disposition-type.vala geary-3.32.0/src/engine/mime/mime-disposition-type.vala --- geary-0.12.4/src/engine/mime/mime-disposition-type.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/mime/mime-disposition-type.vala 2019-03-17 13:39:29.000000000 +0000 @@ -21,7 +21,7 @@ UNSPECIFIED = -1, ATTACHMENT = 0, INLINE = 1; - + /** * Convert the disposition-type field into an internal representation. * @@ -31,24 +31,24 @@ */ public static DispositionType deserialize(string? str, out bool is_unknown) { is_unknown = false; - + if (String.is_empty_or_whitespace(str)) return UNSPECIFIED; - + switch (Ascii.strdown(str)) { case "inline": return INLINE; - + case "attachment": return ATTACHMENT; - + default: is_unknown = true; - + return ATTACHMENT; } } - + /** * Returns null if value is {@link UNSPECIFIED} */ @@ -56,26 +56,26 @@ switch (this) { case UNSPECIFIED: return null; - + case ATTACHMENT: return "attachment"; - + case INLINE: return "inline"; - + default: assert_not_reached(); } } - + internal static DispositionType from_int(int i) { switch (i) { case INLINE: return INLINE; - + case UNSPECIFIED: return UNSPECIFIED; - + // see note in class description for why unknown content-dispositions are treated as // attachments case ATTACHMENT: diff -Nru geary-0.12.4/src/engine/mime/mime-multipart-subtype.vala geary-3.32.0/src/engine/mime/mime-multipart-subtype.vala --- geary-0.12.4/src/engine/mime/mime-multipart-subtype.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/mime/mime-multipart-subtype.vala 2019-03-17 13:39:29.000000000 +0000 @@ -39,7 +39,7 @@ * See [[http://tools.ietf.org/html/rfc2387]] */ RELATED; - + /** * Converts a {@link ContentType} into a {@link MultipartSubtype}. * @@ -48,24 +48,24 @@ public static MultipartSubtype from_content_type(ContentType? content_type, out bool is_unknown) { if (content_type == null || !content_type.has_media_type("multipart")) { is_unknown = true; - + return MIXED; } - + is_unknown = false; switch (Ascii.strdown(content_type.media_subtype)) { case "mixed": return MIXED; - + case "alternative": return ALTERNATIVE; - + case "related": return RELATED; - + default: is_unknown = true; - + return MIXED; } } diff -Nru geary-0.12.4/src/engine/nonblocking/nonblocking-abstract-semaphore.vala geary-3.32.0/src/engine/nonblocking/nonblocking-abstract-semaphore.vala --- geary-0.12.4/src/engine/nonblocking/nonblocking-abstract-semaphore.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/nonblocking/nonblocking-abstract-semaphore.vala 1970-01-01 00:00:00.000000000 +0000 @@ -1,170 +0,0 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. - * - * This software is licensed under the GNU Lesser General Public License - * (version 2.1 or later). See the COPYING file in this distribution. - */ - -public abstract class Geary.Nonblocking.AbstractSemaphore : BaseObject { - private class Pending : BaseObject { - public unowned SourceFunc cb; - public Cancellable? cancellable; - public bool passed = false; - public bool scheduled = false; - - public signal void cancelled(); - - public Pending(SourceFunc cb, Cancellable? cancellable) { - this.cb = cb; - this.cancellable = cancellable; - - if (cancellable != null) - cancellable.cancelled.connect(on_cancelled); - } - - ~Pending() { - if (cancellable != null) - cancellable.cancelled.disconnect(on_cancelled); - } - - private void on_cancelled() { - cancelled(); - } - - public void schedule(bool passed) { - assert(!scheduled); - - this.passed = passed; - - Scheduler.on_idle(cb); - scheduled = true; - } - } - - private bool broadcast; - private bool autoreset; - private Cancellable? cancellable; - private bool passed = false; - private Gee.List pending_queue = new Gee.LinkedList(); - - protected AbstractSemaphore(bool broadcast, bool autoreset, Cancellable? cancellable = null) { - this.broadcast = broadcast; - this.autoreset = autoreset; - this.cancellable = cancellable; - - if (cancellable != null) - cancellable.cancelled.connect(on_cancelled); - } - - ~AbstractSemaphore() { - if (pending_queue.size > 0) { - warning("Nonblocking semaphore destroyed with %d pending callers", pending_queue.size); - - foreach (Pending pending in pending_queue) - pending.cancelled.disconnect(on_pending_cancelled); - } - - if (cancellable != null) - cancellable.cancelled.disconnect(on_cancelled); - } - - private void trigger(bool all) { - if (pending_queue.size == 0) - return; - - // in both cases, mark the Pending object(s) as passed in case this is an auto-reset - // semaphore - if (all) { - foreach (Pending pending in pending_queue) - pending.schedule(passed); - - pending_queue.clear(); - } else { - Pending pending = pending_queue.remove_at(0); - pending.schedule(passed); - } - } - - public virtual new void notify() throws Error { - check_cancelled(); - - passed = true; - - trigger(broadcast); - - if (autoreset) - reset(); - } - - /** - * Calls notify() without throwing an Exception, which is merely logged if encountered. - */ - public void blind_notify() { - try { - notify(); - } catch (Error err) { - message("Error notifying semaphore: %s", err.message); - } - } - - public virtual async void wait_async(Cancellable? cancellable = null) throws Error { - for (;;) { - check_user_cancelled(cancellable); - check_cancelled(); - - if (passed) - return; - - Pending pending = new Pending(wait_async.callback, cancellable); - pending.cancelled.connect(on_pending_cancelled); - - pending_queue.add(pending); - yield; - - pending.cancelled.disconnect(on_pending_cancelled); - - if (pending.passed) { - check_user_cancelled(cancellable); - - return; - } - } - } - - public virtual void reset() { - passed = false; - } - - public bool is_passed() { - return passed; - } - - public bool is_cancelled() { - return (cancellable != null) ? cancellable.is_cancelled() : false; - } - - private void check_cancelled() throws Error { - if (is_cancelled()) - throw new IOError.CANCELLED("Semaphore cancelled"); - } - - private static void check_user_cancelled(Cancellable? cancellable) throws Error { - if (cancellable != null && cancellable.is_cancelled()) - throw new IOError.CANCELLED("User cancelled operation"); - } - - private void on_pending_cancelled(Pending pending) { - // if already scheduled, the cancellation will be dealt with when they wake up - if (pending.scheduled) - return; - - bool removed = pending_queue.remove(pending); - assert(removed); - - Scheduler.on_idle(pending.cb); - } - - private void on_cancelled() { - trigger(true); - } -} - diff -Nru geary-0.12.4/src/engine/nonblocking/nonblocking-batch.vala geary-3.32.0/src/engine/nonblocking/nonblocking-batch.vala --- geary-0.12.4/src/engine/nonblocking/nonblocking-batch.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/nonblocking/nonblocking-batch.vala 2019-03-17 13:39:29.000000000 +0000 @@ -56,9 +56,9 @@ * An invalid {@link BatchOperation} identifier. */ public const int INVALID_ID = -1; - + private const int START_ID = 1; - + private class BatchContext : BaseObject { public int id; public Nonblocking.BatchOperation op; @@ -66,77 +66,77 @@ public bool completed = false; public Object? returned = null; public Error? threw = null; - + public BatchContext(int id, Nonblocking.BatchOperation op) { this.id = id; this.op = op; } - + public void schedule(Nonblocking.Batch owner, Cancellable? cancellable) { // hold a strong ref to the owner until the operation is completed this.owner = owner; - + op.execute_async.begin(cancellable, on_op_completed); } - + private void on_op_completed(Object? source, AsyncResult result) { completed = true; - + try { returned = op.execute_async.end(result); } catch (Error err) { threw = err; } - + owner.on_context_completed(this); - + // drop the reference to the owner to prevent a reference loop owner = null; } } - + /** * Returns the number of {@link BatchOperation}s added to the batch. */ public int size { get { return contexts.size; } } - + /** * Returns the first exception encountered after completing {@link execute_all_async}. */ public Error? first_exception { get; private set; default = null; } - + private Gee.HashMap contexts = new Gee.HashMap(); private Nonblocking.Semaphore sem = new Nonblocking.Semaphore(); private int next_result_id = START_ID; private bool locked = false; private int completed_ops = 0; - + /** * Fired when a {@link BatchOperation} is added to the batch. */ public signal void added(Nonblocking.BatchOperation op, int id); - + /** * Fired when batch execution has started. */ public signal void started(int count); - + /** * Fired when a {@link BatchOperation} has completed. */ public signal void operation_completed(Nonblocking.BatchOperation op, Object? returned, Error? threw); - + /** * Fired when all {@link BatchOperation}s have completed. */ public signal void completed(int count, Error? first_error); - + public Batch() { } - + /** * Adds a {@link BatchOperation} for later execution. * @@ -151,18 +151,18 @@ public int add(Nonblocking.BatchOperation op) { if (locked) { warning("NonblockingBatch already executed or executing"); - + return INVALID_ID; } - + int id = next_result_id++; contexts.set(id, new BatchContext(id, op)); - + added(op, id); - + return id; } - + /** * Executes all the {@link BatchOperation}s added to the batch. * @@ -170,7 +170,7 @@ * * If the batch is executing or already executed, IOError.PENDING will be thrown. If the * Cancellable is already cancelled, IOError.CANCELLED is thrown. Other errors may be thrown - * as well; see {@link AbstractSemaphore.wait_async}. + * as well; see {@link Lock.wait_async}. * * Batch will launch each BatchOperation in the order added. Depending on the BatchOperation, * this does not guarantee that they'll complete in any particular order. @@ -180,42 +180,42 @@ public async void execute_all_async(Cancellable? cancellable = null) throws Error { if (locked) throw new IOError.PENDING("NonblockingBatch already executed or executing"); - + locked = true; - + // if empty, quietly exit (leaving the object locked; NonblockingBatch is a one-shot deal) if (contexts.size == 0) return; - + // if already cancelled, not-so-quietly exit if (cancellable != null && cancellable.is_cancelled()) throw new IOError.CANCELLED("NonblockingBatch cancelled before executing"); - + started(contexts.size); - + // fire them off in order they were submitted; this may hide bugs, but it also makes other // bugs reproducible int count = 0; for (int id = START_ID; id < next_result_id; id++) { BatchContext? context = contexts.get(id); assert(context != null); - + context.schedule(this, cancellable); count++; } - + assert(count == contexts.size); - + yield sem.wait_async(cancellable); } - + /** * Returns a Set of identifiers for all added {@link BatchOperation}s. */ public Gee.Set get_ids() { return contexts.keys; } - + /** * Returns the NonblockingBatchOperation for the supplied identifier. * @@ -223,10 +223,10 @@ */ public Nonblocking.BatchOperation? get_operation(int id) { BatchContext? context = contexts.get(id); - + return (context != null) ? context.op : null; } - + /** * Returns the resulting Object from the operation for the supplied identifier. * @@ -242,16 +242,16 @@ BatchContext? context = contexts.get(id); if (context == null) return null; - + if (!context.completed) throw new IOError.BUSY("NonblockingBatchOperation %d not completed", id); - + if (context.threw != null) throw context.threw; - + return context.returned; } - + /** * If no results are examined via {@link get_result}, this method can be used to manually throw * the first seen Error from the operations. @@ -260,20 +260,20 @@ if (first_exception != null) throw first_exception; } - + /** * Returns the Error message if an exception was encountered, null otherwise. */ public string? get_first_exception_message() { return (first_exception != null) ? first_exception.message : null; } - + private void on_context_completed(BatchContext context) { if (first_exception == null && context.threw != null) first_exception = context.threw; - + operation_completed(context.op, context.returned, context.threw); - + assert(completed_ops < contexts.size); if (++completed_ops == contexts.size) { try { @@ -281,7 +281,7 @@ } catch (Error err) { debug("Unable to notify NonblockingBatch semaphore: %s", err.message); } - + completed(completed_ops, first_exception); } } diff -Nru geary-0.12.4/src/engine/nonblocking/nonblocking-concurrent.vala geary-3.32.0/src/engine/nonblocking/nonblocking-concurrent.vala --- geary-0.12.4/src/engine/nonblocking/nonblocking-concurrent.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/nonblocking/nonblocking-concurrent.vala 2019-03-17 13:39:29.000000000 +0000 @@ -13,7 +13,7 @@ public class Geary.Nonblocking.Concurrent : BaseObject { public const int DEFAULT_MAX_THREADS = 4; - + /** * A callback invoked from a {@link Concurrent} background thread. * @@ -24,33 +24,33 @@ * foreground caller on behalf of the callback. */ public delegate void ConcurrentCallback(Cancellable? cancellable) throws Error; - + private class ConcurrentOperation : BaseObject { private unowned ConcurrentCallback cb; private Cancellable? cancellable; private Error? caught_err = null; private Event event = new Event(); - + public ConcurrentOperation(ConcurrentCallback cb, Cancellable? cancellable) { this.cb = cb; this.cancellable = cancellable; } - + // Called from the foreground thread to wait for the background to complete. // // Can't cancel here because we *must* wait for the operation to be executed by the // thread and complete public async void wait_async() throws Error { yield event.wait_async(); - + if (caught_err != null) throw caught_err; - + // now deal with cancellation if (cancellable != null && cancellable.is_cancelled()) throw new IOError.CANCELLED("Geary.Nonblocking.Concurrent cancelled"); } - + // Called from a background thread public void execute() { // only execute if not already cancelled @@ -61,28 +61,28 @@ caught_err = err; } } - + // can't notify event here, Nonblocking.Event is not thread safe // // artificially increment the ref count of this object, schedule a completion callback // on the forground thread, and signal there ref(); - + Idle.add(on_notify_completed); } - + // Called in the context of the Event loop in the foreground thread private bool on_notify_completed() { // alert waiters event.blind_notify(); - + // unref; do not touch "self" from here on, it's possibly deallocated unref(); - + return false; } } - + private static Concurrent? _global = null; /** * Returns the global instance of a {@link Concurrent} scheduler. @@ -95,10 +95,10 @@ return (_global != null) ? _global : _global = new Concurrent(); } } - + private ThreadPool? thread_pool = null; private ThreadError? init_err = null; - + /** * Creates a new Concurrent pool for scheduling background work. * @@ -111,11 +111,11 @@ max_threads, false); } catch (ThreadError err) { init_err = err; - + warning("Unable to create Geary.Nonblocking.Concurrent: %s", err.message); } } - + /** * Schedule a callback to be invoked in a background thread. * @@ -128,14 +128,14 @@ throws Error { if (init_err != null) throw init_err; - + // hold ConcurrentOperation ref until thread completes ConcurrentOperation op = new ConcurrentOperation(cb, cancellable); thread_pool.add(op); - + yield op.wait_async(); } - + private void on_work_ready(owned ConcurrentOperation op) { op.execute(); } diff -Nru geary-0.12.4/src/engine/nonblocking/nonblocking-counting-semaphore.vala geary-3.32.0/src/engine/nonblocking/nonblocking-counting-semaphore.vala --- geary-0.12.4/src/engine/nonblocking/nonblocking-counting-semaphore.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/nonblocking/nonblocking-counting-semaphore.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,32 +1,37 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ /** - * A nonblocking semaphore which allows for any number of tasks to run, but only signalling - * completion when all have finished. + * A counting, asynchronous semaphore. * - * Unlike the other {@link AbstractSemaphore} variants, a task must {@link acquire} before it - * can {@link notify}. The number of acquired tasks is kept in the {@link count} property. + * Unlike the other {@link Lock} variants, a task must {@link acquire} + * before it can {@link notify}. The number of acquired tasks is kept + * in the {@link count} property. Waiting tasks are released only when + * the count returns to zero. + * + * This class is ''not'' thread safe and should only be used by + * asynchronous tasks. */ -public class Geary.Nonblocking.CountingSemaphore : Geary.Nonblocking.AbstractSemaphore { +public class Geary.Nonblocking.CountingSemaphore : Geary.Nonblocking.Lock { /** * The number of tasks which have {@link acquire} the semaphore. */ public int count { get; private set; default = 0; } - + /** * Indicates that the {@link count} has changed due to either {@link acquire} or * {@link notify} being invoked. */ public signal void count_changed(int count); - + public CountingSemaphore(Cancellable? cancellable) { base (true, true, cancellable); } - + /** * Called by a task to acquire (and, hence, lock) the semaphore. * @@ -34,15 +39,15 @@ */ public int acquire() { count++; - + // store on stack in case of reentrancy from signal handler; also note that Vala doesn't // deal well with properties, pre/post-inc, and assignment on same line int new_count = count; count_changed(new_count); - + return new_count; } - + /** * Called by a task which has previously {@link acquire}d the semaphore. * @@ -55,18 +60,18 @@ public override void notify() throws Error { if (count == 0) throw new NonblockingError.INVALID("notify() on a zeroed CountingSemaphore"); - + count--; - + // store on stack in case of reentrancy from signal handler; also note that Vala doesn't // deal well with properties, pre/post-inc, and assignment on same line int new_count = count; count_changed(new_count); - + if (new_count == 0) base.notify(); } - + /** * Wait for all tasks which have {@link acquire}d this semaphore to release it. * diff -Nru geary-0.12.4/src/engine/nonblocking/nonblocking-lock.vala geary-3.32.0/src/engine/nonblocking/nonblocking-lock.vala --- geary-0.12.4/src/engine/nonblocking/nonblocking-lock.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/src/engine/nonblocking/nonblocking-lock.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,231 @@ +/* + * Copyright 2016 Software Freedom Conservancy Inc. + * Copyright 2018 Michael Gratton . + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +/** + * A generic asynchronous lock data type. + * + * This class provides an asynchronous, queue-based lock + * implementation to allow implementing safe access to resources that + * are shared by asynchronous tasks. An asynchronous task may call + * {@link wait_async} to wait for the lock to be marked as safe to + * pass. Another asynchronous task may call {@link notify} to mark the + * lock as being safe, notifying waiting tasks. Once marked as being + * safe to pass, a lock may be reset to being unsafe by calling {@link + * reset}. The lock cannot be passed initially, if this is desired + * call notify after constructing it. + * + * See the specialised sub-classes for concrete implementations, + * which vary based on two features: + * + * //Broadcasting//: Whether all waiting tasks are notified when the + * lock may be passed, or just the next earliest waiting task. + * + * //Autoreset//: Whether the lock is automatically reset after + * notifying all waiting tasks, or if it must be manually reset by + * calling {@link reset}. + * + * This class is ''not'' thread safe and should only be used by + * asynchronous tasks. + */ +public abstract class Geary.Nonblocking.Lock : BaseObject { + + private class Pending : BaseObject { + public unowned SourceFunc cb; + public Cancellable? cancellable; + public bool passed = false; + public bool scheduled = false; + + public signal void cancelled(); + + public Pending(SourceFunc cb, Cancellable? cancellable) { + this.cb = cb; + this.cancellable = cancellable; + + if (cancellable != null) + cancellable.cancelled.connect(on_cancelled); + } + + ~Pending() { + if (cancellable != null) + cancellable.cancelled.disconnect(on_cancelled); + } + + private void on_cancelled() { + cancelled(); + } + + public void schedule(bool passed) { + assert(!scheduled); + + this.passed = passed; + + Scheduler.on_idle(cb); + scheduled = true; + } + } + + /** Determines if this lock is marked as safe to pass. */ + public bool can_pass { get { return this.passed; } } + + /** Determines if this lock has been cancelled. */ + public bool is_cancelled { + get { + return this.cancellable != null && this.cancellable.is_cancelled(); + } + } + + private bool broadcast; + private bool autoreset; + private Cancellable? cancellable; + private bool passed = false; + private Gee.List pending_queue = new Gee.LinkedList(); + + /** + * Constructs a new lock that is initially not able to be passed. + */ + protected Lock(bool broadcast, bool autoreset, Cancellable? cancellable = null) { + this.broadcast = broadcast; + this.autoreset = autoreset; + this.cancellable = cancellable; + + if (cancellable != null) + cancellable.cancelled.connect(on_cancelled); + } + + ~Lock() { + if (pending_queue.size > 0) { + warning("Nonblocking lock destroyed with %d pending callers", pending_queue.size); + + foreach (Pending pending in pending_queue) + pending.cancelled.disconnect(on_pending_cancelled); + } + + if (cancellable != null) + cancellable.cancelled.disconnect(on_cancelled); + } + + private void trigger(bool all) { + if (pending_queue.size == 0) + return; + + // in both cases, mark the Pending object(s) as passed in case + // this is an auto-reset lock + if (all) { + foreach (Pending pending in pending_queue) + pending.schedule(passed); + + pending_queue.clear(); + } else { + Pending pending = pending_queue.remove_at(0); + pending.schedule(passed); + } + } + + /** + * Marks the lock as being safe to pass. + * + * Asynchronous tasks waiting on this lock via a call to {@link + * wait_async} are resumed when this method is called. If this + * lock is broadcasting then all pending tasks are released, + * otherwise only the first in the queue is released. + * + * @throws GLib.IOError.CANCELLED if either the lock is cancelled + * or the caller's `cancellable` argument is cancelled. + */ + public virtual new void notify() throws Error { + check_cancelled(); + + passed = true; + + trigger(broadcast); + + if (autoreset) + reset(); + } + + /** + * Calls {@link notify} without throwing an exception. + * + * If an error is thrown, it is logged but otherwise ignored. + */ + public void blind_notify() { + try { + notify(); + } catch (Error err) { + message("Error notifying lock: %s", err.message); + } + } + + /** + * Waits for the lock to be marked as being safe to pass. + * + * If the lock is already marked as being safe to pass, then this + * method will return immediately. If not, the call to this method + * will yield and not resume until the lock as been marked as safe + * by a call to {@link notify}. + * + * @throws GLib.IOError.CANCELLED if either the lock is cancelled or + * the caller's `cancellable` argument is cancelled. + */ + public virtual async void wait_async(Cancellable? cancellable = null) throws Error { + for (;;) { + check_user_cancelled(cancellable); + check_cancelled(); + + if (passed) + return; + + Pending pending = new Pending(wait_async.callback, cancellable); + pending.cancelled.connect(on_pending_cancelled); + + pending_queue.add(pending); + yield; + + pending.cancelled.disconnect(on_pending_cancelled); + + if (pending.passed) { + check_user_cancelled(cancellable); + + return; + } + } + } + + /** + * Marks this lock as being unsafe to pass. + */ + public virtual void reset() { + passed = false; + } + + private void check_cancelled() throws Error { + if (this.is_cancelled) + throw new IOError.CANCELLED("Lock was cancelled"); + } + + private static void check_user_cancelled(Cancellable? cancellable) throws Error { + if (cancellable != null && cancellable.is_cancelled()) + throw new IOError.CANCELLED("User cancelled lock operation"); + } + + private void on_pending_cancelled(Pending pending) { + // if already scheduled, the cancellation will be dealt with when they wake up + if (pending.scheduled) + return; + + bool removed = pending_queue.remove(pending); + assert(removed); + + Scheduler.on_idle(pending.cb); + } + + private void on_cancelled() { + trigger(true); + } + +} diff -Nru geary-0.12.4/src/engine/nonblocking/nonblocking-mailbox.vala geary-3.32.0/src/engine/nonblocking/nonblocking-mailbox.vala --- geary-0.12.4/src/engine/nonblocking/nonblocking-mailbox.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/nonblocking/nonblocking-mailbox.vala 1970-01-01 00:00:00.000000000 +0000 @@ -1,109 +0,0 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. - * - * This software is licensed under the GNU Lesser General Public License - * (version 2.1 or later). See the COPYING file in this distribution. - */ - -public class Geary.Nonblocking.Mailbox : BaseObject { - public int size { get { return queue.size; } } - - public bool allow_duplicates { get; set; default = true; } - - public bool requeue_duplicate { get; set; default = false; } - - private bool _is_paused = false; - public bool is_paused { - get { return _is_paused; } - - set { - // if no longer paused, wake up any waiting recipients - if (_is_paused && !value) - spinlock.blind_notify(); - - _is_paused = value; - } - } - - private Gee.Queue queue; - private Nonblocking.Spinlock spinlock = new Nonblocking.Spinlock(); - - public Mailbox(owned CompareDataFunc? comparator = null) { - if (comparator == null && !typeof(G).is_a(typeof(Gee.Comparable))) - queue = new Gee.LinkedList(); - else - queue = new Gee.PriorityQueue((owned) comparator); - } - - public bool send(G msg) { - if (!allow_duplicates && queue.contains(msg)) { - if (requeue_duplicate) - queue.remove(msg); - else - return false; - } - - if (!queue.offer(msg)) - return false; - - if (!is_paused) - spinlock.blind_notify(); - - return true; - } - - /** - * Returns true if the message was revoked. - */ - public bool revoke(G msg) { - return queue.remove(msg); - } - - /** - * Returns number of removed items. - */ - public int clear() { - int count = queue.size; - if (count != 0) - queue.clear(); - - return count; - } - - /** - * Remove messages matching the given predicate. Return the removed messages. - */ - public Gee.Collection revoke_matching(owned Gee.Predicate predicate) { - Gee.ArrayList removed = new Gee.ArrayList(); - // Iterate over a copy so we can modify the original. - foreach (G msg in queue.to_array()) { - if (predicate(msg)) { - queue.remove(msg); - removed.add(msg); - } - } - - return removed; - } - - public async G recv_async(Cancellable? cancellable = null) throws Error { - for (;;) { - if (queue.size > 0 && !is_paused) - return queue.poll(); - - yield spinlock.wait_async(cancellable); - } - } - - /** - * Returns a read-only version of the mailbox queue that can be iterated in queue-order. - * - * Since the queue could potentially alter when the main loop runs, it's important to only - * examine the queue when not allowing other operations to process. - * - * Altering will not affect the actual queue. Use {@link revoke} to remove enqueued operations. - */ - public Gee.Collection get_all() { - return queue.read_only_view; - } -} - diff -Nru geary-0.12.4/src/engine/nonblocking/nonblocking-mutex.vala geary-3.32.0/src/engine/nonblocking/nonblocking-mutex.vala --- geary-0.12.4/src/engine/nonblocking/nonblocking-mutex.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/nonblocking/nonblocking-mutex.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,41 +1,79 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. + * Copyright 2018 Michael Gratton * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ /** - * A task primitive for creating critical sections inside of asynchronous code. + * A primitive for creating critical sections inside of asynchronous tasks. * - * Like other primitives in {@link Nonblocking}, Mutex is ''not'' designed for a threaded - * environment. + * Two methods can be used for executing code protected by this + * mutex. The easiest is to create a {@link CriticalSection} delegate + * and pass it to {@link execute_locked}. This will manage acquiring + * the lock as needed. The lower-level method is to call {@link + * claim_async}, execute the critical section, then ensure {@link + * release} is always called afterwards. + * + * This class is ''not'' thread safe and should only be used by + * asynchronous tasks. */ - public class Geary.Nonblocking.Mutex : BaseObject { + public const int INVALID_TOKEN = -1; - + + /** A delegate that can be executed by this lock. */ + public delegate void CriticalSection() throws GLib.Error; + private Spinlock spinlock = new Spinlock(); private bool locked = false; private int next_token = INVALID_TOKEN + 1; private int locked_token = INVALID_TOKEN; - - public Mutex() { - } - + + /** * Returns true if the {@link Mutex} has been claimed by a task. */ public bool is_locked() { return locked; } - + + /** + * Executes a critical section while protected by this mutex. + * + * This high-level method takes care of claiming, executing, then + * releasing the mutex, without requiring the caller to manage any + * this. + * + * @throws GLib.IOError.CANCELLED thrown if the caller's + * cancellable is cancelled before execution is completed + * @throws GLib.Error if an error occurred during execution of + * //target//. + */ + public async void execute_locked(Mutex.CriticalSection target, + Cancellable? cancellable = null) + throws Error { + int token = yield claim_async(cancellable); + try { + target(); + } finally { + try { + release(ref token); + } catch (Error err) { + debug("Mutex error releasing token: %s", err.message); + } + } + } + /** - * Claim (i.e. lock) the {@link Mutex} and begin execution inside a critical section. + * Locks the mutex for execution inside a critical section. * - * claim_async will block asynchronously waiting for the Mutex to be released, if it's already - * claimed. + * If already claimed, this call will block asynchronously waiting + * for the mutex to be released. * - * @return A token which must be used to {@link release} the Mutex. + * @return A token which must be passed to {@link release} when + * the critical section has completed executing. */ public async int claim_async(Cancellable? cancellable = null) throws Error { for (;;) { @@ -44,31 +82,33 @@ do { locked_token = next_token++; } while (locked_token == INVALID_TOKEN); - + return locked_token; } - + yield spinlock.wait_async(cancellable); } } - + /** - * Release (i.e. unlock) the {@link Mutex} and end execution inside a critical section. + * Releases the lock at the end of executing a critical section. * - * The token returned by {@link claim_async} must be supplied as a parameter. It will be - * modified by this call so it can't be reused. + * The token returned by {@link claim_async} must be supplied as a + * parameter. It will be modified by this call so it can't be + * reused. * - * Throws IOError.INVALID_ARGUMENT if the token was not the one returned by claim_async. + * Throws IOError.INVALID_ARGUMENT if the token was not the one + * returned by claim_async. */ public void release(ref int token) throws Error { if (token != locked_token || token == INVALID_TOKEN) throw new IOError.INVALID_ARGUMENT("Token %d is not the lock token", token); - + locked = false; token = INVALID_TOKEN; locked_token = INVALID_TOKEN; - + spinlock.notify(); } -} +} diff -Nru geary-0.12.4/src/engine/nonblocking/nonblocking-queue.vala geary-3.32.0/src/engine/nonblocking/nonblocking-queue.vala --- geary-0.12.4/src/engine/nonblocking/nonblocking-queue.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/src/engine/nonblocking/nonblocking-queue.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,193 @@ +/* + * Copyright 2016 Software Freedom Conservancy Inc. + * Copyright 2017 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +/** + * An asynchronous queue, first-in first-out (FIFO) or priority. + * + * This class can be used to asynchronously wait for items to be added + * to the queue, the asynchronous call blocking until an item is + * ready. Multiple asynchronous tasks can queue objects via {@link + * send}, and tasks can wait for items via {@link receive}. If there + * are multiple tasks waiting for items, the first to wait will + * receive the next item. + */ +public class Geary.Nonblocking.Queue : BaseObject { + + /** Returns the number of items currently in the queue. */ + public int size { get { return queue.size; } } + + /** Determines if any items are in the queue. */ + public bool is_empty { get { return queue.is_empty; } } + + /** + * Determines if duplicate items can be added to the queue. + * + * If a priory queue, this applies to items of the same priority, + * otherwise uses the item's natural identity. + */ + public bool allow_duplicates { get; set; default = true; } + + /** + * Determines if duplicate items will be added to the queue. + * + * If {@link allow_duplicates} is `true` and an item is already in + * the queue, this determines if it will be added again. + */ + public bool requeue_duplicate { get; set; default = false; } + + /** + * Determines if the queue is currently running. + */ + public bool is_paused { + get { return _is_paused; } + + set { + // if no longer paused, wake up any waiting recipients + if (_is_paused && !value) + spinlock.blind_notify(); + + _is_paused = value; + } + } + private bool _is_paused = false; + + private Gee.Queue queue; + private Nonblocking.Spinlock spinlock = new Nonblocking.Spinlock(); + + + /** + * Constructs a new first-in first-out (FIFO) queue. + * + * If `equalator` is not null it will be used to determine the + * identity of objects in the queue, else the items' natural + * identity will be used. + */ + public Queue.fifo(owned Gee.EqualDataFunc? equalator = null) { + this(new Gee.LinkedList((owned) equalator)); + } + + /** + * Constructs a new priority queue. + * + * If `comparator` is not null it will be used to determine the + * ordering of objects in the queue, else the items' natural + * ordering will be used. + */ + public Queue.priority(owned CompareDataFunc? comparator = null) { + this(new Gee.PriorityQueue((owned) comparator)); + } + + /** + * Constructs a new queue. + */ + protected Queue(Gee.Queue queue) { + this.queue = queue; + } + + /** + * Adds an item to the queue. + * + * If the queue is a priority queue, it is added according to its + * relative priority, else it is added to the end. + * + * Returns `true` if the item was added to the queue. + */ + public bool send(G msg) { + if (!allow_duplicates && queue.contains(msg)) { + if (requeue_duplicate) + queue.remove(msg); + else + return false; + } + + if (!queue.offer(msg)) + return false; + + if (!is_paused) + spinlock.blind_notify(); + + return true; + } + + /** + * Removes and returns the next queued item, blocking until available. + * + * If the queue is paused, this will continue to wait until + * unpaused and an item is ready. If `cancellable` is non-null, + * when used will cancel this call. + */ + public async G receive(Cancellable? cancellable = null) throws Error { + for (;;) { + if (queue.size > 0 && !is_paused) + return queue.poll(); + + yield spinlock.wait_async(cancellable); + } + } + + /** + * Returns the next queued item without removal, blocking until available. + * + * If the queue is paused, this will continue to wait until + * unpaused and an item is ready. If `cancellable` is non-null, + * when used will cancel this call. + */ + public async G peek(Cancellable? cancellable = null) throws Error { + for (;;) { + if (queue.size > 0 && !is_paused) + return queue.peek(); + + yield spinlock.wait_async(cancellable); + } + } + + /** + * Removes all items in queue, returning the number of removed items. + */ + public int clear() { + int count = queue.size; + if (count != 0) + queue.clear(); + + return count; + } + + /** + * Removes an item from the queue, returning `true` if removed. + */ + public bool revoke(G msg) { + return queue.remove(msg); + } + + /** + * Remove items matching the given predicate, returning those removed. + */ + public Gee.Collection revoke_matching(owned Gee.Predicate predicate) { + Gee.ArrayList removed = new Gee.ArrayList(); + // Iterate over a copy so we can modify the original. + foreach (G msg in queue.to_array()) { + if (predicate(msg)) { + queue.remove(msg); + removed.add(msg); + } + } + + return removed; + } + + /** + * Returns a read-only version of the queue queue. + * + * Since the queue could potentially alter when the main loop + * runs, it's important to only examine the queue when not + * allowing other operations to process. + */ + public Gee.Collection get_all() { + return queue.read_only_view; + } +} diff -Nru geary-0.12.4/src/engine/nonblocking/nonblocking-reporting-semaphore.vala geary-3.32.0/src/engine/nonblocking/nonblocking-reporting-semaphore.vala --- geary-0.12.4/src/engine/nonblocking/nonblocking-reporting-semaphore.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/nonblocking/nonblocking-reporting-semaphore.vala 2019-03-17 13:39:29.000000000 +0000 @@ -7,45 +7,45 @@ public class Geary.Nonblocking.ReportingSemaphore : Geary.Nonblocking.Semaphore { public G result { get; private set; } public Error? err { get; private set; default = null; } - + private G default_result; - + public ReportingSemaphore(G default_result, Cancellable? cancellable = null) { base (cancellable); - + this.default_result = default_result; result = default_result; } - + public override void reset() { result = default_result; err = null; - + base.reset(); } - + public void notify_result(G result, Error? err) throws Error { this.result = result; this.err = err; - + notify(); } - + public void throw_if_error() throws Error { if (err != null) throw err; } - + public async G wait_for_result_async(Cancellable? cancellable = null) throws Error { // check before waiting throw_if_error(); - + // wait yield base.wait_async(cancellable); - + // if notified of error while waiting, throw that throw_if_error(); - + return result; } } diff -Nru geary-0.12.4/src/engine/nonblocking/nonblocking-variants.vala geary-3.32.0/src/engine/nonblocking/nonblocking-variants.vala --- geary-0.12.4/src/engine/nonblocking/nonblocking-variants.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/nonblocking/nonblocking-variants.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,36 +1,82 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. + * Copyright 2018 Michael Gratton . * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ /** - * A Semaphore is a broadcasting, manually-resetting {@link AbstractSemaphore}. + * A broadcasting, manually-resetting asynchronous lock. + * + * This lock type will notify all waiting asynchronous tasks when + * marked as being safe to pass, and requires a call to {@link + * Lock.reset} to be marked as unsafe again. + * + * This class is ''not'' thread safe and should only be used by + * asynchronous tasks. + * + * @see Lock */ +public class Geary.Nonblocking.Semaphore : Geary.Nonblocking.Lock { -public class Geary.Nonblocking.Semaphore : Geary.Nonblocking.AbstractSemaphore { + /** + * Constructs a new semaphore lock. + * + * The new lock is initially not able to be passed. + */ public Semaphore(Cancellable? cancellable = null) { base (true, false, cancellable); } + } /** - * An Event is a broadcasting, auto-resetting {@link AbstractSemaphore}. + * A broadcasting, automatically-resetting asynchronous lock. + * + * This lock type will notify all waiting asynchronous tasks when + * marked as being safe to pass, and will automatically reset as being + * unsafe to pass after doing so. + * + * This class is ''not'' thread safe and should only be used by + * asynchronous tasks. + * + * @see Lock */ +public class Geary.Nonblocking.Event : Geary.Nonblocking.Lock { -public class Geary.Nonblocking.Event : Geary.Nonblocking.AbstractSemaphore { + /** + * Constructs a new event lock. + * + * The new lock is initially not able to be passed. + */ public Event(Cancellable? cancellable = null) { base (true, true, cancellable); } + } /** - * A Spinlock is a single-notifying, auto-resetting {@link AbstractSemaphore}. + * A single-task-notifying, automatically-resetting asynchronous lock. + * + * This lock type will the first asynchronous task waiting when marked + * as being safe to pass, and will automatically reset as being unsafe + * to pass after doing so. + * + * This class is ''not'' thread safe and should only be used by + * asynchronous tasks. + * + * @see Lock */ +public class Geary.Nonblocking.Spinlock : Geary.Nonblocking.Lock { -public class Geary.Nonblocking.Spinlock : Geary.Nonblocking.AbstractSemaphore { + /** + * Constructs a new spin lock. + * + * The new lock is initially not able to be passed. + */ public Spinlock(Cancellable? cancellable = null) { base (false, true, cancellable); } -} +} diff -Nru geary-0.12.4/src/engine/outbox/outbox-email-identifier.vala geary-3.32.0/src/engine/outbox/outbox-email-identifier.vala --- geary-0.12.4/src/engine/outbox/outbox-email-identifier.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/src/engine/outbox/outbox-email-identifier.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,27 @@ +/* + * Copyright 2016 Software Freedom Conservancy Inc. + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +private class Geary.Outbox.EmailIdentifier : Geary.EmailIdentifier { + + + public int64 ordering { get; private set; } + + + public EmailIdentifier(int64 message_id, int64 ordering) { + base("Outbox.EmailIdentifier:%s".printf(message_id.to_string())); + this.ordering = ordering; + } + + public override int natural_sort_comparator(Geary.EmailIdentifier o) { + EmailIdentifier? other = o as EmailIdentifier; + if (other == null) { + return 1; + } + return (int) (ordering - other.ordering).clamp(-1, 1); + } + +} diff -Nru geary-0.12.4/src/engine/outbox/outbox-email-properties.vala geary-3.32.0/src/engine/outbox/outbox-email-properties.vala --- geary-0.12.4/src/engine/outbox/outbox-email-properties.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/src/engine/outbox/outbox-email-properties.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,19 @@ +/* + * Copyright 2016 Software Freedom Conservancy Inc. + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +private class Geary.Outbox.EmailProperties : Geary.EmailProperties { + + + public EmailProperties(GLib.DateTime date_received, long total_bytes) { + base(date_received, total_bytes); + } + + public override string to_string() { + return "Outbox.Properties"; + } + +} diff -Nru geary-0.12.4/src/engine/outbox/outbox-folder-properties.vala geary-3.32.0/src/engine/outbox/outbox-folder-properties.vala --- geary-0.12.4/src/engine/outbox/outbox-folder-properties.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/src/engine/outbox/outbox-folder-properties.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,22 @@ +/* + * Copyright 2016 Software Freedom Conservancy Inc. + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +private class Geary.Outbox.FolderProperties : Geary.FolderProperties { + + public FolderProperties(int total, int unread) { + base( + total, unread, + Trillian.FALSE, Trillian.FALSE, Trillian.TRUE, + true, false, false + ); + } + + public void set_total(int total) { + this.email_total = total; + } + +} diff -Nru geary-0.12.4/src/engine/outbox/outbox-folder.vala geary-3.32.0/src/engine/outbox/outbox-folder.vala --- geary-0.12.4/src/engine/outbox/outbox-folder.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/src/engine/outbox/outbox-folder.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,530 @@ +/* + * Copyright 2016 Software Freedom Conservancy Inc. + * Copyright 2017-2018 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +/** + * Local folder for storing outgoing mail. + */ +private class Geary.Outbox.Folder : + Geary.AbstractLocalFolder, + Geary.FolderSupport.Create, + Geary.FolderSupport.Mark, + Geary.FolderSupport.Remove { + + + /** The canonical name of the outbox folder. */ + public const string MAGIC_BASENAME = "$GearyOutbox$"; + + + private class OutboxRow { + public int64 id; + public int position; + public int64 ordering; + public bool sent; + public Memory.Buffer? message; + public EmailIdentifier outbox_id; + + public OutboxRow(int64 id, int position, int64 ordering, bool sent, Memory.Buffer? message) { + assert(position >= 1); + + this.id = id; + this.position = position; + this.ordering = ordering; + this.sent = sent; + this.message = message; + + outbox_id = new EmailIdentifier(id, ordering); + } + } + + + /** {@inheritDoc} */ + public override Account account { get { return this._account; } } + + /** {@inheritDoc} */ + public override Geary.FolderProperties properties { + get { return _properties; } + } + + /** + * Returns the path to this folder. + * + * This is always the child of the root given to the constructor, + * with the name given by @{link MAGIC_BASENAME}. + */ + public override FolderPath path { + get { + return _path; + } + } + private FolderPath _path; + + /** + * Returns the type of this folder. + * + * This is always {@link Geary.SpecialFolderType.OUTBOX} + */ + public override SpecialFolderType special_folder_type { + get { + return Geary.SpecialFolderType.OUTBOX; + } + } + + private weak Account _account; + private weak ImapDB.Account local; + private Db.Database? db = null; + private FolderProperties _properties = new FolderProperties(0, 0); + private int64 next_ordering = 0; + + + // Requires the Database from the get-go because it runs a background task that access it + // whether open or not + public Folder(Account account, FolderRoot root, ImapDB.Account local) { + this._account = account; + this._path = root.get_child(MAGIC_BASENAME, Trillian.TRUE); + this.local = local; + } + + public override async bool open_async(Geary.Folder.OpenFlags open_flags, + GLib.Cancellable? cancellable = null) + throws GLib.Error { + bool opened = yield base.open_async(open_flags, cancellable); + if (opened) { + this.db = this.local.db; + } + return opened; + } + + public override async bool close_async(GLib.Cancellable? cancellable = null) + throws GLib.Error { + bool closed = yield base.close_async(cancellable); + if (closed) { + this.db = null; + } + return closed; + } + + public virtual async Geary.EmailIdentifier? + create_email_async(RFC822.Message rfc822, + Geary.EmailFlags? flags, + GLib.DateTime? date_received, + Geary.EmailIdentifier? id = null, + GLib.Cancellable? cancellable = null) + throws GLib.Error { + check_open(); + + int email_count = 0; + OutboxRow? row = null; + yield db.exec_transaction_async(Db.TransactionType.WR, (cx) => { + int64 ordering = do_get_next_ordering(cx, cancellable); + + // save in database ready for SMTP, but without dot-stuffing + Db.Statement stmt = cx.prepare( + "INSERT INTO SmtpOutboxTable (message, ordering) VALUES (?, ?)"); + stmt.bind_string_buffer(0, rfc822.get_network_buffer(false)); + stmt.bind_int64(1, ordering); + + int64 new_id = stmt.exec_insert(cancellable); + int position = do_get_position_by_ordering(cx, ordering, cancellable); + + row = new OutboxRow(new_id, position, ordering, false, null); + email_count = do_get_email_count(cx, cancellable); + + return Db.TransactionOutcome.COMMIT; + }, cancellable); + + // update properties + _properties.set_total(yield get_email_count_async(cancellable)); + + Gee.List list = new Gee.ArrayList(); + list.add(row.outbox_id); + + notify_email_appended(list); + notify_email_locally_appended(list); + notify_email_count_changed(email_count, CountChangeReason.APPENDED); + + return row.outbox_id; + } + + public virtual async void + mark_email_async(Gee.Collection to_mark, + EmailFlags? flags_to_add, + EmailFlags? flags_to_remove, + GLib.Cancellable? cancellable = null) + throws GLib.Error { + check_open(); + Gee.Map changed = + new Gee.HashMap(); + + foreach (Geary.EmailIdentifier id in to_mark) { + EmailIdentifier? outbox_id = id as EmailIdentifier; + if (outbox_id != null) { + yield db.exec_transaction_async(Db.TransactionType.WR, (cx) => { + do_mark_email_as_sent(cx, outbox_id, cancellable); + return Db.TransactionOutcome.COMMIT; + }, cancellable + ); + changed.set(id, flags_to_add); + } + } + + notify_email_flags_changed(changed); + } + + public virtual async void + remove_email_async(Gee.Collection email_ids, + GLib.Cancellable? cancellable = null) + throws GLib.Error { + check_open(); + + Gee.List removed = new Gee.ArrayList(); + int final_count = 0; + yield db.exec_transaction_async(Db.TransactionType.WR, (cx) => { + foreach (Geary.EmailIdentifier id in email_ids) { + // ignore anything not belonging to the outbox, but also don't report it as removed + // either + EmailIdentifier? outbox_id = id as EmailIdentifier; + if (outbox_id == null) + continue; + + // Even though we discard the new value here, this check must + // occur before any insert/delete on the table, to ensure we + // never reuse an ordering value while Geary is running. + do_get_next_ordering(cx, cancellable); + + if (do_remove_email(cx, outbox_id, cancellable)) + removed.add(outbox_id); + } + + final_count = do_get_email_count(cx, cancellable); + + return Db.TransactionOutcome.COMMIT; + }, cancellable); + + if (removed.size >= 0) { + _properties.set_total(final_count); + + notify_email_removed(removed); + notify_email_count_changed(final_count, CountChangeReason.REMOVED); + } + } + + public override async Gee.List? + list_email_by_id_async(Geary.EmailIdentifier? _initial_id, + int count, + Geary.Email.Field required_fields, + Geary.Folder.ListFlags flags, + GLib.Cancellable? cancellable = null) + throws GLib.Error { + check_open(); + + EmailIdentifier? initial_id = _initial_id as EmailIdentifier; + if (_initial_id != null && initial_id == null) { + throw new EngineError.BAD_PARAMETERS("EmailIdentifier %s not for Outbox", + initial_id.to_string()); + } + + if (count <= 0) + return null; + + bool list_all = (required_fields != Email.Field.NONE); + + string select = "id, ordering"; + if (list_all) { + select = select + ", message, sent"; + } + + Gee.List? list = null; + yield db.exec_transaction_async(Db.TransactionType.RO, (cx) => { + string dir = flags.is_newest_to_oldest() ? "DESC" : "ASC"; + + Db.Statement stmt; + if (initial_id != null) { + stmt = cx.prepare(""" + SELECT %s + FROM SmtpOutboxTable + WHERE ordering >= ? + ORDER BY ordering %s + LIMIT ? + """.printf(select ,dir)); + stmt.bind_int64(0, + flags.is_including_id() ? initial_id.ordering : initial_id.ordering + 1); + stmt.bind_int(1, count); + } else { + stmt = cx.prepare(""" + SELECT %s + FROM SmtpOutboxTable + ORDER BY ordering %s + LIMIT ? + """.printf(select, dir)); + stmt.bind_int(0, count); + } + + Db.Result results = stmt.exec(cancellable); + if (results.finished) + return Db.TransactionOutcome.DONE; + + list = new Gee.ArrayList(); + int position = -1; + do { + int64 ordering = results.int64_at(1); + if (position == -1) { + position = do_get_position_by_ordering( + cx, ordering, cancellable + ); + assert(position >= 1); + } + + list.add( + row_to_email( + new OutboxRow( + results.rowid_at(0), + position, + ordering, + list_all ? results.bool_at(3) : false, + list_all ? results.string_buffer_at(2) : null + ) + ) + ); + position += flags.is_newest_to_oldest() ? -1 : 1; + assert(position >= 1); + } while (results.next()); + + return Db.TransactionOutcome.DONE; + }, cancellable); + + return list; + } + + public override async Gee.List? + list_email_by_sparse_id_async(Gee.Collection ids, + Geary.Email.Field required_fields, + Geary.Folder.ListFlags flags, + GLib.Cancellable? cancellable = null) + throws GLib.Error { + check_open(); + + Gee.List list = new Gee.ArrayList(); + yield db.exec_transaction_async(Db.TransactionType.RO, (cx) => { + foreach (Geary.EmailIdentifier id in ids) { + EmailIdentifier? outbox_id = id as EmailIdentifier; + if (outbox_id == null) + throw new EngineError.BAD_PARAMETERS("%s is not outbox EmailIdentifier", id.to_string()); + + OutboxRow? row = do_fetch_row_by_ordering(cx, outbox_id.ordering, cancellable); + if (row == null) + continue; + + list.add(row_to_email(row)); + } + + return Db.TransactionOutcome.DONE; + }, cancellable); + + return (list.size > 0) ? list : null; + } + + public override async Gee.Map? + list_local_email_fields_async(Gee.Collection ids, + GLib.Cancellable? cancellable = null) + throws GLib.Error { + check_open(); + + Gee.Map map = new Gee.HashMap< + Geary.EmailIdentifier, Geary.Email.Field>(); + yield db.exec_transaction_async(Db.TransactionType.RO, (cx) => { + Db.Statement stmt = cx.prepare( + "SELECT id FROM SmtpOutboxTable WHERE ordering=?"); + foreach (Geary.EmailIdentifier id in ids) { + EmailIdentifier? outbox_id = id as EmailIdentifier; + if (outbox_id == null) + throw new EngineError.BAD_PARAMETERS("%s is not outbox EmailIdentifier", id.to_string()); + + stmt.reset(Db.ResetScope.CLEAR_BINDINGS); + stmt.bind_int64(0, outbox_id.ordering); + + // merely checking for presence, all emails in outbox have same fields + Db.Result results = stmt.exec(cancellable); + if (!results.finished) + map.set(outbox_id, Geary.Email.Field.ALL); + } + + return Db.TransactionOutcome.DONE; + }, cancellable); + + return (map.size > 0) ? map : null; + } + + public override async Email + fetch_email_async(Geary.EmailIdentifier id, + Geary.Email.Field required_fields, + Geary.Folder.ListFlags flags, + GLib.Cancellable? cancellable = null) + throws GLib.Error { + check_open(); + + EmailIdentifier? outbox_id = id as EmailIdentifier; + if (outbox_id == null) + throw new EngineError.BAD_PARAMETERS("%s is not outbox EmailIdentifier", id.to_string()); + + OutboxRow? row = null; + yield db.exec_transaction_async(Db.TransactionType.RO, (cx) => { + row = do_fetch_row_by_ordering(cx, outbox_id.ordering, cancellable); + + return Db.TransactionOutcome.DONE; + }, cancellable); + + if (row == null) + throw new EngineError.NOT_FOUND("No message with ID %s in outbox", id.to_string()); + + return row_to_email(row); + } + + internal async void + add_to_containing_folders_async(Gee.Collection ids, + Gee.MultiMap map, + GLib.Cancellable? cancellable) + throws GLib.Error { + check_open(); + yield db.exec_transaction_async(Db.TransactionType.RO, (cx, cancellable) => { + foreach (Geary.EmailIdentifier id in ids) { + EmailIdentifier? outbox_id = id as EmailIdentifier; + if (outbox_id == null) + continue; + + OutboxRow? row = do_fetch_row_by_ordering(cx, outbox_id.ordering, cancellable); + if (row == null) + continue; + + map.set(id, path); + } + + return Db.TransactionOutcome.DONE; + }, cancellable); + } + + // Utility for getting an email object back from an outbox row. + private Geary.Email row_to_email(OutboxRow row) throws Error { + Geary.Email? email = null; + + // If the row doesn't contain any message, just the id will do + if (row.message == null) { + email = new Email(row.outbox_id); + } else { + RFC822.Message message = new RFC822.Message.from_buffer(row.message); + email = message.get_email(row.outbox_id); + + // TODO: Determine message's total size (header + body) to + // store in Properties. + email.set_email_properties( + new EmailProperties(new DateTime.now_local(), -1) + ); + Geary.EmailFlags flags = new Geary.EmailFlags(); + if (row.sent) + flags.add(Geary.EmailFlags.OUTBOX_SENT); + email.set_flags(flags); + } + + return email; + } + + private async int get_email_count_async(Cancellable? cancellable) throws Error { + int count = 0; + yield db.exec_transaction_async(Db.TransactionType.RO, (cx) => { + count = do_get_email_count(cx, cancellable); + + return Db.TransactionOutcome.DONE; + }, cancellable); + + return count; + } + + // + // Transaction helper methods + // + + private int64 do_get_next_ordering(Db.Connection cx, Cancellable? cancellable) throws Error { + lock (next_ordering) { + if (next_ordering == 0) { + Db.Statement stmt = cx.prepare("SELECT COALESCE(MAX(ordering), 0) + 1 FROM SmtpOutboxTable"); + + Db.Result result = stmt.exec(cancellable); + if (!result.finished) + next_ordering = result.int64_at(0); + + assert(next_ordering > 0); + } + + return next_ordering++; + } + } + + private int do_get_email_count(Db.Connection cx, Cancellable? cancellable) throws Error { + Db.Statement stmt = cx.prepare("SELECT COUNT(*) FROM SmtpOutboxTable"); + + Db.Result results = stmt.exec(cancellable); + + return (!results.finished) ? results.int_at(0) : 0; + } + + private int do_get_position_by_ordering(Db.Connection cx, int64 ordering, Cancellable? cancellable) + throws Error { + Db.Statement stmt = cx.prepare( + "SELECT COUNT(*), MAX(ordering) FROM SmtpOutboxTable WHERE ordering <= ? ORDER BY ordering ASC"); + stmt.bind_int64(0, ordering); + + Db.Result results = stmt.exec(cancellable); + if (results.finished) + return -1; + + // without the MAX it's possible to overshoot, so the MAX(ordering) *must* match the argument + if (results.int64_at(1) != ordering) + return -1; + + return results.int_at(0) + 1; + } + + private OutboxRow? do_fetch_row_by_ordering(Db.Connection cx, int64 ordering, Cancellable? cancellable) + throws Error { + Db.Statement stmt = cx.prepare(""" + SELECT id, message, sent + FROM SmtpOutboxTable + WHERE ordering=? + """); + stmt.bind_int64(0, ordering); + + Db.Result results = stmt.exec(cancellable); + if (results.finished) + return null; + + int position = do_get_position_by_ordering(cx, ordering, cancellable); + if (position < 1) + return null; + + return new OutboxRow(results.rowid_at(0), position, ordering, results.bool_at(2), + results.string_buffer_at(1)); + } + + private void do_mark_email_as_sent(Db.Connection cx, + EmailIdentifier id, + Cancellable? cancellable) + throws Error { + Db.Statement stmt = cx.prepare("UPDATE SmtpOutboxTable SET sent = 1 WHERE ordering = ?"); + stmt.bind_int64(0, id.ordering); + + stmt.exec(cancellable); + } + + private bool do_remove_email(Db.Connection cx, EmailIdentifier id, Cancellable? cancellable) + throws Error { + Db.Statement stmt = cx.prepare("DELETE FROM SmtpOutboxTable WHERE ordering=?"); + stmt.bind_int64(0, id.ordering); + + return stmt.exec_get_modified(cancellable) > 0; + } + +} diff -Nru geary-0.12.4/src/engine/rfc822/rfc822-gmime-filter-blockquotes.vala geary-3.32.0/src/engine/rfc822/rfc822-gmime-filter-blockquotes.vala --- geary-0.12.4/src/engine/rfc822/rfc822-gmime-filter-blockquotes.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/rfc822/rfc822-gmime-filter-blockquotes.vala 2019-03-17 13:39:29.000000000 +0000 @@ -10,25 +10,25 @@ // Invariant: True iff we are either at the beginning of a line, or all characters seen so far // have been quote markers or part of a tag. private bool in_prefix; - + // True if we're in a tag in the prefix only. private bool in_tag; - + // Invariant: The quote depth of the last complete line seen, or 0 if we have not yet seen a // complete line. private uint last_quote_level; - + // Invariant: The number of QUOTE_MARKERs seen so far if we are parsing the prefix, or 0 if we // are not parsing the prefix. private uint current_quote_level; - + // Have we inserted the initial element? private bool initial_element; - + public FilterBlockquotes() { reset(); } - + public override void reset() { in_prefix = true; in_tag = false; @@ -36,25 +36,25 @@ current_quote_level = 0; initial_element = false; } - + public override GMime.Filter copy() { FilterBlockquotes new_filter = new FilterBlockquotes(); - + new_filter.in_prefix = in_prefix; new_filter.in_tag = in_tag; new_filter.last_quote_level = last_quote_level; new_filter.current_quote_level = current_quote_level; new_filter.initial_element = initial_element; - + return new_filter; } - + private void do_filter(char[] inbuf, size_t prespace, out unowned char[] processed_buffer, out size_t outprespace, bool flush) { - + // This may not be strictly necessary. set_size(inbuf.length, false); - + uint out_index = 0; if (!initial_element) { // We set the style explicitly so it will be set in HTML emails. We also give it a @@ -62,10 +62,10 @@ insert_string("
", ref out_index); initial_element = true; } - + for (uint i = 0; i < inbuf.length; i++) { char c = inbuf[i]; - + if (in_prefix && !in_tag) { if (c == Geary.RFC822.Utils.QUOTE_MARKER) { current_quote_level++; @@ -76,7 +76,7 @@ outbuf[out_index++] = c; continue; } - + while (current_quote_level > last_quote_level) { insert_string("
", ref out_index); last_quote_level += 1; @@ -85,11 +85,11 @@ insert_string("
", ref out_index); last_quote_level -= 1; } - + // We saw a character other than '>', so we're done scanning the prefix. in_prefix = false; } - + if (c == '\n') { // Was this last line a signature marker? if(out_index > 3 && @@ -108,7 +108,7 @@ outbuf[out_index++] = c; } } - + if (flush) { while (last_quote_level > 0) { insert_string("", ref out_index); @@ -116,22 +116,22 @@ } insert_string("
", ref out_index); } - + // Slicing the buffer is important, because the buffer is not null-terminated, processed_buffer = outbuf[0:out_index]; outprespace = this.outpre; } - + public override void filter(char[] inbuf, size_t prespace, out unowned char[] processed_buffer, out size_t outprespace) { do_filter(inbuf, prespace, out processed_buffer, out outprespace, false); } - + public override void complete(char[] inbuf, size_t prespace, out unowned char[] processed_buffer, out size_t outprespace) { do_filter(inbuf, prespace, out processed_buffer, out outprespace, true); } - + private void insert_string(string str, ref uint out_index) { set_size(outbuf.length + str.length, true); for (int i = 0; i < str.length; i++) diff -Nru geary-0.12.4/src/engine/rfc822/rfc822-gmime-filter-plain.vala geary-3.32.0/src/engine/rfc822/rfc822-gmime-filter-plain.vala --- geary-0.12.4/src/engine/rfc822/rfc822-gmime-filter-plain.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/rfc822/rfc822-gmime-filter-plain.vala 2019-03-17 13:39:29.000000000 +0000 @@ -9,53 +9,53 @@ // Invariant: True iff we are either at the beginning of a line, or all characters seen so far // have been quote markers. private bool in_prefix; - + public FilterPlain() { reset(); } - + public override void reset() { in_prefix = true; } - + public override GMime.Filter copy() { FilterPlain new_filter = new FilterPlain(); - + new_filter.in_prefix = in_prefix; - + return new_filter; } - + public override void filter(char[] inbuf, size_t prespace, out unowned char[] processed_buffer, out size_t outprespace) { - + // This may not be strictly necessary. set_size(inbuf.length, false); - + uint out_index = 0; for (uint i = 0; i < inbuf.length; i++) { char c = inbuf[i]; - + if (in_prefix) { if (c == '>') { outbuf[out_index++] = Geary.RFC822.Utils.QUOTE_MARKER; continue; } - + // We saw a character other than '>', so we're done scanning the prefix. in_prefix = false; } - + if (c == '\n') in_prefix = true; outbuf[out_index++] = c; } - + // Slicing the buffer is important, because the buffer is not null-terminated, processed_buffer = outbuf[0:out_index]; outprespace = this.outpre; } - + public override void complete(char[] inbuf, size_t prespace, out unowned char[] processed_buffer, out size_t outprespace) { filter(inbuf, prespace, out processed_buffer, out outprespace); diff -Nru geary-0.12.4/src/engine/rfc822/rfc822-mailbox-addresses.vala geary-3.32.0/src/engine/rfc822/rfc822-mailbox-addresses.vala --- geary-0.12.4/src/engine/rfc822/rfc822-mailbox-addresses.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/rfc822/rfc822-mailbox-addresses.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,24 +1,75 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ -public class Geary.RFC822.MailboxAddresses : Geary.MessageData.AbstractMessageData, - Geary.MessageData.SearchableMessageData, Geary.RFC822.MessageData, Gee.Hashable { - - public int size { get { return addrs.size; } } - +/** + * An immutable representation an RFC 822 address list. + * + * This would typically be found as the value of the To, CC, BCC and + * other headers fields. + * + * See [[https://tools.ietf.org/html/rfc5322#section-3.4]] + */ +public class Geary.RFC822.MailboxAddresses : + Geary.MessageData.AbstractMessageData, + Geary.MessageData.SearchableMessageData, + Geary.RFC822.MessageData, Gee.Hashable { + + + /** + * Converts a list of mailbox addresses to a string. + * + * The delegate //to_s// is used for converting addresses in the + * given list. If the list is empty, the given empty string is + * returned. + */ + private static string list_to_string(Gee.List addrs, + ListToStringDelegate to_s) { + switch (addrs.size) { + case 0: + return ""; + + case 1: + return to_s(addrs[0]); + + default: + StringBuilder builder = new StringBuilder(); + foreach (MailboxAddress addr in addrs) { + if (!String.is_empty(builder.str)) + builder.append(", "); + + builder.append(to_s(addr)); + } + + return builder.str; + } + } + + + /** Signature for "to_string" implementation for {@link list_to_string}. */ + private delegate string ListToStringDelegate(MailboxAddress address); + + /** Returns the number of addresses in this list. */ + public int size { + get { return this.addrs.size; } + } + private Gee.List addrs = new Gee.ArrayList(); - - public MailboxAddresses(Gee.Collection addrs) { - this.addrs.add_all(addrs); + + + public MailboxAddresses(Gee.Collection? addrs = null) { + if (addrs != null) { + this.addrs.add_all(addrs); + } } - + public MailboxAddresses.single(MailboxAddress addr) { - addrs.add(addr); + this.addrs.add(addr); } - + public MailboxAddresses.from_rfc822_string(string rfc822) { InternetAddressList addrlist = InternetAddressList.parse_string(rfc822); if (addrlist == null) @@ -27,102 +78,138 @@ int length = addrlist.length(); for (int ctr = 0; ctr < length; ctr++) { InternetAddress? addr = addrlist.get_address(ctr); - - // TODO: Handle group lists + InternetAddressMailbox? mbox_addr = addr as InternetAddressMailbox; - if (mbox_addr == null) - continue; - - addrs.add(new MailboxAddress(mbox_addr.get_name(), mbox_addr.get_addr())); + if (mbox_addr != null) { + this.addrs.add(new MailboxAddress.gmime(mbox_addr)); + } else { + // XXX this is pretty bad - we just flatten the + // group's addresses into this list, merging lists and + // losing the group names. + InternetAddressGroup? mbox_group = addr as InternetAddressGroup; + if (mbox_group != null) { + InternetAddressList group_list = mbox_group.get_members(); + for (int i = 0; i < group_list.length(); i++) { + InternetAddressMailbox? group_addr = + addrlist.get_address(i) as InternetAddressMailbox; + if (group_addr != null) { + this.addrs.add(new MailboxAddress.gmime(group_addr)); + } + } + } + } } } - + public new MailboxAddress? get(int index) { return addrs.get(index); } - + public Gee.Iterator iterator() { return addrs.iterator(); } - + public Gee.List get_all() { return addrs.read_only_view; } - + public bool contains_normalized(string address) { if (addrs.size < 1) return false; - + string normalized_address = address.normalize().casefold(); - + foreach (MailboxAddress mailbox_address in addrs) { if (mailbox_address.address.normalize().casefold() == normalized_address) return true; } - + return false; } - + public bool contains(string address) { if (addrs.size < 1) return false; - + foreach (MailboxAddress a in addrs) if (a.address == address) return true; - + return false; } - + /** - * Returns the addresses suitable for insertion into an RFC822 message. RFC822 quoting is - * performed if required. + * Returns a new list with the given addresses appended to this list's. + */ + public MailboxAddresses append(MailboxAddresses others) { + MailboxAddresses new_addrs = new MailboxAddresses(this.addrs); + new_addrs.addrs.add_all(others.addrs); + return new_addrs; + } + + /** + * Returns the addresses suitable for display to a human. + * + * @return a string containing each message in the list, + * serialised by a call to {@link MailboxAddress.to_full_display} + * for each, separated by commas. + */ + public string to_full_display() { + return list_to_string(addrs, (a) => a.to_full_display()); + } + + /** + * Returns the addresses suitable for insertion into an RFC822 message. + * + * RFC822 quoting is performed if required. * * @see MailboxAddress.to_rfc822_string */ public string to_rfc822_string() { - return MailboxAddress.list_to_string(addrs, "", (a) => a.to_rfc822_string()); + return list_to_string(addrs, (a) => a.to_rfc822_string()); } - + public uint hash() { // create sorted set to ensure ordering no matter the list's order Gee.TreeSet sorted_addresses = traverse(addrs) .map(m => m.address) .to_tree_set(String.stri_cmp); - + // xor all strings in sorted order uint xor = 0; foreach (string address in sorted_addresses) xor ^= address.hash(); - + return xor; } - + public bool equal_to(MailboxAddresses other) { if (this == other) return true; - + if (addrs.size != other.addrs.size) return false; - + Gee.HashSet first = new Gee.HashSet(); first.add_all(addrs); - + Gee.HashSet second = new Gee.HashSet(); second.add_all(other.addrs); - + return Collection.are_sets_equal(first, second); } - + /** * See Geary.MessageData.SearchableMessageData. */ public string to_searchable_string() { - return MailboxAddress.list_to_string(addrs, "", (a) => a.to_searchable_string()); + return list_to_string(addrs, (a) => a.to_searchable_string()); } - + public override string to_string() { - return MailboxAddress.list_to_string(addrs, "(no addresses)", (a) => a.to_string()); + return this.size > 0 + ? list_to_string(addrs, (a) => a.to_string()) + : "(no addresses)"; } -} +} diff -Nru geary-0.12.4/src/engine/rfc822/rfc822-mailbox-address.vala geary-3.32.0/src/engine/rfc822/rfc822-mailbox-address.vala --- geary-0.12.4/src/engine/rfc822/rfc822-mailbox-address.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/rfc822/rfc822-mailbox-address.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,75 +1,208 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. + * Copyright 2018 Michael Gratton * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ /** - * An immutable object containing a representation of an Internet email address. + * An immutable representation of an RFC 822 mailbox address. * - * See [[https://tools.ietf.org/html/rfc2822#section-3.4]] + * The properties of this class such as {@link name} and {@link + * address} are stores decoded UTF-8, thus they must be re-encoded + * using methods such as {@link to_rfc822_string} before being re-used + * in a message envelope. + * + * See [[https://tools.ietf.org/html/rfc5322#section-3.4]] */ +public class Geary.RFC822.MailboxAddress : + Geary.MessageData.SearchableMessageData, + Gee.Hashable, + BaseObject { + + private static char[] ATEXT = { + '!', '#', '$', '%', '&', '\'', '*', '+', '-', + '/', '=', '?', '^', '_', '`', '{', '|', '}', '~' + }; + + /** Determines if a string contains a valid RFC822 mailbox address. */ + public static bool is_valid_address(string address) { + try { + // http://www.regular-expressions.info/email.html + // matches john@dep.aol.museum not john@aol...com + Regex email_regex = + new Regex("[A-Z0-9._%+-]+@((?:[A-Z0-9-]+\\.)+[A-Z]{2}|localhost)", + RegexCompileFlags.CASELESS); + return email_regex.match(address); + } catch (RegexError e) { + debug("Regex error validating email address: %s", e.message); + return false; + } + } + + private static string decode_name(string name) { + return GMime.utils_header_decode_phrase(prepare_header_text_part(name)); + } + + private static string decode_address_part(string mailbox) { + return GMime.utils_header_decode_text(prepare_header_text_part(mailbox)); + } + + private static bool display_name_needs_quoting(string name) { + // Currently we only care if the name contains a comma, since + // that will screw up the composer's address entry fields. See + // issue #282. This might be able to be removed when the + // composer doesn't parse recipients as a text list of + // addresses. + return (name.index_of(",") != -1); + } + + private static bool local_part_needs_quoting(string local_part) { + bool needs_quote = false; + bool is_dot = false; + if (!String.is_empty(local_part)) { + int index = 0; + for (;;) { + char ch = local_part[index++]; + if (ch == String.EOS) + break; + + is_dot = (ch == '.'); + + if (!(ch >= 0x41 && ch <= 0x5A) && // A-Z + !(ch >= 0x61 && ch <= 0x7A) && // a-z + !(ch >= 0x30 && ch <= 0x39) && // 0-9 + !(ch in ATEXT) && + !(is_dot && index > 1)) { // no leading dots + needs_quote = true; + break; + } + } + } + return needs_quote || is_dot; // no trailing dots + } + + private static string quote_string(string needs_quoting) { + StringBuilder builder = new StringBuilder(); + if (!String.is_empty(needs_quoting)) { + builder.append_c('"'); + int index = 0; + for (;;) { + char ch = needs_quoting[index++]; + if (ch == String.EOS) + break; + + if (ch == '"' || ch == '\\') { + builder.append_c('\\'); + } + + builder.append_c(ch); + } + builder.append_c('"'); + } + return builder.str; + } + + private static string prepare_header_text_part(string part) { + // Borrowed liberally from GMime's internal + // _internet_address_decode_name() function. + + // see if a broken mailer has sent raw 8-bit information + string text = GMime.utils_text_is_8bit(part, part.length) + ? part : GMime.utils_decode_8bit(part, part.length); + + // unquote the string then decode the text + GMime.utils_unquote_string(text); + + // Sometimes quoted printables contain unencoded spaces which trips up GMime, so we want to + // encode them all here. + int offset = 0; + int start; + while ((start = text.index_of("=?", offset)) != -1) { + // Find the closing marker. + int end = text.index_of("?=", start + 2) + 2; + if (end == -1) { + end = text.length; + } + + // Replace any spaces inside the encoded string. + string encoded = text.substring(start, end - start); + if (encoded.contains("\x20")) { + text = text.replace(encoded, encoded.replace("\x20", "_")); + } + offset = end; + } + + return text; + } + -public class Geary.RFC822.MailboxAddress : Geary.MessageData.SearchableMessageData, - Gee.Hashable, BaseObject { - internal delegate string ListToStringDelegate(MailboxAddress address); - /** - * The optional user-friendly name associated with the {@link MailboxAddress}. + * The optional human-readable part of the mailbox address. * * For "Dirk Gently ", this would be "Dirk Gently". + * + * The returned value has been decoded into UTF-8. */ public string? name { get; private set; } - + /** * The routing of the message (optional, obsolete). + * + * The returned value has been decoded into UTF-8. */ public string? source_route { get; private set; } - + /** - * The mailbox (local-part) portion of the {@link MailboxAddress}. + * The mailbox (local-part) portion of the mailbox's address. * * For "Dirk Gently ", this would be "dirk". + * + * The returned value has been decoded into UTF-8. */ public string mailbox { get; private set; } - + /** - * The domain portion of the {@link MailboxAddress}. + * The domain portion of the mailbox's address. * * For "Dirk Gently ", this would be "example.com". + * + * The returned value has been decoded into UTF-8. */ public string domain { get; private set; } - + /** - * The address specification of the {@link MailboxAddress}. + * The complete address part of the mailbox address. * * For "Dirk Gently ", this would be "dirk@example.com". + * + * The returned value has been decoded into UTF-8. */ public string address { get; private set; } + public MailboxAddress(string? name, string address) { this.name = name; + this.source_route = null; this.address = address; - source_route = null; - - int atsign = Ascii.index_of(address, '@'); + int atsign = Ascii.last_index_of(address, '@'); if (atsign > 0) { - mailbox = address.slice(0, atsign); - domain = address.slice(atsign + 1, address.length); + this.mailbox = address[0:atsign]; + this.domain = address[atsign + 1:address.length]; } else { - mailbox = ""; - domain = ""; + this.mailbox = ""; + this.domain = ""; } } - + public MailboxAddress.imap(string? name, string? source_route, string mailbox, string domain) { this.name = (name != null) ? decode_name(name) : null; this.source_route = source_route; - this.mailbox = mailbox; + this.mailbox = decode_address_part(mailbox); this.domain = domain; - - address = "%s@%s".printf(mailbox, domain); + this.address = "%s@%s".printf(mailbox, domain); } public MailboxAddress.from_rfc822_string(string rfc822) throws RFC822Error { @@ -84,67 +217,110 @@ // TODO: Handle group lists InternetAddressMailbox? mbox_addr = addr as InternetAddressMailbox; if (mbox_addr != null) { - this(mbox_addr.get_name(), mbox_addr.get_addr()); + this.gmime(mbox_addr); return; } } throw new RFC822Error.INVALID("Could not parse RFC822 address: %s", rfc822); } - // Borrowed liberally from GMime's internal _internet_address_decode_name() function. - private static string decode_name(string name) { - // see if a broken mailer has sent raw 8-bit information - string text = name.validate() ? name : GMime.utils_decode_8bit(name, name.length); - - // unquote the string and decode the text - GMime.utils_unquote_string(text); - - // Sometimes quoted printables contain unencoded spaces which trips up GMime, so we want to - // encode them all here. - int offset = 0; - int start; - while ((start = text.index_of("=?", offset)) != -1) { - // Find the closing marker. - int end = text.index_of("?=", start + 2) + 2; - if (end == -1) { - end = text.length; - } + public MailboxAddress.gmime(InternetAddressMailbox mailbox) { + // GMime strips source route for us, so the address part + // should only ever contain a single '@' + string? name = mailbox.get_name(); + if (name != null) { + this.name = decode_name(name); + } - // Replace any spaces inside the encoded string. - string encoded = text.substring(start, end - start); - if (encoded.contains("\x20")) { - text = text.replace(encoded, encoded.replace("\x20", "_")); - } - offset = end; + string address = mailbox.get_addr(); + int atsign = Ascii.last_index_of(address, '@'); + if (atsign == -1) { + // No @ detected, try decoding in case a mailer (wrongly) + // encoded the whole thing and re-try + address = decode_address_part(address); + atsign = Ascii.last_index_of(address, '@'); } - return GMime.utils_header_decode_text(text); + if (atsign >= 0) { + this.mailbox = decode_address_part(address[0:atsign]); + this.domain = address[atsign + 1:address.length]; + this.address = "%s@%s".printf(this.mailbox, this.domain); + } else { + this.mailbox = ""; + this.domain = ""; + this.address = address; + } } /** - * Returns a human-readable formatted address, showing the name (if available) and the email - * address in angled brackets. No RFC822 quoting is performed. + * Returns a full human-readable version of the mailbox address. * - * @see to_rfc822_string + * This returns a formatted version of the address including + * {@link name} (if present, not a spoof, and distinct from the + * address) and {@link address} parts, suitable for display to + * people. The string will have white space reduced and + * non-printable characters removed, and the address will be + * surrounded by angle brackets if a name is present, and if the + * name contains a reserved character, it will be quoted. + * + * If you need a form suitable for sending a message, see {@link + * to_rfc822_string} instead. + * + * @see has_distinct_name + * @see is_spoofed + * @param open optional string to use as the opening bracket for + * the address part, defaults to //// + * @return the cleaned //name// part if present, not spoofed and + * distinct from //address//, followed by a space then the cleaned + * //address// part, cleaned and enclosed within the specified + * brackets. */ - public string get_full_address() { - return String.is_empty(name) ? address : "%s <%s>".printf(name, address); + public string to_full_display(string open = "<", string close = ">") { + string clean_name = Geary.String.reduce_whitespace(this.name); + if (display_name_needs_quoting(clean_name)) { + clean_name = quote_string(clean_name); + } + string clean_address = Geary.String.reduce_whitespace(this.address); + return (!has_distinct_name() || is_spoofed()) + ? clean_address + : "%s %s%s%s".printf(clean_name, open, clean_address, close); } - + /** - * Returns a simple address, that is, no human-readable name and the email address in angled + * Returns a short human-readable version of the mailbox address. + * + * This returns a shortened version of the address suitable for + * display to people: Either the {@link name} (if present and not + * a spoof) or the {@link address} part otherwise. The string will + * have white space reduced and non-printable characters removed. + * + * @see is_spoofed + * @return the cleaned //name// part if present and not spoofed, + * or else the cleaned //address// part, cleaned but without * brackets. */ - public string get_simple_address() { - return "<%s>".printf(address); + public string to_short_display() { + string clean_name = Geary.String.reduce_whitespace(this.name); + string clean_address = Geary.String.reduce_whitespace(this.address); + return String.is_empty(clean_name) || is_spoofed() + ? clean_address + : clean_name; } - + /** - * Returns a human-readable pretty address, showing only the name, but if unavailable, the - * mailbox name (that is, the account name without the domain). + * Returns a human-readable version of the address part. + * + * @param open optional string to use as the opening bracket, + * defaults to //// + * @return the {@link address} part, cleaned and enclosed within the + * specified brackets. */ - public string get_short_address() { - return name ?? mailbox; + public string to_address_display(string open = "<", string close = ">") { + return open + Geary.String.reduce_whitespace(this.address) + close; } /** @@ -153,82 +329,173 @@ public bool is_valid() { return is_valid_address(address); } - + /** - * Returns true if the email syntax is valid. - */ - public static bool is_valid_address(string address) { - try { - // http://www.regular-expressions.info/email.html - // matches john@dep.aol.museum not john@aol...com - Regex email_regex = - new Regex("[A-Z0-9._%+-]+@(?:[A-Z0-9-]+\\.)+[A-Z]{2,5}", - RegexCompileFlags.CASELESS); - return email_regex.match(address); - } catch (RegexError e) { - debug("Regex error validating email address: %s", e.message); - return false; + * Determines if the mailbox address appears to have been spoofed. + * + * Using recipient and sender mailbox addresses where the name + * part is also actually a valid RFC822 address + * (e.g. "you@example.com ") is a common tactic + * used by spammers and malware authors to exploit MUAs that will + * display the name part only if present. It also enables more + * sophisticated attacks such as + * [[https://www.mailsploit.com/|Mailsploit]], which uses + * Quoted-Printable or Base64 encoded nulls, new lines, @'s and + * other characters to further trick MUAs into displaying a bogus + * address. + * + * This method attempts to detect such attacks by examining the + * {@link name} for non-printing characters and determining if it + * is by itself also a valid RFC822 address. + * + * @return //true// if the complete decoded address contains any + * non-printing characters, if the name part is also a valid + * RFC822 address, or if the address part is not a valid RFC822 + * address. + */ + public bool is_spoofed() { + // Empty test and regexes must apply to the raw values, not + // clean ones, otherwise any control chars present will have + // been lost + const string CONTROLS = "[[:cntrl:]]+"; + + bool is_spoof = false; + + // 1. Check the name part contains no controls and doesn't + // look like an email address (unless it's the same as the + // address part). + if (!Geary.String.is_empty(this.name)) { + if (Regex.match_simple(CONTROLS, this.name)) { + is_spoof = true; + } else if (has_distinct_name()) { + // Clean up the name as usual, but remove all + // whitespace so an attack can't get away with a name + // like "potus @ whitehouse . gov" + string clean_name = Geary.String.reduce_whitespace(this.name); + clean_name = clean_name.replace(" ", ""); + if (is_valid_address(clean_name)) { + is_spoof = true; + } + } + } + + // 2. Check the mailbox part of the address doesn't contain an + // @. Is actually legal if quoted, but rarely (never?) found + // in the wild and better be safe than sorry. + if (!is_spoof && this.mailbox.contains("@")) { + is_spoof = true; + } + + // 3. Check the address doesn't contain any spaces or + // controls. Again, space in the mailbox is allowed if quoted, + // but in practice should rarely be used. + if (!is_spoof && Regex.match_simple(Geary.String.WS_OR_NP, this.address)) { + is_spoof = true; } + + return is_spoof; + } + + /** + * Determines if the name part is different to the address part. + * + * @return //true// if {@link name} is not empty, and the cleaned + * versions of the name part and {@link address} are not equal. + */ + public bool has_distinct_name() { + string clean_name = Geary.String.reduce_whitespace(this.name); + return ( + !Geary.String.is_empty(clean_name) && + clean_name != Geary.String.reduce_whitespace(this.address) + ); } - + /** - * Returns the address suitable for insertion into an RFC822 message. RFC822 quoting is - * performed if required. + * Returns the complete mailbox address, armoured for RFC 822 use. * - * @see get_full_address + * This method is similar to {@link to_full_display}, but only + * checks for a distinct address (per Postel's Law) and not for + * any spoofing, and does not strip extra white space or + * non-printing characters. + * + * @return the RFC822 encoded form of the full address. */ public string to_rfc822_string() { - return String.is_empty(name) - ? address - : "%s <%s>".printf(GMime.utils_quote_string(name), address); + return has_distinct_name() + ? "%s <%s>".printf( + GMime.utils_header_encode_phrase(this.name), + to_rfc822_address() + ) + : to_rfc822_address(); } - + + /** + * Returns the address part only, armoured for RFC 822 use. + * + * @return the RFC822 encoded form of the address, without angle + * brackets. + */ + public string to_rfc822_address() { + // XXX GMime.utils_header_encode_text won't quote if spaces or + // quotes present, and GMime.utils_quote_string will + // erroneously quote if a '.' is present (which at least + // Yahoo doesn't like in SMTP return paths), so need to quote + // manually. + string local_part = GMime.utils_header_encode_text(this.mailbox); + if (local_part_needs_quoting(local_part)) { + local_part = quote_string(local_part); + } + return "%s@%s".printf( + local_part, + // XXX Need to punycode international domains. + this.domain + ); + } + /** * See Geary.MessageData.SearchableMessageData. */ public string to_searchable_string() { - return get_full_address(); + return has_distinct_name() + ? "%s <%s>".printf(this.name, this.address) + : this.address; } - + public uint hash() { return String.stri_hash(address); } - + /** - * Equality is defined as a case-insensitive comparison of the {@link address}. + * Determines if this mailbox is equal to another by address. + * + * Equality is defined as case-insensitive comparison of the + * {@link address} of both mailboxes. */ public bool equal_to(MailboxAddress other) { - return this != other ? String.stri_equal(address, other.address) : true; + return this == other || String.stri_equal(address, other.address); } + /** + * Determines if this mailbox is equal to another by address. + * + * This is suitable for determining equality for weaker cases such + * as user searches. Here equality is defined as case-insensitive + * comparison of the normalised, case-folded {@link address} and + * the same for the given string. + */ public bool equal_normalized(string address) { - return this.address.normalize().casefold() == address.normalize().casefold(); + return ( + this.address.normalize().casefold() == address.normalize().casefold() + ); } + /** + * Returns the RFC822 formatted version of the address. + * + * @see to_rfc822_string + */ public string to_string() { - return get_full_address(); + return to_rfc822_string(); } - - internal static string list_to_string(Gee.List addrs, - string empty, ListToStringDelegate to_s) { - switch (addrs.size) { - case 0: - return empty; - - case 1: - return to_s(addrs[0]); - - default: - StringBuilder builder = new StringBuilder(); - foreach (MailboxAddress addr in addrs) { - if (!String.is_empty(builder.str)) - builder.append(", "); - - builder.append(to_s(addr)); - } - - return builder.str; - } - } -} +} diff -Nru geary-0.12.4/src/engine/rfc822/rfc822-message-data.vala geary-3.32.0/src/engine/rfc822/rfc822-message-data.vala --- geary-0.12.4/src/engine/rfc822/rfc822-message-data.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/rfc822/rfc822-message-data.vala 2019-03-17 13:39:29.000000000 +0000 @@ -24,14 +24,14 @@ string? normalized = normalize(value); base (normalized ?? value); } - + // Adds brackets if required, null if no change required private static string? normalize(string value) { bool needs_prefix = !value.has_prefix("<"); bool needs_suffix = !value.has_suffix(">"); if (!needs_prefix && !needs_suffix) return null; - + return "%s%s%s".printf(needs_prefix ? "<" : "", value, needs_suffix ? ">" : ""); } } @@ -41,27 +41,27 @@ */ public class Geary.RFC822.MessageIDList : Geary.MessageData.AbstractMessageData, Geary.RFC822.MessageData { public Gee.List list { get; private set; } - + public MessageIDList() { list = new Gee.ArrayList(); } - + public MessageIDList.from_collection(Gee.Collection collection) { this (); - + foreach(MessageID msg_id in collection) this.list.add(msg_id); } - + public MessageIDList.single(MessageID msg_id) { this (); - + list.add(msg_id); } - + public MessageIDList.from_rfc822_string(string value) { this (); - + // Have seen some mailers use commas between Message-IDs and whitespace inside Message-IDs, // meaning that the standard whitespace tokenizer is not sufficient. The only guarantee // made of a Message-ID is that it's surrounded by angle brackets, so save anything inside @@ -87,7 +87,7 @@ in_message_id = true; bracketed = true; break; - + case '(': if (!in_message_id) { in_message_id = true; @@ -96,18 +96,18 @@ add_char = true; } break; - + case '>': in_message_id = false; break; - + case ')': if (in_message_id) in_message_id = false; else add_char = true; break; - + default: // deal with Message-IDs without brackets ... bracketed is set to true the // moment the first one is found, so this doesn't deal with combinations of @@ -119,39 +119,49 @@ else if (in_message_id && ch.isspace()) in_message_id = false; } - + // only add characters inside the brackets or, if not bracketed, work around add_char = in_message_id; break; } - + if (add_char) canonicalized.append_c(ch); - + if (!in_message_id && !String.is_empty(canonicalized.str)) { list.add(new MessageID(canonicalized.str)); - + canonicalized = new StringBuilder(); } } - + // pick up anything that doesn't end with brackets if (!String.is_empty(canonicalized.str)) list.add(new MessageID(canonicalized.str)); - + // don't assert that list.size > 0; even though this method should generated a decoded ID // from any non-empty string, an empty Message-ID (i.e. "<>") won't. } - + + /** + * Returns a new list with the given messages ids appended to this list's. + */ + public MessageIDList append(MessageIDList others) { + MessageIDList new_ids = new MessageIDList(); + new_ids.list.add_all(this.list); + new_ids.list.add_all(others.list); + return new_ids; + } + public override string to_string() { return "MessageIDList (%d)".printf(list.size); } - + public virtual string to_rfc822_string() { string[] strings = new string[list.size]; for(int i = 0; i < list.size; ++i) strings[i] = list[i].value; - + return string.joinv(" ", strings); } } @@ -163,22 +173,30 @@ public string? original { get; private set; } public DateTime value { get; private set; } - + public Date(string iso8601) throws ImapError { this.as_time_t = GMime.utils_header_decode_date(iso8601, null); if (as_time_t == 0) - throw new ImapError.PARSE_ERROR("Unable to parse \"%s\": not ISO-8601 date", iso8601); - - value = new DateTime.from_unix_local(this.as_time_t); - original = iso8601; + throw new ImapError.PARSE_ERROR( + "Unable to parse \"%s\": Not ISO-8601 date", iso8601 + ); + + DateTime? value = new DateTime.from_unix_local(this.as_time_t); + if (value == null) { + throw new ImapError.PARSE_ERROR( + "Unable to parse \"%s\": Outside supported range", iso8601 + ); + } + this.value = value; + this.original = iso8601; } - + public Date.from_date_time(DateTime datetime) { original = null; value = datetime; this.as_time_t = Time.datetime_to_time_t(datetime); } - + /** * Returns the {@link Date} in ISO-8601 format. */ @@ -205,15 +223,15 @@ public virtual string serialize() { return to_iso_8601(); } - + public virtual bool equal_to(Geary.RFC822.Date other) { return (this != other) ? value.equal(other.value) : true; } - + public virtual uint hash() { return value.hash(); } - + public override string to_string() { return original ?? value.to_string(); } @@ -229,37 +247,37 @@ Geary.MessageData.SearchableMessageData, Geary.RFC822.MessageData { public const string REPLY_PREFACE = "Re:"; public const string FORWARD_PREFACE = "Fwd:"; - + public string original { get; private set; } - + public Subject(string value) { base (value); original = value; } - + public Subject.decode(string value) { base (GMime.utils_header_decode_text(value)); original = value; } - + public bool is_reply() { return value.down().has_prefix(REPLY_PREFACE.down()); } - + public Subject create_reply() { return is_reply() ? new Subject(value) : new Subject("%s %s".printf(REPLY_PREFACE, value)); } - + public bool is_forward() { return value.down().has_prefix(FORWARD_PREFACE.down()); } - + public Subject create_forward() { return is_forward() ? new Subject(value) : new Subject("%s %s".printf(FORWARD_PREFACE, value)); } - + /** * Returns the Subject: line stripped of reply and forwarding prefixes. * @@ -275,23 +293,23 @@ try { Regex re_regex = new Regex("^(?i:Re:\\s*)+"); stripped = re_regex.replace(subject_base, -1, 0, ""); - + Regex fwd_regex = new Regex("^(?i:Fwd:\\s*)+"); stripped = fwd_regex.replace(stripped, -1, 0, ""); } catch (RegexError e) { debug("Failed to clean up subject line \"%s\": %s", value, e.message); - + break; } - + changed = (stripped != subject_base); if (changed) subject_base = stripped; } while (changed); - + return String.reduce_whitespace(subject_base); } - + /** * See Geary.MessageData.SearchableMessageData. */ @@ -303,44 +321,44 @@ public class Geary.RFC822.Header : Geary.MessageData.BlockMessageData, Geary.RFC822.MessageData { private GMime.Message? message = null; private string[]? names = null; - + public Header(Memory.Buffer buffer) { base ("RFC822.Header", buffer); } - + private unowned GMime.HeaderList get_headers() throws RFC822Error { if (message != null) return message.get_header_list(); - + GMime.Parser parser = new GMime.Parser.with_stream(Utils.create_stream_mem(buffer)); parser.set_respect_content_length(false); parser.set_scan_from(false); - + message = parser.construct_message(); if (message == null) throw new RFC822Error.INVALID("Unable to parse RFC 822 headers"); - + return message.get_header_list(); } - + public string? get_header(string name) throws RFC822Error { return get_headers().get(name); } - + public string[] get_header_names() throws RFC822Error { if (names != null) return names; - + names = new string[0]; - + unowned GMime.HeaderIter iter; if (!get_headers().get_iter(out iter)) return names; - + do { names += iter.get_name(); } while (iter.next()); - + return names; } } @@ -363,49 +381,46 @@ base (_buffer); } - public PreviewText.with_header(Memory.Buffer preview, Memory.Buffer preview_header) { - string? charset = null; - string? encoding = null; - bool is_plain = false; - bool is_html = false; + public PreviewText.with_header(Memory.Buffer preview_header, Memory.Buffer preview) { + string preview_text = ""; // Parse the header. GMime.Stream header_stream = Utils.create_stream_mem(preview_header); GMime.Parser parser = new GMime.Parser.with_stream(header_stream); - GMime.Part? part = parser.construct_part() as GMime.Part; - if (part != null) { - Mime.ContentType? content_type = null; - if (part.get_content_type() != null) { - content_type = new Mime.ContentType.from_gmime(part.get_content_type()); - is_plain = content_type.is_type("text", "plain"); - is_html = content_type.is_type("text", "html"); - charset = content_type.params.get_value("charset"); + GMime.Part? gpart = parser.construct_part() as GMime.Part; + if (gpart != null) { + Part part = new Part(gpart); + + Mime.ContentType content_type = part.get_effective_content_type(); + bool is_plain = content_type.is_type("text", "plain"); + bool is_html = content_type.is_type("text", "html"); + + if (is_plain || is_html) { + // Parse the partial body + GMime.DataWrapper body = new GMime.DataWrapper.with_stream( + new GMime.StreamMem.with_buffer(preview.get_uint8_array()), + gpart.get_content_encoding() + ); + gpart.set_content_object(body); + + ByteArray output = new ByteArray(); + GMime.StreamMem output_stream = + new GMime.StreamMem.with_byte_array(output); + output_stream.set_owner(false); + + try { + part.write_to_stream(output_stream); + uint8[] data = output.data; + data += (uint8) '\0'; + + preview_text = Geary.RFC822.Utils.to_preview_text( + (string) data, + is_html ? TextFormat.HTML : TextFormat.PLAIN + ); + } catch (RFC822Error err) { + debug("Failed to parse preview body: %s", err.message); + } } - - encoding = part.get_header("Content-Transfer-Encoding"); - } - - string preview_text = ""; - if (is_plain || is_html) { - // Parse the preview - GMime.StreamMem input_stream = Utils.create_stream_mem(preview); - ByteArray output = new ByteArray(); - GMime.StreamMem output_stream = new GMime.StreamMem.with_byte_array(output); - output_stream.set_owner(false); - - // Convert the encoding and character set. - GMime.StreamFilter filter = new GMime.StreamFilter(output_stream); - if (encoding != null) - filter.add(new GMime.FilterBasic(GMime.content_encoding_from_string(encoding), false)); - - filter.add(Geary.RFC822.Utils.create_utf8_filter_charset(charset)); - filter.add(new GMime.FilterCRLF(false, false)); - - input_stream.write_to_stream(filter); - uint8[] data = output.data; - data += (uint8) '\0'; - - preview_text = Geary.RFC822.Utils.to_preview_text((string) data, is_html ? TextFormat.HTML : TextFormat.PLAIN); } base(new Geary.Memory.StringBuffer(preview_text)); diff -Nru geary-0.12.4/src/engine/rfc822/rfc822-message.vala geary-3.32.0/src/engine/rfc822/rfc822-message.vala --- geary-0.12.4/src/engine/rfc822/rfc822-message.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/rfc822/rfc822-message.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,39 +1,80 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. + * Copyright 2018 Michael Gratton * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ -public class Geary.RFC822.Message : BaseObject { +/** + * An RFC-822 style email message. + * + * Unlike {@link Email}, these objects are always a complete + * representation of an email message, and contain no information + * other than what RFC-822 and its successor RFC documents specify. + */ +public class Geary.RFC822.Message : BaseObject, EmailHeaderSet { + /** - * This delegate is an optional parameter to the body constructers that allows callers - * to process arbitrary non-text, inline MIME parts. + * Callback for including non-text MIME entities in message bodies. * - * This is only called for non-text MIME parts in mixed multipart sections. Inline parts - * referred to by rich text in alternative or related documents must be located by the caller - * and appropriately presented. + * This delegate is an optional parameter to the body constructors + * that allows callers to process arbitrary non-text, inline MIME + * parts. + * + * This is only called for non-text MIME parts in mixed multipart + * sections. Inline parts referred to by rich text in alternative + * or related documents must be located by the caller and + * appropriately presented. */ - public delegate string? InlinePartReplacer(string? filename, Mime.ContentType? content_type, - Mime.ContentDisposition? disposition, string? content_id, Geary.Memory.Buffer buffer); + public delegate string? InlinePartReplacer(Part part); private const string HEADER_SENDER = "Sender"; private const string HEADER_IN_REPLY_TO = "In-Reply-To"; private const string HEADER_REFERENCES = "References"; private const string HEADER_MAILER = "X-Mailer"; private const string HEADER_BCC = "Bcc"; - - // Internal note: If a field is added here, it *must* be set in stock_from_gmime(). - public RFC822.MailboxAddress? sender { get; private set; default = null; } - public RFC822.MailboxAddresses? from { get; private set; default = null; } - public RFC822.MailboxAddresses? to { get; private set; default = null; } - public RFC822.MailboxAddresses? cc { get; private set; default = null; } - public RFC822.MailboxAddresses? bcc { get; private set; default = null; } - public RFC822.MailboxAddresses? reply_to { get; private set; default = null; } - public RFC822.MessageIDList? in_reply_to { get; private set; default = null; } - public RFC822.MessageIDList? references { get; private set; default = null; } - public RFC822.Subject? subject { get; private set; default = null; } - public string? mailer { get; private set; default = null; } - public Geary.RFC822.Date? date { get; private set; default = null; } + + // Internal note: If a header field is added here, it *must* be + // set in stock_from_gmime(). + + /** {@inheritDoc} */ + + /** {@inheritDoc} */ + public RFC822.MailboxAddress? sender { get; protected set; default = null; } + + /** {@inheritDoc} */ + public RFC822.MailboxAddresses? from { get; protected set; default = null; } + + /** {@inheritDoc} */ + public RFC822.MailboxAddresses? to { get; protected set; default = null; } + + /** {@inheritDoc} */ + public RFC822.MailboxAddresses? cc { get; protected set; default = null; } + + /** {@inheritDoc} */ + public RFC822.MailboxAddresses? bcc { get; protected set; default = null; } + + /** {@inheritDoc} */ + public RFC822.MailboxAddresses? reply_to { get; protected set; default = null; } + + /** {@inheritDoc} */ + public RFC822.MessageID? message_id { get; protected set; default = null; } + + /** {@inheritDoc} */ + public RFC822.MessageIDList? in_reply_to { get; protected set; default = null; } + + /** {@inheritDoc} */ + public RFC822.MessageIDList? references { get; protected set; default = null; } + + /** {@inheritDoc} */ + public RFC822.Subject? subject { get; protected set; default = null; } + + /** {@inheritDoc} */ + public Geary.RFC822.Date? date { get; protected set; default = null; } + + /** Value of the X-Mailer header. */ + public string? mailer { get; protected set; default = null; } private GMime.Message message; @@ -43,30 +84,31 @@ // set these easily, so sometimes get_email() won't work. private Memory.Buffer? body_buffer = null; private size_t? body_offset = null; - + + public Message(Full full) throws RFC822Error { GMime.Parser parser = new GMime.Parser.with_stream(Utils.create_stream_mem(full.buffer)); - + message = parser.construct_message(); if (message == null) throw new RFC822Error.INVALID("Unable to parse RFC 822 message"); - + // See the declaration of these fields for why we do this. body_buffer = full.buffer; body_offset = (size_t) parser.get_headers_end(); - + stock_from_gmime(); } - + public Message.from_gmime_message(GMime.Message message) { this.message = message; stock_from_gmime(); } - + public Message.from_buffer(Memory.Buffer full_email) throws RFC822Error { this(new Geary.RFC822.Full(full_email)); } - + public Message.from_parts(Header header, Text body) throws RFC822Error { GMime.StreamCat stream_cat = new GMime.StreamCat(); stream_cat.add_source(new GMime.StreamMem.with_buffer(header.buffer.get_bytes().get_data())); @@ -76,10 +118,10 @@ message = parser.construct_message(); if (message == null) throw new RFC822Error.INVALID("Unable to parse RFC 822 message"); - + body_buffer = body.buffer; body_offset = 0; - + stock_from_gmime(); } @@ -97,8 +139,10 @@ // supports a list of addresses message.set_sender(this.from.to_rfc822_string()); message.set_date_as_string(this.date.serialize()); - if (message_id != null) + if (message_id != null) { + this.message_id = new MessageID(message_id); message.set_message_id(message_id); + } // Optional headers if (email.to != null) { @@ -211,7 +255,7 @@ string cid = ""; do { cid = CID_TEMPLATE.printf(cid_index++); - } while (cid in email.cid_files); + } while (email.cid_files.has_key(cid)); if (email.replace_inline_img_src(name, CID_URL_PREFIX + cid)) { @@ -282,7 +326,7 @@ } catch (Error e) { error("Error creating a memory buffer from a message: %s", e.message); } - + // GMime also drops the ball for the *new* message. When it comes out of the GMime // Parser, its "mime part" somehow isn't realizing it has a Content-Type header // already, so whenever you manipulate the headers, it adds a duplicate one. This @@ -293,10 +337,10 @@ GMime.Object original_mime_part = message.get_mime_part(); GMime.Message empty = new GMime.Message(true); message.set_mime_part(empty.get_mime_part()); - + message.remove_header(HEADER_BCC); bcc = null; - + message.set_mime_part(original_mime_part); } @@ -347,7 +391,7 @@ part.set_content_encoding(Geary.RFC822.Utils.get_best_encoding(stream)); return part; } - + /** * Construct a Geary.Email from a Message. NOTE: this requires you to have created * the Message in such a way that its body_buffer and body_offset fields will be filled @@ -357,15 +401,15 @@ public Geary.Email get_email(Geary.EmailIdentifier id) throws Error { assert(body_buffer != null); assert(body_offset != null); - + Geary.Email email = new Geary.Email(id); - + email.set_message_header(new Geary.RFC822.Header(new Geary.Memory.StringBuffer( message.get_headers()))); email.set_send_date(date); email.set_originators(from, sender, reply_to); email.set_receivers(to, cc, bcc); - email.set_full_references(null, in_reply_to, references); + email.set_full_references(message_id, in_reply_to, references); email.set_message_subject(subject); email.set_message_body(new Geary.RFC822.Text(new Geary.Memory.OffsetBuffer( body_buffer, body_offset))); @@ -397,140 +441,21 @@ : ""; } - /** - * Returns the primary originator of an email, which is defined as the first mailbox address - * in From:, Sender:, or Reply-To:, in that order, depending on availability. - * - * Returns null if no originators are present. - */ - public RFC822.MailboxAddress? get_primary_originator() { - if (from != null && from.size > 0) - return from[0]; - - if (sender != null) - return sender; - - if (reply_to != null && reply_to.size > 0) - return reply_to[0]; - - return null; - } - - private void stock_from_gmime() { - // GMime calls the From address the "sender" - string? message_sender = message.get_sender(); - if (message_sender != null) { - this.from = new RFC822.MailboxAddresses.from_rfc822_string(message_sender); - } - - // And it doesn't provide a convenience method for Sender header - if (!String.is_empty(message.get_header(HEADER_SENDER))) { - string sender = GMime.utils_header_decode_text(message.get_header(HEADER_SENDER)); - try { - this.sender = new RFC822.MailboxAddress.from_rfc822_string(sender); - } catch (RFC822Error e) { - debug("Invalid RDC822 Sender address: %s", sender); - } - } - - if (!String.is_empty(message.get_reply_to())) - this.reply_to = new RFC822.MailboxAddresses.from_rfc822_string(message.get_reply_to()); - - Gee.List? converted = convert_gmime_address_list( - message.get_recipients(GMime.RecipientType.TO)); - if (converted != null && converted.size > 0) - to = new RFC822.MailboxAddresses(converted); - - converted = convert_gmime_address_list(message.get_recipients(GMime.RecipientType.CC)); - if (converted != null && converted.size > 0) - cc = new RFC822.MailboxAddresses(converted); - - converted = convert_gmime_address_list(message.get_recipients(GMime.RecipientType.BCC)); - if (converted != null && converted.size > 0) - bcc = new RFC822.MailboxAddresses(converted); - - if (!String.is_empty(message.get_header(HEADER_IN_REPLY_TO))) - in_reply_to = new RFC822.MessageIDList.from_rfc822_string(message.get_header(HEADER_IN_REPLY_TO)); - - if (!String.is_empty(message.get_header(HEADER_REFERENCES))) - references = new RFC822.MessageIDList.from_rfc822_string(message.get_header(HEADER_REFERENCES)); - - if (!String.is_empty(message.get_subject())) - subject = new RFC822.Subject.decode(message.get_subject()); - - if (!String.is_empty(message.get_header(HEADER_MAILER))) - mailer = message.get_header(HEADER_MAILER); - - if (!String.is_empty(message.get_date_as_string())) { - try { - date = new Geary.RFC822.Date(message.get_date_as_string()); - } catch (Error error) { - debug("Could not get date from message: %s", error.message); - } - } - } - - private Gee.List? convert_gmime_address_list(InternetAddressList? addrlist, - int depth = 0) { - if (addrlist == null || addrlist.length() == 0) - return null; - - Gee.List? converted = new Gee.ArrayList(); - - int length = addrlist.length(); - for (int ctr = 0; ctr < length; ctr++) { - InternetAddress addr = addrlist.get_address(ctr); - - InternetAddressMailbox? mbox_addr = addr as InternetAddressMailbox; - if (mbox_addr != null) { - converted.add(new RFC822.MailboxAddress(mbox_addr.get_name(), mbox_addr.get_addr())); - - continue; - } - - // Two problems here: - // - // First, GMime crashes when parsing a malformed group list (the case seen in the - // wild is -- weirdly enough -- a date appended to the end of a cc: list on a spam - // email. GMime interprets it as a group list but segfaults when destroying the - // InterneAddresses it generated from it. See: - // https://bugzilla.gnome.org/show_bug.cgi?id=695319 - // - // Second, RFC 822 6.2.6: "This standard does not permit recursive specification - // of groups within groups." So don't do it. - InternetAddressGroup? group = addr as InternetAddressGroup; - if (group != null) { - if (depth == 0) { - Gee.List? grouplist = convert_gmime_address_list( - group.get_members(), depth + 1); - if (grouplist != null) - converted.add_all(grouplist); - } - - continue; - } - - warning("Unknown InternetAddress in list: %s", addr.get_type().name()); - } - - return (converted.size > 0) ? converted : null; - } - public Gee.List? get_recipients() { Gee.List addrs = new Gee.ArrayList(); - + if (to != null) addrs.add_all(to.get_all()); - + if (cc != null) addrs.add_all(cc.get_all()); - + if (bcc != null) addrs.add_all(bcc.get_all()); - + return (addrs.size > 0) ? addrs : null; } - + /** * Returns the {@link Message} as a {@link Memory.Buffer} suitable for in-memory use (i.e. * with native linefeed characters). @@ -538,7 +463,7 @@ public Memory.Buffer get_native_buffer() throws RFC822Error { return message_to_memory_buffer(false, false); } - + /** * Returns the {@link Message} as a {@link Memory.Buffer} suitable for transmission or * storage (i.e. using protocol-specific linefeeds). @@ -561,7 +486,7 @@ * Determines if the message has one or plain text display parts. */ public bool has_plain_body() { - return has_body_parts(message.get_mime_part(), "text"); + return has_body_parts(message.get_mime_part(), "plain"); } /** @@ -575,38 +500,29 @@ * construct_body_from_mime_parts. */ private bool has_body_parts(GMime.Object node, string text_subtype) { - bool has_part = false; - Mime.ContentType? this_content_type = null; - if (node.get_content_type() != null) - this_content_type = - new Mime.ContentType.from_gmime(node.get_content_type()); + Part part = new Part(node); + bool is_matching_part = false; - GMime.Multipart? multipart = node as GMime.Multipart; - if (multipart != null) { + if (node is GMime.Multipart) { + GMime.Multipart multipart = (GMime.Multipart) node; int count = multipart.get_count(); - for (int i = 0; i < count && !has_part; ++i) { - has_part = has_body_parts(multipart.get_part(i), text_subtype); + for (int i = 0; i < count && !is_matching_part; i++) { + is_matching_part = has_body_parts( + multipart.get_part(i), text_subtype + ); + } + } else if (node is GMime.Part) { + Mime.DispositionType disposition = Mime.DispositionType.UNSPECIFIED; + if (part.content_disposition != null) { + disposition = part.content_disposition.disposition_type; } - } else { - GMime.Part? part = node as GMime.Part; - if (part != null) { - Mime.ContentDisposition? disposition = null; - if (part.get_content_disposition() != null) - disposition = new Mime.ContentDisposition.from_gmime( - part.get_content_disposition() - ); - if (disposition == null || - disposition.disposition_type != Mime.DispositionType.ATTACHMENT) { - if (this_content_type != null && - this_content_type.has_media_type("text") && - this_content_type.has_media_subtype(text_subtype)) { - has_part = true; - } - } - } + is_matching_part = ( + disposition != Mime.DispositionType.ATTACHMENT && + part.get_effective_content_type().is_type("text", text_subtype) + ); } - return has_part; + return is_matching_part; } /** @@ -623,78 +539,70 @@ * The initial call should pass the root of this message and UNSPECIFIED as its container * subtype. * - * @returns Whether a text part with the desired text_subtype was found + * @return Whether a text part with the desired text_subtype was found */ - private bool construct_body_from_mime_parts(GMime.Object node, Mime.MultipartSubtype container_subtype, - string text_subtype, bool to_html, InlinePartReplacer? replacer, ref string? body) throws RFC822Error { - Mime.ContentType? this_content_type = null; - if (node.get_content_type() != null) - this_content_type = new Mime.ContentType.from_gmime(node.get_content_type()); - + private bool construct_body_from_mime_parts(GMime.Object node, + Mime.MultipartSubtype container_subtype, + string text_subtype, + bool to_html, + InlinePartReplacer? replacer, + ref string? body) + throws RFC822Error { + Part part = new Part(node); + Mime.ContentType content_type = part.get_effective_content_type(); + // If this is a multipart, call ourselves recursively on the children GMime.Multipart? multipart = node as GMime.Multipart; if (multipart != null) { - Mime.MultipartSubtype this_subtype = Mime.MultipartSubtype.from_content_type(this_content_type, - null); - + Mime.MultipartSubtype this_subtype = + Mime.MultipartSubtype.from_content_type(content_type, null); + bool found_text_subtype = false; - + StringBuilder builder = new StringBuilder(); int count = multipart.get_count(); for (int i = 0; i < count; ++i) { GMime.Object child = multipart.get_part(i); - + string? child_body = null; found_text_subtype |= construct_body_from_mime_parts(child, this_subtype, text_subtype, to_html, replacer, ref child_body); if (child_body != null) builder.append(child_body); } - + if (!String.is_empty(builder.str)) body = builder.str; - + return found_text_subtype; } - - // Only process inline leaf parts - GMime.Part? part = node as GMime.Part; - if (part == null) - return false; - - Mime.ContentDisposition? disposition = null; - if (part.get_content_disposition() != null) - disposition = new Mime.ContentDisposition.from_gmime(part.get_content_disposition()); - - // Stop processing if the part is an attachment - if (disposition != null && disposition.disposition_type == Mime.DispositionType.ATTACHMENT) - return false; - - // Assemble body from text parts that are not attachments - if (this_content_type != null && this_content_type.has_media_type("text")) { - if (this_content_type.has_media_subtype(text_subtype)) { - body = mime_part_to_memory_buffer(part, true, to_html).to_string(); - - return true; + + Mime.DispositionType disposition = Mime.DispositionType.UNSPECIFIED; + if (part.content_disposition != null) { + disposition = part.content_disposition.disposition_type; + } + + // Process inline leaf parts + if (node is GMime.Part && + disposition != Mime.DispositionType.ATTACHMENT) { + + // Assemble body from matching text parts, else use inline + // part replacer *only* for inline parts and if in a mixed + // multipart where each element is to be presented to the + // user as structure dictates; For alternative and + // related, the inline part is referred to elsewhere in + // the document and it's the callers responsibility to + // locate them + + if (content_type.is_type("text", text_subtype)) { + body = part.write_to_buffer( + to_html ? Part.BodyFormatting.HTML : Part.BodyFormatting.NONE + ).to_string(); + } else if (replacer != null && + disposition == Mime.DispositionType.INLINE && + container_subtype == Mime.MultipartSubtype.MIXED) { + body = replacer(part); } - - // We were the wrong kind of text part - return false; - } - - // Use inline part replacer *only* for inline parts and if in - // a mixed multipart where each element is to be presented to - // the user as structure dictates; For alternative and - // related, the inline part is referred to elsewhere in the - // document and it's the callers responsibility to locate them - if (replacer != null && disposition != null && - disposition.disposition_type == Mime.DispositionType.INLINE && - container_subtype == Mime.MultipartSubtype.MIXED) { - body = replacer(RFC822.Utils.get_clean_attachment_filename(part), - this_content_type, - disposition, - part.get_content_id(), - mime_part_to_memory_buffer(part)); } return body != null; @@ -774,10 +682,10 @@ // Ignore. } } - + if (body != null && html) body = Geary.HTML.html_to_text(body); - + if (include_sub_messages) { foreach (Message sub_message in get_sub_messages()) { // We index a rough approximation of what a client would be @@ -805,7 +713,7 @@ string? sub_body = sub_message.get_searchable_body(false); if (sub_body != null) sub_full.append(sub_body); - + if (sub_full.len > 0) { if (body == null) body = ""; @@ -813,82 +721,128 @@ } } } - + return body; } - + /** * Return the full list of recipients (to, cc, and bcc) as a searchable * string. Note that values that come out of this function are persisted. */ public string? get_searchable_recipients() { - Gee.List? recipients = get_recipients(); - if (recipients == null) - return null; - - return RFC822.MailboxAddress.list_to_string(recipients, "", (a) => a.to_searchable_string()); - } - - public Memory.Buffer get_content_by_mime_id(string mime_id) throws RFC822Error { - GMime.Part? part = find_mime_part_by_mime_id(message.get_mime_part(), mime_id); - if (part == null) - throw new RFC822Error.NOT_FOUND("Could not find a MIME part with Content-ID %s", mime_id); - - return mime_part_to_memory_buffer(part); - } - - public string? get_content_filename_by_mime_id(string mime_id) throws RFC822Error { - GMime.Part? part = find_mime_part_by_mime_id(message.get_mime_part(), mime_id); - if (part == null) - throw new RFC822Error.NOT_FOUND("Could not find a MIME part with Content-ID %s", mime_id); - - return part.get_filename(); - } - - private GMime.Part? find_mime_part_by_mime_id(GMime.Object root, string mime_id) { - // If this is a multipart container, check each of its children. - if (root is GMime.Multipart) { - GMime.Multipart multipart = root as GMime.Multipart; - int count = multipart.get_count(); - for (int i = 0; i < count; ++i) { - GMime.Part? child_part = find_mime_part_by_mime_id(multipart.get_part(i), mime_id); - if (child_part != null) { - return child_part; - } - } - } - - // Otherwise, check this part's content id. - GMime.Part? part = root as GMime.Part; - if (part != null && part.get_content_id() == mime_id) { - return part; + string searchable = null; + Gee.List? recipient_list = get_recipients(); + if (recipient_list != null) { + MailboxAddresses recipients = new MailboxAddresses(recipient_list); + searchable = recipients.to_searchable_string(); } - return null; + return searchable; } - + // UNSPECIFIED disposition means "return all Mime parts" - internal Gee.List get_attachments( + internal Gee.List get_attachments( Mime.DispositionType disposition = Mime.DispositionType.UNSPECIFIED) throws RFC822Error { - Gee.List attachments = new Gee.ArrayList(); + Gee.List attachments = new Gee.LinkedList(); get_attachments_recursively(attachments, message.get_mime_part(), disposition); return attachments; } - - private void get_attachments_recursively(Gee.List attachments, GMime.Object root, - Mime.DispositionType requested_disposition) throws RFC822Error { - // If this is a multipart container, dive into each of its children. - GMime.Multipart? multipart = root as GMime.Multipart; - if (multipart != null) { + + private void stock_from_gmime() { + this.message.get_header_list().foreach((name, value) => { + switch (name.down()) { + case "from": + this.from = append_address(this.from, value); + break; + + case "sender": + try { + this.sender = new RFC822.MailboxAddress.from_rfc822_string(value); + } catch (Error err) { + debug("Could parse subject: %s", err.message); + } + break; + + case "reply-to": + this.reply_to = append_address(this.reply_to, value); + break; + + case "to": + this.to = append_address(this.to, value); + break; + + case "cc": + this.cc = append_address(this.cc, value); + break; + + case "bcc": + this.bcc = append_address(this.bcc, value); + break; + + case "subject": + this.subject = new RFC822.Subject.decode(value); + break; + + case "date": + try { + this.date = new Geary.RFC822.Date(value); + } catch (Error err) { + debug("Could not parse date: %s", err.message); + } + break; + + case "message-id": + this.message_id = new MessageID(value); + break; + + case "in-reply-to": + this.in_reply_to = append_message_id(this.in_reply_to, value); + break; + + case "references": + this.references = append_message_id(this.references, value); + break; + + case "x-mailer": + this.mailer = GMime.utils_header_decode_text(value); + break; + + default: + break; + } + }); + } + + private MailboxAddresses append_address(MailboxAddresses? existing, + string header_value) { + MailboxAddresses addresses = new MailboxAddresses.from_rfc822_string(header_value); + if (existing != null) { + addresses = existing.append(addresses); + } + return addresses; + } + + private MessageIDList append_message_id(MessageIDList? existing, + string header_value) { + MessageIDList ids = new MessageIDList.from_rfc822_string(header_value); + if (existing != null) { + ids = existing.append(ids); + } + return ids; + } + + private void get_attachments_recursively(Gee.List attachments, + GMime.Object root, + Mime.DispositionType requested_disposition) + throws RFC822Error { + + if (root is GMime.Multipart) { + GMime.Multipart multipart = (GMime.Multipart) root; int count = multipart.get_count(); for (int i = 0; i < count; ++i) { get_attachments_recursively(attachments, multipart.get_part(i), requested_disposition); } - return; - } - - // If this is an attached message, go through it. - GMime.MessagePart? messagepart = root as GMime.MessagePart; - if (messagepart != null) { + } else if (root is GMime.MessagePart) { + GMime.MessagePart messagepart = (GMime.MessagePart) root; GMime.Message message = messagepart.get_message(); bool is_unknown; Mime.DispositionType disposition = Mime.DispositionType.deserialize(root.get_disposition(), @@ -897,7 +851,7 @@ // This is often the case, and we'll treat these as attached disposition = Mime.DispositionType.ATTACHMENT; } - + if (requested_disposition == Mime.DispositionType.UNSPECIFIED || disposition == requested_disposition) { GMime.Stream stream = new GMime.StreamMem(); message.write_to_stream(stream); @@ -906,46 +860,43 @@ GMime.Part part = new GMime.Part.with_type("message", "rfc822"); part.set_content_object(data); part.set_filename((message.get_subject() ?? _("(no subject)")) + ".eml"); - attachments.add(part); + attachments.add(new Part(part)); } - + get_attachments_recursively(attachments, message.get_mime_part(), requested_disposition); - return; - } - - // Otherwise, check if this part should be an attachment - GMime.Part? part = root as GMime.Part; - if (part == null) { - return; - } - - // If requested disposition is not UNSPECIFIED, check if this part matches the requested deposition - Mime.DispositionType part_disposition = Mime.DispositionType.deserialize(part.get_disposition(), - null); - if (requested_disposition != Mime.DispositionType.UNSPECIFIED && requested_disposition != part_disposition) - return; - - // skip text/plain and text/html parts that are INLINE or UNSPECIFIED, as they will be used - // as part of the body - if (part.get_content_type() != null) { - Mime.ContentType content_type = new Mime.ContentType.from_gmime(part.get_content_type()); - if ((part_disposition == Mime.DispositionType.INLINE || part_disposition == Mime.DispositionType.UNSPECIFIED) - && content_type.has_media_type("text") - && (content_type.has_media_subtype("html") || content_type.has_media_subtype("plain"))) { - return; + } else if (root is GMime.Part) { + Part part = new Part(root); + + Mime.DispositionType actual_disposition = + Mime.DispositionType.UNSPECIFIED; + if (part.content_disposition != null) { + actual_disposition = part.content_disposition.disposition_type; + } + + if (requested_disposition == Mime.DispositionType.UNSPECIFIED || + actual_disposition == requested_disposition) { + + Mime.ContentType content_type = + part.get_effective_content_type(); + + // Skip text/plain and text/html parts that are INLINE + // or UNSPECIFIED, as they will be included in the body + if (actual_disposition == Mime.DispositionType.ATTACHMENT || + (!content_type.is_type("text", "plain") && + !content_type.is_type("text", "html"))) { + attachments.add(part); + } } } - - attachments.add(part); } - + public Gee.List get_sub_messages() { Gee.List messages = new Gee.ArrayList(); find_sub_messages(messages, message.get_mime_part()); return messages; } - + private void find_sub_messages(Gee.List messages, GMime.Object root) { // If this is a multipart container, check each of its children. GMime.Multipart? multipart = root as GMime.Multipart; @@ -956,7 +907,7 @@ } return; } - + GMime.MessagePart? messagepart = root as GMime.MessagePart; if (messagepart != null) { GMime.Message sub_message = messagepart.get_message(); @@ -967,73 +918,22 @@ } } } - + private Memory.Buffer message_to_memory_buffer(bool encoded, bool dotstuffed) throws RFC822Error { ByteArray byte_array = new ByteArray(); GMime.StreamMem stream = new GMime.StreamMem.with_byte_array(byte_array); stream.set_owner(false); - + GMime.StreamFilter stream_filter = new GMime.StreamFilter(stream); stream_filter.add(new GMime.FilterCRLF(encoded, dotstuffed)); - + if (message.write_to_stream(stream_filter) < 0) throw new RFC822Error.FAILED("Unable to write RFC822 message to memory buffer"); - + if (stream_filter.flush() != 0) throw new RFC822Error.FAILED("Unable to flush RFC822 message to memory buffer"); - - return new Memory.ByteBuffer.from_byte_array(byte_array); - } - - private Memory.Buffer mime_part_to_memory_buffer(GMime.Part part, - bool to_utf8 = false, bool to_html = false) throws RFC822Error { - Mime.ContentType? content_type = null; - if (part.get_content_type() != null) - content_type = new Mime.ContentType.from_gmime(part.get_content_type()); - - GMime.DataWrapper? wrapper = part.get_content_object(); - if (wrapper == null) { - throw new RFC822Error.INVALID("Could not get the content wrapper for content-type %s", - content_type.to_string()); - } - - ByteArray byte_array = new ByteArray(); - GMime.StreamMem stream = new GMime.StreamMem.with_byte_array(byte_array); - stream.set_owner(false); - - if (to_utf8) { - // Assume encoded text, convert to unencoded UTF-8 - GMime.StreamFilter stream_filter = new GMime.StreamFilter(stream); - string? charset = (content_type != null) ? content_type.params.get_value("charset") : null; - stream_filter.add(Geary.RFC822.Utils.create_utf8_filter_charset(charset)); - - bool flowed = (content_type != null) ? content_type.params.has_value_ci("format", "flowed") : false; - bool delsp = (content_type != null) ? content_type.params.has_value_ci("DelSp", "yes") : false; - - // Unconditionally remove the CR's in any CRLF sequence, since - // they are effectively a wire encoding. - stream_filter.add(new GMime.FilterCRLF(false, false)); - - if (flowed) - stream_filter.add(new Geary.RFC822.FilterFlowed(to_html, delsp)); - - if (to_html) { - if (!flowed) - stream_filter.add(new Geary.RFC822.FilterPlain()); - stream_filter.add(new GMime.FilterHTML( - GMime.FILTER_HTML_CONVERT_URLS | GMime.FILTER_HTML_CONVERT_ADDRESSES, 0)); - stream_filter.add(new Geary.RFC822.FilterBlockquotes()); - } - wrapper.write_to_stream(stream_filter); - stream_filter.flush(); - } else { - // Keep as binary - wrapper.write_to_stream(stream); - stream.flush(); - } - - return new Geary.Memory.ByteBuffer.from_byte_array(byte_array); + return new Memory.ByteBuffer.from_byte_array(byte_array); } public string to_string() { diff -Nru geary-0.12.4/src/engine/rfc822/rfc822-part.vala geary-3.32.0/src/engine/rfc822/rfc822-part.vala --- geary-0.12.4/src/engine/rfc822/rfc822-part.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/src/engine/rfc822/rfc822-part.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,219 @@ +/* + * Copyright 2016 Software Freedom Conservancy Inc. + * Copyright 2018 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +/** + * An RFC-2045 style MIME entity. + * + * This object provides a convenient means accessing the high-level + * MIME entity header field values that are useful to applications and + * decoded forms of the entity body. + */ +public class Geary.RFC822.Part : Object { + + + /** Specifies a format to apply to body data when writing it. */ + public enum BodyFormatting { + + /** No formatting will be applied. */ + NONE, + + /** Plain text bodies will be formatted as HTML. */ + HTML; + } + + + // The set of text/* types that must have CRLF preserved, since it + // is part of their format. These really should be under + // application/*, but here we are. + private static Gee.Set CR_PRESERVING_TEXT_TYPES = + new Gee.HashSet(); + + static construct { + // VCard + CR_PRESERVING_TEXT_TYPES.add("vcard"); + CR_PRESERVING_TEXT_TYPES.add("x-vcard"); + CR_PRESERVING_TEXT_TYPES.add("directory"); + + // iCal + CR_PRESERVING_TEXT_TYPES.add("calendar"); + + // MS RTF + CR_PRESERVING_TEXT_TYPES.add("rtf"); + } + + + /** + * The entity's Content-Type. + * + * See [[https://tools.ietf.org/html/rfc2045#section-5]] + */ + public Mime.ContentType? content_type { get; private set; } + + /** + * The entity's Content-ID. + * + * See [[https://tools.ietf.org/html/rfc2045#section-5]], + * [[https://tools.ietf.org/html/rfc2111]] and {@link + * Email.get_attachment_by_content_id}. + */ + public string? content_id { get; private set; } + + /** + * The entity's Content-Description. + * + * See [[https://tools.ietf.org/html/rfc2045#section-8]] + */ + public string? content_description { get; private set; } + + /** + * The entity's Content-Disposition. + * + * See [[https://tools.ietf.org/html/rfc2183]] + */ + public Mime.ContentDisposition? content_disposition { get; private set; } + + private GMime.Object source_object; + private GMime.Part? source_part; + + + internal Part(GMime.Object source) { + this.source_object = source; + this.source_part = source as GMime.Part; + + GMime.ContentType? part_type = source.get_content_type(); + if (part_type != null) { + this.content_type = new Mime.ContentType.from_gmime(part_type); + } + + this.content_id = source.get_content_id(); + + this.content_description = (this.source_part != null) + ? source_part.get_content_description() : null; + + GMime.ContentDisposition? part_disposition = source.get_content_disposition(); + if (part_disposition != null) { + this.content_disposition = new Mime.ContentDisposition.from_gmime( + part_disposition + ); + } + } + + /** + * The entity's effective Content-Type. + * + * This returns the entity's content type if set, else returns + * {@link Geary.Mime.ContentType.DISPLAY_DEFAULT} this is a + * displayable (i.e. non-attachment) entity, or {@link + * Geary.Mime.ContentType.ATTACHMENT_DEFAULT} if not. + */ + public Mime.ContentType get_effective_content_type() { + Mime.ContentType? type = this.content_type; + if (type == null) { + Mime.DispositionType disposition = Mime.DispositionType.UNSPECIFIED; + if (this.content_disposition != null) { + disposition = this.content_disposition.disposition_type; + } + type = (disposition != Mime.DispositionType.ATTACHMENT) + ? Mime.ContentType.DISPLAY_DEFAULT + : Mime.ContentType.ATTACHMENT_DEFAULT; + } + return type; + } + + /** + * Returns the entity's filename, cleaned for use in the file system. + */ + public string? get_clean_filename() { + string? filename = (this.source_part != null) + ? this.source_part.get_filename() : null; + if (filename != null) { + try { + filename = invalid_filename_character_re.replace_literal( + filename, filename.length, 0, "_" + ); + } catch (RegexError e) { + debug("Error sanitizing attachment filename: %s", e.message); + } + } + return filename; + } + + public Memory.Buffer write_to_buffer(BodyFormatting format = BodyFormatting.NONE) + throws RFC822Error { + ByteArray byte_array = new ByteArray(); + GMime.StreamMem stream = new GMime.StreamMem.with_byte_array(byte_array); + stream.set_owner(false); + + write_to_stream(stream, format); + + return new Geary.Memory.ByteBuffer.from_byte_array(byte_array); + } + + internal void write_to_stream(GMime.Stream destination, + BodyFormatting format = BodyFormatting.NONE) + throws RFC822Error { + GMime.DataWrapper? wrapper = (this.source_part != null) + ? this.source_part.get_content_object() : null; + if (wrapper == null) { + throw new RFC822Error.INVALID( + "Could not get the content wrapper for content-type %s", + content_type.to_string() + ); + } + + Mime.ContentType content_type = this.get_effective_content_type(); + if (content_type.is_type("text", Mime.ContentType.WILDCARD)) { + // Assume encoded text, convert to unencoded UTF-8 + GMime.StreamFilter filter = new GMime.StreamFilter(destination); + string? charset = content_type.params.get_value("charset"); + filter.add( + Geary.RFC822.Utils.create_utf8_filter_charset(charset) + ); + + bool flowed = content_type.params.has_value_ci("format", "flowed"); + bool delsp = content_type.params.has_value_ci("DelSp", "yes"); + + // Remove the CR's in any CRLF sequence since they are + // effectively a wire encoding, unless the format requires + // them. + if (!(content_type.media_subtype in CR_PRESERVING_TEXT_TYPES)) { + filter.add(new GMime.FilterCRLF(false, false)); + } + + if (flowed) { + filter.add( + new Geary.RFC822.FilterFlowed( + format == BodyFormatting.HTML, delsp + ) + ); + } + + if (format == BodyFormatting.HTML) { + if (!flowed) { + filter.add(new Geary.RFC822.FilterPlain()); + } + filter.add( + new GMime.FilterHTML( + GMime.FILTER_HTML_CONVERT_URLS | + GMime.FILTER_HTML_CONVERT_ADDRESSES, + 0 + ) + ); + filter.add(new Geary.RFC822.FilterBlockquotes()); + } + + wrapper.write_to_stream(filter); + filter.flush(); + } else { + // Keep as binary + wrapper.write_to_stream(destination); + destination.flush(); + } + } + +} diff -Nru geary-0.12.4/src/engine/rfc822/rfc822-utils.vala geary-3.32.0/src/engine/rfc822/rfc822-utils.vala --- geary-0.12.4/src/engine/rfc822/rfc822-utils.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/rfc822/rfc822-utils.vala 2019-03-17 13:39:29.000000000 +0000 @@ -54,10 +54,10 @@ // the best of all possible worlds, assuming the Memory.Buffer is not destroyed first GMime.StreamMem stream = new GMime.StreamMem(); stream.set_byte_array(unowned_bytes_array_buffer.to_unowned_byte_array()); - + return stream; } - + Memory.UnownedBytesBuffer? unowned_bytes_buffer = buffer as Memory.UnownedBytesBuffer; if (unowned_bytes_buffer != null) { // StreamMem.with_buffer does do a buffer copy (there's not set_buffer() call like @@ -65,7 +65,7 @@ // Memory.Buffer return new GMime.StreamMem.with_buffer(unowned_bytes_buffer.to_unowned_uint8_array()); } - + // do plain-old buffer copy return new GMime.StreamMem.with_buffer(buffer.get_uint8_array()); } @@ -102,7 +102,7 @@ Gee.List< Geary.RFC822.MailboxAddress>? sender_addresses = null) { Gee.List new_to = new Gee.ArrayList(); - + // If we're replying to something we sent, send it to the same people we originally did. // Otherwise, we'll send to the reply-to address or the from address. if (email.to != null && email_is_from_sender(email, sender_addresses)) @@ -111,35 +111,35 @@ new_to.add_all(email.reply_to.get_all()); else if (email.from != null) new_to.add_all(email.from.get_all()); - + // Exclude the current sender. No need to receive the mail they're sending. if (sender_addresses != null) { foreach (RFC822.MailboxAddress address in sender_addresses) remove_address(new_to, address); } - + return new Geary.RFC822.MailboxAddresses(new_to); } public Geary.RFC822.MailboxAddresses create_cc_addresses_for_reply_all(Geary.Email email, Gee.List? sender_addresses = null) { Gee.List new_cc = new Gee.ArrayList(); - + // If we're replying to something we received, also add other recipients. Don't do this for // emails we sent, since everyone we sent it to is already covered in // create_to_addresses_for_reply(). if (email.to != null && !email_is_from_sender(email, sender_addresses)) new_cc.add_all(email.to.get_all()); - + if (email.cc != null) new_cc.add_all(email.cc.get_all()); - + // Again, exclude the current sender. if (sender_addresses != null) { foreach (RFC822.MailboxAddress address in sender_addresses) remove_address(new_cc, address, true); } - + return new Geary.RFC822.MailboxAddresses(new_cc); } @@ -156,7 +156,7 @@ } else if (second != null) { result.add_all(second.get_all()); } - + return new Geary.RFC822.MailboxAddresses(result); } @@ -175,11 +175,11 @@ public string reply_references(Geary.Email source) { // generate list for References Gee.ArrayList list = new Gee.ArrayList(); - + // 1. Start with the source's References list if (source.references != null && source.references.list.size > 0) list.add_all(source.references.list); - + // 2. If there are In-Reply-To Message-IDs and they're not in the References list, append them if (source.in_reply_to != null) { foreach (RFC822.MessageID reply_id in source.in_reply_to.list) { @@ -187,29 +187,29 @@ list.add(reply_id); } } - + // 3. Append the source's Message-ID, if available. if (source.message_id != null) list.add(source.message_id); - + string[] strings = new string[list.size]; for(int i = 0; i < list.size; ++i) strings[i] = list[i].value; - + return (list.size > 0) ? string.joinv(" ", strings) : ""; } public string email_addresses_for_reply(Geary.RFC822.MailboxAddresses? addresses, TextFormat format) { if (addresses == null) return ""; - + switch (format) { case TextFormat.HTML: return HTML.escape_markup(addresses.to_string()); - + case TextFormat.PLAIN: return addresses.to_string(); - + default: assert_not_reached(); } @@ -221,7 +221,7 @@ * * If there's no message body in the supplied email or quote text, this * function will return the empty string. - * + * * If html_format is true, the message will be quoted in HTML format. * Otherwise it will be in plain text. */ @@ -279,19 +279,29 @@ if (email.body == null && quote == null) return ""; + const string HEADER_FORMAT = "%s %s\n"; + string quoted = _("---------- Forwarded message ----------"); quoted += "\n"; string from_line = email_addresses_for_reply(email.from, format); - if (!String.is_empty_or_whitespace(from_line)) - quoted += _("From: %s\n").printf(from_line); - quoted += _("Subject: %s\n").printf(email.subject != null ? email.subject.to_string() : ""); - quoted += _("Date: %s\n").printf(email.date != null ? email.date.to_string() : ""); + if (!String.is_empty_or_whitespace(from_line)) { + // Translators: Human-readable version of the RFC 822 From header + quoted += HEADER_FORMAT.printf(_("From:"), from_line); + } + // Translators: Human-readable version of the RFC 822 Subject header + quoted += HEADER_FORMAT.printf(_("Subject:"), email.subject != null ? email.subject.to_string() : ""); + // Translators: Human-readable version of the RFC 822 Date header + quoted += HEADER_FORMAT.printf(_("Date:"), email.date != null ? email.date.to_string() : ""); string to_line = email_addresses_for_reply(email.to, format); - if (!String.is_empty_or_whitespace(to_line)) - quoted += _("To: %s\n").printf(to_line); + if (!String.is_empty_or_whitespace(to_line)) { + // Translators: Human-readable version of the RFC 822 To header + quoted += HEADER_FORMAT.printf(_("To:"), to_line); + } string cc_line = email_addresses_for_reply(email.cc, format); - if (!String.is_empty_or_whitespace(cc_line)) - quoted += _("Cc: %s\n").printf(cc_line); + if (!String.is_empty_or_whitespace(cc_line)) { + // Translators: Human-readable version of the RFC 822 CC header + quoted += HEADER_FORMAT.printf(_("Cc:"), cc_line); + } quoted += "\n"; // A blank line between headers and body quoted = quoted.replace("\n", "
"); try { @@ -302,12 +312,14 @@ return quoted; } -private string quote_body(Geary.Email email, string? quote, bool use_quotes, TextFormat format) +private string quote_body(Geary.Email email, + string? html_quote, + bool use_quotes, + TextFormat format) throws Error { Message? message = email.get_message(); - bool preserve_whitespace = !message.has_html_body(); string? body_text = null; - if (quote == null) { + if (String.is_empty(html_quote)) { switch (format) { case TextFormat.HTML: body_text = message.has_html_body() @@ -322,7 +334,7 @@ break; } } else { - body_text = Geary.HTML.smart_escape(quote, preserve_whitespace); + body_text = html_quote; } // Wrap the whole thing in a blockquote. @@ -337,7 +349,7 @@ if (array[start + i] != comp[i]) return false; } - + return true; } @@ -433,19 +445,5 @@ return filter.encoding(GMime.EncodingConstraint.7BIT); } -public string? get_clean_attachment_filename(GMime.Part part) { - string? filename = part.get_filename(); - if (filename != null) { - try { - filename = invalid_filename_character_re.replace_literal( - filename, filename.length, 0, "_" - ); - } catch (RegexError e) { - debug("Error sanitizing attachment filename: %s", e.message); - } - } - return filename; -} - } diff -Nru geary-0.12.4/src/engine/rfc822/rfc822.vala geary-3.32.0/src/engine/rfc822/rfc822.vala --- geary-0.12.4/src/engine/rfc822/rfc822.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/rfc822/rfc822.vala 2019-03-17 13:39:29.000000000 +0000 @@ -19,11 +19,6 @@ */ public const string UTF8_CHARSET = "UTF-8"; -// This has the effect of ensuring all non US-ASCII and non-ISO-8859-1 -// headers are always encoded as UTF-8. This should be fine because -// message bodies are also always sent as UTF-8. -private const string[] USER_CHARSETS = { UTF8_CHARSET }; - private int init_count = 0; internal Regex? invalid_filename_character_re = null; @@ -33,6 +28,16 @@ return; GMime.init(GMime.ENABLE_RFC2047_WORKAROUNDS); + + // This has the effect of ensuring all non US-ASCII and non-ISO-8859-1 + // headers are always encoded as UTF-8. This should be fine because + // message bodies are also always sent as UTF-8. + const string?[] USER_CHARSETS = { + UTF8_CHARSET, + // GMime.set_user_charsets calls g_strdupv under the hood, so + // the array needs to be null-terminated + null + }; GMime.set_user_charsets(USER_CHARSETS); try { diff -Nru geary-0.12.4/src/engine/smtp/smtp-authenticator.vala geary-3.32.0/src/engine/smtp/smtp-authenticator.vala --- geary-0.12.4/src/engine/smtp/smtp-authenticator.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/smtp/smtp-authenticator.vala 2019-03-17 13:39:29.000000000 +0000 @@ -15,22 +15,22 @@ * The user-visible name for this {@link Authenticator}. */ public string name { get; private set; } - + public Credentials credentials { get; private set; } - - public Authenticator(string name, Credentials credentials) { + + protected Authenticator(string name, Credentials credentials) { this.name = name; this.credentials = credentials; - + if (!credentials.is_complete()) message("Incomplete credentials supplied to SMTP authenticator %s", name); } - + /** * Returns a Request that is used to initiate the challenge-response. */ public abstract Request initiate(); - + /** * Returns a block of data that will be sent for that stage of the authentication challenge. * No line terminators should be present. Various authentication schemes may also have their @@ -38,14 +38,14 @@ * * step is the zero-based step number of the challenge-response. Response is the *last* * SMTP response from the server from the prior step (or from the initiate() request). - * + * * Returns null if the Authenticator chooses to end the process in an orderly fashion. - * + * * If an error is thrown, the entire process is aborted without any further I/O with the * server. Generally this leaves the connection in a bad state and should be closed. */ public abstract Memory.Buffer? challenge(int step, Response response) throws SmtpError; - + public virtual string to_string() { return name; } diff -Nru geary-0.12.4/src/engine/smtp/smtp-capabilities.vala geary-3.32.0/src/engine/smtp/smtp-capabilities.vala --- geary-0.12.4/src/engine/smtp/smtp-capabilities.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/smtp/smtp-capabilities.vala 2019-03-17 13:39:29.000000000 +0000 @@ -5,19 +5,21 @@ */ public class Geary.Smtp.Capabilities : Geary.GenericCapabilities { + public const string STARTTLS = "starttls"; public const string AUTH = "auth"; - + public const string AUTH_PLAIN = "plain"; public const string AUTH_LOGIN = "login"; - + public const string AUTH_OAUTH2 = "xoauth2"; + public const string NAME_SEPARATOR = " "; public const string VALUE_SEPARATOR = " "; - + public Capabilities() { base (NAME_SEPARATOR, VALUE_SEPARATOR); } - + /** * Returns number of response lines added. */ @@ -28,10 +30,10 @@ if (add_response_line(response.lines[ctr])) count++; } - + return count; } - + public bool add_response_line(ResponseLine line) { return !String.is_empty(line.explanation) ? parse_and_add_capability(line.explanation) : false; } diff -Nru geary-0.12.4/src/engine/smtp/smtp-client-connection.vala geary-3.32.0/src/engine/smtp/smtp-client-connection.vala --- geary-0.12.4/src/engine/smtp/smtp-client-connection.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/smtp/smtp-client-connection.vala 2019-03-17 13:39:29.000000000 +0000 @@ -5,61 +5,58 @@ */ public class Geary.Smtp.ClientConnection { - public const uint16 DEFAULT_PORT = 25; - public const uint16 DEFAULT_PORT_SSL = 465; - public const uint16 DEFAULT_PORT_STARTTLS = 587; - + public const uint DEFAULT_TIMEOUT_SEC = 20; - + public Geary.Smtp.Capabilities? capabilities { get; private set; default = null; } - + private Geary.Endpoint endpoint; private IOStream? cx = null; private SocketConnection? socket_cx = null; private DataInputStream? dins = null; private DataOutputStream douts = null; - + public ClientConnection(Geary.Endpoint endpoint) { this.endpoint = endpoint; } - + public bool is_connected() { return (cx != null); } - + public async Greeting? connect_async(Cancellable? cancellable = null) throws Error { if (cx != null) { debug("Already connected to %s", to_string()); - + return null; } - + cx = socket_cx = yield endpoint.connect_async(cancellable); set_data_streams(cx); - + // read and deserialize the greeting Greeting greeting = new Greeting(yield recv_response_lines_async(cancellable)); Logging.debug(Logging.Flag.NETWORK, "[%s] SMTP Greeting: %s", to_string(), greeting.to_string()); - + return greeting; } - + public async bool disconnect_async(Cancellable? cancellable = null) throws Error { if (cx == null) return false; - + Error? disconnect_error = null; try { yield cx.close_async(Priority.DEFAULT, cancellable); } catch (Error err) { disconnect_error = err; } - + cx = null; - + if (disconnect_error != null) throw disconnect_error; - + return true; } @@ -69,12 +66,12 @@ public async Response authenticate_async(Authenticator authenticator, Cancellable? cancellable = null) throws Error { check_connected(); - + Response response = yield transaction_async(authenticator.initiate(), cancellable); - + Logging.debug(Logging.Flag.NETWORK, "[%s] Initiated SMTP %s authentication", to_string(), authenticator.to_string()); - + // Possible for initiate() Request to: // (a) immediately generate success (due to valid authentication being passed in Request); // (b) immediately fails; @@ -87,16 +84,16 @@ Memory.Buffer? data = authenticator.challenge(step++, response); if (data == null || data.size == 0) data = new Memory.StringBuffer(DataFormat.CANCEL_AUTHENTICATION); - + Logging.debug(Logging.Flag.NETWORK, "[%s] SMTP AUTH Challenge recvd", to_string()); - + yield Stream.write_all_async(douts, data, cancellable); douts.put_string(DataFormat.LINE_TERMINATOR); yield douts.flush_async(Priority.DEFAULT, cancellable); - + response = yield recv_response_async(cancellable); } - + return response; } @@ -113,31 +110,31 @@ public async Response send_data_async(Memory.Buffer data, bool already_dotstuffed, Cancellable? cancellable = null) throws Error { check_connected(); - + // In the case of DATA, want to receive an intermediate response code, specifically 354 Response response = yield transaction_async(new Request(Command.DATA), cancellable); if (!response.code.is_start_data()) return response; - - Logging.debug(Logging.Flag.NETWORK, "[%s] SMTP Data: <%ldb>", to_string(), data.size); - + + Logging.debug(Logging.Flag.NETWORK, "[%s] SMTP Data: <%z>", to_string(), data.size); + if (!already_dotstuffed) { // By using DataStreamNewlineType.ANY, we're assured to get each line and convert to // a proper line terminator for SMTP DataInputStream dins = new DataInputStream(data.get_input_stream()); dins.set_newline_type(DataStreamNewlineType.ANY); - + // Read each line and dot-stuff if necessary for (;;) { size_t length; string? line = yield dins.read_line_async(Priority.DEFAULT, cancellable, out length); if (line == null) break; - + // stuffing if (line[0] == '.') yield Stream.write_string_async(douts, ".", cancellable); - + yield Stream.write_string_async(douts, line, cancellable); yield Stream.write_string_async(douts, DataFormat.LINE_TERMINATOR, cancellable); } @@ -145,50 +142,50 @@ // ready to go, send and commit yield Stream.write_all_async(douts, data, cancellable); } - + // terminate buffer and flush to server yield Stream.write_string_async(douts, DataFormat.DATA_TERMINATOR, cancellable); yield douts.flush_async(Priority.DEFAULT, cancellable); - + return yield recv_response_async(cancellable); } - + public async void send_request_async(Request request, Cancellable? cancellable = null) throws Error { check_connected(); - + Logging.debug(Logging.Flag.NETWORK, "[%s] SMTP Request: %s", to_string(), request.to_string()); - + douts.put_string(request.serialize()); douts.put_string(DataFormat.LINE_TERMINATOR); yield douts.flush_async(Priority.DEFAULT, cancellable); } - + private async Gee.List recv_response_lines_async(Cancellable? cancellable) throws Error { check_connected(); - + Gee.List lines = new Gee.ArrayList(); for (;;) { ResponseLine line = ResponseLine.deserialize(yield read_line_async(cancellable)); lines.add(line); - + if (!line.continued) break; } - + // lines should never be empty; if it is, then somebody didn't throw an exception assert(lines.size > 0); - + return lines; } - + public async Response recv_response_async(Cancellable? cancellable = null) throws Error { Response response = new Response(yield recv_response_lines_async(cancellable)); - + Logging.debug(Logging.Flag.NETWORK, "[%s] SMTP Response: %s", to_string(), response.to_string()); - + return response; } - + /** * Sends the appropriate HELO/EHLO command and returns the response of the one that worked. * Also saves the server's capabilities in the capabilities property (overwriting any that may @@ -198,7 +195,7 @@ // get local address as FQDN to greet server ... note that this merely returns the DHCP address // for machines behind a NAT InetAddress local_addr = ((InetSocketAddress) socket_cx.get_local_address()).get_address(); - + // only attempt to produce a FQDN if not a local address and use the local address if // unavailable string? fqdn = null; @@ -210,7 +207,7 @@ local_addr.to_string(), err.message); } } - + // try EHLO first, then fall back on HELO EhloRequest ehlo = !String.is_empty(fqdn) ? new EhloRequest(fqdn) : new EhloRequest.for_local_address(local_addr); Response response = yield transaction_async(ehlo, cancellable); @@ -227,10 +224,10 @@ response.to_string().strip()); } } - + return response; } - + /** * Sends the appropriate hello command to the server (EHLO / HELO) and establishes whatever * additional connection features are available (STARTTLS, compression). For general-purpose @@ -244,38 +241,34 @@ */ public async Response establish_connection_async(Cancellable? cancellable = null) throws Error { check_connected(); - + // issue first HELO/EHLO, which will generate a set of capabiltiies Smtp.Response response = yield say_hello_async(cancellable); - + // STARTTLS, if required - switch (endpoint.attempt_starttls(capabilities.has_capability(Capabilities.STARTTLS))) { - case Endpoint.AttemptStarttls.YES: - Response starttls_response = yield transaction_async(new Request(Command.STARTTLS)); - if (!starttls_response.code.is_starttls_ready()) - throw new SmtpError.STARTTLS_FAILED("STARTTLS failed: %s", response.to_string()); - - TlsClientConnection tls_cx = yield endpoint.starttls_handshake_async(cx, cancellable); - cx = tls_cx; - set_data_streams(tls_cx); - - // Now that we are on an encrypted line we need to say hello again in order to get the - // updated capabilities. - response = yield say_hello_async(cancellable); - break; - - case Endpoint.AttemptStarttls.NO: - // do nothing - break; - - case Endpoint.AttemptStarttls.HALT: - default: - throw new SmtpError.NOT_SUPPORTED("STARTTLS not available for %s", endpoint.to_string()); + if (endpoint.tls_method == TlsNegotiationMethod.START_TLS) { + if (!capabilities.has_capability(Capabilities.STARTTLS)) { + throw new SmtpError.NOT_SUPPORTED( + "STARTTLS not available for %s", endpoint.to_string() + ); + } + + Response starttls_response = yield transaction_async(new Request(Command.STARTTLS)); + if (!starttls_response.code.is_starttls_ready()) + throw new SmtpError.STARTTLS_FAILED("STARTTLS failed: %s", response.to_string()); + + TlsClientConnection tls_cx = yield endpoint.starttls_handshake_async(cx, cancellable); + cx = tls_cx; + set_data_streams(tls_cx); + + // Now that we are on an encrypted line we need to say hello again in order to get the + // updated capabilities. + response = yield say_hello_async(cancellable); } - + return response; } - + public async Response quit_async(Cancellable? cancellable = null) throws Error { capabilities = null; return yield transaction_async(new Request(Command.QUIT), cancellable); @@ -284,25 +277,25 @@ public async Response transaction_async(Request request, Cancellable? cancellable = null) throws Error { yield send_request_async(request, cancellable); - + return yield recv_response_async(cancellable); } - + private async string read_line_async(Cancellable? cancellable) throws Error { size_t length; string? line = yield dins.read_line_async(Priority.DEFAULT, cancellable, out length); - + if (String.is_empty(line)) throw new IOError.CLOSED("End of stream detected on %s", to_string()); - + return line; } - + private void check_connected() throws Error { if (cx == null) throw new SmtpError.NOT_CONNECTED("Not connected to %s", to_string()); } - + public string to_string() { return endpoint.to_string(); } diff -Nru geary-0.12.4/src/engine/smtp/smtp-client-service.vala geary-3.32.0/src/engine/smtp/smtp-client-service.vala --- geary-0.12.4/src/engine/smtp/smtp-client-service.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/src/engine/smtp/smtp-client-service.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,342 @@ +/* + * Copyright 2016 Software Freedom Conservancy Inc. + * Copyright 2018 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +/** + * Manages connecting to an SMTP network service. + * + * This class maintains a queue of email messages to be delivered, and + * opens SMTP connections to deliver queued messages as needed. + */ +internal class Geary.Smtp.ClientService : Geary.ClientService { + + + // Used solely for debugging, hence "(no subject)" not marked for translation + private static string message_subject(RFC822.Message message) { + return (message.subject != null && !String.is_empty(message.subject.to_string())) + ? message.subject.to_string() : "(no subject)"; + } + + + /** Folder used for storing and retrieving queued mail. */ + public Outbox.Folder? outbox { get; internal set; default = null; } + + /** Progress monitor indicating when email is being sent. */ + public ProgressMonitor sending_monitor { + get; + private set; + default = new SimpleProgressMonitor(ProgressType.ACTIVITY); + } + + private Account owner { get { return this.outbox.account; } } + + private Nonblocking.Queue outbox_queue = + new Nonblocking.Queue.fifo(); + private Cancellable? queue_cancellable = null; + + /** Emitted when the manager has sent an email. */ + public signal void email_sent(Geary.RFC822.Message rfc822); + + /** Emitted when an error occurred sending an email. */ + public signal void report_problem(Geary.ProblemReport problem); + + + public ClientService(AccountInformation account, + ServiceInformation service, + Endpoint remote) { + base(account, service, remote); + } + + /** + * Starts the manager opening IMAP client sessions. + */ + public override async void start(GLib.Cancellable? cancellable = null) + throws GLib.Error { + yield this.outbox.open_async(Folder.OpenFlags.NONE, cancellable); + yield this.fill_outbox_queue(cancellable); + notify_started(); + } + + /** + * Stops the manager running, closing any existing sessions. + */ + public override async void stop(GLib.Cancellable? cancellable = null) + throws GLib.Error { + notify_stopped(); + this.stop_postie(); + // Wait for the postie to actually stop before closing the + // folder so w don't interrupt e.g. sending/saving/deleting + // mail + while (this.queue_cancellable != null) { + GLib.Idle.add(this.stop.callback); + yield; + } + yield this.outbox.close_async(cancellable); + } + + /** + * Saves and queues an email in the outbox for delivery. + */ + public async void queue_email(RFC822.Message rfc822, + GLib.Cancellable? cancellable) + throws GLib.Error { + debug("Queuing message for sending: %s", message_subject(rfc822)); + + EmailIdentifier id = yield this.outbox.create_email_async( + rfc822, null, null, null, cancellable + ); + this.outbox_queue.send(id); + } + + /** Starts the postie delivering messages. */ + protected override void became_reachable() { + this.start_postie.begin(); + } + + /** Stops the postie delivering. */ + protected override void became_unreachable() { + this.stop_postie(); + } + + /** + * Starts delivery of messages in the queue. + */ + private async void start_postie() { + debug("Starting outbox postie with %u messages queued", this.outbox_queue.size); + if (this.queue_cancellable != null) { + return; + } + + Cancellable cancellable = this.queue_cancellable = + new GLib.Cancellable(); + + // Start the send queue. + while (!cancellable.is_cancelled()) { + // yield until a message is ready + EmailIdentifier id = null; + bool email_handled = false; + try { + id = yield this.outbox_queue.receive(cancellable); + yield process_email(id, cancellable); + email_handled = true; + } catch (SmtpError err) { + if (err is SmtpError.AUTHENTICATION_FAILED) { + notify_authentication_failed(); + } else if (err is SmtpError.STARTTLS_FAILED || + err is SmtpError.NOT_CONNECTED) { + notify_connection_failed(new ErrorContext(err)); + } else if (err is SmtpError.PARSE_ERROR || + err is SmtpError.SERVER_ERROR || + err is SmtpError.NOT_SUPPORTED) { + notify_unrecoverable_error(new ErrorContext(err)); + } + cancellable.cancel(); + } catch (GLib.IOError.CANCELLED err) { + // Nothing to do here — we're already cancelled. + } catch (GLib.Error err) { + notify_connection_failed(new ErrorContext(err)); + cancellable.cancel(); + } + + if (!email_handled && id != null) { + // Send was bad, try sending again later + this.outbox_queue.send(id); + } + } + + this.queue_cancellable = null; + debug("Outbox postie exited"); + } + + /** + * Stops delivery of messages in the queue. + */ + private void stop_postie() { + debug("Stopping outbox postie"); + if (this.queue_cancellable != null) { + this.queue_cancellable.cancel(); + } + } + + /** + * Loads any email in the outbox and adds them to the queue. + */ + private async void fill_outbox_queue(GLib.Cancellable cancellable) { + debug("Filling queue"); + try { + Gee.List? queued = yield this.outbox.list_email_by_id_async( + null, + int.MAX, // fetch all + Email.Field.NONE, // ids only + Folder.ListFlags.OLDEST_TO_NEWEST, + cancellable + ); + if (queued != null) { + foreach (Email email in queued) { + this.outbox_queue.send(email.id); + } + } + } catch (Error err) { + warning("Error filling queue: %s", err.message); + } + } + + // Returns true if email was successfully processed, else false + private async void process_email(EmailIdentifier id, Cancellable cancellable) + throws GLib.Error { + // To prevent spurious connection failures, ensure tokens are + // up-to-date before attempting to send the email + if (!yield this.account.load_outgoing_credentials(cancellable)) { + throw new SmtpError.AUTHENTICATION_FAILED("Credentials not loaded"); + } + + Email? email = null; + try { + email = yield this.outbox.fetch_email_async( + id, Email.Field.ALL, Folder.ListFlags.NONE, cancellable + ); + } catch (EngineError.NOT_FOUND err) { + debug("Queued email %s not found in outbox, ignoring: %s", + id.to_string(), err.message); + } + + if (!email.email_flags.contains(EmailFlags.OUTBOX_SENT)) { + RFC822.Message message = email.get_message(); + debug("Outbox postie: Sending \"%s\" (ID:%s)...", + message_subject(message), email.id.to_string()); + yield send_email(message, cancellable); + + // Mark as sent, so if there's a problem pushing up to + // Sent, we don't retry sending. Don't pass the + // cancellable here - if it's been sent we want to try to + // update the sent flag anyway + debug("Outbox postie: Marking %s as sent", email.id.to_string()); + Geary.EmailFlags flags = new Geary.EmailFlags(); + flags.add(Geary.EmailFlags.OUTBOX_SENT); + yield this.outbox.mark_email_async( + Collection.single(email.id), flags, null, null + ); + + if (cancellable.is_cancelled()) { + throw new GLib.IOError.CANCELLED("Send has been cancelled"); + } + } + + // If we get to this point, the message has either been just + // sent, or previously sent but not saved. So now try flagging + // as such and saving it. + if (this.account.save_sent) { + debug("Outbox postie: Saving %s to sent mail", email.id.to_string()); + yield save_sent_mail_async(email, cancellable); + } + + // Again, don't observe the cancellable here - if it's been + // send and saved we want to try to remove it anyway. + debug("Outbox postie: Deleting row %s", email.id.to_string()); + yield this.outbox.remove_email_async(Collection.single(email.id), null); + } + + private async void send_email(Geary.RFC822.Message rfc822, Cancellable? cancellable) + throws Error { + Credentials? login = this.account.get_outgoing_credentials(); + if (login != null && !login.is_complete()) { + throw new SmtpError.AUTHENTICATION_FAILED("Token not loaded"); + } + + Smtp.ClientSession smtp = new Geary.Smtp.ClientSession(this.remote); + sending_monitor.notify_start(); + + Error? smtp_err = null; + try { + yield smtp.login_async(login, cancellable); + } catch (Error login_err) { + debug("SMTP login error: %s", login_err.message); + smtp_err = login_err; + } + + if (smtp_err == null) { + // Determine the SMTP reverse path, this gets used for + // bounce notifications, etc. Use the sender by default, + // since if specified the message is explicitly being sent + // on behalf of someone else. + RFC822.MailboxAddress? reverse_path = rfc822.sender; + if (reverse_path == null) { + // If no sender specified, use the first from address + // that is accountured for this account. + if (rfc822.from != null) { + foreach (RFC822.MailboxAddress from in rfc822.from) { + if (this.account.has_sender_mailbox(from)) { + reverse_path = from; + break; + } + } + } + + if (reverse_path == null) { + // Fall back to using the account's primary + // mailbox if nether a sender nor a from address + // from this account is found. + reverse_path = this.account.primary_mailbox; + } + } + + // Now send it + try { + yield smtp.send_email_async(reverse_path, rfc822, cancellable); + } catch (Error send_err) { + debug("SMTP send mail error: %s", send_err.message); + smtp_err = send_err; + } + } + + try { + // always logout + yield smtp.logout_async(false, null); + } catch (Error err) { + debug("Unable to disconnect from SMTP server %s: %s", smtp.to_string(), err.message); + } + + sending_monitor.notify_finish(); + + if (smtp_err != null) + throw smtp_err; + + email_sent(rfc822); + } + + private async void save_sent_mail_async(Geary.Email email, + GLib.Cancellable? cancellable) + throws GLib.Error { + Geary.FolderSupport.Create? create = ( + yield this.owner.get_required_special_folder_async( + Geary.SpecialFolderType.SENT, cancellable + ) + ) as Geary.FolderSupport.Create; + if (create == null) { + throw new EngineError.UNSUPPORTED( + "Save sent mail enabled, but no writable sent mail folder" + ); + } + + RFC822.Message message = email.get_message(); + bool open = false; + try { + yield create.open_async(Geary.Folder.OpenFlags.NO_DELAY, cancellable); + open = true; + yield create.create_email_async(message, null, null, null, cancellable); + } finally { + if (open) { + try { + yield create.close_async(null); + } catch (Error e) { + debug("Error closing folder %s: %s", create.to_string(), e.message); + } + } + } + } + +} diff -Nru geary-0.12.4/src/engine/smtp/smtp-client-session.vala geary-3.32.0/src/engine/smtp/smtp-client-session.vala --- geary-0.12.4/src/engine/smtp/smtp-client-session.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/smtp/smtp-client-session.vala 2019-03-17 13:39:29.000000000 +0000 @@ -7,82 +7,112 @@ public class Geary.Smtp.ClientSession { private ClientConnection cx; private bool rset_required = false; - + public virtual signal void connected(Greeting greeting) { } - + public virtual signal void authenticated(Authenticator authenticator) { } - + public virtual signal void disconnected() { } - + public ClientSession(Geary.Endpoint endpoint) { cx = new ClientConnection(endpoint); } - + protected virtual void notify_connected(Greeting greeting) { connected(greeting); } - + protected virtual void notify_authenticated(Authenticator authenticator) { authenticated(authenticator); } - + protected virtual void notify_disconnected() { disconnected(); } - + public async Greeting? login_async(Credentials? creds, Cancellable? cancellable = null) throws Error { if (cx.is_connected()) throw new SmtpError.ALREADY_CONNECTED("Connection to %s already exists", to_string()); - + // Greet the SMTP server. Greeting? greeting = yield cx.connect_async(cancellable); if (greeting == null) throw new SmtpError.ALREADY_CONNECTED("Connection to %s already exists", to_string()); yield cx.establish_connection_async(cancellable); - + notify_connected(greeting); - + // authenticate if credentials supplied (they should be if ESMTP is supported) if (creds != null) notify_authenticated(yield attempt_authentication_async(creds, cancellable)); - + return greeting; } - + // Returns authenticator used for successful authentication, otherwise throws exception private async Authenticator attempt_authentication_async(Credentials creds, Cancellable? cancellable) throws Error { - // build an authentication style ordering to attempt, going from reported capabilities to standard - // fallbacks, while avoiding repetition ... this is necessary due to server bugs that report - // an authentication type is available but actually isn't, see + // build an authentication style ordering to attempt, going + // from reported capabilities to standard fallbacks, while + // avoiding repetition ... this is necessary due to server + // bugs that report an authentication type is available but + // actually isn't, see + // // http://redmine.yorba.org/issues/6091 + // // and + // // http://comments.gmane.org/gmane.mail.pine.general/4004 Gee.ArrayList auth_order = new Gee.ArrayList(String.stri_equal); - - // start with advertised authentication styles, in order of our preference (PLAIN - // only requires one round-trip) - if (cx.capabilities != null) { - if (cx.capabilities.has_setting(Capabilities.AUTH, Capabilities.AUTH_PLAIN)) + + switch (creds.supported_method) { + case Credentials.Method.PASSWORD: + // start with advertised authentication styles, in order of our preference (PLAIN + // only requires one round-trip) + if (cx.capabilities != null) { + if (cx.capabilities.has_setting(Capabilities.AUTH, Capabilities.AUTH_PLAIN)) + auth_order.add(Capabilities.AUTH_PLAIN); + + if (cx.capabilities.has_setting(Capabilities.AUTH, Capabilities.AUTH_LOGIN)) + auth_order.add(Capabilities.AUTH_LOGIN); + } + + // fallback on commonly-implemented styles, again in our order of preference + if (!auth_order.contains(Capabilities.AUTH_PLAIN)) auth_order.add(Capabilities.AUTH_PLAIN); - - if (cx.capabilities.has_setting(Capabilities.AUTH, Capabilities.AUTH_LOGIN)) + + if (!auth_order.contains(Capabilities.AUTH_LOGIN)) auth_order.add(Capabilities.AUTH_LOGIN); + + if (auth_order.is_empty) { + throw new SmtpError.AUTHENTICATION_FAILED( + "Unable to authenticate using PASSWORD credentials against %s", + to_string() + ); + } + break; + + case Credentials.Method.OAUTH2: + if (cx.capabilities != null && + !cx.capabilities.has_setting(Capabilities.AUTH, + Capabilities.AUTH_OAUTH2)) { + throw new SmtpError.AUTHENTICATION_FAILED( + "Unable to authenticate using OAUTH2 credentials against %s", + to_string() + ); + } + auth_order.add(Capabilities.AUTH_OAUTH2); + break; + + default: + throw new SmtpError.AUTHENTICATION_FAILED( + "Unsupported auth method: %s", creds.supported_method.to_string() + ); } - - // fallback on commonly-implemented styles, again in our order of preference - if (!auth_order.contains(Capabilities.AUTH_PLAIN)) - auth_order.add(Capabilities.AUTH_PLAIN); - - if (!auth_order.contains(Capabilities.AUTH_LOGIN)) - auth_order.add(Capabilities.AUTH_LOGIN); - - // in current situation, should always have one authentication type to attempt - assert(auth_order.size > 0); - + // go through the list, in order, until one style is accepted do { Authenticator? authenticator; @@ -90,25 +120,29 @@ case Capabilities.AUTH_PLAIN: authenticator = new PlainAuthenticator(creds); break; - + case Capabilities.AUTH_LOGIN: authenticator = new LoginAuthenticator(creds); break; - + + case Capabilities.AUTH_OAUTH2: + authenticator = new OAuth2Authenticator(creds); + break; + default: assert_not_reached(); } - + debug("[%s] Attempting %s authenticator", to_string(), authenticator.to_string()); - + Response response = yield cx.authenticate_async(authenticator, cancellable); if (response.code.is_success_completed()) return authenticator; } while (auth_order.size > 0); - + throw new SmtpError.AUTHENTICATION_FAILED("Unable to authenticate with %s", to_string()); } - + public async Response? logout_async(bool force, Cancellable? cancellable = null) throws Error { Response? response = null; try { @@ -118,7 +152,7 @@ // catch because although error occurred, still attempt to close the connection message("Unable to QUIT: %s", err.message); } - + try { if (yield cx.disconnect_async(cancellable)) disconnected(); @@ -126,44 +160,45 @@ // again, catch error but still shut down message("Unable to disconnect: %s", err2.message); } - + rset_required = false; return response; } - - public async void send_email_async(Geary.RFC822.MailboxAddress from, - Geary.RFC822.Message email, Cancellable? cancellable = null) - throws Error { + + public async void send_email_async(Geary.RFC822.MailboxAddress reverse_path, + Geary.RFC822.Message email, + Cancellable? cancellable = null) + throws GLib.Error { if (!cx.is_connected()) throw new SmtpError.NOT_CONNECTED("Not connected to %s", to_string()); - + // RSET if required if (rset_required) { Response rset_response = yield cx.transaction_async(new Request(Command.RSET), cancellable); if (!rset_response.code.is_success_completed()) rset_response.throw_error("Unable to RSET"); - + rset_required = false; } // MAIL - MailRequest mail_request = new MailRequest(from); + MailRequest mail_request = new MailRequest(reverse_path); Response response = yield cx.transaction_async(mail_request, cancellable); if (!response.code.is_success_completed()) response.throw_error("\"%s\" failed".printf(mail_request.to_string())); - + // at this point in the session state machine, a RSET is required to start a new // transmission if this fails at any point rset_required = true; - + // RCPTs Gee.List? addrlist = email.get_recipients(); if (addrlist == null || addrlist.size == 0) throw new SmtpError.REQUIRED_FIELD("No recipients in message"); - + yield send_rcpts_async(addrlist, cancellable); - + // DATA Geary.RFC822.Message email_copy = new Geary.RFC822.Message.without_bcc(email); response = yield cx.send_data_async(email_copy.get_network_buffer(true), true, @@ -174,15 +209,15 @@ // if message was transmitted successfully, the state machine resets automatically rset_required = false; } - + private async void send_rcpts_async(Gee.List? addrlist, Cancellable? cancellable) throws Error { if (addrlist == null) return; - + // TODO: Support mailbox groups foreach (RFC822.MailboxAddress mailbox in addrlist) { - RcptRequest rcpt_request = new RcptRequest.plain(mailbox.address); + RcptRequest rcpt_request = new RcptRequest.plain(mailbox.to_rfc822_address()); Response response = yield cx.transaction_async(rcpt_request, cancellable); if (!response.code.is_success_completed()) { @@ -194,7 +229,7 @@ } } } - + public string to_string() { return cx.to_string(); } diff -Nru geary-0.12.4/src/engine/smtp/smtp-command.vala geary-3.32.0/src/engine/smtp/smtp-command.vala --- geary-0.12.4/src/engine/smtp/smtp-command.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/smtp/smtp-command.vala 2019-03-17 13:39:29.000000000 +0000 @@ -16,76 +16,76 @@ RCPT, DATA, STARTTLS; - + public string serialize() { switch (this) { case HELO: return "helo"; - + case EHLO: return "ehlo"; - + case QUIT: return "quit"; - + case HELP: return "help"; - + case NOOP: return "noop"; - + case RSET: return "rset"; - + case AUTH: - return "auth"; - + return "AUTH"; + case MAIL: return "mail"; - + case RCPT: return "rcpt"; - + case DATA: return "data"; case STARTTLS: - return "starttls"; + return "STARTTLS"; default: assert_not_reached(); } } - + public static Command deserialize(string str) throws SmtpError { switch (Ascii.strdown(str)) { case "helo": return HELO; - + case "ehlo": return EHLO; - + case "quit": return QUIT; - + case "help": return HELP; - + case "noop": return NOOP; - + case "rset": return RSET; - + case "auth": return AUTH; - + case "mail": return MAIL; - + case "rcpt": return RCPT; - + case "data": return DATA; diff -Nru geary-0.12.4/src/engine/smtp/smtp-error.vala geary-3.32.0/src/engine/smtp/smtp-error.vala --- geary-0.12.4/src/engine/smtp/smtp-error.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/smtp/smtp-error.vala 2019-03-17 13:39:29.000000000 +0000 @@ -4,14 +4,33 @@ * (version 2.1 or later). See the COPYING file in this distribution. */ + +/** + * Thrown when an error occurs communicating with a SMTP server. + */ public errordomain Geary.SmtpError { - PARSE_ERROR, - STARTTLS_FAILED, - AUTHENTICATION_FAILED, - SERVER_ERROR, + + /** The client already has a connection to the server. */ ALREADY_CONNECTED, + + /** The credentials presented for authentication were rejected. */ + AUTHENTICATION_FAILED, + + /** The client does not have a connection to the server. */ NOT_CONNECTED, + + /** The server does not support an SMTP feature required by the engine. */ + NOT_SUPPORTED, + + /** A response from the server could not be parsed. */ + PARSE_ERROR, + + /** A message could not be sent because a field required by SMTP was missing. */ REQUIRED_FIELD, - NOT_SUPPORTED -} + /** The server reported an error. */ + SERVER_ERROR, + + /** Establishing STARTTLS was attempted, but failed. */ + STARTTLS_FAILED +} diff -Nru geary-0.12.4/src/engine/smtp/smtp-greeting.vala geary-3.32.0/src/engine/smtp/smtp-greeting.vala --- geary-0.12.4/src/engine/smtp/smtp-greeting.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/smtp/smtp-greeting.vala 2019-03-17 13:39:29.000000000 +0000 @@ -9,7 +9,7 @@ SMTP, ESMTP, UNSPECIFIED; - + /** * Returns an empty string if UNSPECIFIED. */ @@ -17,45 +17,45 @@ switch (this) { case SMTP: return "SMTP"; - + case ESMTP: return "ESMTP"; - + default: return ""; } } - + public static ServerFlavor deserialize(string str) { switch (Ascii.strup(str)) { case "SMTP": return SMTP; - + case "ESMTP": return ESMTP; - + default: return UNSPECIFIED; } } } - + public string? domain { get; private set; default = null; } public ServerFlavor flavor { get; private set; default = ServerFlavor.UNSPECIFIED; } public string? message { get; private set; default = null; } - + public Greeting(Gee.List lines) { base (lines); - + // tokenize first line explanation for domain, server flavor, and greeting message if (!String.is_empty(first_line.explanation)) { string[] tokens = first_line.explanation.substring(ResponseCode.STRLEN + 1, -1).split(" "); int length = tokens.length; int index = 0; - + if (index < length) domain = tokens[index++]; - + if (index < length) { string f = tokens[index++]; flavor = ServerFlavor.deserialize(f); @@ -64,7 +64,7 @@ message = f; } } - + while (index < length) { if (String.is_empty(message)) message = tokens[index++]; diff -Nru geary-0.12.4/src/engine/smtp/smtp-login-authenticator.vala geary-3.32.0/src/engine/smtp/smtp-login-authenticator.vala --- geary-0.12.4/src/engine/smtp/smtp-login-authenticator.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/smtp/smtp-login-authenticator.vala 2019-03-17 13:39:29.000000000 +0000 @@ -16,19 +16,19 @@ public LoginAuthenticator(Credentials credentials) { base ("LOGIN", credentials); } - + public override Request initiate() { return new Request(Command.AUTH, { "login" }); } - + public override Memory.Buffer? challenge(int step, Response response) throws SmtpError { switch (step) { case 0: return new Memory.StringBuffer(Base64.encode(credentials.user.data)); - + case 1: - return new Memory.StringBuffer(Base64.encode((credentials.pass ?? "").data)); - + return new Memory.StringBuffer(Base64.encode((credentials.token ?? "").data)); + default: return null; } diff -Nru geary-0.12.4/src/engine/smtp/smtp-oauth2-authenticator.vala geary-3.32.0/src/engine/smtp/smtp-oauth2-authenticator.vala --- geary-0.12.4/src/engine/smtp/smtp-oauth2-authenticator.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/src/engine/smtp/smtp-oauth2-authenticator.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,55 @@ +/* + * Copyright 2018 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +/** + * Google's proprietary OAuth 2 authentication. + * + * See [[http://tools.ietf.org/html/rfc4616]] + */ + +public class Geary.Smtp.OAuth2Authenticator : Geary.Smtp.Authenticator { + + + private const string OAUTH2_RESP = "user=%s\001auth=Bearer %s\001\001"; + + + public OAuth2Authenticator(Credentials credentials) { + base ("XOAUTH2", credentials); + } + + public override Request initiate() { + return new Request(Command.AUTH, { "xoauth2" }); + } + + public override Memory.Buffer? challenge(int step, Response response) + throws SmtpError { + Memory.Buffer? buf = null; + switch (step) { + case 0: + // The initial AUTH command + buf = new Memory.StringBuffer( + Base64.encode( + OAUTH2_RESP.printf( + credentials.user ?? "", + credentials.token ?? "" + ).data + ) + ); + break; + + case 1: + // Server sent a challenge, which will be a Base64 encoded + // JSON blob and which indicates a login failure. We don't + // really care about that (do we?) though since once + // we acknowledge it with a zero-length string the server + // will respond with a SMTP error. + buf = new Memory.StringBuffer(""); + break; + } + return buf; + } +} diff -Nru geary-0.12.4/src/engine/smtp/smtp-plain-authenticator.vala geary-3.32.0/src/engine/smtp/smtp-plain-authenticator.vala --- geary-0.12.4/src/engine/smtp/smtp-plain-authenticator.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/smtp/smtp-plain-authenticator.vala 2019-03-17 13:39:29.000000000 +0000 @@ -12,27 +12,27 @@ public class Geary.Smtp.PlainAuthenticator : Geary.Smtp.Authenticator { private static uint8[] nul = { '\0' }; - + public PlainAuthenticator(Credentials credentials) { base ("PLAIN", credentials); } - + public override Request initiate() { - return new Request(Command.AUTH, { "plain" }); + return new Request(Command.AUTH, { "PLAIN" }); } - + public override Memory.Buffer? challenge(int step, Response response) throws SmtpError { // only a single challenge is issued in PLAIN if (step > 0) return null; - + Memory.GrowableBuffer growable = new Memory.GrowableBuffer(); // skip the "authorize" field, which we don't support growable.append(nul); growable.append(credentials.user.data); growable.append(nul); - growable.append((credentials.pass ?? "").data); - + growable.append((credentials.token ?? "").data); + // convert to Base64 return new Memory.StringBuffer(Base64.encode(growable.get_bytes().get_data())); } diff -Nru geary-0.12.4/src/engine/smtp/smtp-request.vala geary-3.32.0/src/engine/smtp/smtp-request.vala --- geary-0.12.4/src/engine/smtp/smtp-request.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/smtp/smtp-request.vala 2019-03-17 13:39:29.000000000 +0000 @@ -7,29 +7,29 @@ public class Geary.Smtp.Request { public Command cmd { get; private set; } public string[]? args { get; private set; } - + public Request(Command cmd, string[]? args = null) { this.cmd = cmd; this.args = args; } - + public string serialize() { // fast-path if (args == null || args.length == 0) return cmd.serialize(); - + StringBuilder builder = new StringBuilder(); - + builder.append(cmd.serialize()); - + foreach (string arg in args) { builder.append_c(' '); builder.append(arg); } - + return builder.str; } - + public string to_string() { return serialize(); } @@ -39,7 +39,7 @@ public HeloRequest(string domain) { base (Command.HELO, { domain }); } - + public HeloRequest.for_local_address(InetAddress local_addr) { this ("[%s]".printf(local_addr.to_string())); } @@ -49,7 +49,7 @@ public EhloRequest(string domain) { base (Command.EHLO, { domain }); } - + public EhloRequest.for_local_address(InetAddress local_addr) { string prefix = (local_addr.family == SocketFamily.IPV6) ? "IPv6:" : ""; this ("[%s%s]".printf(prefix, local_addr.to_string())); @@ -57,10 +57,10 @@ } public class Geary.Smtp.MailRequest : Geary.Smtp.Request { - public MailRequest(Geary.RFC822.MailboxAddress from) { - base (Command.MAIL, { "from:%s".printf(from.get_simple_address()) }); + public MailRequest(Geary.RFC822.MailboxAddress reverse_path) { + base (Command.MAIL, { "from:<%s>".printf(reverse_path.to_rfc822_address()) }); } - + public MailRequest.plain(string addr) { base (Command.MAIL, { "from:<%s>".printf(addr) }); } @@ -68,9 +68,9 @@ public class Geary.Smtp.RcptRequest : Geary.Smtp.Request { public RcptRequest(Geary.RFC822.MailboxAddress to) { - base (Command.RCPT, { "to:%s".printf(to.get_simple_address()) }); + base (Command.RCPT, { "to:%s".printf(to.to_address_display("<", ">")) }); } - + public RcptRequest.plain(string addr) { base (Command.RCPT, { "to:<%s>".printf(addr) }); } diff -Nru geary-0.12.4/src/engine/smtp/smtp-response-code.vala geary-3.32.0/src/engine/smtp/smtp-response-code.vala --- geary-0.12.4/src/engine/smtp/smtp-response-code.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/smtp/smtp-response-code.vala 2019-03-17 13:39:29.000000000 +0000 @@ -6,14 +6,14 @@ public class Geary.Smtp.ResponseCode { public const int STRLEN = 3; - + public const int MIN = 100; public const int MAX = 599; - + public const string START_DATA_CODE = "354"; public const string STARTTLS_READY_CODE = "220"; public const string DENIED_CODE = "550"; - + public enum Status { POSITIVE_PRELIMINARY = 1, POSITIVE_COMPLETION = 2, @@ -22,7 +22,7 @@ PERMANENT_FAILURE = 5, UNKNOWN = -1; } - + public enum Condition { SYNTAX = 0, ADDITIONAL_INFO = 1, @@ -30,69 +30,69 @@ MAIL_SYSTEM = 5, UNKNOWN = -1 } - + private string str; - + public ResponseCode(string str) throws SmtpError { // these two checks are sufficient to make sure the Status is valid, but not the Condition if (str.length != STRLEN) throw new SmtpError.PARSE_ERROR("Reply code wrong length: %s (%d)", str, str.length); - + int as_int = int.parse(str); if (as_int < MIN || as_int > MAX) throw new SmtpError.PARSE_ERROR("Reply code out of range: %s", str); - + this.str = str; } - + public Status get_status() { int i = Ascii.digit_to_int(str[0]); - + // This works because of the checks in the constructor; Condition can't be checked so // easily return (i != -1) ? (Status) i : Status.UNKNOWN; } - + public Condition get_condition() { switch (Ascii.digit_to_int(str[1])) { case Condition.SYNTAX: return Condition.SYNTAX; - + case Condition.ADDITIONAL_INFO: return Condition.ADDITIONAL_INFO; - + case Condition.COMM_CHANNEL: return Condition.COMM_CHANNEL; - + case Condition.MAIL_SYSTEM: return Condition.MAIL_SYSTEM; - + default: return Condition.UNKNOWN; } } - + public bool is_success_completed() { return get_status() == Status.POSITIVE_COMPLETION; } - + public bool is_success_intermediate() { switch (get_status()) { case Status.POSITIVE_PRELIMINARY: case Status.POSITIVE_INTERMEDIATE: return true; - + default: return false; } } - + public bool is_failure() { switch (get_status()) { case Status.PERMANENT_FAILURE: case Status.TRANSIENT_NEGATIVE: return true; - + default: return false; } @@ -105,11 +105,11 @@ public bool is_starttls_ready() { return str == STARTTLS_READY_CODE; } - + public bool is_denied() { return str == DENIED_CODE; } - + /** * Returns true for [@link Status.PERMANENT_FAILURE} {@link Condition.SYNTAX} errors. * @@ -122,11 +122,11 @@ return get_status() == ResponseCode.Status.PERMANENT_FAILURE && get_condition() == ResponseCode.Condition.SYNTAX; } - + public string serialize() { return str; } - + public string to_string() { return str; } diff -Nru geary-0.12.4/src/engine/smtp/smtp-response-line.vala geary-3.32.0/src/engine/smtp/smtp-response-line.vala --- geary-0.12.4/src/engine/smtp/smtp-response-line.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/smtp/smtp-response-line.vala 2019-03-17 13:39:29.000000000 +0000 @@ -7,17 +7,17 @@ public class Geary.Smtp.ResponseLine { public const char CONTINUED_CHAR = '-'; public const char NOT_CONTINUED_CHAR = ' '; - + public ResponseCode code { get; private set; } public string? explanation { get; private set; } public bool continued { get; private set; } - + public ResponseLine(ResponseCode code, string? explanation, bool continued) { this.code = code; this.explanation = explanation; this.continued = continued; } - + /** * Converts a serialized line into something usable. The CRLF should *not* be included in * the input. @@ -26,7 +26,7 @@ // the ResponseCode is mandatory if (line.length < ResponseCode.STRLEN) throw new SmtpError.PARSE_ERROR("Line too short: %s", line); - + // Only one of two separators allowed, as well as no separator (which means no explanation // and not continued) string? explanation; @@ -36,25 +36,25 @@ explanation = line.substring(ResponseCode.STRLEN + 1, -1); continued = false; break; - + case String.EOS: explanation = null; continued = false; break; - + case CONTINUED_CHAR: explanation = explanation = line.substring(ResponseCode.STRLEN + 1, -1); continued = true; break; - + default: throw new SmtpError.PARSE_ERROR("Invalid response line separator: %s", line); } - + return new ResponseLine(new ResponseCode(line.substring(0, ResponseCode.STRLEN)), explanation, continued); } - + /** * Serializes the Reply into a line for transmission. Note that the CRLF is *not* included. */ @@ -64,7 +64,7 @@ continued ? CONTINUED_CHAR : NOT_CONTINUED_CHAR, explanation ?? ""); } - + public string to_string() { return serialize(); } diff -Nru geary-0.12.4/src/engine/smtp/smtp-response.vala geary-3.32.0/src/engine/smtp/smtp-response.vala --- geary-0.12.4/src/engine/smtp/smtp-response.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/smtp/smtp-response.vala 2019-03-17 13:39:29.000000000 +0000 @@ -8,28 +8,28 @@ public ResponseCode code { get; private set; } public ResponseLine first_line { get; private set; } public Gee.List lines { get; private set; } - + public Response(Gee.List lines) { assert(lines.size > 0); - + code = lines[0].code; first_line = lines[0]; this.lines = lines.read_only_view; } - + [NoReturn] public void throw_error(string msg) throws SmtpError { throw new SmtpError.SERVER_ERROR("%s: %s", msg, first_line.to_string()); } - + public string to_string() { StringBuilder builder = new StringBuilder(); - + foreach (ResponseLine line in lines) { builder.append(line.to_string()); builder.append("\n"); } - + return builder.str; } } diff -Nru geary-0.12.4/src/engine/state/state-machine-descriptor.vala geary-3.32.0/src/engine/state/state-machine-descriptor.vala --- geary-0.12.4/src/engine/state/state-machine-descriptor.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/state/state-machine-descriptor.vala 2019-03-17 13:39:29.000000000 +0000 @@ -11,10 +11,10 @@ public uint start_state { get; private set; } public uint state_count { get; private set; } public uint event_count { get; private set; } - + private unowned StateEventToString? state_to_string; private unowned StateEventToString? event_to_string; - + public MachineDescriptor(string name, uint start_state, uint state_count, uint event_count, StateEventToString? state_to_string, StateEventToString? event_to_string) { this.name = name; @@ -23,15 +23,15 @@ this.event_count = event_count; this.state_to_string = state_to_string; this.event_to_string = event_to_string; - + // starting state should be valid assert(start_state < state_count); } - + public string get_state_string(uint state) { return (state_to_string != null) ? state_to_string(state) : "%s STATE %u".printf(name, state); } - + public string get_event_string(uint event) { return (event_to_string != null) ? event_to_string(event) : "%s EVENT %u".printf(name, event); } diff -Nru geary-0.12.4/src/engine/state/state-machine.vala geary-3.32.0/src/engine/state/state-machine.vala --- geary-0.12.4/src/engine/state/state-machine.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/state/state-machine.vala 2019-03-17 13:39:29.000000000 +0000 @@ -16,19 +16,19 @@ private void *post_user = null; private Object? post_object = null; private Error? post_err = null; - + public Machine(MachineDescriptor descriptor, Mapping[] mappings, Transition? default_transition) { this.descriptor = descriptor; this.default_transition = default_transition; - + // verify that each state and event in the mappings are valid foreach (Mapping mapping in mappings) { assert(mapping.state < descriptor.state_count); assert(mapping.event < descriptor.event_count); } - + state = descriptor.start_state; - + // build a transition map with state/event IDs (i.e. offsets) pointing directly into the // map transitions = new Mapping[descriptor.state_count, descriptor.event_count]; @@ -38,46 +38,46 @@ transitions[mapping.state, mapping.event] = mapping; } } - + public uint get_state() { return state; } - + public bool get_abort_on_no_transition() { return abort_on_no_transition; } - + public void set_abort_on_no_transition(bool abort) { abort_on_no_transition = abort; } - + public void set_logging(bool logging) { this.logging = logging; } - + public bool is_logging() { return logging; } - + public uint issue(uint event, void *user = null, Object? object = null, Error? err = null) { assert(event < descriptor.event_count); assert(state < descriptor.state_count); - + unowned Mapping? mapping = transitions[state, event]; - + unowned Transition? transition = (mapping != null) ? mapping.transition : default_transition; if (transition == null) { string msg = "%s: No transition defined for %s@%s".printf(to_string(), descriptor.get_event_string(event), descriptor.get_state_string(state)); - + if (get_abort_on_no_transition()) error(msg); else critical(msg); - + return state; } - + // guard against reentrancy ... don't want to use a non-reentrant lock because then // the machine will simply hang; assertion is better to ferret out design flaws if (locked) { @@ -85,20 +85,20 @@ get_event_issued_string(state, event)); } locked = true; - + uint old_state = state; state = transition(state, event, user, object, err); assert(state < descriptor.state_count); - + if (!locked) { error("Exited transition to unlocked state machine %s: %s", descriptor.name, get_transition_string(old_state, event, state)); } locked = false; - + if (is_logging()) message("%s: %s", to_string(), get_transition_string(old_state, event, state)); - + // Perform post-transition if registered if (post_transition != null) { // clear post-transition before calling, in case this method is re-entered @@ -106,18 +106,18 @@ void* perform_user = post_user; Object? perform_object = post_object; Error? perform_err = post_err; - + post_transition = null; post_user = null; post_object = null; post_err = null; - + perform(perform_user, perform_object, perform_err); } - + return state; } - + // Must be used carefully: this allows for a delegate (callback) to be called after the // *current* transition has occurred; thus, this method can *only* be called from within // a TransitionHandler. @@ -131,35 +131,35 @@ Object? object = null, Error? err = null) { if (!locked) { warning("%s: Attempt to register post-transition while machine is unlocked", to_string()); - + return false; } - + this.post_transition = post_transition; post_user = user; post_object = object; post_err = err; - + return true; } - + public string get_state_string(uint state) { return descriptor.get_state_string(state); } - + public string get_event_string(uint event) { return descriptor.get_event_string(event); } - + public string get_event_issued_string(uint state, uint event) { return "%s@%s".printf(descriptor.get_state_string(state), descriptor.get_event_string(event)); } - + public string get_transition_string(uint old_state, uint event, uint new_state) { return "%s@%s -> %s".printf(descriptor.get_state_string(old_state), descriptor.get_event_string(event), descriptor.get_state_string(new_state)); } - + public string to_string() { return "Machine %s [%s]".printf(descriptor.name, descriptor.get_state_string(state)); } diff -Nru geary-0.12.4/src/engine/state/state-mapping.vala geary-3.32.0/src/engine/state/state-mapping.vala --- geary-0.12.4/src/engine/state/state-mapping.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/state/state-mapping.vala 2019-03-17 13:39:29.000000000 +0000 @@ -14,7 +14,7 @@ public uint state; public uint event; public unowned Transition transition; - + public Mapping(uint state, uint event, Transition transition) { this.state = state; this.event = event; diff -Nru geary-0.12.4/src/engine/util/util-ascii.vala geary-3.32.0/src/engine/util/util-ascii.vala --- geary-0.12.4/src/engine/util/util-ascii.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/util/util-ascii.vala 2019-03-17 13:39:29.000000000 +0000 @@ -4,32 +4,53 @@ * (version 2.1 or later). See the COPYING file in this distribution. */ -// These calls are bound to the string class in Vala 0.26. When that version of Vala is the -// minimum, these can be dropped and Ascii.strup and Ascii.strdown can use the string methods. -extern string g_ascii_strup(string str, ssize_t len = -1); -extern string g_ascii_strdown(string str, ssize_t len = -1); - +/** + * US-ASCII string utilities. + * + * Using ASCII-specific, non-localised functions is essential when + * dealing with protocol strings since any case-insensitive + * comparisons may be incorrect under certain locales — especially for + * Turkish, where translating between upper-case and lower-case `i` is + * not necessarily preserved. + */ namespace Geary.Ascii { public int index_of(string str, char ch) { + // Use a pointer and explicit null check, since testing against + // the length of the string as in a traditional for loop will mean + // a call to strlen(), making the loop O(n^2) + int ret = -1; char *strptr = str; - int index = 0; - for (;;) { - char strch = *strptr++; - - if (strch == String.EOS) - return -1; - - if (strch == ch) - return index; - - index++; + int i = 0; + while (*strptr != String.EOS) { + if (*strptr++ == ch) { + ret = i; + break; + } + i++; + } + return ret; +} + +public int last_index_of(string str, char ch) { + // Use a pointer and explicit null check, since testing against + // the length of the string as in a traditional for loop will mean + // a call to strlen(), making the loop O(n^2) + int ret = -1; + char *strptr = str; + int i = 0; + while (*strptr != String.EOS) { + if (*strptr++ == ch) { + ret = i; + } + i++; } + return ret; } public bool get_next_char(string str, ref int index, out char ch) { ch = str[index++]; - + return ch != String.EOS; } @@ -37,20 +58,8 @@ return GLib.strcmp(a, b); } -public int stricmp(string a, string b) { - char *aptr = a; - char *bptr = b; - for (;;) { - int diff = (int) (*aptr).tolower() - (int) (*bptr).tolower(); - if (diff != 0) - return diff; - - if (*aptr == String.EOS) - return 0; - - aptr++; - bptr++; - } +public inline int stricmp(string a, string b) { + return a.ascii_casecmp(b); } public inline bool str_equal(string a, string b) { @@ -58,17 +67,17 @@ } public inline bool stri_equal(string a, string b) { - return stricmp(a, b) == 0; + return a.ascii_casecmp(b) == 0; } public bool nullable_stri_equal(string? a, string? b) { if (a == null) return (b == null); - + // a != null, so always false if (b == null) return false; - + return stri_equal(a, b); } @@ -86,12 +95,12 @@ return (str != null) ? stri_hash(str) : 0; } -public string strdown(string str) { - return g_ascii_strdown(str); +public inline string strdown(string str) { + return str.ascii_down(); } -public string strup(string str) { - return g_ascii_strup(str); +public inline string strup(string str) { + return str.ascii_up(); } /** @@ -102,16 +111,16 @@ char *strptr = str; for (;;) { char ch = *strptr++; - + if (ch == String.EOS) break; - + if (ch.isdigit()) numeric_found = true; else if (!ch.isspace()) return false; } - + return numeric_found; } diff -Nru geary-0.12.4/src/engine/util/util-collection.vala geary-3.32.0/src/engine/util/util-collection.vala --- geary-0.12.4/src/engine/util/util-collection.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/util/util-collection.vala 2019-03-17 13:39:29.000000000 +0000 @@ -12,9 +12,23 @@ return c == null || c.size == 0; } +/** Returns a modifiable collection containing a single element. */ +public Gee.Collection single(T element) { + Gee.Collection single = new Gee.LinkedList(); + single.add(element); + return single; +} + +/** Returns a modifiable map containing a single entry. */ +public Gee.Map single_map(K key, V value) { + Gee.Map single = new Gee.HashMap(); + single.set(key, value); + return single; +} + // A substitute for ArrayList.wrap() for compatibility with older versions of Gee. public Gee.ArrayList array_list_wrap(G[] a, owned Gee.EqualDataFunc? equal_func = null) { - Gee.ArrayList list = new Gee.ArrayList(equal_func); + Gee.ArrayList list = new Gee.ArrayList((owned) equal_func); add_all_array(list, a); return list; } @@ -22,7 +36,7 @@ public Gee.ArrayList to_array_list(Gee.Collection c) { Gee.ArrayList list = new Gee.ArrayList(); list.add_all(c); - + return list; } @@ -41,7 +55,7 @@ public G? get_first(Gee.Collection c) { Gee.Iterator iter = c.iterator(); - + return iter.next() ? iter.get() : null; } @@ -56,19 +70,19 @@ if (pred(iter.get())) return iter.get(); } - + return null; } public bool are_sets_equal(Gee.Set a, Gee.Set b) { if (a.size != b.size) return false; - + foreach (G element in a) { if (!b.contains(element)) return false; } - + return true; } @@ -83,7 +97,7 @@ if (pred(iter.get())) iter.remove(); } - + return c; } @@ -120,7 +134,7 @@ foreach (V value in map.get(key)) reverse.set(value, key); } - + return reverse; } @@ -144,7 +158,7 @@ public bool int64_equal_func(int64? a, int64? b) { int64 *bia = (int64 *) a; int64 *bib = (int64 *) b; - + return (*bia) == (*bib); } @@ -154,14 +168,14 @@ public uint hash_memory(void *ptr, size_t bytes) { if (ptr == null || bytes == 0) return 0; - + uint8 *u8 = (uint8 *) ptr; - + // initialize hash to first byte value and then rotate-XOR from there uint hash = *u8; for (int ctr = 1; ctr < bytes; ctr++) hash = (hash << 4) ^ (hash >> 28) ^ (*u8++); - + return hash; } @@ -175,19 +189,19 @@ */ public uint hash_memory_stream(void *ptr, uint8 terminator, ByteTransformer? cb) { uint8 *u8 = (uint8 *) ptr; - + uint hash = 0; for (;;) { uint8 b = *u8++; if (b == terminator) break; - + if (cb != null) b = cb(b); - + hash = (hash << 4) ^ (hash >> 28) ^ b; } - + return hash; } diff -Nru geary-0.12.4/src/engine/util/util-config-file.vala geary-3.32.0/src/engine/util/util-config-file.vala --- geary-0.12.4/src/engine/util/util-config-file.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/src/engine/util/util-config-file.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,299 @@ +/* + * Copyright 2018 Michael James Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +/** + * A simple ini-file-like configuration file. + * + * This class provides a convenient, high-level API for the {@link + * GLib.KeyFile} class. + */ +public class Geary.ConfigFile { + + + /** A string parser that can be used to extract custom values. */ + public delegate T Parser(string value) throws GLib.KeyFileError; + + + /** + * A set of configuration keys grouped under a "[Name]" heading. + */ + public class Group { + + + private struct GroupLookup { + string group; + string prefix; + + public GroupLookup(string group, string prefix) { + this.group = group; + this.prefix = prefix; + } + } + + + /** The config file this group was obtained from. */ + public ConfigFile file { get; private set; } + + /** The name of this group, as specified by a [Name] heading. */ + public string name { get; private set; } + + /** Determines if this group already exists in the config or not. */ + public bool exists { + get { return this.backing.has_group(this.name); } + } + + private GLib.KeyFile backing; + private GroupLookup[] lookups; + + + internal Group(ConfigFile file, string name, GLib.KeyFile backing) { + this.file = file; + this.name = name; + this.backing = backing; + + this.lookups = { GroupLookup(name, "") }; + } + + + /** + * Sets a fallback lookup for missing keys in the group. + * + * This provides a fallback for looking up a legacy key. If + * set, when performing a lookup for `key` and no such key in + * the group is found, a lookup in the alternative specified + * group for `prefix` + `key` will be performed and returned + * if found. + */ + public void set_fallback(string group, string prefix) { + this.lookups = { this.lookups[0], GroupLookup(group, prefix) }; + } + + /** Determines if this group as a specific config key set. */ + public bool has_key(string name) { + try { + return this.backing.has_key(this.name, name); + } catch (GLib.Error err) { + return false; + } + } + + public string? get_string(string key, string? def = null) { + string? ret = def; + foreach (GroupLookup lookup in this.lookups) { + try { + ret = this.backing.get_string( + lookup.group, lookup.prefix + key + ); + break; + } catch (GLib.KeyFileError err) { + // continue + } + } + return ret; + } + + public string get_required_string(string key) + throws GLib.KeyFileError { + string? ret = null; + GLib.KeyFileError? key_err = null; + foreach (GroupLookup lookup in this.lookups) { + try { + ret = this.backing.get_string( + lookup.group, lookup.prefix + key + ); + break; + } catch (GLib.KeyFileError err) { + if (key_err == null) { + key_err = err; + } + // continue + } + } + + if (key_err != null) { + throw key_err; + } + + return ret; + } + + public void set_string(string key, string value) { + this.backing.set_string(this.name, key, value); + } + + public Gee.List get_string_list(string key) { + try { + string[] list = this.backing.get_string_list(this.name, key); + if (list.length > 0) + return Geary.Collection.array_list_wrap(list); + } catch (GLib.KeyFileError err) { + // Oh well + } + return new Gee.ArrayList(); + } + + public Gee.List get_required_string_list(string key) + throws GLib.KeyFileError { + string[] list = this.backing.get_string_list(this.name, key); + return Geary.Collection.array_list_wrap(list); + } + + public void set_string_list(string key, Gee.List value) { + this.backing.set_string_list(this.name, key, value.to_array()); + } + + public bool get_bool(string key, bool def = false) { + bool ret = def; + foreach (GroupLookup lookup in this.lookups) { + try { + ret = this.backing.get_boolean( + lookup.group, lookup.prefix + key + ); + break; + } catch (GLib.KeyFileError err) { + // continue + } + } + return ret; + } + + public void set_bool(string key, bool value) { + this.backing.set_boolean(this.name, key, value); + } + + public int get_int(string key, int def = 0) { + int ret = def; + foreach (GroupLookup lookup in this.lookups) { + try { + ret = this.backing.get_integer( + lookup.group, lookup.prefix + key + ); + break; + } catch (GLib.KeyFileError err) { + // continue + } + } + return ret; + } + + public void set_int(string key, int value) { + this.backing.set_integer(this.name, key, value); + } + + public uint16 get_uint16(string key, uint16 def = 0) { + return (uint16) get_int(key, (int) def); + } + + public void set_uint16(string key, uint16 value) { + this.backing.set_integer(this.name, key, (int) value); + } + + public T? parse_value(string key, Parser parser, T? def = null) { + T value = def; + string? str = get_string(key); + if (str != null) { + try { + value = parser(str); + } catch (GLib.KeyFileError err) { + debug( + "%s:%s value is invalid: %s", this.name, key, err.message + ); + } + } + return value; + } + + public T parse_required_value(string key, Parser parser) + throws GLib.KeyFileError { + string? str = get_required_string(key); + try { + return parser(str); + } catch (GLib.KeyFileError err) { + throw new GLib.KeyFileError.INVALID_VALUE( + "%s:%s value is invalid: %s", this.name, key, err.message + ); + } + } + + /** Removes a key from this group. */ + public void remove_key(string name) throws GLib.KeyFileError { + this.backing.remove_key(this.name, name); + } + + /** Removes this group from the config file. */ + public void remove() throws GLib.KeyFileError { + this.backing.remove_group(this.name); + } + + } + + + /** The file this config will be read from and written to. */ + public GLib.File file { get { return this.config_file; } } + + private GLib.File config_file; + private GLib.KeyFile backing = new KeyFile(); + + + /** + * Constructs a config file using the specified on-disk file. + */ + public ConfigFile(GLib.File config_file) { + this.config_file = config_file; + } + + /** + * Returns the config key group under the given heading name. + * + * If the group does not already exist, it will be created when a + * key is first set, but an error will be thrown if a value is + * accessed from it before doing so. Use {@link Group.exists} to + * determine if the group has previously been created. + */ + public Group get_group(string name) { + return new Group(this, name, this.backing); + } + + /** + * Loads config data from the underlying config file. + */ + public async void load(GLib.Cancellable? cancellable = null) + throws GLib.Error { + GLib.Error? thrown = null; + yield Nonblocking.Concurrent.global.schedule_async(() => { + try { + this.backing.load_from_file( + this.config_file.get_path(), KeyFileFlags.NONE + ); + } catch (GLib.Error err) { + thrown = err; + } + }, cancellable); + if (thrown != null) { + throw thrown; + } + } + + /** + * Saves config data to the underlying config file. + */ + public async void save(GLib.Cancellable? cancellable = null) + throws GLib.Error { + GLib.Error? thrown = null; + yield Nonblocking.Concurrent.global.schedule_async(() => { + try { + this.backing.save_to_file(this.config_file.get_path()); + } catch (GLib.Error err) { + thrown = err; + } + }, cancellable); + if (thrown != null) { + throw thrown; + } + } + +} diff -Nru geary-0.12.4/src/engine/util/util-connectivity-manager.vala geary-3.32.0/src/engine/util/util-connectivity-manager.vala --- geary-0.12.4/src/engine/util/util-connectivity-manager.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/util/util-connectivity-manager.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,18 +1,18 @@ /* - * Copyright 2017 Michael Gratton + * Copyright 2017-2018 Michael Gratton * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ /** - * Keeps track of network connectivity changes for an endpoint. + * Keeps track of network connectivity changes for a network endpoint. * * This class is a convenience API for the GIO NetworkMonitor. Since * when connecting and disconnecting from a network, multiple * network-changed signals may be sent, this class coalesces these as * best as possible so the rest of the engine is only notified once - * when an endpoint becomes reachable, and once when it becomes + * when a remote becomes reachable, and once when it becomes * unreachable. * * Note this class is not thread safe and should only be invoked from @@ -20,113 +20,266 @@ */ public class Geary.ConnectivityManager : BaseObject { - /** Determines if the managed endpoint is currently reachable. */ - public bool is_reachable { get; private set; default = false; } + private const uint CHECK_QUIESCENCE_MS = 60 * 1000; - private Endpoint endpoint; + + /** The endpoint being monitored. */ + public GLib.SocketConnectable remote { get; private set; default = null; } + + /** Determines if the managed endpoint is currently reachable. */ + public Trillian is_reachable { + get; private set; default = Geary.Trillian.UNKNOWN; + } + + /** + * Determines if the remote endpoint could be resolved. + * + * This will become certain if the remote becomes reachable, and + * will become impossible if a fatal error is reported. + */ + public Trillian is_valid { + get; private set; default = Geary.Trillian.UNKNOWN; + } private NetworkMonitor monitor; - private Cancellable? existing_check = null; + private Cancellable? existing_check = null; + + // Wall time the next already-connected check should not occur before + private int64 next_check = 0; + + private TimeoutManager delayed_check; + + + /** + * Fired when a fatal error was reported checking the remote. + * + * This is typically caused by an an authoritative DNS name not + * found error, but may be anything else that indicates that the + * remote will be unusable as-is without some kind of user or + * server administrator intervention. + */ + public signal void remote_error_reported(Error error); /** - * Constructs a new manager for a specific endpoint. + * Constructs a new manager for a specific remote. */ - public ConnectivityManager(Endpoint endpoint) { - this.endpoint = endpoint; + public ConnectivityManager(GLib.SocketConnectable remote) { + this.remote = remote; this.monitor = NetworkMonitor.get_default(); this.monitor.network_changed.connect(on_network_changed); + + this.delayed_check = new TimeoutManager.seconds( + // Don't use the milliseconds ctor since we don't want + // high-frequency timing. + CHECK_QUIESCENCE_MS / 1000, + () => { this.check_reachable.begin(); } + ); } ~ConnectivityManager() { this.monitor.network_changed.disconnect(on_network_changed); } - /** - * Starts checking if the manager's endpoint is reachable. - * - * This will cancel any existing check, and start a new one - * running, updating the `is_reachable` property on completion. - */ + /** + * Starts checking if the manager's remote is reachable. + * + * This will cancel any existing check, and start a new one + * running, updating the `is_reachable` property on completion. + */ public async void check_reachable() { - // We use a cancellable here as a guard instead of a boolean - // "is_checking" var since when a series of checks are - // requested in quick succession (as is the case when - // e.g. connecting or disconnecting from a network), the - // result of the *last* check is authoritative, not the first - // one. - cancel_check(); + // We use a cancellable here as a guard instead of a boolean + // "is_checking" var since when a series of checks are + // requested in quick succession (as is the case when + // e.g. connecting or disconnecting from a network), the + // result of the *last* check is authoritative, not the first + // one. + cancel_check(); - Cancellable cancellable = new Cancellable(); - this.existing_check = cancellable; + Cancellable cancellable = new Cancellable(); + this.existing_check = cancellable; - string endpoint = this.endpoint.to_string(); - bool is_reachable = this.is_reachable; + string endpoint = this.remote.to_string(); + bool is_reachable = false; try { - debug("Checking if %s reachable...", endpoint); + // Check first, and ask questions only if an error occurs, + // because if we can connect, then we can connect. + debug("Checking if %s reachable...", endpoint); is_reachable = yield this.monitor.can_reach_async( - this.endpoint.remote_address, - cancellable - ); - } catch (Error err) { + this.remote, cancellable + ); + this.next_check = ( + GLib.get_real_time() + (CHECK_QUIESCENCE_MS * 1000) + ); + } catch (GLib.IOError.CANCELLED err) { + // User cancelled, so leave as unreachable + } catch (GLib.IOError.HOST_UNREACHABLE err) { + // Despite returning a boolean, per its API docs + // NetworkMonitor.can_reach() should never actually return + // false under Vala since it will throw an error instead, + // and usually this one. While that's not 100% always the + // case, we do need to treat this error as meaning + // unreachable. + // + // However if the monitor says there actually is a network + // available, we may be running under Flatpak with Network + // Manager connectivity checking enabled and hitting issue + // GNOME/glib#1705. Pull this debug logging out once that + // is fixed. + if (this.monitor.network_available) { + debug("Assuming %s is unreachable, despite network availability", + endpoint); + } + } catch (GLib.DBusError err) { + // Running under Flatpak can cause a DBus error if the + // portal is malfunctioning (e.g. Geary #97 & #82 and + // xdg-desktop-portal #208). We must treat this as + // reachable so we make a connection attempt, otherwise it + // will never happen. + debug("DBus error checking %s reachable, treating as reachable: %s", + endpoint, err.message); + is_reachable = true; + } catch (GLib.ResolverError.TEMPORARY_FAILURE err) { + // Host name could not be resolved since name servers + // could not be reached, so treat as being offline. + debug("Transient error checking %s reachable, treating offline: %s", + endpoint, err.message); + } catch (GLib.Error err) { if (err is IOError.NETWORK_UNREACHABLE && - this.monitor.network_available) { - // If we get a network unreachable error, but the monitor - // says there actually is a network available, we may be - // running in a Flatpak and hitting Bug 777706. If so, - // just assume the service is reachable is for now. :( - is_reachable = true; - debug("Assuming %s is reachable, despite network unavailability", - endpoint); - } else if (!(err is IOError.CANCELLED)) { - // Service is unreachable - debug("Error checking %s reachable, treating as unreachable: %s", - endpoint, err.message); - is_reachable = false; - } + this.monitor.network_available) { + // If we get a network unreachable error, but the monitor + // says there actually is a network available, we may be + // running in a Flatpak and hitting Bug 777706. If so, + // just assume the service is reachable is for now. :( + // Pull this put once xdg-desktop-portal 1.x is widely + // installed. + debug("Assuming %s is reachable, despite network unavailability", + endpoint); + is_reachable = true; + } else { + // The monitor threw an error, but only notify if it + // looks like we *should* be able to connect + // (i.e. have full network connectivity, or are + // connecting to a local service), so we don't + // needlessly hassle the user with expected error + // messages. + GLib.NetworkConnectivity connectivity = this.monitor.connectivity; + if (connectivity == FULL || + (connectivity == LOCAL && is_local_address())) { + debug("Error checking %s [%s] reachable, treating unreachable: %s", + endpoint, connectivity.to_string(), err.message); + set_invalid(); + remote_error_reported(err); + } else { + debug("Error checking %s [%s] reachable, treating offline: %s", + endpoint, connectivity.to_string(), err.message); + } + } } finally { - if (!cancellable.is_cancelled()) { - set_reachable(is_reachable); - } + if (!cancellable.is_cancelled()) { + set_reachable(is_reachable); + + // Kick off another delayed check in case the network + // changes without the monitor noticing. + this.delayed_check.start(); + } this.existing_check = null; } } - /** - * Cancels any running reachability check, if any. - */ + /** + * Cancels any running or future reachability check, if any. + */ public void cancel_check() { - if (this.existing_check != null) { - this.existing_check.cancel(); - this.existing_check = null; - } - } + if (this.existing_check != null) { + this.existing_check.cancel(); + this.existing_check = null; + } + this.delayed_check.reset(); + } private void on_network_changed(bool some_available) { // Always check if reachable because IMAP server could be on // localhost. (This is a Linux program, after all...) - debug("Network changed: %s", - some_available ? "some available" : "none available"); - if (some_available) { - // Some hosts may have dropped out despite network being - // still xavailable, so need to check again - this.check_reachable.begin(); - } else { - // None available, so definitely not reachable - set_reachable(false); - } - } - - private inline void set_reachable(bool reachable) { - // Coalesce changes to is_reachable, since Vala <= 0.34 always - // fires notify signals on set, even if the value doesn't - // change. 0.36 fixes that, so pull this out when we can - // depend on that as a minimum. - if (this.is_reachable != reachable) { - this.is_reachable = reachable; - } - } + debug("Network changed: %s", + some_available ? "some available" : "none available"); + if (some_available) { + // Some networks may have dropped out despite some being + // still available, so need to check again. Only run the + // check if we are either currently: + // + // 1. Unreachable + // 2. An existing check is already running (i.e. the + // network configuration is changing) + // 3. Reachable, and a check hasn't been run recently + // + // Otherwise, schedule a delayed check to work around the + // issue in Bug 776042. + if (this.is_reachable.is_uncertain() || + this.existing_check != null || + this.next_check <= GLib.get_real_time()) { + this.check_reachable.begin(); + } else if (!this.delayed_check.is_running) { + this.delayed_check.start(); + } + } else { + // None available, so definitely not reachable. + set_reachable(false); + } + } + + private inline void set_reachable(bool reachable) { + // Coalesce changes to is_reachable, since Vala <= 0.34 always + // fires notify signals on set, even if the value doesn't + // change. 0.36 fixes that, so pull this test out when we can + // depend on that as a minimum. + if ((reachable && !this.is_reachable.is_certain()) || + (!reachable && !this.is_reachable.is_impossible())) { + debug("Remote %s became %s", + this.remote.to_string(), reachable ? "reachable" : "unreachable"); + this.is_reachable = reachable ? Trillian.TRUE : Trillian.FALSE; + } + + // We only work out if the name is valid (or becomes valid + // again) if the remote becomes reachable. + if (reachable && this.is_valid.is_uncertain()) { + this.is_valid = Trillian.TRUE; + } + + } + + private inline void set_invalid() { + // Coalesce changes to is_reachable, since Vala <= 0.34 always + // fires notify signals on set, even if the value doesn't + // change. 0.36 fixes that, so pull this method out when we can + // depend on that as a minimum. + if (this.is_valid != Trillian.FALSE) { + this.is_valid = Trillian.FALSE; + } + } + + private bool is_local_address() { + GLib.NetworkAddress? name = this.remote as GLib.NetworkAddress; + if (name != null) { + return ( + name.hostname == "localhost" || + name.hostname.has_prefix("localhost.") || + name.hostname == "127.0.0.1" || + name.hostname == "::1" + ); + } + + GLib.InetSocketAddress? inet = this.remote as GLib.InetSocketAddress; + if (inet != null) { + return ( + inet.address.is_loopback || + inet.address.is_link_local + ); + } + + return false; + } } diff -Nru geary-0.12.4/src/engine/util/util-converter.vala geary-3.32.0/src/engine/util/util-converter.vala --- geary-0.12.4/src/engine/util/util-converter.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/util/util-converter.vala 1970-01-01 00:00:00.000000000 +0000 @@ -1,77 +0,0 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. - * - * This software is licensed under the GNU Lesser General Public License - * (version 2.1 or later). See the COPYING file in this distribution. - */ - -public class Geary.Stream.MidstreamConverter : BaseObject, Converter { - public uint64 total_bytes_read { get; private set; default = 0; } - public uint64 total_bytes_written { get; private set; default = 0; } - public uint64 converted_bytes_read { get; private set; default = 0; } - public uint64 converted_bytes_written { get; private set; default = 0; } - - public bool log_performance { get; set; default = false; } - - private string name; - private Converter? converter = null; - - public MidstreamConverter(string name) { - this.name = name; - } - - public bool install(Converter converter) { - if (this.converter != null) - return false; - - this.converter = converter; - - return true; - } - - public ConverterResult convert(uint8[] inbuf, uint8[] outbuf, ConverterFlags flags, - out size_t bytes_read, out size_t bytes_written) throws Error { - if (converter != null) { - ConverterResult result = converter.convert(inbuf, outbuf, flags, out bytes_read, out bytes_written); - - total_bytes_read += bytes_read; - total_bytes_written += bytes_written; - - converted_bytes_read += bytes_read; - converted_bytes_written += bytes_written; - - if (log_performance && (bytes_read > 0 || bytes_written > 0)) { - double pct = (converted_bytes_read > converted_bytes_written) - ? (double) converted_bytes_written / (double) converted_bytes_read - : (double) converted_bytes_read / (double) converted_bytes_written; - debug("%s read/written: %s/%s (%ld%%)", name, converted_bytes_read.to_string(), - converted_bytes_written.to_string(), (long) (pct * 100.0)); - } - - return result; - } - - // passthrough - size_t copied = size_t.min(inbuf.length, outbuf.length); - if (copied > 0) - GLib.Memory.copy(outbuf, inbuf, copied); - - bytes_read = copied; - bytes_written = copied; - - total_bytes_read += copied; - total_bytes_written += copied; - - if ((flags & ConverterFlags.FLUSH) != 0) - return ConverterResult.FLUSHED; - - if ((flags & ConverterFlags.INPUT_AT_END) != 0) - return ConverterResult.FINISHED; - - return ConverterResult.CONVERTED; - } - - public void reset() { - if (converter != null) - converter.reset(); - } -} diff -Nru geary-0.12.4/src/engine/util/util-error-context.vala geary-3.32.0/src/engine/util/util-error-context.vala --- geary-0.12.4/src/engine/util/util-error-context.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/src/engine/util/util-error-context.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,115 @@ +/* + * Copyright 2019 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +/** + * Provides additional context information for an error when thrown. + * + * This class allows the engine to provide additional context + * information such as stack traces, the cause of this error, or + * engine state when an error is thrown. A stack trace will be + * generated for the context when instantiated, so context instances + * should be constructed as close to when the error was thrown as + * possible. + * + * This works around the GLib error system's lack of extensibility. + */ +public class Geary.ErrorContext : BaseObject { + + + /** Represents an individual stack frame in a call back-trace. */ + public class StackFrame { + + + /** Name of the function being called. */ + public string name = "unknown"; + + +#if HAVE_LIBUNWIND + internal StackFrame(Unwind.Cursor frame) { + uint8 proc_name[256]; + int ret = -frame.get_proc_name(proc_name); + if (ret == Unwind.Error.SUCCESS || + ret == Unwind.Error.NOMEM) { + this.name = (string) proc_name; + } + } +#endif + + public string to_string() { + return this.name; + } + + } + + + /** The error thrown that this context is describing. */ + public GLib.Error thrown { get; private set; } + + /** A back trace from when the context was constructed. */ + public Gee.List? backtrace { + get; private set; default = new Gee.LinkedList(); + } + + + public ErrorContext(GLib.Error thrown) { + this.thrown = thrown; + +#if HAVE_LIBUNWIND + Unwind.Context trace = Unwind.Context(); + Unwind.Cursor cursor = Unwind.Cursor.local(trace); + + // This misses the first frame, but that's this + // constructor call, so we don't really care. + while (cursor.step() != 0) { + this.backtrace.add(new StackFrame(cursor)); + } +#endif + } + + /** Returns a string representation of the error type, for debugging. */ + public string? format_error_type() { + string type = null; + if (this.thrown != null) { + const string QUARK_SUFFIX = "-quark"; + string ugly_domain = this.thrown.domain.to_string(); + if (ugly_domain.has_suffix(QUARK_SUFFIX)) { + ugly_domain = ugly_domain.substring( + 0, ugly_domain.length - QUARK_SUFFIX.length + ); + } + StringBuilder nice_domain = new StringBuilder(); + string separator = (ugly_domain.index_of("_") != -1) ? "_" : "-"; + foreach (string part in ugly_domain.split(separator)) { + if (part.length > 0) { + if (part == "io") { + nice_domain.append("IO"); + } else { + nice_domain.append(part.up(1)); + nice_domain.append(part.substring(1)); + } + } + } + + type = "%s %i".printf(nice_domain.str, this.thrown.code); + } + return type; + } + + /** Returns a string representation of the complete error, for debugging. */ + public string? format_full_error() { + string error = null; + if (this.thrown != null) { + error = String.is_empty(this.thrown.message) + ? "%s: no message specified".printf(format_error_type()) + : "%s: \"%s\"".printf( + format_error_type(), this.thrown.message + ); + } + return error; + } + +} diff -Nru geary-0.12.4/src/engine/util/util-files.vala geary-3.32.0/src/engine/util/util-files.vala --- geary-0.12.4/src/engine/util/util-files.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/util/util-files.vala 2019-03-17 13:39:29.000000000 +0000 @@ -14,52 +14,66 @@ * This method is designed to keep chugging along even if an error occurs. * If this method is called with a file, it will simply be deleted. */ -public async void recursive_delete_async(File folder, Cancellable? cancellable = null) { +public async void recursive_delete_async(GLib.File folder, + int priority = GLib.Priority.DEFAULT, + GLib.Cancellable? cancellable = null) { // If this is a folder, recurse children. FileType file_type = FileType.UNKNOWN; try { file_type = yield query_file_type_async(folder, true, cancellable); } catch (Error err) { debug("Unable to get file type of %s: %s", folder.get_path(), err.message); - + if (err is IOError.CANCELLED) return; } - + if (file_type == FileType.DIRECTORY) { FileEnumerator? enumerator = null; try { - enumerator = yield folder.enumerate_children_async(FileAttribute.STANDARD_NAME, - FileQueryInfoFlags.NOFOLLOW_SYMLINKS, Priority.DEFAULT, cancellable); + enumerator = yield folder.enumerate_children_async( + FileAttribute.STANDARD_NAME, + FileQueryInfoFlags.NOFOLLOW_SYMLINKS, + priority, + cancellable + ); } catch (Error e) { debug("Error enumerating files for deletion: %s", e.message); } - + // Iterate the enumerated files in batches. if (enumerator != null) { try { while (true) { - List? info_list = yield enumerator.next_files_async(RECURSIVE_DELETE_BATCH_SIZE, - Priority.DEFAULT, cancellable); + List? info_list = yield enumerator.next_files_async( + RECURSIVE_DELETE_BATCH_SIZE, + priority, + cancellable + ); if (info_list == null) break; // Stop condition. - + // Recursive step. - foreach (FileInfo info in info_list) - yield recursive_delete_async(folder.get_child(info.get_name()), cancellable); + foreach (FileInfo info in info_list) { + yield recursive_delete_async( + folder.get_child(info.get_name()), + priority, + cancellable + ); + } } } catch (Error e) { debug("Error enumerating batch of files: %s", e.message); - + if (e is IOError.CANCELLED) return; } } } - + // Children have been deleted, it's now safe to delete this file/folder. try { - yield folder.delete_async(Priority.DEFAULT, cancellable); + yield folder.delete_async(priority, cancellable); } catch (Error e) { debug("Error removing file: %s", e.message); } @@ -77,7 +91,7 @@ else throw err; } - + // exists if got this far return true; } @@ -90,10 +104,39 @@ FileInfo info = yield file.query_info_async("standard::type", follow_symlinks ? FileQueryInfoFlags.NONE : FileQueryInfoFlags.NOFOLLOW_SYMLINKS, Priority.DEFAULT, cancellable); - + return info.get_file_type(); } +/** + * Ensure a directory exists, asynchronously. + * + * Returns true if the directory ws created. A {@link GLib.Error} is + * thrown if the directory cannot be created, but not if it already + * exists. + */ +public async bool make_directory_with_parents(File dir, + Cancellable? cancellable = null) + throws Error { + bool ret = false; + GLib.IOError? create_err = null; + yield Nonblocking.Concurrent.global.schedule_async(() => { + try { + dir.make_directory_with_parents(cancellable); + } catch (GLib.IOError err) { + create_err = err; + } + }); + + if (create_err == null) { + ret = true; + } else if (!(create_err is GLib.IOError.EXISTS)) { + throw create_err; + } + + return ret; +} + public uint hash(File file) { return file.hash(); } @@ -109,10 +152,10 @@ public bool nullable_equal(File? a, File? b) { if (a == null && b == null) return true; - + if (a == null || b == null) return false; - + return a.equal(b); } diff -Nru geary-0.12.4/src/engine/util/util-generic-capabilities.vala geary-3.32.0/src/engine/util/util-generic-capabilities.vala --- geary-0.12.4/src/engine/util/util-generic-capabilities.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/util/util-generic-capabilities.vala 2019-03-17 13:39:29.000000000 +0000 @@ -11,34 +11,34 @@ public class Geary.GenericCapabilities : BaseObject { public string name_separator { get; private set; } public string? value_separator { get; private set; } - + // All params must be nullable to support both libgee 0.8.0 and 0.8.6 (for Quantal and Rarring, respectively.) // This behavior was changed in the following libgee commit: // https://git.gnome.org/browse/libgee/commit/?id=5a35303cb04154d0e929a7d8895d4a4812ba7a1c private Gee.HashMultiMap map = new Gee.HashMultiMap( Ascii.nullable_stri_hash, Ascii.nullable_stri_equal, Ascii.nullable_stri_hash, Ascii.nullable_stri_equal); - + /** * Creates an empty set of capabilities. */ public GenericCapabilities(string name_separator, string? value_separator) { assert(!String.is_empty(name_separator)); - + this.name_separator = name_separator; this.value_separator = !String.is_empty(value_separator) ? value_separator : null; } - + public bool is_empty() { return (map.size == 0); } - + public bool parse_and_add_capability(string text) { string[] name_values = text.split(name_separator, 2); switch (name_values.length) { case 1: add_capability(name_values[0]); break; - + case 2: if (value_separator == null) { add_capability(name_values[0], name_values[1]); @@ -53,25 +53,25 @@ } } break; - + default: return false; } - + return true; } - + public void add_capability(string name, string? setting = null) { map.set(name, String.is_empty(setting) ? null : setting); } - + /** * Returns true only if the capability was named as available by the server. */ public bool has_capability(string name) { return map.contains(name); } - + /** * Returns true only if the capability and the associated setting were both named as available * by the server. @@ -79,13 +79,13 @@ public bool has_setting(string name, string? setting) { if (!map.contains(name)) return false; - + if (String.is_empty(setting)) return true; - + return map.get(name).contains(setting); } - + /** * Returns null if either the capability is available but has no associated settings, or if the * capability is not available. Thus, use has_capability() to determine if available, then @@ -93,28 +93,28 @@ */ public Gee.Collection? get_settings(string name) { Gee.Collection settings = map.get(name); - + return (settings.size > 0) ? settings : null; } - + public Gee.Set? get_all_names() { Gee.Set names = map.get_keys(); - + return (names.size > 0) ? names : null; } - + private void append(StringBuilder builder, string text) { if (!String.is_empty(builder.str)) builder.append(String.is_empty(value_separator) ? " " : value_separator); - + builder.append(text); } - + public virtual string to_string() { Gee.Set? names = get_all_names(); if (names == null || names.size == 0) return ""; - + StringBuilder builder = new StringBuilder(); foreach (string name in names) { Gee.Collection? settings = get_settings(name); @@ -129,7 +129,7 @@ } } } - + return builder.str; } } diff -Nru geary-0.12.4/src/engine/util/util-html.vala geary-3.32.0/src/engine/util/util-html.vala --- geary-0.12.4/src/engine/util/util-html.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/util/util-html.vala 2019-03-17 13:39:29.000000000 +0000 @@ -10,6 +10,8 @@ // Originally from here: http://daringfireball.net/2010/07/improved_regex_for_matching_urls public const string URL_REGEX = "(?i)\\b((?:[a-z][\\w-]+:(?:/{1,3}|[a-z0-9%])|www\\d{0,3}[.]|[a-z0-9.\\-]+[.][a-z]{2,4}/)(?:[^\\s()<>]+|\\(([^\\s()<>]+|(\\([^\\s()<>]+\\)))*\\))+(?:\\(([^\\s()<>]+|(\\([^\\s()<>]+\\)))*\\)|[^\\s`!()\\[\\]{};:'\".,<>?«»“”‘’]))"; +private Regex WHITESPACE_REGEX; + private int init_count = 0; private Gee.HashSet? breaking_elements; private Gee.HashSet? spacing_elements; @@ -27,6 +29,12 @@ return; init_element_sets(); + + try { + WHITESPACE_REGEX = new Regex("(\\R|\\t|[ ]+)"); + } catch (GLib.Error err) { + assert(true); + } } private void init_element_sets() { @@ -95,20 +103,58 @@ }); } +/** Converts plain text to HTML with reserved characters escaped. */ public inline string escape_markup(string? plain) { - return (!String.is_empty(plain) && plain.validate()) ? Markup.escape_text(plain) : ""; + return (!String.is_empty(plain) && plain.validate()) + ? Markup.escape_text(plain) : ""; } +/** Converts plain text to HTML with whitespace (SP, CR, LF) preserved. */ public string preserve_whitespace(string? text) { - if (String.is_empty(text)) - return ""; - - string output = text.replace(" ", " "); - output = output.replace("\r\n", "
"); - output = output.replace("\n", "
"); - output = output.replace("\r", "
"); + string preserved = ""; + if (!String.is_empty(text)) { + try { + preserved = WHITESPACE_REGEX.replace_eval( + text, -1, 0, 0, (info, result) => { + string match = info.fetch(0); + if (match[0] == ' ') { + result.append_c(' '); + for (int len = match.length - 1; len > 0; len--) { + result.append(" "); + } + } else if (match == "\t") { + result.append("    "); + } else { + result.append("
"); + } + return false; + }); + } catch (Error err) { + debug("Error preserving whitespace: %s", err.message); + } + } + return preserved; +} - return output; +/** + * Escape reserved HTML entities and preserves whitespace, if needed. + * + * Returns a string with reserved HTML entities escaped and + * whitespace preserved if the given string does not have HTML + * tags. + */ +public string smart_escape(string? text) { + string escaped = text ?? ""; + if (text != null) { + bool is_html = Regex.match_simple( + "<[A-Z]+ ?(?: [^>]*)?\\/?>", text, RegexCompileFlags.CASELESS + ); + if (!is_html) { + escaped = escape_markup(escaped); + escaped = preserve_whitespace(escaped); + } + } + return escaped; } /** @@ -168,22 +214,4 @@ } } -// Escape reserved HTML entities if the string does not have HTML -// tags. If there are no tags, or if preserve_whitespace_in_html is -// true, wrap the string a div to preserve whitespace. -public string smart_escape(string? text, bool preserve_whitespace_in_html) { - if (text == null) - return text; - - string res = text; - if (!Regex.match_simple("<([A-Z]*)(?: [^>]*)?>.*|<[A-Z]*(?: [^>]*)?/>", res, - RegexCompileFlags.CASELESS)) { - res = Geary.HTML.escape_markup(res); - preserve_whitespace_in_html = true; - } - if (preserve_whitespace_in_html) - res = @"
$res
"; - return res; -} - } diff -Nru geary-0.12.4/src/engine/util/util-idle-manager.vala geary-3.32.0/src/engine/util/util-idle-manager.vala --- geary-0.12.4/src/engine/util/util-idle-manager.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/util/util-idle-manager.vala 2019-03-17 13:39:29.000000000 +0000 @@ -32,6 +32,32 @@ /** The idle callback function prototype. */ public delegate void IdleFunc(IdleManager manager); + + // Instances of this are passed to GLib.Idle to execute the + // callback. This holds a weak ref to the manager itself allowing + // it to be destroyed if all of its references are broken, even if + // it is still queued via GLib.Idle. + private class HandlerRef : GLib.Object { + + private GLib.WeakRef manager; + + + public HandlerRef(IdleManager manager) { + this.manager = GLib.WeakRef(manager); + } + + public bool execute() { + bool ret = Source.REMOVE; + IdleManager? manager = this.manager.get() as IdleManager; + if (manager != null) { + ret = manager.execute(); + } + return ret; + } + + } + + /** Determines if the function will be re-scheduled after being run. */ public Repeat repetition = Repeat.ONCE; @@ -43,17 +69,15 @@ get { return this.source_id >= 0; } } - // Callback must be unowned to avoid reference loop with owner's - // class when a closure is used as the callback. private unowned IdleFunc callback; - private int source_id = -1; + private int64 source_id = -1; /** * Constructs a new idle manager with an interval in seconds. * * The idle function will be by default not running, and hence - * needs to be started by a call to {@link start}. + * needs to be started by a call to {@link schedule}. */ public IdleManager(IdleFunc callback) { this.callback = callback; @@ -70,7 +94,11 @@ */ public void schedule() { reset(); - this.source_id = (int) GLib.Idle.add_full(this.priority, on_trigger); + + HandlerRef handler = new HandlerRef(this); + this.source_id = (int) GLib.Idle.add_full( + this.priority, handler.execute + ); } /** @@ -79,15 +107,14 @@ * @return `true` if function was already scheduled, else `false` */ public bool reset() { - bool is_running = this.is_running; - if (is_running) { - Source.remove(this.source_id); + if (this.is_running) { + Source.remove((uint) this.source_id); this.source_id = -1; } - return is_running; + return this.is_running; } - private bool on_trigger() { + private bool execute() { bool ret = Source.CONTINUE; // If running only once, reset the source id now in case the // callback resets the timer while it is executing, so we @@ -97,6 +124,14 @@ this.source_id = -1; ret = Source.REMOVE; } + + unowned IdleFunc? callback = this.callback; + if (callback != null) { + callback(this); + } else { + ret = Source.REMOVE; + } + callback(this); return ret; } diff -Nru geary-0.12.4/src/engine/util/util-imap-utf7.vala geary-3.32.0/src/engine/util/util-imap-utf7.vala --- geary-0.12.4/src/engine/util/util-imap-utf7.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/util/util-imap-utf7.vala 2019-03-17 13:39:29.000000000 +0000 @@ -100,7 +100,7 @@ /* no characters that need to be encoded */ return str; } - + /* at least one encoded character */ StringBuilder dest = new StringBuilder(); dest.append_len(str, p); @@ -115,7 +115,7 @@ p++; continue; } - + uint8[] utf16 = {}; while ((uint8) str[p] >= 0x80) { int next_p = p; @@ -143,7 +143,7 @@ private void utf16buf_to_utf8(StringBuilder dest, uint8[] output, ref int pos, int len) throws ConvertError { if (len % 2 != 0) throw new ConvertError.ILLEGAL_SEQUENCE("Odd number of bytes in UTF-16 data"); - + uint16 high = (output[pos % 4] << 8) | output[(pos+1) % 4]; if (high < UTF16_SURROGATE_HIGH_FIRST || high > UTF16_SURROGATE_HIGH_MAX) { @@ -155,18 +155,18 @@ pos = (pos + 2) % 4; return; } - + if (high > UTF16_SURROGATE_HIGH_LAST) throw new ConvertError.ILLEGAL_SEQUENCE("UTF-16 data out of range"); if (len != 4) { /* missing the second character */ throw new ConvertError.ILLEGAL_SEQUENCE("Truncated UTF-16 data"); } - + uint16 low = (output[(pos+2)%4] << 8) | output[(pos+3) % 4]; if (low < UTF16_SURROGATE_LOW_FIRST || low > UTF16_SURROGATE_LOW_LAST) throw new ConvertError.ILLEGAL_SEQUENCE("Illegal UTF-16 surrogate"); - + unichar chr = UTF16_SURROGATE_BASE + (((high & UTF16_SURROGATE_MASK) << UTF16_SURROGATE_SHIFT) | (low & UTF16_SURROGATE_MASK)); @@ -179,52 +179,52 @@ private void mbase64_decode_to_utf8(StringBuilder dest, string str, ref int p) throws ConvertError { uint8 input[4], output[4]; int outstart = 0, outpos = 0; - + while (str[p] != '-') { input[0] = imap_b64dec[(uint8) str[p + 0]]; input[1] = imap_b64dec[(uint8) str[p + 1]]; if (input[0] == 0xff || input[1] == 0xff) throw new ConvertError.ILLEGAL_SEQUENCE("Illegal character in IMAP base-64 encoded sequence"); - + output[outpos % 4] = (input[0] << 2) | (input[1] >> 4); if (++outpos % 4 == outstart) { utf16buf_to_utf8(dest, output, ref outstart, 4); } - + input[2] = imap_b64dec[(uint8) str[p + 2]]; if (input[2] == 0xff) { if (str[p + 2] != '-') throw new ConvertError.ILLEGAL_SEQUENCE("Illegal character in IMAP base-64 encoded sequence"); - + p += 2; break; } - + output[outpos % 4] = (input[1] << 4) | (input[2] >> 2); if (++outpos % 4 == outstart) { utf16buf_to_utf8(dest, output, ref outstart, 4); } - + input[3] = imap_b64dec[(uint8) str[p + 3]]; if (input[3] == 0xff) { if (str[p + 3] != '-') throw new ConvertError.ILLEGAL_SEQUENCE("Illegal character in IMAP base-64 encoded sequence"); - + p += 3; break; } - + output[outpos % 4] = ((input[2] << 6) & 0xc0) | input[3]; if (++outpos % 4 == outstart) { utf16buf_to_utf8(dest, output, ref outstart, 4); } - + p += 4; } if (outstart != outpos % 4) { utf16buf_to_utf8(dest, output, ref outstart, (4 + outpos - outstart) % 4); } - + /* found ending '-' */ p++; } @@ -243,7 +243,7 @@ /* 8bit characters - the input is broken */ throw new ConvertError.ILLEGAL_SEQUENCE("IMAP UTF-7 input string contains 8-bit data"); } - + /* at least one encoded character */ StringBuilder dest = new StringBuilder(); dest.append_len(str, p); diff -Nru geary-0.12.4/src/engine/util/util-iterable.vala geary-3.32.0/src/engine/util/util-iterable.vala --- geary-0.12.4/src/engine/util/util-iterable.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/util/util-iterable.vala 2019-03-17 13:39:29.000000000 +0000 @@ -11,7 +11,7 @@ public Geary.Iterable traverse(Gee.Iterable i) { return new Geary.Iterable(i.iterator()); } - + /** * Take some non-null items (all must be of type G) and return a * Geary.Iterable for convenience. @@ -19,15 +19,15 @@ public Geary.Iterable iterate(G g, ...) { va_list args = va_list(); G arg = g; - + Gee.ArrayList list = new Gee.ArrayList(); do { list.add(arg); } while((arg = args.arg()) != null); - + return Geary.traverse(list); } - + /** * Take an array of items and return a Geary.Iterable for convenience. */ @@ -53,15 +53,15 @@ */ private class GeeIterable : Gee.Traversable, Gee.Iterable, BaseObject { private Gee.Iterator i; - + public GeeIterable(Gee.Iterator iterator) { i = iterator; } - + public Gee.Iterator iterator() { return i; } - + // Unfortunately necessary for Gee.Traversable. public virtual bool @foreach(Gee.ForallFunc f) { foreach (G g in this) { @@ -73,37 +73,71 @@ } private Gee.Iterator i; - - public Iterable(Gee.Iterator iterator) { + + + /** + * Internal only constructor. + * + * Applications should use {@link traverse}, {@link iterate} or + * {@link iterate_array} instead. + */ + internal Iterable(Gee.Iterator iterator) { i = iterator; } - + + /** Returns the iterable's underlying iterator. */ public virtual Gee.Iterator iterator() { return i; } - + + /** + * Applies a function to each iterable element. + * + * @see Gee.Traversable.map + */ public Iterable map(Gee.MapFunc f) { return new Iterable(i.map(f)); } - + + /** + * Applies a function to each element and previous result. + * + * @see Gee.Traversable.scan + */ public Iterable scan(Gee.FoldFunc f, owned A seed) { return new Iterable(i.scan(f, seed)); } - + + /** + * Returns the elements which satisfy the given predicate. + * + * @see Gee.Traversable.filter + */ public Iterable filter(owned Gee.Predicate f) { - return new Iterable(i.filter(f)); + return new Iterable(i.filter((owned) f)); } - + + /** + * Truncates the start and optionally end of the iterable. + * + * @see Gee.Traversable.chop + */ public Iterable chop(int offset, int length = -1) { return new Iterable(i.chop(offset, length)); } - + + /** + * Returns the non-null results of a call to {@link map}. + * + * @see Gee.Traversable.chop + */ public Iterable map_nonnull(Gee.MapFunc f) { return new Iterable(i.map(f).filter(g => g != null)); } - + /** - * Return only objects of the destination type, as the destination type. + * Return only objects of the destination type, as that type. + * * Only works on types derived from Object. */ public Iterable cast_object() { @@ -113,11 +147,13 @@ i.filter(g => ((Object) g).get_type().is_a(typeof(A))) .map(g => { return (A) g; })); } - + + /** Returns the first element of the iterable. */ public G? first() { return (i.next() ? i.@get() : null); } - + + /** Returns the first element that satisfies the given predicate. */ public G? first_matching(owned Gee.Predicate f) { foreach (G g in this) { if (f(g)) @@ -125,7 +161,8 @@ } return null; } - + + /* Returns true if at least one element satisfies the predicate. */ public bool any(owned Gee.Predicate f) { foreach (G g in this) { if (f(g)) @@ -133,7 +170,8 @@ } return false; } - + + /* Returns true if all elements satisfies the predicate. */ public bool all(owned Gee.Predicate f) { foreach (G g in this) { if (!f(g)) @@ -141,7 +179,8 @@ } return true; } - + + /* Returns the number of elements satisfying the predicate. */ public int count_matching(owned Gee.Predicate f) { int count = 0; foreach (G g in this) { @@ -150,7 +189,7 @@ } return count; } - + /** * The resulting Gee.Iterable comes with the same caveat that you may only * iterate over it once. @@ -158,43 +197,55 @@ public Gee.Iterable to_gee_iterable() { return new GeeIterable(i); } - + + /** Adds all elements to the given collection. */ public Gee.Collection add_all_to(Gee.Collection c) { while (i.next()) c.add(i.@get()); return c; } - + + /** Adds all elements to a map, with keys generated by key_func. */ + public Gee.Map add_all_to_map(Gee.Map c, Gee.MapFunc key_func) { + while (i.next()) { + G g = i.@get(); + c.@set(key_func(g), g); + } + return c; + } + + /** Returns a new array list containing all elements. */ public Gee.ArrayList to_array_list(owned Gee.EqualDataFunc? equal_func = null) { - return (Gee.ArrayList) add_all_to(new Gee.ArrayList(equal_func)); + return (Gee.ArrayList) add_all_to(new Gee.ArrayList((owned) equal_func)); } - + + /** Returns a new linked list containing all elements. */ public Gee.LinkedList to_linked_list(owned Gee.EqualDataFunc? equal_func = null) { - return (Gee.LinkedList) add_all_to(new Gee.LinkedList(equal_func)); + return (Gee.LinkedList) add_all_to(new Gee.LinkedList((owned) equal_func)); } - + + /** Returns a new hash set containing all elements. */ public Gee.HashSet to_hash_set(owned Gee.HashDataFunc? hash_func = null, owned Gee.EqualDataFunc? equal_func = null) { - return (Gee.HashSet) add_all_to(new Gee.HashSet(hash_func, equal_func)); + return (Gee.HashSet) add_all_to(new Gee.HashSet((owned) hash_func, (owned) equal_func)); } - + + /** Returns a new tree set with all elements added to it. */ public Gee.TreeSet to_tree_set(owned CompareDataFunc? compare_func = null) { - return (Gee.TreeSet) add_all_to(new Gee.TreeSet(compare_func)); + return (Gee.TreeSet) add_all_to(new Gee.TreeSet((owned) compare_func)); } - - public Gee.Map add_all_to_map(Gee.Map c, Gee.MapFunc key_func) { - while (i.next()) { - G g = i.@get(); - c.@set(key_func(g), g); - } - return c; - } - - public Gee.HashMap to_hash_map(Gee.MapFunc key_func, - owned Gee.HashDataFunc? key_hash_func = null, - owned Gee.EqualDataFunc? key_equal_func = null, - owned Gee.EqualDataFunc? value_equal_func = null) { - return (Gee.HashMap) add_all_to_map(new Gee.HashMap( - key_hash_func, key_equal_func, value_equal_func), key_func); + + /** Returns a new hash map, adding elements with keys generated by key_func. */ + public Gee.HashMap + to_hash_map(Gee.MapFunc key_func, + owned Gee.HashDataFunc? key_hash_func = null, + owned Gee.EqualDataFunc? key_equal_func = null, + owned Gee.EqualDataFunc? value_equal_func = null) { + return (Gee.HashMap) add_all_to_map( + new Gee.HashMap((owned) key_hash_func, + (owned) key_equal_func, + (owned) value_equal_func), + key_func + ); } } diff -Nru geary-0.12.4/src/engine/util/util-js.vala geary-3.32.0/src/engine/util/util-js.vala --- geary-0.12.4/src/engine/util/util-js.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/util/util-js.vala 2019-03-17 13:39:29.000000000 +0000 @@ -10,6 +10,16 @@ */ namespace Geary.JS { +#if !VALA_0_42 + // Workaround broken version of this in the vala bindings. See Bug + // 788113. + [CCode (cname = "JSStringGetUTF8CString")] + private extern size_t js_string_get_utf8_cstring( + global::JS.String js, + [CCode (array_length_type = "gsize")] char[] buffer + ); +#endif + /** * Errors produced by functions in {@link Geary.JS}. */ @@ -72,7 +82,7 @@ global::JS.String js_str = value.to_string_copy(context, out err); Geary.JS.check_exception(context, err); - return Geary.JS.to_string_released(js_str); + return to_native_string(js_str); } /** @@ -101,12 +111,15 @@ /** * Returns a JSC {@link JS.String} as a Vala {@link string}. */ - public inline string to_string_released(global::JS.String js) { - int len = js.get_maximum_utf8_cstring_size(); - string str = string.nfill(len, 0); - js.get_utf8_cstring(str, len); - js.release(); - return str; + public inline string to_native_string(global::JS.String js) { + size_t len = js.get_maximum_utf8_cstring_size(); + uint8[] str = new uint8[len]; +#if VALA_0_42 + js.get_utf8_cstring(str); +#else + js_string_get_utf8_cstring(js, (char[]) str); +#endif + return (string) str; } /** @@ -125,11 +138,8 @@ global::JS.String js_name = new global::JS.String.create_with_utf8_cstring(name); global::JS.Value? err = null; global::JS.Value prop = object.get_property(context, js_name, out err); - try { - Geary.JS.check_exception(context, err); - } finally { - js_name.release(); - } + Geary.JS.check_exception(context, err); + return prop; } @@ -157,7 +167,7 @@ throw new Error.EXCEPTION( "JS exception thrown [%s]: %s" - .printf(err_type.to_string(), to_string_released(err_str)) + .printf(err_type.to_string(), to_native_string(err_str)) ); } } @@ -177,31 +187,31 @@ builder.append("\x00"); break; case '\'': - builder.append("""\'"""); + builder.append("\\\'"); break; case '"': - builder.append("""\""""); + builder.append("\\\""); break; case '\\': - builder.append("""\\"""); + builder.append("\\\\"); break; case '\n': - builder.append("""\n"""); + builder.append("\\n"); break; case '\r': - builder.append("""\r"""); + builder.append("\\r"); break; case '\x0b': builder.append("\x0b"); break; case '\t': - builder.append("""\t"""); + builder.append("\\t"); break; case '\b': - builder.append("""\b"""); + builder.append("\\b"); break; case '\f': - builder.append("""\f"""); + builder.append("\\f"); break; default: builder.append_unichar(c); diff -Nru geary-0.12.4/src/engine/util/util-object.vala geary-3.32.0/src/engine/util/util-object.vala --- geary-0.12.4/src/engine/util/util-object.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/util/util-object.vala 2019-03-17 13:39:29.000000000 +0000 @@ -9,7 +9,7 @@ /** * Creates a set of property bindings from source to dest with the given binding flags. */ -public Gee.List? mirror_properties(Object source, Object dest, BindingFlags +public Gee.List? mirror_properties(Object source, Object dest, BindingFlags flags = GLib.BindingFlags.DEFAULT | GLib.BindingFlags.SYNC_CREATE) { // Make sets of both object's properties. Gee.HashSet source_properties = new Gee.HashSet(); @@ -18,17 +18,17 @@ Gee.HashSet dest_properties = new Gee.HashSet(); Collection.add_all_array(dest_properties, dest.get_class().list_properties()); - + // Remove properties from source_properties that are not in both sets. source_properties.retain_all(dest_properties); - + // Create all bindings. Gee.List bindings = new Gee.ArrayList(); foreach(ParamSpec ps in source_properties) { if ((ps.flags & ParamFlags.WRITABLE) != 0) bindings.add(source.bind_property(ps.name, dest, ps.name, flags)); } - + return bindings.size > 0 ? bindings : null; } @@ -38,9 +38,26 @@ public void unmirror_properties(Gee.List bindings) { foreach(Binding b in bindings) b.unref(); - + bindings.clear(); } +/** Convenience method for getting an enum value's nick name. */ +public string to_enum_nick(GLib.Type type, E value) { + GLib.EnumClass enum_type = (GLib.EnumClass) type.class_ref(); + return enum_type.get_value((int) value).value_nick; } +/** Convenience method for getting an enum value's from its nick name. */ +public E from_enum_nick(GLib.Type type, string nick) throws EngineError { + GLib.EnumClass enum_type = (GLib.EnumClass) type.class_ref(); + unowned GLib.EnumValue? e_value = enum_type.get_value_by_nick(nick); + if (e_value == null) { + throw new EngineError.BAD_PARAMETERS( + "Unknown %s enum value: %s", typeof(E).name(), nick + ); + } + return (E) e_value.value; +} + +} diff -Nru geary-0.12.4/src/engine/util/util-reference-semantics.vala geary-3.32.0/src/engine/util/util-reference-semantics.vala --- geary-0.12.4/src/engine/util/util-reference-semantics.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/util/util-reference-semantics.vala 2019-03-17 13:39:29.000000000 +0000 @@ -36,30 +36,30 @@ */ public interface Geary.ReferenceSemantics : BaseObject { protected abstract int manual_ref_count { get; protected set; } - + /** * A ReferenceSemantics object can fire this signal for force all SmartReferences to drop their * reference to it. */ public signal void release_now(); - + /** * This signal is fired when all SmartReferences to the ReferenceSemantics object have dropped * their reference. */ public signal void freed(); - + internal void claim() { manual_ref_count++; } - + internal void release() { assert(manual_ref_count > 0); - + if (--manual_ref_count == 0) freed(); } - + public bool is_freed() { return (manual_ref_count == 0); } @@ -71,7 +71,7 @@ */ public abstract class Geary.SmartReference : BaseObject { private ReferenceSemantics? reffed; - + /** * This signal is fired when the SmartReference drops its reference to a ReferenceSemantics * object due to it firing "release-now". @@ -80,28 +80,28 @@ */ public virtual signal void reference_broken() { } - - public SmartReference(ReferenceSemantics reffed) { + + protected SmartReference(ReferenceSemantics reffed) { this.reffed = reffed; - + reffed.release_now.connect(on_release_now); - + reffed.claim(); } - + ~SmartReference() { if (reffed != null) reffed.release(); } - + public ReferenceSemantics? get_reference() { return reffed; } - + private void on_release_now() { reffed.release(); reffed = null; - + reference_broken(); } } diff -Nru geary-0.12.4/src/engine/util/util-scheduler.vala geary-3.32.0/src/engine/util/util-scheduler.vala --- geary-0.12.4/src/engine/util/util-scheduler.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/util/util-scheduler.vala 2019-03-17 13:39:29.000000000 +0000 @@ -16,60 +16,60 @@ private class ScheduledInstance : BaseObject, Geary.ReferenceSemantics { protected int manual_ref_count { get; protected set; } - + private unowned SourceFunc? cb; private uint sched_id; - + // Can't rely on ReferenceSemantic's "freed" signal because it's possible all the SmartReferences // have been dropped but the callback is still pending. This signal is fired when all references // are dropped and the callback is not pending or has been cancelled. public signal void dead(); - + public ScheduledInstance.on_idle(SourceFunc cb, int priority) { this.cb = cb; sched_id = Idle.add(on_callback, priority); - + freed.connect(on_freed); } - + public ScheduledInstance.after_msec(uint msec, SourceFunc cb, int priority) { this.cb = cb; sched_id = Timeout.add(msec, on_callback, priority); - + freed.connect(on_freed); } - + public ScheduledInstance.after_sec(uint sec, SourceFunc cb, int priority) { this.cb = cb; sched_id = Timeout.add_seconds(sec, on_callback, priority); - + freed.connect(on_freed); } - + public void cancel() { if (sched_id == 0) return; - + // cancel callback Source.remove(sched_id); - + // mark as cancelled cb = null; sched_id = 0; - + // tell SmartReferences to drop their refs // (this in turn will call "freed", firing the "dead" signal) release_now(); } - + private bool on_callback() { bool again = (cb != null) ? cb() : false; - + if (!again) { // mark as cancelled cb = null; sched_id = 0; - + // tell the SmartReferences to drop their refs // (this in turn will call "freed", firing the "dead" signal, unless all refs were // released earlier and the callback was pending, so fire "dead" now) @@ -78,10 +78,10 @@ else release_now(); } - + return again; } - + private void on_freed() { // only fire "dead" if marked as cancelled, otherwise wait until callback completes if (sched_id == 0) @@ -93,7 +93,7 @@ internal Scheduled(ScheduledInstance instance) { base (instance); } - + public void cancel() { ScheduledInstance? instance = get_reference() as ScheduledInstance; if (instance != null) @@ -115,18 +115,18 @@ private Scheduled schedule_instance(ScheduledInstance inst) { inst.dead.connect(on_scheduled_dead); - + if (scheduled_map == null) scheduled_map = new Gee.HashSet(); - + scheduled_map.add(inst); - + return new Scheduled(inst); } private void on_scheduled_dead(ScheduledInstance inst) { inst.dead.disconnect(on_scheduled_dead); - + bool removed = scheduled_map.remove(inst); assert(removed); } diff -Nru geary-0.12.4/src/engine/util/util-stream.vala geary-3.32.0/src/engine/util/util-stream.vala --- geary-0.12.4/src/engine/util/util-stream.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/util/util-stream.vala 2019-03-17 13:39:29.000000000 +0000 @@ -6,40 +6,169 @@ namespace Geary.Stream { -/** - * Provides an asynchronous version of OutputStream.write_all(). - */ -public async void write_all_async(OutputStream outs, Memory.Buffer buffer, Cancellable? cancellable) + /** + * Provides an asynchronous version of OutputStream.write_all(). + */ + public async void write_all_async(OutputStream outs, Memory.Buffer buffer, Cancellable? cancellable) throws Error { - if (buffer.size == 0) - return; - - // use an unowned bytes buffer whenever possible - Bytes? bytes = null; - unowned uint8[] data; - Memory.UnownedBytesBuffer? unowned_bytes = buffer as Memory.UnownedBytesBuffer; - if (unowned_bytes != null) { - data = unowned_bytes.to_unowned_uint8_array(); - } else { - // hold the reference to the Bytes object until finished - bytes = buffer.get_bytes(); - data = bytes.get_data(); + if (buffer.size == 0) + return; + + // use an unowned bytes buffer whenever possible + Bytes? bytes = null; + unowned uint8[] data; + Memory.UnownedBytesBuffer? unowned_bytes = buffer as Memory.UnownedBytesBuffer; + if (unowned_bytes != null) { + data = unowned_bytes.to_unowned_uint8_array(); + } else { + // hold the reference to the Bytes object until finished + bytes = buffer.get_bytes(); + data = bytes.get_data(); + } + + ssize_t offset = 0; + do { + offset += yield outs.write_async(data[offset:data.length], Priority.DEFAULT, cancellable); + } while (offset < data.length); } - - ssize_t offset = 0; - do { - offset += yield outs.write_async(data[offset:data.length], Priority.DEFAULT, cancellable); - } while (offset < data.length); -} -/** - * Asynchronously writes the entire string to the OutputStream. - */ -public async void write_string_async(OutputStream outs, string? str, Cancellable? cancellable) + /** + * Asynchronously writes the entire string to the OutputStream. + */ + public async void write_string_async(OutputStream outs, string? str, Cancellable? cancellable) throws Error { - if (!String.is_empty(str)) - yield write_all_async(outs, new Memory.StringBuffer(str), cancellable); -} + if (!String.is_empty(str)) + yield write_all_async(outs, new Memory.StringBuffer(str), cancellable); + } -} + public class MidstreamConverter : BaseObject, Converter { + public uint64 total_bytes_read { get; private set; default = 0; } + public uint64 total_bytes_written { get; private set; default = 0; } + public uint64 converted_bytes_read { get; private set; default = 0; } + public uint64 converted_bytes_written { get; private set; default = 0; } + + public bool log_performance { get; set; default = false; } + + private string name; + private Converter? converter = null; + + public MidstreamConverter(string name) { + this.name = name; + } + + public bool install(Converter converter) { + if (this.converter != null) + return false; + + this.converter = converter; + + return true; + } + + public ConverterResult convert(uint8[] inbuf, uint8[] outbuf, ConverterFlags flags, + out size_t bytes_read, out size_t bytes_written) throws Error { + if (converter != null) { + ConverterResult result = converter.convert(inbuf, outbuf, flags, out bytes_read, out bytes_written); + + total_bytes_read += bytes_read; + total_bytes_written += bytes_written; + + converted_bytes_read += bytes_read; + converted_bytes_written += bytes_written; + + if (log_performance && (bytes_read > 0 || bytes_written > 0)) { + double pct = (converted_bytes_read > converted_bytes_written) + ? (double) converted_bytes_written / (double) converted_bytes_read + : (double) converted_bytes_read / (double) converted_bytes_written; + debug("%s read/written: %s/%s (%lld%%)", name, converted_bytes_read.to_string(), + converted_bytes_written.to_string(), (long) (pct * 100.0)); + } + + return result; + } + + // passthrough + size_t copied = size_t.min(inbuf.length, outbuf.length); + if (copied > 0) + GLib.Memory.copy(outbuf, inbuf, copied); + + bytes_read = copied; + bytes_written = copied; + + total_bytes_read += copied; + total_bytes_written += copied; + + if ((flags & ConverterFlags.FLUSH) != 0) + return ConverterResult.FLUSHED; + + if ((flags & ConverterFlags.INPUT_AT_END) != 0) + return ConverterResult.FINISHED; + + return ConverterResult.CONVERTED; + } + + public void reset() { + if (converter != null) + converter.reset(); + } + } + + + /** + * Adaptor from a GMime stream to a GLib OutputStream. + */ + public class MimeOutputStream : GMime.Stream { + + GLib.OutputStream dest; + int64 written = 0; + + + public MimeOutputStream(GLib.OutputStream dest) { + this.dest = dest; + } + + public override int64 length() { + // This is a bit of a kludge, but we use it in + // ImapDB.Attachment + return this.written; + } + + public override ssize_t write(string buf, size_t len) { + ssize_t ret = -1; + try { + ret = this.dest.write(buf.data[0:len]); + this.written += len; + } catch (IOError err) { + // Oh well + } + return ret; + } + + public override int close() { + int ret = -1; + try { + ret = this.dest.close() ? 0 : -1; + } catch (IOError err) { + // Oh well + } + return ret; + } + + public override int flush () { + int ret = -1; + try { + ret = this.dest.flush() ? 0 : -1; + } catch (Error err) { + // Oh well + } + return ret; + } + + public override bool eos () { + return this.dest.is_closed() || this.dest.is_closing(); + } + } + + +} diff -Nru geary-0.12.4/src/engine/util/util-string.vala geary-3.32.0/src/engine/util/util-string.vala --- geary-0.12.4/src/engine/util/util-string.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/util/util-string.vala 2019-03-17 13:39:29.000000000 +0000 @@ -10,8 +10,13 @@ namespace Geary.String { +/** The end-of-string character, NUL. */ public const char EOS = '\0'; +/** A regex that matches one or more whitespace or non-printing chars. */ +public const string WS_OR_NP = "[[:space:][:cntrl:]]+"; + + public bool is_empty_or_whitespace(string? str) { return (str == null || str[0] == EOS || str.strip()[0] == EOS); } @@ -34,7 +39,7 @@ if (ch in chars) return true; } - + return false; } @@ -50,23 +55,23 @@ return strcmp(a.down(), b.down()); } -// Removes redundant spaces, tabs, and newlines. -public string reduce_whitespace(string _s) { - string s = _s; - s = s.replace("\n", " "); - s = s.replace("\r", " "); - s = s.replace("\t", " "); - s = s.strip(); - - // Condense multiple spaces to one. - for (int i = 1; i < s.length; i++) { - if (s.get_char(i) == ' ' && s.get_char(i - 1) == ' ') { - s = s.slice(0, i - 1) + s.slice(i, s.length); - i--; - } +/** + * Removes redundant white space and non-printing characters. + * + * @return the input string /str/, modified so that any non-printing + * characters are converted to spaces, all consecutive spaces are + * coalesced into a single space, and stripped of leading and trailing + * white space. If //null// is passed in, the empty string is + * returned. + */ +public string reduce_whitespace(string? str) { + string s = str ?? ""; + try { + s = new Regex(WS_OR_NP).replace(s, -1, 0, " "); + } catch (Error err) { + // Oh well } - - return s; + return s.strip(); } // Slices a string to, at most, max_length number of bytes (NOT including the null.) @@ -76,7 +81,7 @@ public string safe_byte_substring(string s, ssize_t max_length) { if (s.length < max_length) return s; - + return glib_substring(s, 0, s.char_count(max_length)); } diff -Nru geary-0.12.4/src/engine/util/util-synchronization.vala geary-3.32.0/src/engine/util/util-synchronization.vala --- geary-0.12.4/src/engine/util/util-synchronization.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/util/util-synchronization.vala 2019-03-17 13:39:29.000000000 +0000 @@ -15,13 +15,13 @@ public class SpinWaiter : BaseObject { public delegate bool PollService(); - + private int poll_msec; private PollService cb; private Mutex mutex = Mutex(); private Cond cond = Cond(); private bool notified = false; - + /** * Create a {@link SpinWaiter}. * @@ -29,11 +29,11 @@ * the {@link PollService} to execute. If poll_msec is zero or less, PollService will be * called constantly. */ - public SpinWaiter(int poll_msec, PollService cb) { + public SpinWaiter(int poll_msec, owned PollService cb) { this.poll_msec = poll_msec; - this.cb = cb; + this.cb = (owned) cb; } - + /** * Spins waiting for a completion state to be reached. * @@ -50,15 +50,15 @@ public bool wait(Cancellable? cancellable = null) throws Error { // normalize poll_msec; negative values are zeroed int64 actual_poll_msec = Numeric.int64_floor(0, poll_msec); - + bool result; - + mutex.lock(); - + while (!notified) { if (cancellable != null && cancellable.is_cancelled()) break; - + int64 end_time = get_monotonic_time() + (actual_poll_msec * TimeSpan.MILLISECOND); if (!cond.wait_until(mutex, end_time)) { // timeout passed, allow the callback to run @@ -66,23 +66,23 @@ if (!cb()) { // PollService returned false, abort mutex.lock(); - + break; } mutex.lock(); } } - + result = notified; - + mutex.unlock(); - + if (cancellable.is_cancelled()) throw new IOError.CANCELLED("SpinWaiter.wait cancelled"); - + return result; } - + /** * Signals a completion state to a thread calling {@link wait}. * @@ -91,13 +91,13 @@ */ public new void notify() { mutex.lock(); - + notified = true; cond.broadcast(); - + mutex.unlock(); } - + /** * Indicates if the {@link SpinWaiter} has been notified. * @@ -110,13 +110,13 @@ */ public bool is_notified() { bool result; - + mutex.lock(); - + result = notified; - + mutex.unlock(); - + return result; } } diff -Nru geary-0.12.4/src/engine/util/util-timeout-manager.vala geary-3.32.0/src/engine/util/util-timeout-manager.vala --- geary-0.12.4/src/engine/util/util-timeout-manager.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/util/util-timeout-manager.vala 2019-03-17 13:39:29.000000000 +0000 @@ -33,6 +33,31 @@ public delegate void TimeoutFunc(TimeoutManager manager); + // Instances of this are passed to GLib.Timeout to execute the + // callback. This holds a weak ref to the manager itself allowing + // it to be destroyed if all of its references are broken, even if + // it is still queued via GLib.Timeout. + private class HandlerRef : GLib.Object { + + private GLib.WeakRef manager; + + + public HandlerRef(TimeoutManager manager) { + this.manager = GLib.WeakRef(manager); + } + + public bool execute() { + bool ret = Source.REMOVE; + TimeoutManager? manager = this.manager.get() as TimeoutManager; + if (manager != null) { + ret = manager.execute(); + } + return ret; + } + + } + + /** Determines if {@link interval} represent seconds. */ public bool use_seconds; @@ -50,10 +75,8 @@ get { return this.source_id >= 0; } } - // Callback must be unowned to avoid reference loop with owner's - // class when a closure is used as the callback. private unowned TimeoutFunc callback; - private int source_id = -1; + private int64 source_id = -1; /** @@ -91,10 +114,16 @@ */ public void start() { reset(); + + HandlerRef handler = new HandlerRef(this); this.source_id = (int) ( (this.use_seconds) - ? GLib.Timeout.add_seconds(this.interval, on_trigger, this.priority) - : GLib.Timeout.add(this.interval, on_trigger, this.priority) + ? GLib.Timeout.add_seconds( + this.interval, handler.execute, this.priority + ) + : GLib.Timeout.add( + this.interval, handler.execute, this.priority + ) ); } @@ -107,25 +136,26 @@ * @return `true` if the timeout was already running, else `false` */ public bool reset() { - bool is_running = this.is_running; - if (is_running) { - Source.remove(this.source_id); + if (this.is_running) { + Source.remove((uint) this.source_id); this.source_id = -1; } - return is_running; + return this.is_running; } - private bool on_trigger() { + private bool execute() { bool ret = Source.CONTINUE; + // If running only once, reset the source id now in case the // callback resets the timer while it is executing, so we // avoid removing the source just before it would be removed - // after this call anyway + // after this call anyway. if (this.repetition == Repeat.ONCE) { this.source_id = -1; ret = Source.REMOVE; } - callback(this); + + this.callback(this); return ret; } diff -Nru geary-0.12.4/src/engine/util/util-time.vala geary-3.32.0/src/engine/util/util-time.vala --- geary-0.12.4/src/engine/util/util-time.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/util/util-time.vala 2019-03-17 13:39:29.000000000 +0000 @@ -23,7 +23,7 @@ // Time's year is number of years after 1900 tm.year = Numeric.int_floor(datetime.get_year() - 1900, 0); tm.isdst = datetime.is_daylight_savings() ? 1 : 0; - + return tm.mktime(); } diff -Nru geary-0.12.4/src/engine/util/util-trillian.vala geary-3.32.0/src/engine/util/util-trillian.vala --- geary-0.12.4/src/engine/util/util-trillian.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/engine/util/util-trillian.vala 2019-03-17 13:39:29.000000000 +0000 @@ -13,71 +13,71 @@ UNKNOWN = -1, FALSE = 0, TRUE = 1; - + public bool to_boolean(bool if_unknown) { switch (this) { case UNKNOWN: return if_unknown; - + case FALSE: return false; - + case TRUE: return true; - + default: assert_not_reached(); } } - + public inline static Trillian from_boolean(bool b) { return b ? TRUE : FALSE; } - + public inline int to_int() { return (int) this; } - + public static Trillian from_int(int i) { switch (i) { case 0: return FALSE; - + case 1: return TRUE; - + default: return UNKNOWN; } } - + public inline bool is_certain() { return this == TRUE; } - + public inline bool is_uncertain() { return this != TRUE; } - + public inline bool is_possible() { return this != FALSE; } - + public inline bool is_impossible() { return this == FALSE; } - + public string to_string() { switch (this) { case UNKNOWN: return "unknown"; - + case FALSE: return "false"; - + case TRUE: return "true"; - + default: assert_not_reached(); } diff -Nru geary-0.12.4/src/mailer/main.vala geary-3.32.0/src/mailer/main.vala --- geary-0.12.4/src/mailer/main.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/mailer/main.vala 2019-03-17 13:39:29.000000000 +0000 @@ -12,13 +12,13 @@ async void main_async() throws Error { Geary.Smtp.ClientSession session = new Geary.Smtp.ClientSession(endpoint); - + Geary.Smtp.Greeting? greeting = yield session.login_async(credentials); stdout.printf("%s\n", greeting.to_string()); - + for (int ctr = 0; ctr < arg_count; ctr++) { Geary.RFC822.Message msg; - + if (arg_full_file != null) { debug("%s", arg_full_file); msg = new Geary.RFC822.Message.from_buffer(new Geary.Memory.FileBuffer( @@ -26,33 +26,33 @@ } else { string subj_msg = "#%d".printf(ctr + 1); composed_email.subject = subj_msg; - + if (Geary.String.is_empty(arg_file)) { composed_email.body_text = subj_msg; } else { string contents; FileUtils.get_contents(arg_file, out contents); - + composed_email.body_text = contents; } - + if (!Geary.String.is_empty(arg_html)) { string contents; FileUtils.get_contents(arg_html, out contents); - + composed_email.body_html = contents; } - + msg = new Geary.RFC822.Message.from_composed_email(composed_email, null); } - + stdout.printf("\n\n%s\n\n", msg.to_string()); - - yield session.send_email_async(msg.sender, msg); - + + yield session.send_email_async(msg.from.get(0), msg); + stdout.printf("Sent email #%d\n", ctr); } - + Geary.Smtp.Response? logout = yield session.logout_async(false); stdout.printf("%s\n", logout.to_string()); } @@ -64,7 +64,7 @@ stderr.printf("%s\n", err.message); ec = 1; } - + if (main_loop != null) main_loop.quit(); } @@ -99,12 +99,14 @@ { null } }; +const int SMTP_TIMEOUT_SEC = 30; + bool verify_required(string? arg, string name) { if (!Geary.String.is_empty(arg)) return true; - + stdout.printf("%s required\n", name); - + return false; } @@ -117,61 +119,70 @@ } catch (Error err) { error ("Failed to parse command line: %s", err.message); } - + if (!arg_gmail && !verify_required(arg_hostname, "Hostname")) return 1; - + if (arg_full_file == null && !verify_required(arg_from, "From:")) return 1; - + if (arg_full_file == null && !verify_required(arg_to, "To:")) return 1; - + if (!verify_required(arg_user, "Username")) return 1; - + if (!verify_required(arg_pass, "Password")) return 1; - + if (arg_count < 1) arg_count = 1; - + if (arg_gmail) { - endpoint = new Geary.Endpoint("smtp.gmail.com", Geary.Smtp.ClientConnection.DEFAULT_PORT_STARTTLS, - Geary.Endpoint.Flags.STARTTLS, - Geary.Smtp.ClientConnection.DEFAULT_TIMEOUT_SEC); + endpoint = new Geary.Endpoint( + new GLib.NetworkAddress("smtp.gmail.com", Geary.Smtp.SUBMISSION_PORT), + Geary.TlsNegotiationMethod.START_TLS, + SMTP_TIMEOUT_SEC + ); } else { - Geary.Endpoint.Flags flags = Geary.Endpoint.Flags.NONE; - if (!arg_no_tls) - flags |= Geary.Endpoint.Flags.SSL; - - endpoint = new Geary.Endpoint(arg_hostname, (uint16) arg_port, flags, - Geary.Smtp.ClientConnection.DEFAULT_TIMEOUT_SEC); + Geary.TlsNegotiationMethod method = Geary.TlsNegotiationMethod.TRANSPORT; + if (arg_no_tls) { + method = Geary.TlsNegotiationMethod.START_TLS; + } + endpoint = new Geary.Endpoint( + new GLib.NetworkAddress(arg_hostname, (uint16) arg_port), + method, + SMTP_TIMEOUT_SEC + ); } stdout.printf("Enabling debug: %s\n", arg_debug.to_string()); if (arg_debug) { Geary.Logging.init(); Geary.Logging.log_to(stdout); + Geary.Logging.enable_flags(Geary.Logging.Flag.NETWORK); + GLib.Log.set_default_handler(Geary.Logging.default_handler); } - + Geary.RFC822.init(); - - credentials = new Geary.Credentials(arg_user, arg_pass); - + + credentials = new Geary.Credentials( + Geary.Credentials.Method.PASSWORD, arg_user, arg_pass + ); + if (arg_full_file == null) { composed_email = new Geary.ComposedEmail(new DateTime.now_local(), new Geary.RFC822.MailboxAddresses.single(new Geary.RFC822.MailboxAddress(null, arg_from))); composed_email.to = new Geary.RFC822.MailboxAddresses.single( new Geary.RFC822.MailboxAddress(null, arg_to)); } - + main_loop = new MainLoop(); - + main_async.begin(on_main_completed); - + main_loop.run(); - + return ec; } diff -Nru geary-0.12.4/src/mailer/meson.build geary-3.32.0/src/mailer/meson.build --- geary-0.12.4/src/mailer/meson.build 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/src/mailer/meson.build 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,18 @@ +geary_mailer_sources = files( + 'main.vala', +) + +geary_mailer_dependencies = [ + gee, + gmime, + webkit2gtk, + geary_engine_dep, +] + +geary_mailer = executable('geary-mailer', + geary_mailer_sources, + dependencies: geary_mailer_dependencies, + include_directories: config_h_dir, + vala_args: geary_vala_options, + c_args: geary_c_options, +) diff -Nru geary-0.12.4/src/meson.build geary-3.32.0/src/meson.build --- geary-0.12.4/src/meson.build 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/src/meson.build 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,119 @@ +# Version + +if meson.project_version().endswith('-dev') + # Development build + geary_version_vala = vcs_tag( + command: '../build-aux/git_version.py', + input: 'geary-version.vala.in', + output: 'geary-version.vala', + replace_string: '@VERSION@' + ) +else + # Release build + geary_version_vala = configure_file( + input: 'geary-version.vala.in', + output: 'geary-version.vala', + configuration: conf, + ) +endif + +# Common vala options +geary_vala_options = [ + '--target-glib=@0@'.format(target_glib), + '--thread', + '--enable-checking', +] + +# Symbols for valac's preprocessor must be defined as compiler args, +# not in the code or in config.h +if reference_tracking + geary_vala_options += [ '--define=REF_TRACKING' ] +endif +if not poodle + geary_vala_options += [ '--define=DISABLE_POODLE' ] +endif + +geary_c_options = [ + '-include', 'config.h', + # Select libunwind's optimised, local-only backtrace unwiding. See + # libunwind(3). + '-DUNW_LOCAL_ONLY', + # Neither GOA nor GCK want to hang out unless you are cool enough + '-DGOA_API_IS_SUBJECT_TO_CHANGE', + '-DGCK_API_SUBJECT_TO_CHANGE', +] + +subdir('sqlite3-unicodesn') +subdir('engine') +subdir('client') +subdir('console') +subdir('mailer') + +# Web process extension library +geary_web_process = library('geary-web-process', + join_paths('client', 'web-process', 'web-process-extension.vala'), + c_args: geary_c_options, + dependencies: [ + gee, + gmime, + webkit2gtk_web_extension , + geary_engine_dep, + ], + install: true, + install_dir: web_extensions_dir +) + +# Now finally, make the geary executable +geary_bin_sources = files( + join_paths('client', 'application', 'main.vala'), +) + +geary_bin_sources += [ + geary_compiled_schema, + geary_resources # Included here so they show up in the executable. +] +geary_bin_dependencies = [ + folks, + gdk, + geary_client_dep, + geary_engine_dep, + gee, + gmime, + goa, + gtk, + libmath, + libsoup, + webkit2gtk, +] + +geary_bin = executable('geary', + geary_bin_sources, + dependencies: geary_bin_dependencies, + vala_args: geary_vala_options, + c_args: geary_c_options, + install: true, +) + +if enable_valadoc + geary_docs = custom_target('valadoc', + build_by_default: true, + depends: geary_engine_lib, + input: geary_engine_sources, + output: 'valadoc', + command: [ valadoc, + '--verbose', + '--force', + '--internal', + '--package-name=@0@-@1@'.format(meson.project_name(), meson.project_version()), + '--package-version=@0@'.format(meson.project_version()), + '--target-glib=@0@'.format(target_glib), + '-b', meson.current_source_dir(), + '-o', '@OUTPUT@', + '--vapidir=@0@'.format(vapi_dir), + '--vapidir=@0@'.format(meson.current_build_dir()), + # Hopefully, Meson gets baked-in valadoc support, so we don't have to do this (see also https://github.com/mesonbuild/meson/issues/894) + '--pkg', 'glib-2.0', '--pkg', 'gio-2.0', '--pkg', 'gee-0.8', '--pkg', 'sqlite3', '--pkg', 'gmime-2.6', '--pkg', 'javascriptcoregtk-4.0', '--pkg', 'libxml-2.0', '--pkg', 'libunwind', + '@INPUT@', + ] + ) +endif diff -Nru geary-0.12.4/src/sqlite3-unicodesn/CMakeLists.txt geary-3.32.0/src/sqlite3-unicodesn/CMakeLists.txt --- geary-0.12.4/src/sqlite3-unicodesn/CMakeLists.txt 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/sqlite3-unicodesn/CMakeLists.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,42 +0,0 @@ -# CMake definitions to build sqlite3-unicodesn as a static library. This file -# was added for Geary based on the project's Makefile. - -# If you update this list, you should also double-check -# find_appropriate_search_stemmer() in src/engine/imap-db/imap-db-database.vala -set(STEMMERS - danish dutch english finnish french german hungarian - italian norwegian portuguese romanian russian spanish - swedish turkish -) - -set(SQLITE3_UNICODESN_SRC - fts3_unicode2.c - fts3_unicodesn.c - static.c - - libstemmer_c/runtime/api_sq3.c - libstemmer_c/runtime/utilities_sq3.c -) - -add_definitions( - -DSQLITE_ENABLE_FTS4 - -DSQLITE_ENABLE_FTS4_UNICODE61 -) - -if (HAVE_FTS3_TOKENIZER) - message(STATUS "SQLite FTS3 tokenizer support: ON") - add_definitions(-DHAVE_FTS3_TOKENIZER) -endif () - -include_directories( - libstemmer_c/runtime - libstemmer_c/src_c -) - -foreach(stemmer ${STEMMERS}) - list(APPEND SQLITE3_UNICODESN_SRC libstemmer_c/src_c/stem_UTF_8_${stemmer}.c) - add_definitions(-DWITH_STEMMER_${stemmer}) -endforeach() - -add_library(sqlite3-unicodesn STATIC ${SQLITE3_UNICODESN_SRC}) -target_link_libraries(sqlite3-unicodesn sqlite3) diff -Nru geary-0.12.4/src/sqlite3-unicodesn/fts3_unicode2.c geary-3.32.0/src/sqlite3-unicodesn/fts3_unicode2.c --- geary-0.12.4/src/sqlite3-unicodesn/fts3_unicode2.c 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/sqlite3-unicodesn/fts3_unicode2.c 2019-03-17 13:39:29.000000000 +0000 @@ -39,7 +39,7 @@ ** C. It is not possible to represent a range larger than 1023 codepoints ** using this format. */ - const static unsigned int aEntry[] = { + static const unsigned int aEntry[] = { 0x00000030, 0x0000E807, 0x00016C06, 0x0001EC2F, 0x0002AC07, 0x0002D001, 0x0002D803, 0x0002EC01, 0x0002FC01, 0x00035C01, 0x0003DC01, 0x000B0804, 0x000B480E, 0x000B9407, 0x000BB401, @@ -132,7 +132,7 @@ return ( (aAscii[c >> 5] & (1 << (c & 0x001F)))==0 ); }else if( c<(1<<22) ){ unsigned int key = (((unsigned int)c)<<10) | 0x000003FF; - int iRes; + int iRes= 0; int iHi = sizeof(aEntry)/sizeof(aEntry[0]) - 1; int iLo = 0; while( iHi>=iLo ){ @@ -203,7 +203,7 @@ } assert( key>=aDia[iRes] ); return ((c > (aDia[iRes]>>3) + (aDia[iRes]&0x07)) ? c : (int)aChar[iRes]); -}; +} /* diff -Nru geary-0.12.4/src/sqlite3-unicodesn/meson.build geary-3.32.0/src/sqlite3-unicodesn/meson.build --- geary-0.12.4/src/sqlite3-unicodesn/meson.build 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/src/sqlite3-unicodesn/meson.build 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,48 @@ +sqlite3_unicodesn_sources = [ + 'fts3_unicode2.c', + 'fts3_unicodesn.c', + 'static.c', + + join_paths('libstemmer_c', 'runtime', 'api_sq3.c'), + join_paths('libstemmer_c', 'runtime', 'utilities_sq3.c'), +] + +sqlite3_unicodesn_c_flags = [ + '-DSQLITE_ENABLE_FTS4', + '-DSQLITE_ENABLE_FTS4_UNICODE61', +] + +sqlite3_unicodesn_stemmers = [ + 'danish', + 'dutch', + 'english', + 'finnish', + 'french', + 'german', + 'hungarian', + 'italian', + 'norwegian', + 'portuguese', + 'romanian', + 'russian', + 'spanish', + 'swedish', + 'turkish', +] + +foreach stemmer: sqlite3_unicodesn_stemmers + sqlite3_unicodesn_sources += 'libstemmer_c/src_c/stem_UTF_8_@0@.c'.format(stemmer) + sqlite3_unicodesn_c_flags += '-DWITH_STEMMER_@0@'.format(stemmer) +endforeach + +sqlite3_unicodesn_includes = [ + include_directories('libstemmer_c/runtime'), + include_directories('libstemmer_c/src_c'), +] + +sqlite3_unicodesn_lib = static_library('sqlite3-unicodesn', + sqlite3_unicodesn_sources, + dependencies: sqlite, + c_args: sqlite3_unicodesn_c_flags, + include_directories: sqlite3_unicodesn_includes, +) diff -Nru geary-0.12.4/src/sqlite3-unicodesn/static.c geary-3.32.0/src/sqlite3-unicodesn/static.c --- geary-0.12.4/src/sqlite3-unicodesn/static.c 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/src/sqlite3-unicodesn/static.c 2019-03-17 13:39:29.000000000 +0000 @@ -28,13 +28,11 @@ sqlite3_stmt *pStmt; const char *zSql = "SELECT fts3_tokenizer(?, ?)"; -#ifdef HAVE_FTS3_TOKENIZER /* Enable the 2-argument form of fts3_tokenizer in SQLite >= 3.12 */ rc = sqlite3_db_config(db,SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER,1,0); if( rc!=SQLITE_OK ){ return rc; } -#endif rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); if( rc!=SQLITE_OK ){ diff -Nru geary-0.12.4/test/client/accounts/accounts-manager-test.vala geary-3.32.0/test/client/accounts/accounts-manager-test.vala --- geary-0.12.4/test/client/accounts/accounts-manager-test.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/test/client/accounts/accounts-manager-test.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,278 @@ +/* + * Copyright 2018 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +class Accounts.ManagerTest : TestCase { + + + private const string TEST_ID = "test"; + + private Manager? test = null; + private Geary.CredentialsMediator? mediator = null; + private Geary.AccountInformation? account = null; + private Geary.RFC822.MailboxAddress primary_mailbox; + private File? tmp = null; + + + public ManagerTest() { + base("AccountManagerTest"); + add_test("create_account", create_account); + add_test("create_orphan_account", create_orphan_account); + add_test( + "create_orphan_account_with_legacy", + create_orphan_account_with_legacy + ); + add_test( + "create_orphan_account_with_existing_dirs", + create_orphan_account_with_existing_dirs + ); + add_test("account_config_v1", account_config_v1); + add_test("account_config_legacy", account_config_legacy); + add_test("service_config_v1", service_config_v1); + add_test("service_config_legacy", service_config_legacy); + } + + public override void set_up() throws GLib.Error { + // XXX this whole thing stinks. We need to be able to test the + // engine without creating all of these dirs. + + this.tmp = GLib.File.new_for_path( + GLib.DirUtils.make_tmp("geary-engine-test-XXXXXX") + ); + + GLib.File config = this.tmp.get_child("config"); + config.make_directory(); + + GLib.File data = this.tmp.get_child("data"); + data.make_directory(); + + this.primary_mailbox = new Geary.RFC822.MailboxAddress( + null, "test1@example.com" + ); + + this.mediator = new Geary.MockCredentialsMediator(); + this.account = new Geary.AccountInformation( + TEST_ID, + Geary.ServiceProvider.OTHER, + this.mediator, + this.primary_mailbox + ); + this.test = new Manager(this.mediator, config, data); + } + + public override void tear_down() throws GLib.Error { + this.account = null; + this.mediator = null; + this.test = null; + this.primary_mailbox = null; + @delete(this.tmp); + } + + public void create_account() throws GLib.Error { + bool was_added = false; + bool was_enabled = false; + + this.test.account_added.connect((added, status) => { + was_added = (added == account); + was_enabled = (status == Manager.Status.ENABLED); + }); + + this.test.create_account.begin( + account, new GLib.Cancellable(), + (obj, res) => { async_complete(res); } + ); + this.test.create_account.end(async_result()); + + assert_int(1, this.test.size, "Account manager size"); + assert_equal(account, this.test.get_account(TEST_ID), "Is not contained"); + assert_true(was_added, "Was not added"); + assert_true(was_enabled, "Was not enabled"); + } + + public void create_orphan_account() throws GLib.Error { + this.test.new_orphan_account.begin( + Geary.ServiceProvider.OTHER, this.primary_mailbox, null, + (obj, res) => { async_complete(res); } + ); + Geary.AccountInformation account1 = + this.test.new_orphan_account.end(async_result()); + assert(account1.id == "account_01"); + + this.test.create_account.begin( + account1, new GLib.Cancellable(), + (obj, res) => { async_complete(res); } + ); + this.test.create_account.end(async_result()); + + this.test.new_orphan_account.begin( + Geary.ServiceProvider.OTHER, this.primary_mailbox, null, + (obj, res) => { async_complete(res); } + ); + Geary.AccountInformation account2 = + this.test.new_orphan_account.end(async_result()); + assert(account2.id == "account_02"); + } + + public void create_orphan_account_with_legacy() throws GLib.Error { + this.test.create_account.begin( + account, new GLib.Cancellable(), + (obj, res) => { async_complete(res); } + ); + this.test.create_account.end(async_result()); + + this.test.new_orphan_account.begin( + Geary.ServiceProvider.OTHER, this.primary_mailbox, null, + (obj, res) => { async_complete(res); } + ); + Geary.AccountInformation account1 = + this.test.new_orphan_account.end(async_result()); + assert(account1.id == "account_01"); + + this.test.create_account.begin( + account1, new GLib.Cancellable(), + (obj, res) => { async_complete(res); } + ); + this.test.create_account.end(async_result()); + + this.test.new_orphan_account.begin( + Geary.ServiceProvider.OTHER, this.primary_mailbox, null, + (obj, res) => { async_complete(res); } + ); + Geary.AccountInformation account2 = + this.test.new_orphan_account.end(async_result()); + assert(account2.id == "account_02"); + } + + public void create_orphan_account_with_existing_dirs() throws GLib.Error { + GLib.File existing = this.test.config_dir.get_child("account_01"); + existing.make_directory(); + existing = this.test.data_dir.get_child("account_02"); + existing.make_directory(); + + this.test.new_orphan_account.begin( + Geary.ServiceProvider.OTHER, this.primary_mailbox, null, + (obj, res) => { async_complete(res); } + ); + Geary.AccountInformation account = + this.test.new_orphan_account.end(async_result()); + assert(account.id == "account_03"); + } + + public void account_config_v1() throws GLib.Error { + this.account.label = "test-name"; + this.account.ordinal = 100; + this.account.prefetch_period_days = 42; + this.account.save_drafts = false; + this.account.save_sent = false; + this.account.signature = "blarg"; + this.account.use_signature = false; + Accounts.AccountConfigV1 config = new Accounts.AccountConfigV1(false); + + Geary.ConfigFile file = + new Geary.ConfigFile(this.tmp.get_child("config")); + + config.save(this.account, file); + Geary.AccountInformation copy = config.load( + file, TEST_ID, this.mediator, null, null + ); + + assert_true(this.account.equal_to(copy)); + } + + public void account_config_legacy() throws GLib.Error { + this.account.label = "test-name"; + this.account.ordinal = 100; + this.account.prefetch_period_days = 42; + this.account.save_drafts = false; + this.account.save_sent = false; + this.account.signature = "blarg"; + this.account.use_signature = false; + Accounts.AccountConfigLegacy config = + new Accounts.AccountConfigLegacy(); + + Geary.ConfigFile file = + new Geary.ConfigFile(this.tmp.get_child("config")); + + config.save(this.account, file); + Geary.AccountInformation copy = config.load( + file, TEST_ID, this.mediator, null, null + ); + + assert_true(this.account.equal_to(copy)); + } + + public void service_config_v1() throws GLib.Error { + // take a copy before updating the service info so we don't + // also copy the test data + Geary.AccountInformation copy = new Geary.AccountInformation.copy( + this.account + ); + + this.account.outgoing.host = "blarg"; + this.account.outgoing.port = 1234; + this.account.outgoing.transport_security = Geary.TlsNegotiationMethod.NONE; + this.account.outgoing.credentials = new Geary.Credentials( + Geary.Credentials.Method.PASSWORD, "testerson" + ); + this.account.outgoing.credentials_requirement = + Geary.Credentials.Requirement.NONE; + Accounts.ServiceConfigV1 config = new Accounts.ServiceConfigV1(); + Geary.ConfigFile file = + new Geary.ConfigFile(this.tmp.get_child("config")); + + config.save(this.account, this.account.outgoing, file); + config.load(file, copy, copy.outgoing); + + assert_true(this.account.outgoing.equal_to(copy.outgoing)); + } + + public void service_config_legacy() throws GLib.Error { + // take a copy before updating the service info so we don't + // also copy the test data + Geary.AccountInformation copy = new Geary.AccountInformation.copy( + this.account + ); + + this.account.outgoing.host = "blarg"; + this.account.outgoing.port = 1234; + this.account.outgoing.transport_security = Geary.TlsNegotiationMethod.NONE; + this.account.outgoing.credentials = new Geary.Credentials( + Geary.Credentials.Method.PASSWORD, "testerson" + ); + this.account.outgoing.credentials_requirement = + Geary.Credentials.Requirement.NONE; + Accounts.ServiceConfigLegacy config = new Accounts.ServiceConfigLegacy(); + Geary.ConfigFile file = + new Geary.ConfigFile(this.tmp.get_child("config")); + + config.save(this.account, this.account.outgoing, file); + config.load(file, copy, copy.outgoing); + + assert_true(this.account.outgoing.equal_to(copy.outgoing)); + } + + private void delete(File parent) throws GLib.Error { + FileInfo info = parent.query_info( + "standard::*", + FileQueryInfoFlags.NOFOLLOW_SYMLINKS + ); + + if (info.get_file_type () == FileType.DIRECTORY) { + FileEnumerator enumerator = parent.enumerate_children( + "standard::*", + FileQueryInfoFlags.NOFOLLOW_SYMLINKS + ); + + info = null; + while (((info = enumerator.next_file()) != null)) { + @delete(parent.get_child(info.get_name())); + } + } + + parent.delete(); + } + +} diff -Nru geary-0.12.4/test/client/application/geary-configuration-test.vala geary-3.32.0/test/client/application/geary-configuration-test.vala --- geary-0.12.4/test/client/application/geary-configuration-test.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/test/client/application/geary-configuration-test.vala 2019-03-17 13:39:29.000000000 +0000 @@ -5,7 +5,7 @@ * (version 2.1 or later). See the COPYING file in this distribution. */ -class ConfigurationTest : Gee.TestCase { +class ConfigurationTest : TestCase { private Configuration test_config = null; @@ -19,7 +19,7 @@ this.test_config = new Configuration(GearyApplication.APP_ID); } - public void desktop_environment() { + public void desktop_environment() throws Error { assert(this.test_config.desktop_environment == Configuration.DesktopEnvironment.UNKNOWN); diff -Nru geary-0.12.4/test/client/components/client-web-view-test-case.vala geary-3.32.0/test/client/components/client-web-view-test-case.vala --- geary-0.12.4/test/client/components/client-web-view-test-case.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/test/client/components/client-web-view-test-case.vala 2019-03-17 13:39:29.000000000 +0000 @@ -8,12 +8,12 @@ // Defined by CMake build script. extern const string _BUILD_ROOT_DIR; -public abstract class ClientWebViewTestCase : Gee.TestCase { +public abstract class ClientWebViewTestCase : TestCase { protected V? test_view = null; protected Configuration? config = null; - public ClientWebViewTestCase(string name) { + protected ClientWebViewTestCase(string name) { base(name); this.config = new Configuration(GearyApplication.APP_ID); ClientWebView.init_web_context( @@ -23,8 +23,8 @@ true ); try { - ClientWebView.load_scripts(); - } catch (Error err) { + ClientWebView.load_resources(GLib.File.new_for_path("/tmp")); + } catch (GLib.Error err) { assert_not_reached(); } } diff -Nru geary-0.12.4/test/client/components/client-web-view-test.vala geary-3.32.0/test/client/components/client-web-view-test.vala --- geary-0.12.4/test/client/components/client-web-view-test.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/test/client/components/client-web-view-test.vala 2019-03-17 13:39:29.000000000 +0000 @@ -5,7 +5,7 @@ * (version 2.1 or later). See the COPYING file in this distribution. */ -public class ClientWebViewTest : Gee.TestCase { +public class ClientWebViewTest : TestCase { public ClientWebViewTest() { base("ClientWebViewTest"); @@ -13,7 +13,7 @@ add_test("load_resources", load_resources); } - public void init_web_context() { + public void init_web_context() throws Error { Configuration config = new Configuration(GearyApplication.APP_ID); ClientWebView.init_web_context( config, @@ -23,10 +23,10 @@ ); } - public void load_resources() { + public void load_resources() throws GLib.Error { try { - ClientWebView.load_scripts(); - } catch (Error err) { + ClientWebView.load_resources(GLib.File.new_for_path("/tmp")); + } catch (GLib.Error err) { assert_not_reached(); } } diff -Nru geary-0.12.4/test/client/composer/composer-web-view-test.vala geary-3.32.0/test/client/composer/composer-web-view-test.vala --- geary-0.12.4/test/client/composer/composer-web-view-test.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/test/client/composer/composer-web-view-test.vala 2019-03-17 13:39:29.000000000 +0000 @@ -7,7 +7,6 @@ public class ComposerWebViewTest : ClientWebViewTestCase { - private const string BODY_TEMPLATE = """
%s


"""; public ComposerWebViewTest() { base("ComposerWebViewTest"); @@ -20,9 +19,13 @@ add_test("get_text_with_long_line", get_text_with_long_line); add_test("get_text_with_long_quote", get_text_with_long_quote); add_test("get_text_with_nbsp", get_text_with_nbsp); + add_test("get_text_with_named_link", get_text_with_named_link); + add_test("get_text_with_url_link", get_text_with_named_link); + add_test("get_text_with_surrounding_nbsps", get_text_with_surrounding_nbsps); + add_test("update_signature", update_signature); } - public void load_resources() { + public void load_resources() throws Error { try { ComposerWebView.load_resources(); } catch (Error err) { @@ -30,7 +33,7 @@ } } - public void edit_context() { + public void edit_context() throws Error { assert(!(new ComposerWebView.EditContext("0,,,").is_link)); assert(new ComposerWebView.EditContext("1,,,").is_link); assert(new ComposerWebView.EditContext("1,url,,").link_url == "url"); @@ -42,20 +45,15 @@ assert(new ComposerWebView.EditContext("0,,,12").font_size == 12); } - public void get_html() { - string html = "

para

"; - load_body_fixture(html); + public void get_html() throws GLib.Error { + string BODY = "

para

"; + load_body_fixture(BODY); this.test_view.get_html.begin((obj, ret) => { async_complete(ret); }); - try { - assert(this.test_view.get_html.end(async_result()) == - BODY_TEMPLATE.printf(html)); - } catch (Error err) { - print("Error: %s\n", err.message); - assert_not_reached(); - } + string html = this.test_view.get_html.end(async_result()); + assert_string(ComposerPageStateTest.COMPLETE_BODY_TEMPLATE.printf(BODY), html); } - public void get_text() { + public void get_text() throws Error { load_body_fixture("

para

"); this.test_view.get_text.begin((obj, ret) => { async_complete(ret); }); try { @@ -66,7 +64,7 @@ } } - public void get_text_with_quote() { + public void get_text_with_quote() throws Error { load_body_fixture("

pre

quote

post

"); this.test_view.get_text.begin((obj, ret) => { async_complete(ret); }); try { @@ -78,7 +76,7 @@ } } - public void get_text_with_nested_quote() { + public void get_text_with_nested_quote() throws Error { load_body_fixture("

pre

quote1

quote2

post

"); this.test_view.get_text.begin((obj, ret) => { async_complete(ret); }); try { @@ -90,7 +88,7 @@ } } - public void get_text_with_long_line() { + public void get_text_with_long_line() throws Error { load_body_fixture("""

A long, long, long, long, long, long para. Well, longer than MAX_BREAKABLE_LEN at least. Really long, long, long, long, long long, long long, long long, long.

@@ -112,7 +110,7 @@ } } - public void get_text_with_long_quote() { + public void get_text_with_long_quote() throws Error { load_body_fixture("""

A long, long, long, long, long, long line. Well, longer than MAX_BREAKABLE_LEN at least.

@@ -138,7 +136,7 @@ } } - public void get_text_with_nbsp() { + public void get_text_with_nbsp() throws Error { load_body_fixture("""On Sun, Jan 1, 2017 at 9:55 PM, Michael Gratton <mike@vee.net> wrote:
long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long,

long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, 
@@ -171,12 +169,76 @@ } } + public void get_text_with_named_link() throws Error { + load_body_fixture("Check out Geary!"); + this.test_view.get_text.begin((obj, ret) => { async_complete(ret); }); + try { + assert(this.test_view.get_text.end(async_result()) == + "Check out Geary !\n\n\n\n"); + } catch (Error err) { + print("Error: %s\n", err.message); + assert_not_reached(); + } + } + + public void get_text_with_url_link() throws Error { + load_body_fixture("Check out https://wiki.gnome.org/Apps/Geary!"); + this.test_view.get_text.begin((obj, ret) => { async_complete(ret); }); + try { + assert(this.test_view.get_text.end(async_result()) == + "Check out !\n\n\n\n"); + } catch (Error err) { + print("Error: %s\n", err.message); + assert_not_reached(); + } + } + + public void get_text_with_surrounding_nbsps() throws Error { + load_body_fixture("  I like my space  "); + this.test_view.get_text.begin((obj, ret) => { async_complete(ret); }); + try { + assert(this.test_view.get_text.end(async_result()) == + " I like my space\n\n\n\n"); + } catch (Error err) { + print("Error: %s\n", err.message); + assert_not_reached(); + } + } + + public void update_signature() throws GLib.Error { + const string BODY = "

para

"; + load_body_fixture(BODY); + string html = ""; + + const string SIG1 = "signature text 1"; + this.test_view.update_signature(SIG1); + this.test_view.get_html.begin((obj, ret) => { async_complete(ret); }); + html = this.test_view.get_html.end(async_result()); + assert_true(BODY in html, "Body not present"); + assert_true(SIG1 in html, "Signature 1 not present"); + + const string SIG2 = "signature text 2"; + this.test_view.update_signature(SIG2); + this.test_view.get_html.begin((obj, ret) => { async_complete(ret); }); + html = this.test_view.get_html.end(async_result()); + assert_true(BODY in html, "Body not present"); + assert_false(SIG1 in html, "Signature 1 still present"); + assert_true(SIG2 in html, "Signature 2 not present"); + + this.test_view.update_signature(""); + this.test_view.get_html.begin((obj, ret) => { async_complete(ret); }); + html = this.test_view.get_html.end(async_result()); + assert_true(BODY in html, "Body not present"); + assert_false(SIG1 in html, "Signature 1 still present"); + assert_false(SIG2 in html, "Signature 2 still present"); + } + protected override ComposerWebView set_up_test_view() { return new ComposerWebView(this.config); } protected override void load_body_fixture(string html = "") { - this.test_view.load_html(html, "", "", false, false); + this.test_view.load_html(html, "", false, false); while (this.test_view.is_loading) { Gtk.main_iteration(); } diff -Nru geary-0.12.4/test/client/util/util-avatar-test.vala geary-3.32.0/test/client/util/util-avatar-test.vala --- geary-0.12.4/test/client/util/util-avatar-test.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/test/client/util/util-avatar-test.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,34 @@ +/* + * Copyright 2019 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +public class Util.Avatar.Test : TestCase { + + public Test() { + base("UtilAvatarTest"); + add_test("extract_initials", extract_initials); + } + + public void extract_initials() throws GLib.Error { + assert_string("A", extract_initials_from_name("aardvark")); + assert_string("AB", extract_initials_from_name("aardvark baardvark")); + assert_string("AB", extract_initials_from_name("aardvark baardvark")); + assert_string("AC", extract_initials_from_name("aardvark baardvark caardvark")); + + assert_string("A", extract_initials_from_name("!aardvark")); + assert_string("AB", extract_initials_from_name("aardvark !baardvark")); + assert_string("AC", extract_initials_from_name("aardvark baardvark !caardvark")); + + assert_true(extract_initials_from_name("") == null); + assert_true(extract_initials_from_name(" ") == null); + assert_true(extract_initials_from_name(" ") == null); + assert_true(extract_initials_from_name("!") == null); + assert_true(extract_initials_from_name("!!") == null); + assert_true(extract_initials_from_name("! !") == null); + assert_true(extract_initials_from_name("! !!") == null); + } + +} diff -Nru geary-0.12.4/test/client/util/util-email-test.vala geary-3.32.0/test/client/util/util-email-test.vala --- geary-0.12.4/test/client/util/util-email-test.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/test/client/util/util-email-test.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,115 @@ +/* + * Copyright 2019 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +public class Util.Email.Test : TestCase { + + public Test() { + base("UtilEmailTest"); + add_test("null_originator", null_originator); + add_test("from_originator", from_originator); + add_test("sender_originator", sender_originator); + add_test("reply_to_originator", reply_to_originator); + add_test("reply_to_via_originator", reply_to_via_originator); + add_test("plain_via_originator", plain_via_originator); + } + + public void null_originator() throws GLib.Error { + Geary.RFC822.MailboxAddress? originator = get_primary_originator( + new_email(null, null, null) + ); + + assert_null(originator); + } + + public void from_originator() throws GLib.Error { + Geary.RFC822.MailboxAddress? originator = get_primary_originator( + new_email( + new Geary.RFC822.MailboxAddress("from", "from@example.com"), + new Geary.RFC822.MailboxAddress("sender", "sender@example.com"), + new Geary.RFC822.MailboxAddress("reply-to", "reply-to@example.com") + ) + ); + + assert_non_null(originator); + assert_string("from", originator.name); + assert_string("from@example.com", originator.address); + } + + public void sender_originator() throws GLib.Error { + Geary.RFC822.MailboxAddress? originator = get_primary_originator( + new_email( + null, + new Geary.RFC822.MailboxAddress("sender", "sender@example.com"), + new Geary.RFC822.MailboxAddress("reply-to", "reply-to@example.com") + ) + ); + + assert_non_null(originator); + assert_string("sender", originator.name); + assert_string("sender@example.com", originator.address); + } + + public void reply_to_originator() throws GLib.Error { + Geary.RFC822.MailboxAddress? originator = get_primary_originator( + new_email( + null, + null, + new Geary.RFC822.MailboxAddress("reply-to", "reply-to@example.com") + ) + ); + + assert_non_null(originator); + assert_string("reply-to", originator.name); + assert_string("reply-to@example.com", originator.address); + } + + public void reply_to_via_originator() throws GLib.Error { + Geary.RFC822.MailboxAddress? originator = get_primary_originator( + new_email( + new Geary.RFC822.MailboxAddress("test via bot", "bot@example.com"), + null, + new Geary.RFC822.MailboxAddress("test", "test@example.com") + ) + ); + + assert_non_null(originator); + assert_string("test", originator.name); + assert_string("test@example.com", originator.address); + } + + public void plain_via_originator() throws GLib.Error { + Geary.RFC822.MailboxAddress? originator = get_primary_originator( + new_email( + new Geary.RFC822.MailboxAddress("test via bot", "bot@example.com"), + null, + null + ) + ); + + assert_non_null(originator); + assert_string("test", originator.name); + assert_string("bot@example.com", originator.address); + } + + private Geary.Email new_email(Geary.RFC822.MailboxAddress? from, + Geary.RFC822.MailboxAddress? sender, + Geary.RFC822.MailboxAddress? reply_to) + throws GLib.Error { + Geary.Email email = new Geary.Email(new Geary.MockEmailIdentifer(1)); + email.set_originators( + from != null + ? new Geary.RFC822.MailboxAddresses(Geary.Collection.single(from)) + : null, + sender, + reply_to != null + ? new Geary.RFC822.MailboxAddresses(Geary.Collection.single(reply_to)) + : null + ); + return email; + } + +} diff -Nru geary-0.12.4/test/CMakeLists.txt geary-3.32.0/test/CMakeLists.txt --- geary-0.12.4/test/CMakeLists.txt 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/test/CMakeLists.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,106 +0,0 @@ -# Geary build script -# Copyright 2016 Software Freedom Conservancy Inc. -# Copyright 2016 Michael Gratton - -set(TEST_SRC - main.vala - testcase.vala # Based on same file in libgee, courtesy Julien Peeters - - engine/api/geary-attachment-test.vala - engine/api/geary-engine-test.vala - engine/imap/message/imap-data-format-test.vala - engine/imap/message/imap-mailbox-specifier-test.vala - engine/imap/transport/imap-deserializer-test.vala - engine/mime-content-type-test.vala - engine/rfc822-mailbox-address-test.vala - engine/rfc822-message-test.vala - engine/rfc822-message-data-test.vala - engine/rfc822-utils-test.vala - engine/util-html-test.vala - engine/util-idle-manager-test.vala - engine/util-inet-test.vala - engine/util-js-test.vala - engine/util-timeout-manager-test.vala - - client/application/geary-configuration-test.vala - client/components/client-web-view-test.vala - client/components/client-web-view-test-case.vala - client/composer/composer-web-view-test.vala - - js/client-page-state-test.vala - js/composer-page-state-test.vala - js/conversation-page-state-test.vala -) - -# Vala -find_package(Vala REQUIRED) -include(ValaVersion) -include(ValaPrecompile) - -pkg_check_modules(DEPS REQUIRED - gee-0.8 - gio-2.0 - glib-2.0 - gmime-2.6 - gthread-2.0 - gtk+-3.0 - javascriptcoregtk-4.0 - libsoup-2.4 - webkit2gtk-4.0 - libxml-2.0 -) - -set(TEST_PACKAGES - geary-client - geary-engine - gee-0.8 - gio-2.0 - glib-2.0 - gmime-2.6 - gtk+-3.0 - javascriptcore-4.0 - libsoup-2.4 - webkit2gtk-4.0 -) - -set(CFLAGS - ${DEPS_CFLAGS} - ${DEPS_CFLAGS_OTHER} - -D_BUILD_ROOT_DIR=\"${CMAKE_BINARY_DIR}\" - -D_GSETTINGS_DIR=\"${CMAKE_BINARY_DIR}/gsettings\" - -D_SOURCE_ROOT_DIR=\"${CMAKE_SOURCE_DIR}\" -) - -include_directories(${CMAKE_BINARY_DIR}/src) - -set(LIB_PATHS ${DEPS_LIBRARY_DIRS}) -link_directories(${LIB_PATHS}) -add_definitions(${CFLAGS}) - -# GResources must be compiled into the binary?? -set_property(SOURCE ${RESOURCES_C} PROPERTY GENERATED TRUE) - -set(VALAC_OPTIONS - --vapidir=${CMAKE_BINARY_DIR}/src - --vapidir=${CMAKE_SOURCE_DIR}/bindings/vapi - --metadatadir=${CMAKE_SOURCE_DIR}/bindings/metadata - --target-glib=${TARGET_GLIB} - --thread - --debug - --enable-checking - --enable-deprecated - --fatal-warnings - ${EXTRA_VALA_OPTIONS} -) - -vala_precompile(TEST_VALA_C geary-test - ${TEST_SRC} -PACKAGES - ${TEST_PACKAGES} -OPTIONS - ${VALAC_OPTIONS} -) - -# Exclude from all so tests aren't built by default -add_executable(geary-test EXCLUDE_FROM_ALL ${TEST_VALA_C} ${RESOURCES_C}) -target_link_libraries(geary-test ${DEPS_LIBRARIES} geary-client geary-engine) diff -Nru geary-0.12.4/test/data/basic-multipart-alternative.eml geary-3.32.0/test/data/basic-multipart-alternative.eml --- geary-0.12.4/test/data/basic-multipart-alternative.eml 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/test/data/basic-multipart-alternative.eml 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,36 @@ +From: Alice +Sender: Bob +To: Charlie +CC: Dave +BCC: Eve +Reply-To: \"Alice: Personal Account\" +Subject: Re: Basic text/html message +Date: Fri, 21 Nov 1997 10:01:10 -0600 +MIME-Version: 1.0 +Content-Type: multipart/alternative; boundary="=-NJextDaQ1tE2ZGhW9Wm0" +Message-ID: <3456@example.net> +In-Reply-To: <1234@local.machine.example> +References: <1234@local.machine.example> +X-Mailer: Geary Test Suite 1.0 + +--=-NJextDaQ1tE2ZGhW9Wm0 +Content-Type: text/plain; charset=UTF-8; format=flowed +Content-Transfer-Encoding: quoted-printable + +This is the first line. + +This is the second line. + += + +--=-NJextDaQ1tE2ZGhW9Wm0 +Content-Type: text/html; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable + +

This is the first line. + +

This is the second line. + += + +--=-NJextDaQ1tE2ZGhW9Wm0-- diff -Nru geary-0.12.4/test/data/basic-text-html.eml geary-3.32.0/test/data/basic-text-html.eml --- geary-0.12.4/test/data/basic-text-html.eml 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/test/data/basic-text-html.eml 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,18 @@ +From: Alice +Sender: Bob +To: Charlie +CC: Dave +BCC: Eve +Reply-To: \"Alice: Personal Account\" +Subject: Re: Basic text/html message +Date: Fri, 21 Nov 1997 10:01:10 -0600 +Content-Type: text/html; charset=UTF-8 +Message-ID: <3456@example.net> +In-Reply-To: <1234@local.machine.example> +References: <1234@local.machine.example> +X-Mailer: Geary Test Suite 1.0 + +

This is the first line. + +

This is the second line. + diff -Nru geary-0.12.4/test/data/basic-text-plain.eml geary-3.32.0/test/data/basic-text-plain.eml --- geary-0.12.4/test/data/basic-text-plain.eml 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/test/data/basic-text-plain.eml 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,17 @@ +From: Alice +Sender: Bob +To: Charlie +CC: Dave +BCC: Eve +Reply-To: "Alice: Personal Account" +Subject: Re: Basic text/plain message +Date: Fri, 21 Nov 1997 10:01:10 -0600 +Message-ID: <3456@example.net> +In-Reply-To: <1234@local.machine.example> +References: <1234@local.machine.example> +X-Mailer: Geary Test Suite 1.0 + +This is the first line. + +This is the second line. + Binary files /tmp/tmprRlvAF/aVx0iOUZeI/geary-0.12.4/test/data/geary-0.6-db.tar.xz and /tmp/tmprRlvAF/yMyovUyAjh/geary-3.32.0/test/data/geary-0.6-db.tar.xz differ diff -Nru geary-0.12.4/test/data/meson.build geary-3.32.0/test/data/meson.build --- geary-0.12.4/test/data/meson.build 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/test/data/meson.build 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,3 @@ +geary_test_engine_resources = gnome.compile_resources('org.gnome.GearyTest', + files('org.gnome.GearyTest.gresource.xml'), +) diff -Nru geary-0.12.4/test/data/org.gnome.GearyTest.gresource.xml geary-3.32.0/test/data/org.gnome.GearyTest.gresource.xml --- geary-0.12.4/test/data/org.gnome.GearyTest.gresource.xml 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/test/data/org.gnome.GearyTest.gresource.xml 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,9 @@ + + + + basic-text-plain.eml + basic-text-html.eml + basic-multipart-alternative.eml + geary-0.6-db.tar.xz + + diff -Nru geary-0.12.4/test/engine/api/geary-account-information-test.vala geary-3.32.0/test/engine/api/geary-account-information-test.vala --- geary-0.12.4/test/engine/api/geary-account-information-test.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/test/engine/api/geary-account-information-test.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,136 @@ +/* + * Copyright 2018 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +class Geary.AccountInformationTest : TestCase { + + + public AccountInformationTest() { + base("Geary.AccountInformationTest"); + add_test("test_save_sent_defaults", test_save_sent_defaults); + add_test("test_sender_mailboxes", test_sender_mailboxes); + add_test("test_service_label", test_service_label); + } + + public void test_save_sent_defaults() throws GLib.Error { + assert_true( + new AccountInformation( + "test", + ServiceProvider.OTHER, + new MockCredentialsMediator(), + new RFC822.MailboxAddress(null, "test1@example.com") + ).save_sent + ); + assert_false( + new AccountInformation( + "test", + ServiceProvider.GMAIL, + new MockCredentialsMediator(), + new RFC822.MailboxAddress(null, "test1@example.com") + ).save_sent + ); + assert_false( + new AccountInformation( + "test", + ServiceProvider.OUTLOOK, + new MockCredentialsMediator(), + new RFC822.MailboxAddress(null, "test1@example.com") + ).save_sent + ); + assert_true( + new AccountInformation( + "test", + ServiceProvider.YAHOO, + new MockCredentialsMediator(), + new RFC822.MailboxAddress(null, "test1@example.com") + ).save_sent + ); + } + + public void test_sender_mailboxes() throws GLib.Error { + AccountInformation test = new AccountInformation( + "test", + ServiceProvider.OTHER, + new MockCredentialsMediator(), + new RFC822.MailboxAddress(null, "test1@example.com") + ); + + assert_true(test.primary_mailbox.equal_to( + new RFC822.MailboxAddress(null, "test1@example.com"))); + assert_false(test.has_sender_aliases); + + test.append_sender(new RFC822.MailboxAddress(null, "test2@example.com")); + assert_true(test.has_sender_aliases); + + test.append_sender(new RFC822.MailboxAddress(null, "test3@example.com")); + assert_true(test.has_sender_aliases); + + assert_true( + test.has_sender_mailbox(new RFC822.MailboxAddress(null, "test1@example.com")), + "Primary address not found" + ); + assert_true( + test.has_sender_mailbox(new RFC822.MailboxAddress(null, "test2@example.com")), + "First alt address not found" + ); + assert_true( + test.has_sender_mailbox(new RFC822.MailboxAddress(null, "test3@example.com")), + "Second alt address not found" + ); + assert_false( + test.has_sender_mailbox(new RFC822.MailboxAddress(null, "unknowne@example.com")), + "Unknown address found" + ); + } + + public void test_service_label() throws GLib.Error { + AccountInformation test = new_information(); + assert_string("", test.service_label); + + test = new_information(); + test.incoming.host = "example.com"; + assert_string( + "example.com", test.service_label, "Email domain equals host name" + ); + + test = new_information(); + test.incoming.host = "test.example.com"; + assert_string( + "example.com", test.service_label, "Email domain host name suffix" + ); + + test = new_information(); + test.incoming.host = "other.com"; + test.outgoing.host = "other.com"; + assert_string("other.com", test.service_label); + + test = new_information(); + test.incoming.host = "mail.other.com"; + test.outgoing.host = "mail.other.com"; + assert_string("other.com", test.service_label); + + test = new_information(); + test.incoming.host = "imap.other.com"; + test.outgoing.host = "smtp.other.com"; + assert_string("other.com", test.service_label); + + test = new_information(); + test.incoming.host = "not-mail.other.com"; + test.outgoing.host = "not-mail.other.com"; + assert_string("other.com", test.service_label); + } + + private AccountInformation new_information(ServiceProvider provider = + ServiceProvider.OTHER) { + return new AccountInformation( + "test", + provider, + new MockCredentialsMediator(), + new RFC822.MailboxAddress(null, "test1@example.com") + ); + } + +} diff -Nru geary-0.12.4/test/engine/api/geary-account-mock.vala geary-3.32.0/test/engine/api/geary-account-mock.vala --- geary-0.12.4/test/engine/api/geary-account-mock.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/test/engine/api/geary-account-mock.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,249 @@ +/* + * Copyright 2017 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +public class Geary.MockAccount : Account, MockObject { + + + public class MockSearchQuery : SearchQuery { + + internal MockSearchQuery() { + base("", SearchQuery.Strategy.EXACT); + } + + } + + public class MockContactStore : ContactStore { + + internal MockContactStore() { + + } + + public override async void + mark_contacts_async(Gee.Collection contacts, + ContactFlags? to_add, + ContactFlags? to_remove) throws Error { + throw new EngineError.UNSUPPORTED("Mock method"); + } + } + + + public class MockClientService : ClientService { + + public MockClientService(AccountInformation account, + ServiceInformation configuration, + Endpoint remote) { + base(account, configuration, remote); + } + + public override async void start(GLib.Cancellable? cancellable = null) + throws GLib.Error { + throw new EngineError.UNSUPPORTED("Mock method"); + } + + public override async void stop(GLib.Cancellable? cancellable = null) + throws GLib.Error { + throw new EngineError.UNSUPPORTED("Mock method"); + } + + public override void became_reachable() { + + } + + public override void became_unreachable() { + + } + + } + + + protected Gee.Queue expected { + get; set; default = new Gee.LinkedList(); + } + + + public MockAccount(AccountInformation config) { + base(config, + new MockClientService( + config, + config.incoming, + new Endpoint( + new GLib.NetworkAddress( + config.incoming.host, config.incoming.port + ), + 0, 0 + ) + ), + new MockClientService( + config, + config.outgoing, + new Endpoint( + new GLib.NetworkAddress( + config.outgoing.host, config.outgoing.port + ), + 0, 0 + ) + ) + ); + } + + public override async void open_async(Cancellable? cancellable = null) throws Error { + void_call("open_async", { cancellable }); + } + + public override async void close_async(Cancellable? cancellable = null) throws Error { + void_call("close_async", { cancellable }); + } + + public override bool is_open() { + try { + return boolean_call("is_open", {}, false); + } catch (Error err) { + return false; + } + } + + public override async void rebuild_async(Cancellable? cancellable = null) throws Error { + void_call("rebuild_async", { cancellable }); + } + + public override Gee.Collection list_matching_folders(FolderPath? parent) { + try { + return object_call>( + "get_containing_folders_async", {parent}, Gee.List.empty() + ); + } catch (GLib.Error err) { + return Gee.Collection.empty(); + } + } + + public override Gee.Collection list_folders() throws Error { + return object_call>( + "list_folders", {}, Gee.List.empty() + ); + } + + public override Geary.ContactStore get_contact_store() { + return new MockContactStore(); + } + + public override async bool folder_exists_async(FolderPath path, + Cancellable? cancellable = null) + throws Error { + return boolean_call("folder_exists_async", {path, cancellable}, false); + } + + public override async Folder fetch_folder_async(FolderPath path, + Cancellable? cancellable = null) + throws Error { + return object_or_throw_call( + "fetch_folder_async", + {path, cancellable}, + new EngineError.NOT_FOUND("Mock call") + ); + } + + public override Folder? get_special_folder(SpecialFolderType special) + throws Error { + return object_call( + "get_special_folder", {box_arg(special)}, null + ); + } + + public override async Folder get_required_special_folder_async(SpecialFolderType special, + Cancellable? cancellable = null) + throws Error { + return object_or_throw_call( + "get_required_special_folder_async", + {box_arg(special), cancellable}, + new EngineError.NOT_FOUND("Mock call") + ); + } + + public override async void send_email_async(ComposedEmail composed, + Cancellable? cancellable = null) + throws Error { + void_call("send_email_async", {composed, cancellable}); + } + + public override async Gee.MultiMap? + local_search_message_id_async(RFC822.MessageID message_id, + Email.Field requested_fields, + bool partial_ok, + Gee.Collection? folder_blacklist, + EmailFlags? flag_blacklist, + Cancellable? cancellable = null) + throws Error { + return object_call?>( + "local_search_message_id_async", + { + message_id, + box_arg(requested_fields), + box_arg(partial_ok), + folder_blacklist, + flag_blacklist, + cancellable + }, + null + ); + } + + public override async Email local_fetch_email_async(EmailIdentifier email_id, + Email.Field required_fields, + Cancellable? cancellable = null) + throws Error { + return object_or_throw_call( + "local_fetch_email_async", + {email_id, box_arg(required_fields), cancellable}, + new EngineError.NOT_FOUND("Mock call") + ); + } + + public override SearchQuery open_search(string query, SearchQuery.Strategy strategy) { + return new MockSearchQuery(); + } + + public override async Gee.Collection? + local_search_async(SearchQuery query, + int limit = 100, + int offset = 0, + Gee.Collection? folder_blacklist = null, + Gee.Collection? search_ids = null, + Cancellable? cancellable = null) + throws Error { + return object_call?>( + "local_search_async", + { + query, + box_arg(limit), + box_arg(offset), + folder_blacklist, + search_ids, + cancellable + }, + null + ); + } + + public override async Gee.Set? + get_search_matches_async(SearchQuery query, + Gee.Collection ids, + Cancellable? cancellable = null) + throws Error { + return object_call?>( + "get_search_matches_async", {query, ids, cancellable}, null + ); + } + + public override async Gee.MultiMap? + get_containing_folders_async(Gee.Collection ids, + Cancellable? cancellable) throws Error { + return object_call?>( + "get_containing_folders_async", {ids, cancellable}, null + ); + } + +} diff -Nru geary-0.12.4/test/engine/api/geary-attachment-test.vala geary-3.32.0/test/engine/api/geary-attachment-test.vala --- geary-0.12.4/test/engine/api/geary-attachment-test.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/test/engine/api/geary-attachment-test.vala 2019-03-17 13:39:29.000000000 +0000 @@ -8,32 +8,31 @@ // Defined by CMake build script. extern const string _SOURCE_ROOT_DIR; -class Geary.AttachmentTest : Gee.TestCase { +class Geary.AttachmentTest : TestCase { - private const string ATTACHMENT_ID = "test-id"; - private const string CONTENT_TYPE = "image/png"; + private const string CONTENT_TYPE = "image/svg+xml"; private const string CONTENT_ID = "test-content-id"; private const string CONTENT_DESC = "Mea navis volitans anguillis plena est"; - private const string FILE_PATH = "icons/hicolor/16x16/apps/org.gnome.Geary.png"; + private const string FILE_PATH = "icons/hicolor/scalable/apps/org.gnome.Geary.svg"; private Mime.ContentType? content_type; private Mime.ContentType? default_type; private Mime.ContentDisposition? content_disposition; private File? file; + private class TestAttachment : Attachment { // A test article - internal TestAttachment(string id, - Mime.ContentType content_type, + internal TestAttachment(Mime.ContentType content_type, string? content_id, string? content_description, Mime.ContentDisposition content_disposition, string? content_filename, - File file, - int64 filesize) { - base(id, content_type, content_id, content_description, - content_disposition, content_filename, file, filesize); + GLib.File file) { + base(content_type, content_id, content_description, + content_disposition, content_filename); + set_file_info(file, 742); } } @@ -63,7 +62,7 @@ public override void set_up() { try { this.content_type = Mime.ContentType.deserialize(CONTENT_TYPE); - this.default_type = Mime.ContentType.deserialize(Mime.ContentType.DEFAULT_CONTENT_TYPE); + this.default_type = Mime.ContentType.ATTACHMENT_DEFAULT; this.content_disposition = new Mime.ContentDisposition("attachment", null); File source = File.new_for_path(_SOURCE_ROOT_DIR); @@ -73,17 +72,15 @@ } } - public void get_safe_file_name_with_content_name() { - const string TEST_FILENAME = "test-filename.png"; + public void get_safe_file_name_with_content_name() throws Error { + const string TEST_FILENAME = "test-filename.svg"; Attachment test = new TestAttachment( - ATTACHMENT_ID, this.content_type, CONTENT_ID, CONTENT_DESC, content_disposition, TEST_FILENAME, - this.file, - 742 + this.file ); test.get_safe_file_name.begin(null, (obj, ret) => { @@ -93,18 +90,16 @@ assert(test.get_safe_file_name.end(async_result()) == TEST_FILENAME); } - public void get_safe_file_name_with_bad_content_name() { + public void get_safe_file_name_with_bad_content_name() throws Error { const string TEST_FILENAME = "test-filename.jpg"; - const string RESULT_FILENAME = "test-filename.jpg.png"; + const string RESULT_FILENAME = "test-filename.jpg.svg"; Attachment test = new TestAttachment( - ATTACHMENT_ID, this.content_type, CONTENT_ID, CONTENT_DESC, content_disposition, TEST_FILENAME, - this.file, - 742 + this.file ); test.get_safe_file_name.begin(null, (obj, ret) => { @@ -114,18 +109,16 @@ assert(test.get_safe_file_name.end(async_result()) == RESULT_FILENAME); } - public void get_safe_file_name_with_bad_file_name() { + public void get_safe_file_name_with_bad_file_name() throws Error { const string TEST_FILENAME = "test-filename"; - const string RESULT_FILENAME = "test-filename.png"; + const string RESULT_FILENAME = "test-filename.svg"; Attachment test = new TestAttachment( - ATTACHMENT_ID, this.content_type, CONTENT_ID, CONTENT_DESC, content_disposition, TEST_FILENAME, - this.file, - 742 + this.file ); test.get_safe_file_name.begin(null, (obj, ret) => { @@ -135,17 +128,15 @@ assert(test.get_safe_file_name.end(async_result()) == RESULT_FILENAME); } - public void get_safe_file_name_with_no_content_name() { - const string RESULT_FILENAME = CONTENT_ID + ".png"; + public void get_safe_file_name_with_no_content_name() throws Error { + const string RESULT_FILENAME = CONTENT_ID + ".svg"; Attachment test = new TestAttachment( - ATTACHMENT_ID, this.content_type, CONTENT_ID, CONTENT_DESC, content_disposition, null, - this.file, - 742 + this.file ); test.get_safe_file_name.begin(null, (obj, ret) => { @@ -155,17 +146,15 @@ assert(test.get_safe_file_name.end(async_result()) == RESULT_FILENAME); } - public void get_safe_file_name_with_no_content_name_or_id() { - const string RESULT_FILENAME = ATTACHMENT_ID + ".png"; + public void get_safe_file_name_with_no_content_name_or_id() throws Error { + const string RESULT_FILENAME = "attachment.svg"; Attachment test = new TestAttachment( - ATTACHMENT_ID, this.content_type, null, CONTENT_DESC, content_disposition, null, - this.file, - 742 + this.file ); test.get_safe_file_name.begin(null, (obj, ret) => { @@ -175,18 +164,16 @@ assert(test.get_safe_file_name.end(async_result()) == RESULT_FILENAME); } - public void get_safe_file_name_with_alt_file_name() { + public void get_safe_file_name_with_alt_file_name() throws Error { const string ALT_TEXT = "some text"; - const string RESULT_FILENAME = "some text.png"; + const string RESULT_FILENAME = "some text.svg"; Attachment test = new TestAttachment( - ATTACHMENT_ID, this.content_type, null, CONTENT_DESC, content_disposition, null, - this.file, - 742 + this.file ); test.get_safe_file_name.begin(ALT_TEXT, (obj, ret) => { @@ -196,17 +183,15 @@ assert(test.get_safe_file_name.end(async_result()) == RESULT_FILENAME); } - public void get_safe_file_name_with_default_content_type() { - const string TEST_FILENAME = "test-filename.png"; + public void get_safe_file_name_with_default_content_type() throws Error { + const string TEST_FILENAME = "test-filename.svg"; Attachment test = new TestAttachment( - ATTACHMENT_ID, this.default_type, CONTENT_ID, CONTENT_DESC, content_disposition, TEST_FILENAME, - this.file, - 742 + this.file ); test.get_safe_file_name.begin(null, (obj, ret) => { @@ -216,18 +201,17 @@ assert(test.get_safe_file_name.end(async_result()) == TEST_FILENAME); } - public void get_safe_file_name_with_default_content_type_bad_file_name() { + public void get_safe_file_name_with_default_content_type_bad_file_name() + throws Error { const string TEST_FILENAME = "test-filename.jpg"; - const string RESULT_FILENAME = "test-filename.jpg.png"; + const string RESULT_FILENAME = "test-filename.jpg.svg"; Attachment test = new TestAttachment( - ATTACHMENT_ID, this.default_type, CONTENT_ID, CONTENT_DESC, content_disposition, TEST_FILENAME, - this.file, - 742 + this.file ); test.get_safe_file_name.begin(null, (obj, ret) => { @@ -237,24 +221,23 @@ assert(test.get_safe_file_name.end(async_result()) == RESULT_FILENAME); } - public void get_safe_file_name_with_unknown_content_type() { + public void get_safe_file_name_with_unknown_content_type() + throws Error { const string TEST_FILENAME = "test-filename.unlikely"; Attachment test = new TestAttachment( - ATTACHMENT_ID, this.default_type, CONTENT_ID, CONTENT_DESC, content_disposition, TEST_FILENAME, - File.new_for_path(TEST_FILENAME), - 742 + File.new_for_path(TEST_FILENAME) ); test.get_safe_file_name.begin(null, (obj, ret) => { async_complete(ret); }); - assert(TEST_FILENAME == test.get_safe_file_name.end(async_result())); + assert_string(TEST_FILENAME, test.get_safe_file_name.end(async_result())); } } diff -Nru geary-0.12.4/test/engine/api/geary-credentials-mediator-mock.vala geary-3.32.0/test/engine/api/geary-credentials-mediator-mock.vala --- geary-0.12.4/test/engine/api/geary-credentials-mediator-mock.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/test/engine/api/geary-credentials-mediator-mock.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,42 @@ +/* + * Copyright 2017 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +public class Geary.MockCredentialsMediator : + GLib.Object, CredentialsMediator, MockObject { + + + protected Gee.Queue expected { + get; set; default = new Gee.LinkedList(); + } + + public virtual async bool load_token(AccountInformation account, + ServiceInformation service, + GLib.Cancellable? cancellable) + throws GLib.Error { + return object_call("load_token", { service, cancellable }, false); + } + + /** + * Prompt the user to enter passwords for the given services. + * + * Set the out parameters for the services to the values entered + * by the user (out parameters for services not being prompted for + * are ignored). Return false if the user tried to cancel the + * interaction, or true if they tried to proceed. + */ + public virtual async bool prompt_token(AccountInformation account, + ServiceInformation service, + GLib.Cancellable? cancellable) + throws GLib.Error { + return boolean_call( + "prompt_token", + { account, service, cancellable }, + false + ); + } + +} diff -Nru geary-0.12.4/test/engine/api/geary-email-identifier-mock.vala geary-3.32.0/test/engine/api/geary-email-identifier-mock.vala --- geary-0.12.4/test/engine/api/geary-email-identifier-mock.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/test/engine/api/geary-email-identifier-mock.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,24 @@ +/* + * Copyright 2017 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +public class Geary.MockEmailIdentifer : EmailIdentifier { + + + private int id; + + + public MockEmailIdentifer(int id) { + base(id.to_string()); + this.id = id; + } + + public override int natural_sort_comparator(Geary.EmailIdentifier other) { + MockEmailIdentifer? other_mock = other as MockEmailIdentifer; + return (other_mock == null) ? 1 : this.id - other_mock.id; + } + +} diff -Nru geary-0.12.4/test/engine/api/geary-email-properties-mock.vala geary-3.32.0/test/engine/api/geary-email-properties-mock.vala --- geary-0.12.4/test/engine/api/geary-email-properties-mock.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/test/engine/api/geary-email-properties-mock.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,21 @@ +/* + * Copyright 2017 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +public class Geary.MockEmailProperties : EmailProperties { + + + public MockEmailProperties(GLib.DateTime received) { + base(received, 0); + } + + public override string to_string() { + return "MockEmailProperties: %s/%lli".printf( + this.date_received.to_string(), this.total_bytes + ); + } + +} diff -Nru geary-0.12.4/test/engine/api/geary-engine-test.vala geary-3.32.0/test/engine/api/geary-engine-test.vala --- geary-0.12.4/test/engine/api/geary-engine-test.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/test/engine/api/geary-engine-test.vala 2019-03-17 13:39:29.000000000 +0000 @@ -5,18 +5,20 @@ * (version 2.1 or later). See the COPYING file in this distribution. */ -class Geary.EngineTest : Gee.TestCase { +class Geary.EngineTest : TestCase { + private Engine? engine = null; private File? tmp = null; - private File? config = null; - private File? data = null; private File? res = null; + private Geary.AccountInformation? account = null; + public EngineTest() { base("Geary.EngineTest"); - add_test("create_orphan_account", create_orphan_account); - add_test("create_orphan_account_with_legacy", create_orphan_account_with_legacy); + add_test("add_account", add_account); + add_test("remove_account", remove_account); + add_test("re_add_account", re_add_account); } ~EngineTest() { @@ -31,95 +33,80 @@ } } - public override void set_up() { + public override void set_up() throws GLib.Error { // XXX this whole thing stinks. We need to be able to test the // engine without creating all of these dirs. - try { - this.tmp = File.new_for_path(Environment.get_tmp_dir()).get_child("geary-test"); - this.tmp.make_directory(); - - this.config = this.tmp.get_child("config"); - this.config.make_directory(); - - this.data = this.tmp.get_child("data"); - this.data.make_directory(); - - this.res = this.tmp.get_child("res"); - this.res.make_directory(); - - this.engine = new Engine(); - this.engine.open_async.begin( - config, data, res, null, null, - (obj, res) => { - async_complete(res); - }); - this.engine.open_async.end(async_result()); - } catch (Error err) { - assert_not_reached(); - } + this.tmp = GLib.File.new_for_path( + GLib.DirUtils.make_tmp("geary-engine-test-XXXXXX") + ); + + this.res = this.tmp.get_child("res"); + this.res.make_directory(); + + this.engine = new Engine(); + this.engine.open_async.begin( + res, null, + (obj, res) => { + async_complete(res); + }); + this.engine.open_async.end(async_result()); + + this.account = new AccountInformation( + "test", + ServiceProvider.OTHER, + new MockCredentialsMediator(), + new RFC822.MailboxAddress(null, "test1@example.com") + ); } - public override void tear_down () { + public override void tear_down () { + this.account = null; try { this.res.delete(); - this.data.delete(); - this.config.delete(); this.tmp.delete(); this.tmp = null; } catch (Error err) { assert_not_reached(); } - } + } - public void create_orphan_account() { - try { - AccountInformation info = this.engine.create_orphan_account(); - assert(info.id == "account_01"); - this.engine.add_account(info, true); - - info = this.engine.create_orphan_account(); - assert(info.id == "account_02"); - this.engine.add_account(info, true); - - info = this.engine.create_orphan_account(); - assert(info.id == "account_03"); - this.engine.add_account(info, true); + public void add_account() throws GLib.Error { + assert_false(this.engine.has_account(this.account.id)); - info = this.engine.create_orphan_account(); - assert(info.id == "account_04"); - } catch (Error err) { - print("\nerr: %s\n", err.message); + this.engine.add_account(this.account); + assert_true(this.engine.has_account(this.account.id), "Account not added"); + + try { + this.engine.add_account(this.account); assert_not_reached(); + } catch (GLib.Error err) { + // expected } } - public void create_orphan_account_with_legacy() { - try { - this.engine.add_account( - new AccountInformation("foo", this.config, this.data), - true - ); - - AccountInformation info = this.engine.create_orphan_account(); - assert(info.id == "account_01"); - this.engine.add_account(info, true); - - assert(this.engine.create_orphan_account().id == "account_02"); - - this.engine.add_account( - new AccountInformation("bar", this.config, this.data), - true - ); + public void remove_account() throws GLib.Error { + this.engine.add_account(this.account); + assert_true(this.engine.has_account(this.account.id)); - assert(this.engine.create_orphan_account().id == "account_02"); - } catch (Error err) { - print("\nerr: %s\n", err.message); - assert_not_reached(); - } + this.engine.remove_account(this.account); + assert_false(this.engine.has_account(this.account.id), "Account not rmoeved"); + + // Should not throw an error + this.engine.remove_account(this.account); + } + + public void re_add_account() throws GLib.Error { + assert_false(this.engine.has_account(this.account.id)); + + this.engine.add_account(this.account); + this.engine.remove_account(this.account); + this.engine.add_account(this.account); + + assert_true(this.engine.has_account(this.account.id)); } - private void delete(File parent) throws Error { + private void delete(File parent) throws Error { FileInfo info = parent.query_info( "standard::*", FileQueryInfoFlags.NOFOLLOW_SYMLINKS diff -Nru geary-0.12.4/test/engine/api/geary-folder-mock.vala geary-3.32.0/test/engine/api/geary-folder-mock.vala --- geary-0.12.4/test/engine/api/geary-folder-mock.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/test/engine/api/geary-folder-mock.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,124 @@ +/* + * Copyright 2017 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +public class Geary.MockFolder : Folder, MockObject { + + + public override Account account { + get { return this._account; } + } + + public override FolderProperties properties { + get { return this._properties; } + } + + public override FolderPath path { + get { return this._path; } + } + + public override SpecialFolderType special_folder_type { + get { return this._type; } + } + + public override ProgressMonitor opening_monitor { + get { return this._opening_monitor; } + } + + protected Gee.Queue expected { + get; set; default = new Gee.LinkedList(); + } + + + private Account _account; + private FolderProperties _properties; + private FolderPath _path; + private SpecialFolderType _type; + private ProgressMonitor _opening_monitor; + + + public MockFolder(Account? account, + FolderProperties? properties, + FolderPath? path, + SpecialFolderType type, + ProgressMonitor? monitor) { + this._account = account; + this._properties = properties; + this._path = path; + this._type = type; + this._opening_monitor = monitor; + } + + public override Folder.OpenState get_open_state() { + return OpenState.CLOSED; + } + + public override async bool open_async(Folder.OpenFlags open_flags, + Cancellable? cancellable = null) + throws Error { + return boolean_call( + "open_async", + { int_arg(open_flags), cancellable }, + false + ); + } + + public override async bool close_async(Cancellable? cancellable = null) + throws Error { + return boolean_call( + "close_async", { cancellable }, false + ); + } + + public override async void wait_for_close_async(Cancellable? cancellable = null) + throws Error { + throw new EngineError.UNSUPPORTED("Mock method"); + } + + public override async Gee.List? + list_email_by_id_async(Geary.EmailIdentifier? initial_id, + int count, + Geary.Email.Field required_fields, + Folder.ListFlags flags, + Cancellable? cancellable = null) + throws Error { + return object_call?>( + "list_email_by_id_async", + {initial_id, int_arg(count), box_arg(required_fields), box_arg(flags), cancellable}, + null + ); + } + + public override async Gee.List? + list_email_by_sparse_id_async(Gee.Collection ids, + Geary.Email.Field required_fields, + Folder.ListFlags flags, + Cancellable? cancellable = null) + throws Error { + return object_call?>( + "list_email_by_sparse_id_async", + {ids, box_arg(required_fields), box_arg(flags), cancellable}, + null + ); + } + + public override async Gee.Map? + list_local_email_fields_async(Gee.Collection ids, + Cancellable? cancellable = null) + throws Error { + throw new EngineError.UNSUPPORTED("Mock method"); + } + + public override async Geary.Email + fetch_email_async(Geary.EmailIdentifier email_id, + Geary.Email.Field required_fields, + Folder.ListFlags flags, + Cancellable? cancellable = null) + throws Error { + throw new EngineError.UNSUPPORTED("Mock method"); + } + +} diff -Nru geary-0.12.4/test/engine/api/geary-folder-path-test.vala geary-3.32.0/test/engine/api/geary-folder-path-test.vala --- geary-0.12.4/test/engine/api/geary-folder-path-test.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/test/engine/api/geary-folder-path-test.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,308 @@ +/* + * Copyright 2019 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +public class Geary.FolderPathTest : TestCase { + + + private FolderRoot? root = null; + + + public FolderPathTest() { + base("Geary.FolderPathTest"); + add_test("get_child_from_root", get_child_from_root); + add_test("get_child_from_child", get_child_from_child); + add_test("root_is_root", root_is_root); + add_test("child_is_not_root", root_is_root); + add_test("as_array", as_array); + add_test("is_top_level", is_top_level); + add_test("path_to_string", path_to_string); + add_test("path_parent", path_parent); + add_test("path_equal", path_equal); + add_test("path_hash", path_hash); + add_test("path_compare", path_compare); + add_test("path_compare_normalised", path_compare_normalised); + add_test("distinct_roots_compare", distinct_roots_compare); + } + + public override void set_up() { + this.root = new FolderRoot(false); + } + + public override void tear_down() { + this.root = null; + } + + public void get_child_from_root() throws GLib.Error { + assert_string( + "test", + this.root.get_child("test").name + ); + } + + public void get_child_from_child() throws GLib.Error { + assert_string( + "test2", + this.root.get_child("test1").get_child("test2").name + ); + } + + public void root_is_root() throws GLib.Error { + assert_true(this.root.is_root); + } + + public void child_root_is_not_root() throws GLib.Error { + assert_false(this.root.get_child("test").is_root); + } + + public void as_array() throws GLib.Error { + assert_true(this.root.as_array().length == 0, "Root list"); + assert_int( + 1, + this.root.get_child("test").as_array().length, + "Child array length" + ); + assert_string( + "test", + this.root.get_child("test").as_array()[0], + "Child array contents" + ); + assert_int( + 2, + this.root.get_child("test1").get_child("test2").as_array().length, + "Descendent array length" + ); + assert_string( + "test1", + this.root.get_child("test1").get_child("test2").as_array()[0], + "Descendent first child" + ); + assert_string( + "test2", + this.root.get_child("test1").get_child("test2").as_array()[1], + "Descendent second child" + ); + } + + public void is_top_level() throws GLib.Error { + assert_false(this.root.is_top_level, "Root is top_level"); + assert_true( + this.root.get_child("test").is_top_level, + "Top level is top_level" + ); + assert_false( + this.root.get_child("test").get_child("test").is_top_level, + "Descendent is top_level" + ); + } + + public void path_to_string() throws GLib.Error { + assert_string(">", this.root.to_string()); + assert_string(">test", this.root.get_child("test").to_string()); + assert_string( + ">test1>test2", + this.root.get_child("test1").get_child("test2").to_string() + ); + } + + public void path_parent() throws GLib.Error { + assert_null(this.root.parent, "Root parent"); + assert_string( + "", + this.root.get_child("test").parent.name, + "Root child parent"); + assert_string( + "test1", + this.root.get_child("test1").get_child("test2").parent.name, + "Child parent"); + } + + public void path_equal() throws GLib.Error { + assert_true(this.root.equal_to(this.root), "Root equality"); + assert_true( + this.root.get_child("test").equal_to(this.root.get_child("test")), + "Child equality" + ); + assert_false( + this.root.get_child("test1").equal_to(this.root.get_child("test2")), + "Child names" + ); + assert_false( + this.root.get_child("test1").get_child("test") + .equal_to(this.root.get_child("test2").get_child("test")), + "Disjoint parents" + ); + + assert_false( + this.root.get_child("test").equal_to( + this.root.get_child("").get_child("test")), + "Pathological case" + ); + } + + public void path_hash() throws GLib.Error { + assert_true( + this.root.hash() != + this.root.get_child("test").hash() + ); + assert_true( + this.root.get_child("test1").hash() != + this.root.get_child("test2").hash() + ); + } + + public void path_compare() throws GLib.Error { + assert_int(0, this.root.compare_to(this.root), "Root equality"); + assert_int(0, + this.root.get_child("a").compare_to(this.root.get_child("a")), + "Equal child comparison" + ); + + // a is less than b + assert_true( + this.root.get_child("a").compare_to(this.root.get_child("b")) < 0, + "Greater than child comparison" + ); + + // b is greater than than a + assert_true( + this.root.get_child("b").compare_to(this.root.get_child("a")) > 0, + "Less than child comparison" + ); + + assert_true( + this.root.get_child("a").get_child("test") + .compare_to(this.root.get_child("a")) > 0, + "Greater than descendant" + ); + assert_true( + this.root.get_child("a") + .compare_to(this.root.get_child("a").get_child("test")) < 0, + "Less than descendant" + ); + + assert_true( + this.root.get_child("a").get_child("b") + .compare_to(this.root.get_child("a").get_child("b")) == 0, + "N-path equality" + ); + + assert_true( + this.root.get_child("a").get_child("test") + .compare_to(this.root.get_child("b").get_child("test")) < 0, + "Greater than disjoint paths" + ); + assert_true( + this.root.get_child("b").get_child("test") + .compare_to(this.root.get_child("a").get_child("test")) > 0, + "Less than disjoint paths" + ); + + assert_true( + this.root.get_child("a").get_child("d") + .compare_to(this.root.get_child("b").get_child("c")) < 0, + "Greater than double disjoint" + ); + assert_true( + this.root.get_child("b").get_child("c") + .compare_to(this.root.get_child("a").get_child("d")) > 0, + "Less than double disjoint" + ); + + } + + public void path_compare_normalised() throws GLib.Error { + assert_int(0, this.root.compare_normalized_ci(this.root), "Root equality"); + assert_int(0, + this.root.get_child("a").compare_normalized_ci(this.root.get_child("a")), + "Equal child comparison" + ); + + // a is less than b + assert_true( + this.root.get_child("a").compare_normalized_ci(this.root.get_child("b")) < 0, + "Greater than child comparison" + ); + + // b is greater than than a + assert_true( + this.root.get_child("b").compare_normalized_ci(this.root.get_child("a")) > 0, + "Less than child comparison" + ); + + assert_true( + this.root.get_child("a").get_child("test") + .compare_normalized_ci(this.root.get_child("b").get_child("test")) < 0, + "Greater than disjoint parents" + ); + assert_true( + this.root.get_child("b").get_child("test") + .compare_normalized_ci(this.root.get_child("a").get_child("test")) > 0, + "Less than disjoint parents" + ); + + assert_true( + this.root.get_child("a").get_child("test") + .compare_normalized_ci(this.root.get_child("a")) > 0, + "Greater than descendant" + ); + assert_true( + this.root.get_child("a") + .compare_normalized_ci(this.root.get_child("a").get_child("test")) < 0, + "Less than descendant" + ); + } + + public void distinct_roots_compare() throws GLib.Error { + assert_int(0, this.root.compare_to(new FolderRoot(false)), "Root equality"); + assert_int(0, + this.root.get_child("a").compare_to(new FolderRoot(false).get_child("a")), + "Equal child comparison" + ); + + // a is less than b + assert_true( + this.root.get_child("a").compare_to(new FolderRoot(false).get_child("b")) < 0, + "Greater than child comparison" + ); + + // b is greater than than a + assert_true( + this.root.get_child("b").compare_to(new FolderRoot(false).get_child("a")) > 0, + "Less than child comparison" + ); + + assert_true( + this.root.get_child("a").get_child("test") + .compare_to(new FolderRoot(false).get_child("a")) > 0, + "Greater than descendant" + ); + assert_true( + this.root.get_child("a") + .compare_to(new FolderRoot(false).get_child("a").get_child("test")) < 0, + "Less than descendant" + ); + + assert_true( + this.root.get_child("a").get_child("b") + .compare_to(new FolderRoot(false).get_child("a").get_child("b")) == 0, + "N-path equality" + ); + + assert_true( + this.root.get_child("a").get_child("a") + .compare_to(new FolderRoot(false).get_child("b").get_child("b")) < 0, + "Less than double disjoint" + ); + assert_true( + this.root.get_child("b").get_child("a") + .compare_to(new FolderRoot(false).get_child("a").get_child("a")) > 0, + "Greater than double disjoint" + ); + + } + +} diff -Nru geary-0.12.4/test/engine/api/geary-service-information-test.vala geary-3.32.0/test/engine/api/geary-service-information-test.vala --- geary-0.12.4/test/engine/api/geary-service-information-test.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/test/engine/api/geary-service-information-test.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,34 @@ +/* + * Copyright 2018 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +class Geary.TlsNegotiationMethodTest : TestCase { + + + public TlsNegotiationMethodTest() { + base("Geary.TlsNegotiationMethodTest"); + add_test("to_value", to_value); + add_test("for_value", for_value); + } + + public void to_value() throws GLib.Error { + assert_string("start-tls", TlsNegotiationMethod.START_TLS.to_value()); + } + + public void for_value() throws GLib.Error { + assert_int( + TlsNegotiationMethod.START_TLS, + TlsNegotiationMethod.for_value("start-tls"), + "start-tls" + ); + assert_int( + TlsNegotiationMethod.START_TLS, + TlsNegotiationMethod.for_value("Start-TLS"), + "Start-TLS" + ); + } + +} \ No newline at end of file diff -Nru geary-0.12.4/test/engine/app/app-conversation-monitor-test.vala geary-3.32.0/test/engine/app/app-conversation-monitor-test.vala --- geary-0.12.4/test/engine/app/app-conversation-monitor-test.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/test/engine/app/app-conversation-monitor-test.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,502 @@ +/* + * Copyright 2018 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + + +class Geary.App.ConversationMonitorTest : TestCase { + + + AccountInformation? account_info = null; + MockAccount? account = null; + FolderRoot? folder_root = null; + MockFolder? base_folder = null; + MockFolder? other_folder = null; + + + public ConversationMonitorTest() { + base("Geary.App.ConversationMonitorTest"); + add_test("start_stop_monitoring", start_stop_monitoring); + add_test("open_error", open_error); + add_test("load_single_message", load_single_message); + add_test("load_multiple_messages", load_multiple_messages); + add_test("load_related_message", load_related_message); + add_test("base_folder_message_appended", base_folder_message_appended); + add_test("base_folder_message_removed", base_folder_message_removed); + add_test("external_folder_message_appended", external_folder_message_appended); + add_test("conversation_marked_as_deleted", conversation_marked_as_deleted); + } + + public override void set_up() { + this.account_info = new AccountInformation( + "account_01", + ServiceProvider.OTHER, + new MockCredentialsMediator(), + new RFC822.MailboxAddress(null, "test1@example.com") + ); + this.account = new MockAccount(this.account_info); + this.folder_root = new FolderRoot(false); + this.base_folder = new MockFolder( + this.account, + null, + this.folder_root.get_child("base"), + SpecialFolderType.NONE, + null + ); + this.other_folder = new MockFolder( + this.account, + null, + this.folder_root.get_child("other"), + SpecialFolderType.NONE, + null + ); + } + + public override void tear_down() { + this.other_folder = null; + this.base_folder = null; + this.folder_root = null; + this.account_info = null; + this.account = null; + } + + public void start_stop_monitoring() throws Error { + ConversationMonitor monitor = new ConversationMonitor( + this.base_folder, Folder.OpenFlags.NONE, Email.Field.NONE, 10 + ); + Cancellable test_cancellable = new Cancellable(); + + bool saw_scan_started = false; + bool saw_scan_completed = false; + monitor.scan_started.connect(() => { saw_scan_started = true; }); + monitor.scan_completed.connect(() => { saw_scan_completed = true; }); + + this.base_folder.expect_call( + "open_async", + { MockObject.int_arg(Folder.OpenFlags.NONE), test_cancellable } + ); + this.base_folder.expect_call("list_email_by_id_async"); + this.base_folder.expect_call("close_async"); + + monitor.start_monitoring_async.begin( + test_cancellable, (obj, res) => { async_complete(res); } + ); + monitor.start_monitoring_async.end(async_result()); + + // Process all of the async tasks arising from the open + while (this.main_loop.pending()) { + this.main_loop.iteration(true); + } + + monitor.stop_monitoring_async.begin( + test_cancellable, (obj, res) => { async_complete(res); } + ); + monitor.stop_monitoring_async.end(async_result()); + + assert_true(saw_scan_started, "scan_started not fired"); + assert_true(saw_scan_completed, "scan_completed not fired"); + + this.base_folder.assert_expectations(); + } + + public void open_error() throws Error { + ConversationMonitor monitor = new ConversationMonitor( + this.base_folder, Folder.OpenFlags.NONE, Email.Field.NONE, 10 + ); + + ExpectedCall open = this.base_folder + .expect_call("open_async") + .throws(new EngineError.SERVER_UNAVAILABLE("Mock error")); + + monitor.start_monitoring_async.begin( + null, (obj, res) => { async_complete(res); } + ); + try { + monitor.start_monitoring_async.end(async_result()); + assert_not_reached(); + } catch (Error err) { + assert_error(open.throw_error, err); + } + + this.base_folder.assert_expectations(); + } + + public void load_single_message() throws Error { + Email e1 = setup_email(1); + + Gee.MultiMap paths = + new Gee.HashMultiMap(); + paths.set(e1.id, this.base_folder.path); + + ConversationMonitor monitor = setup_monitor({e1}, paths); + + assert_int(1, monitor.size, "Conversation count"); + assert_non_null(monitor.window_lowest, "Lowest window id"); + assert_equal(e1.id, monitor.window_lowest, "Lowest window id"); + + Conversation c1 = Geary.Collection.get_first(monitor.read_only_view); + assert_equal(e1, c1.get_email_by_id(e1.id), "Email not present in conversation"); + } + + public void load_multiple_messages() throws Error { + Email e1 = setup_email(1, null); + Email e2 = setup_email(2, null); + Email e3 = setup_email(3, null); + + Gee.MultiMap paths = + new Gee.HashMultiMap(); + paths.set(e1.id, this.base_folder.path); + paths.set(e2.id, this.base_folder.path); + paths.set(e3.id, this.base_folder.path); + + ConversationMonitor monitor = setup_monitor({e3, e2, e1}, paths); + + assert_int(3, monitor.size, "Conversation count"); + assert_non_null(monitor.window_lowest, "Lowest window id"); + assert_equal(e1.id, monitor.window_lowest, "Lowest window id"); + } + + public void load_related_message() throws Error { + Email e1 = setup_email(1); + Email e2 = setup_email(2, e1); + + Gee.MultiMap paths = + new Gee.HashMultiMap(); + paths.set(e1.id, this.other_folder.path); + paths.set(e2.id, this.base_folder.path); + + Gee.MultiMap related_paths = + new Gee.HashMultiMap(); + related_paths.set(e1, this.other_folder.path); + related_paths.set(e2, this.base_folder.path); + + ConversationMonitor monitor = setup_monitor({e2}, paths, {related_paths}); + + assert_int(1, monitor.size, "Conversation count"); + assert_non_null(monitor.window_lowest, "Lowest window id"); + assert_equal(e2.id, monitor.window_lowest, "Lowest window id"); + + Conversation c1 = Geary.Collection.get_first(monitor.read_only_view); + assert_equal(e1, c1.get_email_by_id(e1.id), "Related email not present in conversation"); + assert_equal(e2, c1.get_email_by_id(e2.id), "In folder not present in conversation"); + } + + public void base_folder_message_appended() throws Error { + Email e1 = setup_email(1); + + Gee.MultiMap paths = + new Gee.HashMultiMap(); + paths.set(e1.id, this.base_folder.path); + + ConversationMonitor monitor = setup_monitor(); + assert_int(0, monitor.size, "Initial conversation count"); + + this.base_folder.expect_call("list_email_by_sparse_id_async") + .returns_object(new Gee.ArrayList.wrap({e1})); + + this.account.expect_call("get_special_folder"); + this.account.expect_call("get_special_folder"); + this.account.expect_call("get_special_folder"); + this.account.expect_call("local_search_message_id_async"); + + this.account.expect_call("get_containing_folders_async") + .returns_object(paths); + + this.base_folder.email_appended(new Gee.ArrayList.wrap({e1.id})); + + wait_for_signal(monitor, "conversations-added"); + this.base_folder.assert_expectations(); + this.account.assert_expectations(); + + assert_int(1, monitor.size, "Conversation count"); + } + + public void base_folder_message_removed() throws Error { + Email e1 = setup_email(1); + Email e2 = setup_email(2, e1); + Email e3 = setup_email(3); + + Gee.MultiMap paths = + new Gee.HashMultiMap(); + paths.set(e1.id, this.other_folder.path); + paths.set(e2.id, this.base_folder.path); + paths.set(e3.id, this.base_folder.path); + + Gee.MultiMap e2_related_paths = + new Gee.HashMultiMap(); + e2_related_paths.set(e1, this.other_folder.path); + e2_related_paths.set(e2, this.base_folder.path); + + ConversationMonitor monitor = setup_monitor( + {e3, e2}, paths, {null, e2_related_paths} + ); + assert_int(2, monitor.size, "Initial conversation count"); + assert_equal(e2.id, monitor.window_lowest, "Lowest window id"); + + // Removing a message will trigger another async load + this.base_folder.expect_call("list_email_by_id_async"); + + this.base_folder.email_removed(new Gee.ArrayList.wrap({e2.id})); + wait_for_signal(monitor, "conversations-removed"); + assert_int(1, monitor.size, "Conversation count"); + assert_equal(e3.id, monitor.window_lowest, "Lowest window id"); + + this.base_folder.email_removed(new Gee.ArrayList.wrap({e3.id})); + wait_for_signal(monitor, "conversations-removed"); + assert_int(0, monitor.size, "Conversation count"); + assert_null(monitor.window_lowest, "Lowest window id"); + + // Close the monitor to cancel the final load so it does not + // error out during later tests + this.base_folder.expect_call("close_async"); + monitor.stop_monitoring_async.begin( + null, (obj, res) => { async_complete(res); } + ); + monitor.stop_monitoring_async.end(async_result()); + } + + public void external_folder_message_appended() throws Error { + Email e1 = setup_email(1); + Email e2 = setup_email(2, e1); + Email e3 = setup_email(3, e1); + + Gee.MultiMap paths = + new Gee.HashMultiMap(); + paths.set(e1.id, this.base_folder.path); + paths.set(e2.id, this.base_folder.path); + paths.set(e3.id, this.other_folder.path); + + Gee.MultiMap related_paths = + new Gee.HashMultiMap(); + related_paths.set(e1, this.base_folder.path); + related_paths.set(e3, this.other_folder.path); + + ConversationMonitor monitor = setup_monitor({e1}, paths); + assert_int(1, monitor.size, "Initial conversation count"); + + this.other_folder.expect_call("open_async"); + this.other_folder.expect_call("list_email_by_sparse_id_async") + .returns_object(new Gee.ArrayList.wrap({e3})); + this.other_folder.expect_call("list_email_by_sparse_id_async") + .returns_object(new Gee.ArrayList.wrap({e3})); + this.other_folder.expect_call("close_async"); + + // ExternalAppendOperation's blacklist check + this.account.expect_call("get_special_folder"); + this.account.expect_call("get_special_folder"); + this.account.expect_call("get_special_folder"); + + ///////////////////////////////////////////////////////// + // First call to expand_conversations_async for e3's refs + + // LocalSearchOperationAppendOperation's blacklist check + this.account.expect_call("get_special_folder"); + this.account.expect_call("get_special_folder"); + this.account.expect_call("get_special_folder"); + + // Search for e1's ref + this.account.expect_call("local_search_message_id_async") + .returns_object(related_paths); + + // Search for e2's ref + this.account.expect_call("local_search_message_id_async"); + + ////////////////////////////////////////////////////////// + // Second call to expand_conversations_async for e1's refs + + this.account.expect_call("get_special_folder"); + this.account.expect_call("get_special_folder"); + this.account.expect_call("get_special_folder"); + this.account.expect_call("local_search_message_id_async"); + + // Finally, the call to process_email_complete_async + + this.account.expect_call("get_containing_folders_async") + .returns_object(paths); + + // Should not be added, since it's actually in the base folder + this.account.email_appended( + this.base_folder, + new Gee.ArrayList.wrap({e2.id}) + ); + + // Should be added, since it's an external message + this.account.email_appended( + this.other_folder, + new Gee.ArrayList.wrap({e3.id}) + ); + + wait_for_signal(monitor, "conversations-added"); + this.base_folder.assert_expectations(); + this.other_folder.assert_expectations(); + this.account.assert_expectations(); + + assert_int(1, monitor.size, "Conversation count"); + + Conversation c1 = Geary.Collection.get_first(monitor.read_only_view); + assert_int(2, c1.get_count(), "Conversation message count"); + assert_equal(e3, c1.get_email_by_id(e3.id), + "Appended email not present in conversation"); + } + + public void conversation_marked_as_deleted() throws Error { + Email e1 = setup_email(1); + + Gee.MultiMap paths = + new Gee.HashMultiMap(); + paths.set(e1.id, this.base_folder.path); + + ConversationMonitor monitor = setup_monitor({e1}, paths); + assert_int(1, monitor.size, "Conversation count"); + + // Mark message as deleted + Gee.HashMap flags_changed = + new Gee.HashMap(); + flags_changed.set(e1.id, new EmailFlags.with(EmailFlags.DELETED)); + this.account.email_flags_changed(this.base_folder, flags_changed); + + this.base_folder.expect_call("list_email_by_sparse_id_async"); + this.base_folder.expect_call("list_email_by_id_async"); + + wait_for_signal(monitor, "email-flags-changed"); + + assert_int(0, monitor.size, "Conversation count should now be zero after being marked deleted."); + } + + private Email setup_email(int id, Email? references = null) { + Email email = new Email(new MockEmailIdentifer(id)); + DateTime now = new DateTime.now_local(); + Geary.RFC822.MessageID mid = new Geary.RFC822.MessageID( + "test%d@localhost".printf(id) + ); + + Geary.RFC822.MessageIDList refs_list = null; + if (references != null) { + refs_list = new Geary.RFC822.MessageIDList.single( + references.message_id + ); + } + email.set_send_date(new Geary.RFC822.Date.from_date_time(now)); + email.set_email_properties(new MockEmailProperties(now)); + email.set_full_references(mid, null, refs_list); + return email; + } + + private ConversationMonitor + setup_monitor(Email[] base_folder_email = {}, + Gee.MultiMap? paths = null, + Gee.MultiMap[] related_paths = {}) + throws Error { + ConversationMonitor monitor = new ConversationMonitor( + this.base_folder, Folder.OpenFlags.NONE, Email.Field.NONE, 10 + ); + Cancellable test_cancellable = new Cancellable(); + + /* + * The process for loading messages looks roughly like this: + * - load_by_id_async + * - base_folder.list_email_by_id_async + * - process_email_async + * - gets all related messages from listing + * - expand_conversations_async + * - get_search_folder_blacklist (i.e. account.get_special_folder × 3) + * - foreach related: account.local_search_message_id_async + * - process_email_async + * - process_email_complete_async + * - get_containing_folders_async + */ + + this.base_folder.expect_call("open_async"); + ExpectedCall list_call = this.base_folder + .expect_call("list_email_by_id_async") + .returns_object(new Gee.ArrayList.wrap(base_folder_email)); + + if (base_folder_email.length > 0) { + // expand_conversations_async calls + // Account:get_special_folder() in + // get_search_folder_blacklist, and the default + // implementation of that calls get_special_folder. + this.account.expect_call("get_special_folder"); + this.account.expect_call("get_special_folder"); + this.account.expect_call("get_special_folder"); + + Gee.List base_email_ids = + new Gee.ArrayList(); + foreach (Email base_email in base_folder_email) { + base_email_ids.add(base_email.message_id); + } + + int base_i = 0; + bool has_related = ( + base_folder_email.length == related_paths.length + ); + bool found_related = false; + Gee.Set seen_ids = new Gee.HashSet(); + foreach (Email base_email in base_folder_email) { + ExpectedCall call = + this.account.expect_call("local_search_message_id_async"); + seen_ids.add(base_email.message_id); + if (has_related && related_paths[base_i] != null) { + call.returns_object(related_paths[base_i++]); + found_related = true; + } + + foreach (RFC822.MessageID ancestor in base_email.get_ancestors()) { + if (!seen_ids.contains(ancestor) && !base_email_ids.contains(ancestor)) { + this.account.expect_call("local_search_message_id_async"); + seen_ids.add(ancestor); + } + } + } + + // Second call to expand_conversations_async will be made + // if any related were loaded + if (found_related) { + this.account.expect_call("get_special_folder"); + this.account.expect_call("get_special_folder"); + this.account.expect_call("get_special_folder"); + + seen_ids.clear(); + foreach (Gee.MultiMap related in related_paths) { + if (related != null) { + foreach (Email email in related.get_keys()) { + if (!base_email_ids.contains(email.message_id)) { + foreach (RFC822.MessageID ancestor in email.get_ancestors()) { + if (!seen_ids.contains(ancestor)) { + this.account.expect_call("local_search_message_id_async"); + seen_ids.add(ancestor); + } + } + } + } + } + } + } + + ExpectedCall contains = + this.account.expect_call("get_containing_folders_async"); + if (paths != null) { + contains.returns_object(paths); + } + } + + monitor.start_monitoring_async.begin( + test_cancellable, (obj, res) => { async_complete(res); } + ); + monitor.start_monitoring_async.end(async_result()); + + if (base_folder_email.length == 0) { + wait_for_call(list_call); + } else { + wait_for_signal(monitor, "conversations-added"); + } + + this.base_folder.assert_expectations(); + this.account.assert_expectations(); + + return monitor; + } + +} diff -Nru geary-0.12.4/test/engine/app/app-conversation-set-test.vala geary-3.32.0/test/engine/app/app-conversation-set-test.vala --- geary-0.12.4/test/engine/app/app-conversation-set-test.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/test/engine/app/app-conversation-set-test.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,518 @@ +/* + * Copyright 2017 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +class Geary.App.ConversationSetTest : TestCase { + + + ConversationSet? test = null; + FolderRoot? folder_root = null; + Folder? base_folder = null; + + public ConversationSetTest() { + base("Geary.App.ConversationSetTest"); + add_test("add_all_basic", add_all_basic); + add_test("add_all_duplicate", add_all_duplicate); + add_test("add_all_append_descendants", add_all_append_descendants); + add_test("add_all_append_ancestors", add_all_append_ancestors); + add_test("add_all_merge", add_all_merge); + add_test("add_all_multi_path", add_all_multi_path); + add_test("add_all_append_path", add_all_append_path); + add_test("remove_all_removed", remove_all_removed); + add_test("remove_all_trimmed", remove_all_trimmed); + add_test("remove_all_remove_path", remove_all_remove_path); + add_test("remove_all_base_folder", remove_all_base_folder); + } + + public override void set_up() { + this.folder_root = new FolderRoot(false); + this.base_folder = new MockFolder( + null, + null, + this.folder_root.get_child("test"), + SpecialFolderType.NONE, + null + ); + this.test = new ConversationSet(this.base_folder); + } + + public override void tear_down() { + this.test = null; + this.folder_root = null; + this.base_folder = null; + } + + public void add_all_basic() throws Error { + Email e1 = setup_email(1); + Email e2 = setup_email(2); + + Gee.LinkedList emails = new Gee.LinkedList(); + emails.add(e1); + emails.add(e2); + + Gee.MultiMap email_paths = + new Gee.HashMultiMap(); + email_paths.set(e1.id, this.base_folder.path); + email_paths.set(e2.id, this.base_folder.path); + + Gee.Collection? added = null; + Gee.MultiMap? appended = null; + Gee.Collection? removed = null; + + this.test.add_all_emails( + emails, email_paths, + out added, out appended, out removed + ); + + assert(this.test.size == 2); + assert(this.test.get_email_count() == 2); + + assert(added != null); + assert(appended != null); + assert(removed != null); + + // Work out which collection in the collection corresponds to which + assert(added.size == 2); + Conversation? c1 = null; + Conversation? c2 = null; + foreach (Conversation convo in added) { + if (convo.get_email_by_id(e1.id) != null) { + c1 = convo; + } else if (convo.get_email_by_id(e2.id) != null) { + c2 = convo; + } + } + assert(c1 != null); + assert(c2 != null); + + assert(appended.size == 0); + assert(removed.is_empty); + } + + public void add_all_duplicate() throws Error { + Email e1 = setup_email(1); + + Gee.LinkedList emails = new Gee.LinkedList(); + emails.add(e1); + emails.add(e1); + + Gee.MultiMap email_paths = + new Gee.HashMultiMap(); + email_paths.set(e1.id, this.base_folder.path); + + // Pass 1: Duplicate in same input collection + + Gee.Collection? added = null; + Gee.MultiMap? appended = null; + Gee.Collection? removed = null; + this.test.add_all_emails( + emails, email_paths, out added, out appended, out removed + ); + + assert(this.test.size == 1); + assert(this.test.get_email_count() == 1); + + assert(added.size == 1); + assert(Geary.Collection.get_first(added).get_email_by_id(e1.id) == e1); + + assert(appended.size == 0); + assert(removed.is_empty); + + // Pass 2: Duplicate being re-added + + added = null; + appended = null; + removed = null; + this.test.add_all_emails( + emails, email_paths, out added, out appended, out removed + ); + + assert(this.test.size == 1); + assert(this.test.get_email_count() == 1); + + assert(added.is_empty); + assert(appended.size == 0); + assert(removed.is_empty); + } + + public void add_all_append_descendants() throws Error { + Email e1 = setup_email(1); + Email e2 = setup_email(2, e1); + + // Pass 1: Append two at the same time + + Gee.LinkedList emails = new Gee.LinkedList(); + emails.add(e1); + emails.add(e2); + + Gee.MultiMap email_paths = + new Gee.HashMultiMap(); + email_paths.set(e1.id, this.base_folder.path); + email_paths.set(e2.id, this.folder_root.get_child("other")); + + Gee.Collection? added = null; + Gee.MultiMap? appended = null; + Gee.Collection? removed = null; + this.test.add_all_emails( + emails, email_paths, out added, out appended, out removed + ); + + assert(this.test.size == 1); + assert(this.test.get_email_count() == 2); + + assert(added.size == 1); + + Conversation convo1 = Geary.Collection.get_first(added); + assert(convo1.get_email_by_id(e1.id) == e1); + assert(convo1.get_email_by_id(e2.id) == e2); + + assert(appended.size == 0); + assert(removed.is_empty); + + // Pass 2: Append one to an existing convo + + Email e3 = setup_email(3, e1); + + emails.clear(); + email_paths.clear(); + + emails.add(e3); + email_paths.set(e3.id, this.base_folder.path); + + added = null; + appended = null; + removed = null; + this.test.add_all_emails( + emails, email_paths, out added, out appended, out removed + ); + + assert(this.test.size == 1); + assert(this.test.get_email_count() == 3); + + assert(added.is_empty); + + assert(appended.size == 1); + Conversation convo2 = Geary.Collection.get_first(appended.get_keys()); + assert(convo2.get_email_by_id(e1.id) == e1); + assert(convo2.get_email_by_id(e2.id) == e2); + assert(convo2.get_email_by_id(e3.id) == e3); + + assert(appended.contains(convo2) == true); + assert(appended.get(convo2).contains(e1) != true); + assert(appended.get(convo2).contains(e2) != true); + assert(appended.get(convo2).contains(e3) == true); + + assert(removed.is_empty); + } + + public void add_all_append_ancestors() throws Error { + Email e1 = setup_email(1); + Email e2 = setup_email(2, e1); + + add_email_to_test_set(e2); + + Gee.LinkedList emails = new Gee.LinkedList(); + emails.add(e1); + + Gee.MultiMap email_paths = + new Gee.HashMultiMap(); + email_paths.set(e1.id, this.base_folder.path); + + Gee.Collection? added = null; + Gee.MultiMap? appended = null; + Gee.Collection? removed = null; + this.test.add_all_emails( + emails, email_paths, out added, out appended, out removed + ); + + assert(this.test.size == 1); + assert(this.test.get_email_count() == 2); + + assert(added.is_empty); + + assert(appended.size == 1); + Conversation convo = Geary.Collection.get_first(appended.get_keys()); + assert(convo.get_email_by_id(e1.id) == e1); + assert(convo.get_email_by_id(e2.id) == e2); + + assert(appended.contains(convo) == true); + assert(appended.get(convo).contains(e1) == true); + + assert(removed.is_empty); + } + + public void add_all_merge() throws Error { + Email e1 = setup_email(1); + add_email_to_test_set(e1); + + Email e2 = setup_email(2, e1); + + Email e3 = setup_email(3, e2); + add_email_to_test_set(e3); + + assert(this.test.size == 2); + assert(this.test.get_email_count() == 2); + + Conversation? c1 = this.test.get_by_email_identifier(e1.id); + Conversation? c3 = this.test.get_by_email_identifier(e3.id); + + assert(c1 != null); + assert(c3 != null); + assert(c1 != c3); + + Gee.LinkedList emails = new Gee.LinkedList(); + emails.add(e2); + + Gee.MultiMap email_paths = + new Gee.HashMultiMap(); + email_paths.set(e2.id, this.base_folder.path); + + Gee.Collection? added = null; + Gee.MultiMap? appended = null; + Gee.Collection? removed = null; + this.test.add_all_emails( + emails, email_paths, out added, out appended, out removed + ); + + assert(this.test.size == 1); + assert(this.test.get_email_count() == 3); + + Conversation? c2 = this.test.get_by_email_identifier(e2.id); + assert(c2 != null); + assert(c2.get_email_by_id(e1.id) == e1); + assert(c2.get_email_by_id(e2.id) == e2); + assert(c2.get_email_by_id(e3.id) == e3); + + // e2 might have been appended to e1's convo with e3, or vice + // versa, depending on the gods of entropy. + assert(c1 == c2 || c3 == c2); + bool e1_won = (c1 == c2); + + assert(appended.size == 2); + assert(appended.get(c2) != null); + assert(appended.get(c2).size == 2); + assert(appended.get(c2).contains(e2) == true); + if (e1_won) { + assert(appended.get(c2).contains(e3) == true); + } else { + assert(appended.get(c2).contains(e1) == true); + } + + assert(added.is_empty); + assert(removed.size == 1); + if (e1_won) { + assert(removed.contains(c3) == true); + } else { + assert(removed.contains(c1) == true); + } + + } + + public void add_all_multi_path() throws Error { + Email e1 = setup_email(1); + FolderPath other_path = this.folder_root.get_child("other"); + + Gee.LinkedList emails = new Gee.LinkedList(); + emails.add(e1); + + Gee.MultiMap email_paths = + new Gee.HashMultiMap(); + email_paths.set(e1.id, this.base_folder.path); + email_paths.set(e1.id, other_path); + + Gee.Collection? added = null; + Gee.MultiMap? appended = null; + Gee.Collection? removed = null; + this.test.add_all_emails( + emails, email_paths, out added, out appended, out removed + ); + + assert(this.test.size == 1); + assert(this.test.get_email_count() == 1); + + Conversation convo = this.test.get_by_email_identifier(e1.id); + assert(convo.is_in_base_folder(e1.id) == true); + assert(convo.get_folder_count(e1.id) == 2); + } + + public void add_all_append_path() throws Error { + Email e1 = setup_email(1); + add_email_to_test_set(e1); + + FolderPath other_path = this.folder_root.get_child("other"); + + Gee.LinkedList emails = new Gee.LinkedList(); + emails.add(e1); + + Gee.MultiMap email_paths = + new Gee.HashMultiMap(); + email_paths.set(e1.id, other_path); + + Gee.Collection? added = null; + Gee.MultiMap? appended = null; + Gee.Collection? removed = null; + this.test.add_all_emails( + emails, email_paths, out added, out appended, out removed + ); + + assert(this.test.size == 1); + assert(this.test.get_email_count() == 1); + + assert(added.is_empty); + assert(appended.size == 0); + assert(removed.is_empty); + + Conversation convo = this.test.get_by_email_identifier(e1.id); + assert(convo.is_in_base_folder(e1.id) == true); + assert(convo.get_folder_count(e1.id) == 2); + } + + public void remove_all_removed() throws Error { + Email e1 = setup_email(1); + add_email_to_test_set(e1); + + Conversation convo = this.test.get_by_email_identifier(e1.id); + + Gee.LinkedList ids = + new Gee.LinkedList(); + ids.add(e1.id); + + Gee.Set removed = new Gee.HashSet(); + Gee.MultiMap trimmed = + new Gee.HashMultiMap(); + this.test.remove_all_emails_by_identifier( + this.base_folder.path, ids, removed, trimmed + ); + + assert(this.test.size == 0); + assert(this.test.get_email_count() == 0); + + assert(removed != null); + assert(trimmed != null); + + assert(removed.contains(convo) == true); + assert(trimmed.size == 0); + } + + public void remove_all_trimmed() throws Error { + Email e1 = setup_email(1); + add_email_to_test_set(e1); + + Email e2 = setup_email(2, e1); + add_email_to_test_set(e2); + + Conversation convo = this.test.get_by_email_identifier(e1.id); + + Gee.LinkedList ids = + new Gee.LinkedList(); + ids.add(e1.id); + + Gee.Set removed = new Gee.HashSet(); + Gee.MultiMap trimmed = + new Gee.HashMultiMap(); + this.test.remove_all_emails_by_identifier( + this.base_folder.path, ids, removed, trimmed + ); + + assert(this.test.size == 1); + assert(this.test.get_email_count() == 1); + + assert(removed.is_empty == true); + assert(trimmed.contains(convo) == true); + assert(trimmed.get(convo).contains(e1) == true); + } + + public void remove_all_remove_path() throws Error { + FolderPath other_path = this.folder_root.get_child("other"); + Email e1 = setup_email(1); + add_email_to_test_set(e1, other_path); + + Conversation convo = this.test.get_by_email_identifier(e1.id); + assert(convo.get_folder_count(e1.id) == 2); + + Gee.LinkedList ids = + new Gee.LinkedList(); + ids.add(e1.id); + + Gee.Set removed = new Gee.HashSet(); + Gee.MultiMap trimmed = + new Gee.HashMultiMap(); + this.test.remove_all_emails_by_identifier( + other_path, ids, removed, trimmed + ); + + assert(this.test.size == 1); + assert(this.test.get_email_count() == 1); + + assert(removed.is_empty == true); + assert(trimmed.size == 0); + assert(convo.is_in_base_folder(e1.id) == true); + assert(convo.get_folder_count(e1.id) == 1); + } + + public void remove_all_base_folder() throws Error { + FolderPath other_path = this.folder_root.get_child("other"); + Email e1 = setup_email(1); + add_email_to_test_set(e1, other_path); + + Gee.LinkedList ids = + new Gee.LinkedList(); + ids.add(e1.id); + + Gee.List removed = new Gee.LinkedList(); + Gee.MultiMap trimmed = + new Gee.HashMultiMap(); + this.test.remove_all_emails_by_identifier( + this.base_folder.path, ids, removed, trimmed + ); + + assert_int(0, this.test.size, "ConversationSet size"); + assert_int(0, this.test.get_email_count(), "ConversationSet email size"); + + assert_int(1, removed.size, "Removed size"); + assert_int(0, trimmed.size, "Trimmed size"); + } + + private Email setup_email(int id, Email? references = null) { + Email email = new Email(new MockEmailIdentifer(id)); + DateTime now = new DateTime.now_local(); + Geary.RFC822.MessageID mid = new Geary.RFC822.MessageID( + "test%d@localhost".printf(id) + ); + + Geary.RFC822.MessageIDList refs_list = null; + if (references != null) { + refs_list = new Geary.RFC822.MessageIDList.single( + references.message_id + ); + } + email.set_send_date(new Geary.RFC822.Date.from_date_time(now)); + email.set_email_properties(new MockEmailProperties(now)); + email.set_full_references(mid, null, refs_list); + return email; + } + + private void add_email_to_test_set(Email to_add, + FolderPath? other_path=null) { + Gee.LinkedList emails = new Gee.LinkedList(); + emails.add(to_add); + + Gee.MultiMap email_paths = + new Gee.HashMultiMap(); + email_paths.set(to_add.id, this.base_folder.path); + if (other_path != null) { + email_paths.set(to_add.id, other_path); + } + + Gee.Collection? added = null; + Gee.MultiMap? appended = null; + Gee.Collection? removed = null; + this.test.add_all_emails( + emails, email_paths, out added, out appended, out removed + ); + } + +} diff -Nru geary-0.12.4/test/engine/app/app-conversation-test.vala geary-3.32.0/test/engine/app/app-conversation-test.vala --- geary-0.12.4/test/engine/app/app-conversation-test.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/test/engine/app/app-conversation-test.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,296 @@ +/* + * Copyright 2017-2018 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +class Geary.App.ConversationTest : TestCase { + + + Conversation? test = null; + Folder? base_folder = null; + FolderRoot? folder_root = null; + + + public ConversationTest() { + base("Geary.App.ConversationTest"); + add_test("add_basic", add_basic); + add_test("add_duplicate", add_duplicate); + add_test("add_multipath", add_multipath); + add_test("remove_basic", remove_basic); + add_test("remove_nonexistent", remove_nonexistent); + add_test("get_emails", get_emails); + add_test("get_emails_by_location", get_emails_by_location); + add_test("get_emails_blacklist", get_emails_blacklist); + add_test("get_emails_marked_for_deletion", get_emails_marked_for_deletion); + add_test("count_email_in_folder", count_email_in_folder); + } + + public override void set_up() { + this.folder_root = new FolderRoot(false); + this.base_folder = new MockFolder( + null, + null, + this.folder_root.get_child("test"), + SpecialFolderType.NONE, + null + ); + this.test = new Conversation(this.base_folder); + } + + public override void tear_down() { + this.test = null; + this.folder_root = null; + this.base_folder = null; + } + + public void add_basic() throws Error { + Geary.Email e1 = setup_email(1); + Geary.Email e2 = setup_email(2); + uint appended = 0; + this.test.appended.connect(() => { + appended++; + }); + + assert(this.test.add(e1, singleton(this.base_folder.path)) == true); + assert(this.test.is_in_base_folder(e1.id) == true); + assert(this.test.get_folder_count(e1.id) == 1); + assert(appended == 1); + assert(this.test.get_count() == 1); + + assert(this.test.add(e2, singleton(this.base_folder.path)) == true); + assert(this.test.is_in_base_folder(e2.id) == true); + assert(this.test.get_folder_count(e2.id) == 1); + assert(appended == 2); + assert(this.test.get_count() == 2); + } + + public void add_duplicate() throws Error { + Geary.Email e1 = setup_email(1); + uint appended = 0; + this.test.appended.connect(() => { + appended++; + }); + + assert(this.test.add(e1, singleton(this.base_folder.path)) == true); + assert(appended == 1); + assert(this.test.get_count() == 1); + + assert(this.test.add(e1, singleton(this.base_folder.path)) == false); + assert(appended == 1); + assert(this.test.get_count() == 1); + } + + public void add_multipath() throws Error { + Geary.Email e1 = setup_email(1); + this.test.add(e1, singleton(this.base_folder.path)); + + Geary.Email e2 = setup_email(2); + this.test.add(e2, singleton(this.base_folder.path)); + + FolderPath other_path = this.folder_root.get_child("other"); + Gee.LinkedList other_paths = new Gee.LinkedList(); + other_paths.add(other_path); + + assert(this.test.add(e1, other_paths) == false); + assert(this.test.is_in_base_folder(e1.id) == true); + assert(this.test.get_folder_count(e1.id) == 2); + + assert(this.test.is_in_base_folder(e2.id) == true); + assert(this.test.get_folder_count(e2.id) == 1); + + this.test.remove_path(e1.id, other_path); + assert(this.test.is_in_base_folder(e1.id) == true); + assert(this.test.get_folder_count(e1.id) == 1); + } + + public void remove_basic() throws Error { + Geary.Email e1 = setup_email(1); + this.test.add(e1, singleton(this.base_folder.path)); + + Geary.Email e2 = setup_email(2); + this.test.add(e2, singleton(this.base_folder.path)); + + uint trimmed = 0; + this.test.trimmed.connect(() => { + trimmed++; + }); + + Gee.Set? removed = this.test.remove(e1); + assert(removed != null); + assert(removed.size == 1); + assert(removed.contains(e1.message_id)); + assert(trimmed == 1); + assert(this.test.get_count() == 1); + + removed = this.test.remove(e2); + assert(removed != null); + assert(removed.size == 1); + assert(removed.contains(e2.message_id)); + assert(trimmed == 2); + assert(this.test.get_count() == 0); + } + + public void remove_nonexistent() throws Error { + Geary.Email e1 = setup_email(1); + Geary.Email e2 = setup_email(2); + + uint trimmed = 0; + this.test.trimmed.connect(() => { + trimmed++; + }); + + assert(this.test.remove(e2) == null); + assert(trimmed == 0); + assert(this.test.get_count() == 0); + + this.test.add(e1, singleton(this.base_folder.path)); + + assert(this.test.remove(e2) == null); + assert(trimmed == 0); + assert(this.test.get_count() == 1); + } + + public void get_emails() throws GLib.Error { + Geary.Email e1 = setup_email(1); + this.test.add(e1, singleton(this.base_folder.path)); + + FolderPath other_path = this.folder_root.get_child("other"); + Geary.Email e2 = setup_email(2); + this.test.add(e2, singleton(other_path)); + + assert_int( + 2, this.test.get_emails(Conversation.Ordering.NONE).size + ); + } + + public void get_emails_by_location() throws GLib.Error { + Geary.Email e1 = setup_email(1); + this.test.add(e1, singleton(this.base_folder.path)); + + FolderPath other_path = this.folder_root.get_child("other"); + Geary.Email e2 = setup_email(2); + this.test.add(e2, singleton(other_path)); + + assert_int( + 1, this.test.get_emails(Conversation.Ordering.NONE, + Conversation.Location.IN_FOLDER).size, + "Unexpected in-folder size" + ); + assert_equal( + e1, + traverse(this.test.get_emails(Conversation.Ordering.NONE, + Conversation.Location.IN_FOLDER)) + .first(), + "Unexpected in-folder element" + ); + + assert_int( + 1, this.test.get_emails(Conversation.Ordering.NONE, + Conversation.Location.OUT_OF_FOLDER).size, + "Unexpected out-of-folder size" + ); + assert_equal( + e2, + traverse(this.test.get_emails(Conversation.Ordering.NONE, + Conversation.Location.OUT_OF_FOLDER)) + .first(), + "Unexpected out-of-folder element" + ); + } + + public void get_emails_blacklist() throws GLib.Error { + Geary.Email e1 = setup_email(1); + this.test.add(e1, singleton(this.base_folder.path)); + + FolderPath other_path = this.folder_root.get_child("other"); + Geary.Email e2 = setup_email(2); + this.test.add(e2, singleton(other_path)); + + Gee.Collection blacklist = new Gee.ArrayList(); + + blacklist.add(other_path); + assert_int( + 1, this.test.get_emails(Conversation.Ordering.NONE, + Conversation.Location.ANYWHERE, + blacklist + ).size, + "Unexpected other blacklist size" + ); + assert_equal( + e1, + traverse(this.test.get_emails(Conversation.Ordering.NONE, + Conversation.Location.ANYWHERE, + blacklist) + ).first(), + "Unexpected other blacklist element" + ); + + blacklist.clear(); + blacklist.add(this.base_folder.path); + assert_int( + 1, this.test.get_emails(Conversation.Ordering.NONE, + Conversation.Location.ANYWHERE, + blacklist + ).size, + "Unexpected other blacklist size" + ); + assert_equal( + e2, + traverse(this.test.get_emails(Conversation.Ordering.NONE, + Conversation.Location.ANYWHERE, + blacklist) + ).first(), + "Unexpected other blacklist element" + ); + } + + public void get_emails_marked_for_deletion() throws GLib.Error { + Geary.Email e1 = setup_email(1); + e1.set_flags(new Geary.EmailFlags.with(Geary.EmailFlags.DELETED)); + this.test.add(e1, singleton(this.base_folder.path)); + + assert_int( + 0, this.test.get_emails(Conversation.Ordering.NONE, + Conversation.Location.ANYWHERE + ).size, + "Message marked for deletion still present in conversation" + ); + } + + public void count_email_in_folder() throws GLib.Error { + Geary.Email e1 = setup_email(1); + this.test.add(e1, singleton(this.base_folder.path)); + + assert_uint( + 1, this.test.get_count_in_folder(this.base_folder.path), + "In-folder count" + ); + assert_uint( + 0, + this.test.get_count_in_folder(this.folder_root.get_child("other")), + "Out-folder count" + ); + } + + private Gee.Collection singleton(E element) { + Gee.LinkedList collection = new Gee.LinkedList(); + collection.add(element); + return collection; + } + + + private Email setup_email(int id) { + Email email = new Email(new MockEmailIdentifer(id)); + DateTime now = new DateTime.now_local(); + Geary.RFC822.MessageID mid = new Geary.RFC822.MessageID( + "test%d@localhost".printf(id) + ); + email.set_full_references(mid, null, null); + email.set_email_properties(new MockEmailProperties(now)); + email.set_send_date(new Geary.RFC822.Date.from_date_time(now)); + return email; + } + +} diff -Nru geary-0.12.4/test/engine/db/db-database-test.vala geary-3.32.0/test/engine/db/db-database-test.vala --- geary-0.12.4/test/engine/db/db-database-test.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/test/engine/db/db-database-test.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,176 @@ +/* + * Copyright 2018 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +class Geary.Db.DatabaseTest : TestCase { + + + public DatabaseTest() { + base("Geary.Db.DatabaseTest"); + add_test("transient_open", transient_open); + add_test("open_existing", open_existing); + add_test("open_create_file", open_create_file); + add_test("open_create_dir", open_create_dir); + add_test("open_create_dir_existing", open_create_dir_existing); + add_test("open_check_corruption", open_check_corruption); + add_test("open_create_check", open_create_check); + } + + public void transient_open() throws Error { + Database db = new Geary.Db.Database.transient(); + db.open.begin( + Geary.Db.DatabaseFlags.NONE, null, null, + (obj, ret) => { async_complete(ret); } + ); + db.open.end(async_result()); + + // Need to get a connection since the database doesn't + // actually get created until then + db.get_master_connection(); + } + + public void open_existing() throws Error { + GLib.FileIOStream stream; + GLib.File tmp_file = GLib.File.new_tmp( + "geary-db-database-test-XXXXXX", out stream + ); + + Database db = new Geary.Db.Database.persistent(tmp_file); + db.open.begin( + Geary.Db.DatabaseFlags.NONE, null, null, + (obj, ret) => { async_complete(ret); } + ); + db.open.end(async_result()); + + // Need to get a connection since the database doesn't + // actually get created until then + db.get_master_connection(); + + tmp_file.delete(); + } + + public void open_create_file() throws Error { + GLib.File tmp_dir = GLib.File.new_for_path( + GLib.DirUtils.make_tmp("geary-db-database-test-XXXXXX") + ); + + Database db = new Geary.Db.Database.persistent( + tmp_dir.get_child("test.db") + ); + db.open.begin( + Geary.Db.DatabaseFlags.CREATE_FILE, null, null, + (obj, ret) => { async_complete(ret); } + ); + db.open.end(async_result()); + + // Need to get a connection since the database doesn't + // actually get created until then + db.get_master_connection(); + + db.file.delete(); + tmp_dir.delete(); + } + + public void open_create_dir() throws Error { + GLib.File tmp_dir = GLib.File.new_for_path( + GLib.DirUtils.make_tmp("geary-db-database-test-XXXXXX") + ); + + Database db = new Geary.Db.Database.persistent( + tmp_dir.get_child("nonexistent").get_child("test.db") + ); + db.open.begin( + Geary.Db.DatabaseFlags.CREATE_DIRECTORY | + Geary.Db.DatabaseFlags.CREATE_FILE, + null, null, + (obj, ret) => { async_complete(ret); } + ); + db.open.end(async_result()); + + // Need to get a connection since the database doesn't + // actually get created until then + db.get_master_connection(); + + db.file.delete(); + db.file.get_parent().delete(); + tmp_dir.delete(); + } + + public void open_create_dir_existing() throws Error { + GLib.File tmp_dir = GLib.File.new_for_path( + GLib.DirUtils.make_tmp("geary-db-database-test-XXXXXX") + ); + + Database db = new Geary.Db.Database.persistent( + tmp_dir.get_child("test.db") + ); + db.open.begin( + Geary.Db.DatabaseFlags.CREATE_DIRECTORY | + Geary.Db.DatabaseFlags.CREATE_FILE, + null, null, + (obj, ret) => { async_complete(ret); } + ); + db.open.end(async_result()); + + // Need to get a connection since the database doesn't + // actually get created until then + db.get_master_connection(); + + db.file.delete(); + tmp_dir.delete(); + } + + public void open_check_corruption() throws Error { + GLib.File tmp_dir = GLib.File.new_for_path( + GLib.DirUtils.make_tmp("geary-db-database-test-XXXXXX") + ); + + Database db = new Geary.Db.Database.persistent( + tmp_dir.get_child("test.db") + ); + db.open.begin( + Geary.Db.DatabaseFlags.CREATE_FILE | + Geary.Db.DatabaseFlags.CHECK_CORRUPTION, + null, null, + (obj, ret) => { async_complete(ret); } + ); + db.open.end(async_result()); + + // Need to get a connection since the database doesn't + // actually get created until then + db.get_master_connection(); + + db.file.delete(); + tmp_dir.delete(); + } + + public void open_create_check() throws Error { + GLib.File tmp_dir = GLib.File.new_for_path( + GLib.DirUtils.make_tmp("geary-db-database-test-XXXXXX") + ); + + Database db = new Geary.Db.Database.persistent( + tmp_dir.get_child("nonexistent").get_child("test.db") + ); + db.open.begin( + Geary.Db.DatabaseFlags.CREATE_DIRECTORY | + Geary.Db.DatabaseFlags.CREATE_FILE | + Geary.Db.DatabaseFlags.CHECK_CORRUPTION, + null, null, + (obj, ret) => { async_complete(ret); } + ); + db.open.end(async_result()); + + // Need to get a connection since the database doesn't + // actually get created until then + db.get_master_connection(); + + db.file.delete(); + db.file.get_parent().delete(); + tmp_dir.delete(); + } + +} diff -Nru geary-0.12.4/test/engine/db/db-versioned-database-test.vala geary-3.32.0/test/engine/db/db-versioned-database-test.vala --- geary-0.12.4/test/engine/db/db-versioned-database-test.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/test/engine/db/db-versioned-database-test.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,54 @@ +/* + * Copyright 2018 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + + +class Geary.Db.VersionedDatabaseTest : TestCase { + + + public VersionedDatabaseTest() { + base("Geary.Db.VersionedDatabaseTest"); + add_test("open_new", open_new); + } + + public void open_new() throws Error { + GLib.File tmp_dir = GLib.File.new_for_path( + GLib.DirUtils.make_tmp("geary-db-database-test-XXXXXX") + ); + + GLib.File sql1 = tmp_dir.get_child("version-001.sql"); + sql1.create( + GLib.FileCreateFlags.NONE + ).write("CREATE TABLE TestTable (id INTEGER PRIMARY KEY, col TEXT);".data); + + GLib.File sql2 = tmp_dir.get_child("version-002.sql"); + sql2.create( + GLib.FileCreateFlags.NONE + ).write("INSERT INTO TestTable (col) VALUES ('value');".data); + + VersionedDatabase db = new VersionedDatabase.persistent( + tmp_dir.get_child("test.db"), tmp_dir + ); + + db.open.begin( + Geary.Db.DatabaseFlags.CREATE_FILE, null, null, + (obj, ret) => { async_complete(ret); } + ); + db.open.end(async_result()); + + Geary.Db.Result result = db.query("SELECT * FROM TestTable;"); + assert_false(result.finished, "Row not inserted"); + assert_string("value", result.string_for("col")); + assert_false(result.next(), "Multiple rows inserted"); + + db.file.delete(); + sql1.delete(); + sql2.delete(); + tmp_dir.delete(); + } + + +} diff -Nru geary-0.12.4/test/engine/imap/command/imap-create-command-test.vala geary-3.32.0/test/engine/imap/command/imap-create-command-test.vala --- geary-0.12.4/test/engine/imap/command/imap-create-command-test.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/test/engine/imap/command/imap-create-command-test.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,34 @@ +/* + * Copyright 2017 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +class Geary.Imap.CreateCommandTest : TestCase { + + + public CreateCommandTest() { + base("Geary.Imap.CreateCommandTest"); + add_test("basic_create", basic_create); + add_test("special_use", special_use); + } + + public void basic_create() throws Error { + assert_string( + "---- create owatagusiam/", + new CreateCommand(new MailboxSpecifier("owatagusiam/")).to_string() + ); + } + + public void special_use() throws Error { + assert_string( + "---- create Everything (use (\\All))", + new CreateCommand.special_use( + new MailboxSpecifier("Everything"), + SpecialFolderType.ALL_MAIL + ).to_string() + ); + } + +} diff -Nru geary-0.12.4/test/engine/imap/command/imap-fetch-command-test.vala geary-3.32.0/test/engine/imap/command/imap-fetch-command-test.vala --- geary-0.12.4/test/engine/imap/command/imap-fetch-command-test.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/test/engine/imap/command/imap-fetch-command-test.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,110 @@ +/* + * Copyright 2018 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +class Geary.Imap.FetchCommandTest : TestCase { + + + private MessageSet? msg_set = null; + + + public FetchCommandTest() { + base("Geary.Imap.FetchCommandTest"); + add_test("list_ctor_single_data_item", list_ctor_single_data_item); + add_test("list_ctor_single_body_item", list_ctor_single_body_item); + add_test("list_ctor_multiple_data_item", list_ctor_multiple_data_item); + add_test("list_ctor_multiple_body_item", list_ctor_multiple_body_item); + add_test("list_ctor_both", list_ctor_both); + } + + public override void set_up() { + this.msg_set = new MessageSet(new SequenceNumber(1)); + } + + public void list_ctor_single_data_item() throws GLib.Error { + Gee.List data_items = + new Gee.LinkedList(); + data_items.add(FetchDataSpecifier.UID); + + assert_string( + "---- fetch 1 uid", + new FetchCommand(this.msg_set, data_items, null).to_string() + ); + } + + public void list_ctor_single_body_item() throws GLib.Error { + Gee.List body_items = + new Gee.LinkedList(); + body_items.add( + new FetchBodyDataSpecifier( + FetchBodyDataSpecifier.SectionPart.TEXT, null, -1, -1, null + ) + ); + + assert_string( + "---- fetch 1 body[text]", + new FetchCommand(this.msg_set, null, body_items).to_string() + ); + } + + public void list_ctor_multiple_data_item() throws GLib.Error { + Gee.List data_items = + new Gee.LinkedList(); + data_items.add(FetchDataSpecifier.UID); + data_items.add(FetchDataSpecifier.BODY); + + assert_string( + "---- fetch 1 (uid body)", + new FetchCommand(this.msg_set, data_items, null).to_string() + ); + } + + public void list_ctor_multiple_body_item() throws GLib.Error { + Gee.List body_items = + new Gee.LinkedList(); + body_items.add( + new FetchBodyDataSpecifier( + FetchBodyDataSpecifier.SectionPart.HEADER, null, -1, -1, null + ) + ); + body_items.add( + new FetchBodyDataSpecifier( + FetchBodyDataSpecifier.SectionPart.TEXT, null, -1, -1, null + ) + ); + + assert_string( + "---- fetch 1 (body[header] body[text])", + new FetchCommand(this.msg_set, null, body_items).to_string() + ); + } + + public void list_ctor_both() throws GLib.Error { + Gee.List data_items = + new Gee.LinkedList(); + data_items.add(FetchDataSpecifier.UID); + data_items.add(FetchDataSpecifier.FLAGS); + + Gee.List body_items = + new Gee.LinkedList(); + body_items.add( + new FetchBodyDataSpecifier( + FetchBodyDataSpecifier.SectionPart.HEADER, null, -1, -1, null + ) + ); + body_items.add( + new FetchBodyDataSpecifier( + FetchBodyDataSpecifier.SectionPart.TEXT, null, -1, -1, null + ) + ); + + assert_string( + "---- fetch 1 (uid flags body[header] body[text])", + new FetchCommand(this.msg_set, data_items, body_items).to_string() + ); + } + +} diff -Nru geary-0.12.4/test/engine/imap/message/imap-data-format-test.vala geary-3.32.0/test/engine/imap/message/imap-data-format-test.vala --- geary-0.12.4/test/engine/imap/message/imap-data-format-test.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/test/engine/imap/message/imap-data-format-test.vala 2019-03-17 13:39:29.000000000 +0000 @@ -5,7 +5,7 @@ * (version 2.1 or later). See the COPYING file in this distribution. */ -class Geary.Imap.DataFormatTest : Gee.TestCase { +class Geary.Imap.DataFormatTest : TestCase { public DataFormatTest() { @@ -13,73 +13,91 @@ add_test("is_atom_special", is_atom_special); } - public void is_atom_special() { - assert_true( - !DataFormat.is_atom_special('a') && !DataFormat.is_atom_special('z') - ); - assert_true( - !DataFormat.is_atom_special('A') && !DataFormat.is_atom_special('Z') - ); - assert_true( - !DataFormat.is_atom_special('0') && !DataFormat.is_atom_special('9') - ); - assert_true( - !DataFormat.is_atom_special('#') && - !DataFormat.is_atom_special('.') && - !DataFormat.is_atom_special('+') && - !DataFormat.is_atom_special('/') && - !DataFormat.is_atom_special('~') && - !DataFormat.is_atom_special(':') + public void is_atom_special() throws Error { + assert_false( + DataFormat.is_atom_special('a') || DataFormat.is_atom_special('z'), + "Lower case ASCII" + ); + assert_false( + DataFormat.is_atom_special('A') || DataFormat.is_atom_special('Z'), + "Upper case ASCII" + ); + assert_false( + DataFormat.is_atom_special('0') || DataFormat.is_atom_special('9'), + "ASCII numbers" + ); + assert_false( + DataFormat.is_atom_special('#') || + DataFormat.is_atom_special('.') || + DataFormat.is_atom_special('+') || + DataFormat.is_atom_special('/') || + DataFormat.is_atom_special('~') || + DataFormat.is_atom_special(':'), + "Common mailbox ASCII symbols" ); // atom-specials assert_true( - DataFormat.is_atom_special('(') + DataFormat.is_atom_special('('), + "Atom-special: (" ); assert_true( - DataFormat.is_atom_special(')') + DataFormat.is_atom_special(')'), + "Atom-special: )" ); assert_true( - DataFormat.is_atom_special('{') + DataFormat.is_atom_special('{'), + "Atom-special: {" ); assert_true( - DataFormat.is_atom_special(' ') + DataFormat.is_atom_special(' '), + "Atom-special: SP" ); assert_true( - DataFormat.is_atom_special(0x00) + DataFormat.is_atom_special(0x00), + "Atom-special: CTL (NUL)" ); assert_true( - DataFormat.is_atom_special(0x1F) + DataFormat.is_atom_special(0x1F), + "Atom-special: CTL (US)" ); assert_true( - DataFormat.is_atom_special(0x7F) + DataFormat.is_atom_special(0x7F), + "Atom-special: CTL (DEL)" ); assert_true( - DataFormat.is_atom_special(0x80) + DataFormat.is_atom_special(0x80), + "Atom-special: Non-ASCII (0x80)" ); assert_true( - DataFormat.is_atom_special(0xFE) + DataFormat.is_atom_special(0xFE), + "Atom-special: Non-ASCII (0xFE)" ); // list-wildcards assert_true( - DataFormat.is_atom_special('%') + DataFormat.is_atom_special('%'), + "Atom-special: %" ); assert_true( - DataFormat.is_atom_special('*') + DataFormat.is_atom_special('*'), + "Atom-special: *" ); // quoted-specials assert_true( - DataFormat.is_atom_special('\"') + DataFormat.is_atom_special('\"'), + "Atom-special: \"" ); assert_true( - DataFormat.is_atom_special('\\') + DataFormat.is_atom_special('\\'), + "Atom-special: \\" ); // resp-specials assert_true( - DataFormat.is_atom_special(']') + DataFormat.is_atom_special(']'), + "Atom-special: ]" ); } diff -Nru geary-0.12.4/test/engine/imap/message/imap-mailbox-specifier-test.vala geary-3.32.0/test/engine/imap/message/imap-mailbox-specifier-test.vala --- geary-0.12.4/test/engine/imap/message/imap-mailbox-specifier-test.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/test/engine/imap/message/imap-mailbox-specifier-test.vala 2019-03-17 13:39:29.000000000 +0000 @@ -5,22 +5,24 @@ * (version 2.1 or later). See the COPYING file in this distribution. */ -class Geary.Imap.MailboxSpecifierTest : Gee.TestCase { +class Geary.Imap.MailboxSpecifierTest : TestCase { public MailboxSpecifierTest() { base("Geary.Imap.MailboxSpecifierTest"); add_test("to_parameter", to_parameter); add_test("from_parameter", from_parameter); + add_test("from_folder_path", from_folder_path); + add_test("folder_path_is_inbox", folder_path_is_inbox); } - public void to_parameter() { - assert( - "test" == + public void to_parameter() throws Error { + assert_string( + "test", new MailboxSpecifier("test").to_parameter().to_string() ); - assert( - "foo/bar" == + assert_string( + "foo/bar", new MailboxSpecifier("foo/bar").to_parameter().to_string() ); @@ -28,33 +30,113 @@ // QuotedStringParameter doesn't actually handle that, so just // check that it is correct type Parameter quoted = new MailboxSpecifier("""foo\bar""").to_parameter(); - assert(quoted is QuotedStringParameter); + assert_true(quoted is QuotedStringParameter, "Backslash was not quoted"); - assert( - "ol&AOk-" == + assert_string( + "ol&AOk-", new MailboxSpecifier("olé").to_parameter().to_string() ); } - public void from_parameter() { - assert( - "test" == + public void from_parameter() throws Error { + assert_string( + "test", new MailboxSpecifier.from_parameter( new UnquotedStringParameter("test")).name ); // This won't be quoted or escaped since QuotedStringParameter // doesn't actually handle that. - assert( - "foo\\bar" == + assert_string( + "foo\\bar", new MailboxSpecifier.from_parameter( new QuotedStringParameter("""foo\bar""")).name ); - assert( - "olé" == + assert_string( + "olé", new MailboxSpecifier.from_parameter( new UnquotedStringParameter("ol&AOk-")).name ); } + public void from_folder_path() throws Error { + FolderRoot root = new FolderRoot(); + MailboxSpecifier inbox = new MailboxSpecifier("Inbox"); + assert_string( + "Foo", + new MailboxSpecifier.from_folder_path( + root.get_child("Foo"), inbox, "$" + ).name + ); + assert_string( + "Foo$Bar", + new MailboxSpecifier.from_folder_path( + root.get_child("Foo").get_child("Bar"), inbox, "$" + ).name + ); + assert_string( + "Inbox", + new MailboxSpecifier.from_folder_path( + root.get_child(MailboxSpecifier.CANONICAL_INBOX_NAME), + inbox, + "$" + ).name + ); + + try { + new MailboxSpecifier.from_folder_path( + root.get_child(""), inbox, "$" + ); + assert_not_reached(); + } catch (GLib.Error err) { + // all good + } + + try { + new MailboxSpecifier.from_folder_path( + root.get_child("test").get_child(""), inbox, "$" + ); + assert_not_reached(); + } catch (GLib.Error err) { + // all good + } + + try { + new MailboxSpecifier.from_folder_path(root, inbox, "$"); + assert_not_reached(); + } catch (GLib.Error err) { + // all good + } + } + + public void folder_path_is_inbox() throws GLib.Error { + FolderRoot root = new FolderRoot(); + assert_true( + MailboxSpecifier.folder_path_is_inbox(root.get_child("Inbox")) + ); + assert_true( + MailboxSpecifier.folder_path_is_inbox(root.get_child("inbox")) + ); + assert_true( + MailboxSpecifier.folder_path_is_inbox(root.get_child("INBOX")) + ); + + assert_false( + MailboxSpecifier.folder_path_is_inbox(root) + ); + assert_false( + MailboxSpecifier.folder_path_is_inbox(root.get_child("blah")) + ); + assert_false( + MailboxSpecifier.folder_path_is_inbox( + root.get_child("blah").get_child("Inbox") + ) + ); + assert_false( + MailboxSpecifier.folder_path_is_inbox( + root.get_child("Inbox").get_child("Inbox") + ) + ); + } + } diff -Nru geary-0.12.4/test/engine/imap/parameter/imap-list-parameter-test.vala geary-3.32.0/test/engine/imap/parameter/imap-list-parameter-test.vala --- geary-0.12.4/test/engine/imap/parameter/imap-list-parameter-test.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/test/engine/imap/parameter/imap-list-parameter-test.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,30 @@ +/* + * Copyright 2018 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +class Geary.Imap.ListParameterTest : TestCase { + + + public ListParameterTest() { + base("Geary.Imap.ListParameterTest"); + add_test("add_to_multiple_parents", add_to_multiple_parents); + } + + // See GitLab Issue #26 + public void add_to_multiple_parents() throws GLib.Error { + ListParameter child = new ListParameter(); + + ListParameter parent_1 = new ListParameter(); + ListParameter parent_2 = new ListParameter(); + + parent_1.add(child); + parent_2.add(child); + + assert_int(1, parent_1.size, "Parent 1 does not contain child"); + assert_int(1, parent_2.size, "Parent 2 does not contain child"); + } + +} diff -Nru geary-0.12.4/test/engine/imap/response/imap-namespace-response-test.vala geary-3.32.0/test/engine/imap/response/imap-namespace-response-test.vala --- geary-0.12.4/test/engine/imap/response/imap-namespace-response-test.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/test/engine/imap/response/imap-namespace-response-test.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,138 @@ +/* + * Copyright 2017 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +class Geary.Imap.NamespaceResponseTest : TestCase { + + + public NamespaceResponseTest() { + base("Geary.Imap.NamespaceResponseTest"); + add_test("minimal", minimal); + add_test("complete", complete); + add_test("cyrus", cyrus); + add_test("anonymous", anonymous); + } + + public void minimal() throws Error { + // * NAMESPACE NIL NIL NIL + try { + ServerData data = newNamespaceServerData(null, null, null); + + NamespaceResponse response = NamespaceResponse.decode(data); + assert(response.personal == null); + assert(response.user == null); + assert(response.shared == null); + } catch (Error err) { + assert_not_reached(); + } + } + + public void complete() throws Error { + // * NAMESPACE (("" "/")) (("~" "/")) (("#shared/" "/") + ListParameter personal = new ListParameter(); + personal.add(newNamespace("", "/")); + ListParameter user = new ListParameter(); + user.add(newNamespace("~", "/")); + ListParameter shared = new ListParameter(); + shared.add(newNamespace("#shared/", "/")); + try { + ServerData data = newNamespaceServerData(personal, user, shared); + + NamespaceResponse response = NamespaceResponse.decode(data); + assert(response.personal != null); + assert(response.personal.size == 1); + assert(response.personal[0].prefix == ""); + assert(response.personal[0].delim == "/"); + + assert(response.user != null); + assert(response.user.size == 1); + assert(response.user[0].prefix == "~"); + assert(response.user[0].delim == "/"); + + assert(response.shared != null); + assert(response.shared.size == 1); + assert(response.shared[0].prefix == "#shared/"); + assert(response.shared[0].delim == "/"); + } catch (Error err) { + assert_not_reached(); + } + } + + public void cyrus() throws Error { + // * NAMESPACE (("INBOX." ".")) NIL (("" ".")) + ListParameter personal = new ListParameter(); + personal.add(newNamespace("INBOX.", ".")); + ListParameter shared = new ListParameter(); + shared.add(newNamespace("", ".")); + try { + ServerData data = newNamespaceServerData(personal, null, shared); + + NamespaceResponse response = NamespaceResponse.decode(data); + assert(response.personal != null); + assert(response.personal[0].prefix == "INBOX."); + assert(response.personal[0].delim == "."); + assert(response.user == null); + assert(response.shared != null); + assert(response.shared.size == 1); + assert(response.shared[0].prefix == ""); + assert(response.shared[0].delim == "."); + } catch (Error err) { + assert_not_reached(); + } + } + + public void anonymous() throws Error { + // * NAMESPACE NIL NIL (("" ".")) + ListParameter shared = new ListParameter(); + shared.add(newNamespace("", ",")); + try { + ServerData data = newNamespaceServerData(null, null, shared); + + NamespaceResponse response = NamespaceResponse.decode(data); + assert(response.personal == null); + assert(response.user == null); + assert(response.shared != null); + assert(response.shared.size == 1); + assert(response.shared[0].prefix == ""); + assert(response.shared[0].delim == ","); + } catch (Error err) { + assert_not_reached(); + } + } + + private ServerData newNamespaceServerData(ListParameter? personal, + ListParameter? users, + ListParameter? shared) + throws Error { + RootParameters root = new RootParameters(); + root.add(new UnquotedStringParameter("*")); + root.add(new AtomParameter("namespace")); + // Vala's ternary op support is all like :'( + if (personal == null) + root.add(NilParameter.instance); + else + root.add(personal); + + if (users == null) + root.add(NilParameter.instance); + else + root.add(users); + + if (shared == null) + root.add(NilParameter.instance); + else + root.add(shared); + + return new ServerData.migrate(root); + } + + private ListParameter newNamespace(string prefix, string? delim) { + ListParameter ns = new ListParameter(); + ns.add(new QuotedStringParameter(prefix)); + ns.add(delim == null ? (Parameter) NilParameter.instance : new QuotedStringParameter(delim)); + return ns; + } +} diff -Nru geary-0.12.4/test/engine/imap/transport/imap-deserializer-test.vala geary-3.32.0/test/engine/imap/transport/imap-deserializer-test.vala --- geary-0.12.4/test/engine/imap/transport/imap-deserializer-test.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/test/engine/imap/transport/imap-deserializer-test.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,16 +1,17 @@ /* - * Copyright 2017 Michael Gratton + * Copyright 2017-2018 Michael Gratton * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ -class Geary.Imap.DeserializerTest : Gee.TestCase { +class Geary.Imap.DeserializerTest : TestCase { protected enum Expect { MESSAGE, EOS, DESER_FAIL; } private const string ID = "test"; + private const string UNTAGGED = "* "; private const string EOL = "\r\n"; private Deserializer? deser = null; @@ -19,21 +20,29 @@ public DeserializerTest() { base("Geary.Imap.DeserializerTest"); - add_test("test_gmail_greeting", test_gmail_greeting); - add_test("test_cyrus_2_4_greeting", test_cyrus_2_4_greeting); - add_test("test_aliyun_greeting", test_aliyun_greeting); + add_test("parse_unquoted", parse_unquoted); + add_test("parse_quoted", parse_quoted); + add_test("parse_number", parse_number); + add_test("parse_list", parse_list); + add_test("parse_response_code", parse_response_code); + add_test("parse_bad_list", parse_bad_list); + add_test("parse_bad_code", parse_bad_response_code); + + add_test("gmail_greeting", gmail_greeting); + add_test("cyrus_2_4_greeting", cyrus_2_4_greeting); + add_test("aliyun_greeting", aliyun_greeting); + + add_test("invalid_atom_prefix", invalid_atom_prefix); + + add_test("gmail_flags", gmail_flags); + add_test("gmail_permanent_flags", gmail_permanent_flags); + add_test("cyrus_flags", cyrus_flags); - add_test("test_invalid_atom_prefix", test_invalid_atom_prefix); + add_test("runin_special_flag", runin_special_flag); + add_test("invalid_flag_prefix", invalid_flag_prefix); - add_test("test_gmail_flags", test_gmail_flags); - add_test("test_gmail_permanent_flags", test_gmail_permanent_flags); - add_test("test_cyrus_flags", test_cyrus_flags); - - add_test("test_runin_special_flag", test_runin_special_flag); - add_test("test_invalid_flag_prefix", test_invalid_flag_prefix); - - add_test("test_instant_eos", test_instant_eos); - add_test("test_bye_eos", test_bye_eos); + add_test("instant_eos", instant_eos); + add_test("bye_eos", bye_eos); } public override void set_up() { @@ -44,12 +53,111 @@ public override void tear_down() { this.deser.stop_async.begin((obj, ret) => { async_complete(ret); }); async_result(); + this.stream = null; + } + + public void parse_unquoted() throws Error { + string bytes = "OK"; + this.stream.add_data(UNTAGGED.data); + this.stream.add_data(bytes.data); + this.stream.add_data(EOL.data); + + this.process.begin(Expect.MESSAGE, (obj, ret) => { async_complete(ret); }); + RootParameters? message = this.process.end(async_result()); + + assert_int(2, message.size); + assert_true(message.get(1) is UnquotedStringParameter, "Not parsed as atom"); + assert_string(bytes, message.get(1).to_string()); + } + + public void parse_quoted() throws Error { + string bytes = "\"OK\""; + this.stream.add_data(UNTAGGED.data); + this.stream.add_data(bytes.data); + this.stream.add_data(EOL.data); + + this.process.begin(Expect.MESSAGE, (obj, ret) => { async_complete(ret); }); + RootParameters? message = this.process.end(async_result()); + + assert_int(2, message.size); + assert_true(message.get(1) is QuotedStringParameter, "Not parsed as quoted"); + assert_string(bytes, message.get(1).to_string()); } - public void test_gmail_greeting() { + public void parse_number() throws Error { + string bytes = "1234"; + this.stream.add_data(UNTAGGED.data); + this.stream.add_data(bytes.data); + this.stream.add_data(EOL.data); + + this.process.begin(Expect.MESSAGE, (obj, ret) => { async_complete(ret); }); + RootParameters? message = this.process.end(async_result()); + + assert_int(2, message.size); + assert_true(message.get(1) is NumberParameter, "Not parsed as number"); + assert_string(bytes, message.get(1).to_string()); + } + + public void parse_list() throws Error { + string bytes = "(OK)"; + this.stream.add_data(UNTAGGED.data); + this.stream.add_data(bytes.data); + this.stream.add_data(EOL.data); + + this.process.begin(Expect.MESSAGE, (obj, ret) => { async_complete(ret); }); + RootParameters? message = this.process.end(async_result()); + + assert_int(2, message.size); + assert_true(message.get(1) is ListParameter, "Not parsed as list"); + assert_string(bytes, message.get(1).to_string()); + } + + public void parse_response_code() throws Error { + string bytes = "[OK]"; + this.stream.add_data(UNTAGGED.data); + this.stream.add_data(bytes.data); + this.stream.add_data(EOL.data); + + this.process.begin(Expect.MESSAGE, (obj, ret) => { async_complete(ret); }); + RootParameters? message = this.process.end(async_result()); + + assert_int(2, message.size); + assert_true(message.get(1) is ResponseCode, "Not parsed as response code"); + assert_string(bytes, message.get(1).to_string()); + } + + public void parse_bad_list() throws Error { + string bytes = "(UHH"; + this.stream.add_data(UNTAGGED.data); + this.stream.add_data(bytes.data); + this.stream.add_data(EOL.data); + + // XXX We expect EOS here rather than DESER_FAIL since the + // deserializer currently silently ignores lines with + // malformed lists and continues parsing, so we get to the end + // of the stream. + this.process.begin(Expect.EOS, (obj, ret) => { async_complete(ret); }); + this.process.end(async_result()); + } + + public void parse_bad_response_code() throws Error { + string bytes = "[UHH"; + this.stream.add_data(UNTAGGED.data); + this.stream.add_data(bytes.data); + this.stream.add_data(EOL.data); + + // XXX We expect EOS here rather than DESER_FAIL since the + // deserializer currently silently ignores lines with + // malformed lists and continues parsing, so we get to the end + // of the stream. + this.process.begin(Expect.EOS, (obj, ret) => { async_complete(ret); }); + this.process.end(async_result()); + } + + public void gmail_greeting() throws Error { string greeting = "* OK Gimap ready for requests from 115.187.245.46 c194mb399904375ivc"; - this.stream.add_data(greeting.data, g_free); - this.stream.add_data(EOL.data, g_free); + this.stream.add_data(greeting.data); + this.stream.add_data(EOL.data); this.process.begin(Expect.MESSAGE, (obj, ret) => { async_complete(ret); }); RootParameters? message = this.process.end(async_result()); @@ -57,10 +165,10 @@ assert(message.to_string() == greeting); } - public void test_cyrus_2_4_greeting() { + public void cyrus_2_4_greeting() throws Error { string greeting = "* OK [CAPABILITY IMAP4rev1 LITERAL+ ID ENABLE AUTH=PLAIN SASL-IR] mogul Cyrus IMAP v2.4.12-Debian-2.4.12-2 server ready"; - this.stream.add_data(greeting.data, g_free); - this.stream.add_data(EOL.data, g_free); + this.stream.add_data(greeting.data); + this.stream.add_data(EOL.data); this.process.begin(Expect.MESSAGE, (obj, ret) => { async_complete(ret); }); RootParameters? message = this.process.end(async_result()); @@ -68,11 +176,11 @@ assert(message.to_string() == greeting); } - public void test_aliyun_greeting() { + public void aliyun_greeting() throws Error { string greeting = "* OK AliYun IMAP Server Ready(10.147.40.164)"; string parsed = "* OK AliYun IMAP Server Ready (10.147.40.164)"; - this.stream.add_data(greeting.data, g_free); - this.stream.add_data(EOL.data, g_free); + this.stream.add_data(greeting.data); + this.stream.add_data(EOL.data); this.process.begin(Expect.MESSAGE, (obj, ret) => { async_complete(ret); }); RootParameters? message = this.process.end(async_result()); @@ -80,19 +188,19 @@ assert(message.to_string() == parsed); } - public void test_invalid_atom_prefix() { + public void invalid_atom_prefix() throws Error { string flags = """* OK %atom"""; - this.stream.add_data(flags.data, g_free); - this.stream.add_data(EOL.data, g_free); + this.stream.add_data(flags.data); + this.stream.add_data(EOL.data); this.process.begin(Expect.DESER_FAIL, (obj, ret) => { async_complete(ret); }); this.process.end(async_result()); } - public void test_gmail_flags() { + public void gmail_flags() throws Error { string flags = """* FLAGS (\Answered \Flagged \Draft \Deleted \Seen $NotPhishing $Phishing)"""; - this.stream.add_data(flags.data, g_free); - this.stream.add_data(EOL.data, g_free); + this.stream.add_data(flags.data); + this.stream.add_data(EOL.data); this.process.begin(Expect.MESSAGE, (obj, ret) => { async_complete(ret); }); RootParameters? message = this.process.end(async_result()); @@ -100,10 +208,10 @@ assert(message.to_string() == flags); } - public void test_gmail_permanent_flags() { + public void gmail_permanent_flags() throws Error { string flags = """* OK [PERMANENTFLAGS (\Answered \Flagged \Draft \Deleted \Seen $NotPhishing $Phishing \*)] Flags permitted."""; - this.stream.add_data(flags.data, g_free); - this.stream.add_data(EOL.data, g_free); + this.stream.add_data(flags.data); + this.stream.add_data(EOL.data); this.process.begin(Expect.MESSAGE, (obj, ret) => { async_complete(ret); }); RootParameters? message = this.process.end(async_result()); @@ -111,10 +219,10 @@ assert(message.to_string() == flags); } - public void test_cyrus_flags() { + public void cyrus_flags() throws Error { string flags = """* 2934 FETCH (FLAGS (\Answered \Seen $Quuxo::Spam::Trained) UID 3041)"""; - this.stream.add_data(flags.data, g_free); - this.stream.add_data(EOL.data, g_free); + this.stream.add_data(flags.data); + this.stream.add_data(EOL.data); this.process.begin(Expect.MESSAGE, (obj, ret) => { async_complete(ret); }); RootParameters? message = this.process.end(async_result()); @@ -122,14 +230,14 @@ assert(message.to_string() == flags); } - public void test_runin_special_flag() { + public void runin_special_flag() throws Error { // since we must terminate a special flag upon receiving the // '*', the following atom will be treated as a run-on but // distinct atom. string flags = """* OK \*atom"""; string expected = """* OK \* atom"""; - this.stream.add_data(flags.data, g_free); - this.stream.add_data(EOL.data, g_free); + this.stream.add_data(flags.data); + this.stream.add_data(EOL.data); this.process.begin(Expect.MESSAGE, (obj, ret) => { async_complete(ret); }); RootParameters? message = this.process.end(async_result()); @@ -137,24 +245,24 @@ assert(message.to_string() == expected); } - public void test_invalid_flag_prefix() { + public void invalid_flag_prefix() throws Error { string flags = """* OK \%atom"""; - this.stream.add_data(flags.data, g_free); - this.stream.add_data(EOL.data, g_free); + this.stream.add_data(flags.data); + this.stream.add_data(EOL.data); this.process.begin(Expect.DESER_FAIL, (obj, ret) => { async_complete(ret); }); this.process.end(async_result()); } - public void test_instant_eos() { + public void instant_eos() throws Error { this.process.begin(Expect.EOS, (obj, ret) => { async_complete(ret); }); this.process.end(async_result()); assert(this.deser.is_halted()); } - public void test_bye_eos() { + public void bye_eos() throws Error { string bye = """* OK bye"""; - this.stream.add_data(bye.data, g_free); + this.stream.add_data(bye.data); bool eos = false; this.deser.eos.connect(() => { eos = true; }); @@ -211,12 +319,6 @@ assert_not_reached(); } - // Process any remaining async tasks the deserializer might - // have left over. - while (this.main_loop.pending()) { - this.main_loop.iteration(true); - } - return message; } diff -Nru geary-0.12.4/test/engine/imap-db/imap-db-account-test.vala geary-3.32.0/test/engine/imap-db/imap-db-account-test.vala --- geary-0.12.4/test/engine/imap-db/imap-db-account-test.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/test/engine/imap-db/imap-db-account-test.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,311 @@ +/* + * Copyright 2019 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + + +class Geary.ImapDB.AccountTest : TestCase { + + + private GLib.File? tmp_dir = null; + private Geary.AccountInformation? config = null; + private Account? account = null; + private FolderRoot? root = null; + + + public AccountTest() { + base("Geary.ImapDB.AccountTest"); + add_test("create_base_folder", create_base_folder); + add_test("create_child_folder", create_child_folder); + add_test("list_folders", list_folders); + add_test("delete_folder", delete_folder); + add_test("delete_folder_with_child", delete_folder_with_child); + add_test("delete_nonexistent_folder", delete_nonexistent_folder); + add_test("fetch_base_folder", fetch_base_folder); + add_test("fetch_child_folder", fetch_child_folder); + add_test("fetch_nonexistent_folder", fetch_nonexistent_folder); + } + + public override void set_up() throws GLib.Error { + this.tmp_dir = GLib.File.new_for_path( + GLib.DirUtils.make_tmp("geary-imap-db-account-test-XXXXXX") + ); + + this.config = new Geary.AccountInformation( + "test", + ServiceProvider.OTHER, + new MockCredentialsMediator(), + new Geary.RFC822.MailboxAddress(null, "test@example.com") + ); + + this.account = new Account(config); + this.account.open_async.begin( + this.tmp_dir, + GLib.File.new_for_path(_SOURCE_ROOT_DIR).get_child("sql"), + null, + (obj, ret) => { async_complete(ret); } + ); + this.account.open_async.end(async_result()); + + this.root = new FolderRoot(false); + } + + public override void tear_down() throws GLib.Error { + this.root = null; + this.account.close_async.begin( + null, + (obj, ret) => { async_complete(ret); } + ); + this.account.close_async.end(async_result()); + this.account = null; + this.config = null; + + delete_file(this.tmp_dir); + this.tmp_dir = null; + } + + public void create_base_folder() throws GLib.Error { + Imap.Folder folder = new Imap.Folder( + this.root.get_child("test"), + new Imap.FolderProperties.selectable( + new Imap.MailboxAttributes( + Gee.Collection.empty() + ), + new Imap.StatusData( + new Imap.MailboxSpecifier("test"), + 10, // total + 9, // recent + new Imap.UID(8), + new Imap.UIDValidity(7), + 6 //unseen + ), + new Imap.Capabilities(1) + ) + ); + + this.account.clone_folder_async.begin( + folder, + null, + (obj, ret) => { async_complete(ret); } + ); + this.account.clone_folder_async.end(async_result()); + + Geary.Db.Result result = this.account.db.query( + "SELECT * FROM FolderTable;" + ); + assert_false(result.finished, "Folder not created"); + assert_string("test", result.string_for("name"), "Folder name"); + assert_true(result.is_null_for("parent_id"), "Folder parent"); + assert_false(result.next(), "Multiple rows inserted"); + } + + public void create_child_folder() throws GLib.Error { + this.account.db.exec( + "INSERT INTO FolderTable (id, name) VALUES (1, 'test');" + ); + + Imap.Folder folder = new Imap.Folder( + this.root.get_child("test").get_child("child"), + new Imap.FolderProperties.selectable( + new Imap.MailboxAttributes( + Gee.Collection.empty() + ), + new Imap.StatusData( + new Imap.MailboxSpecifier("test>child"), + 10, // total + 9, // recent + new Imap.UID(8), + new Imap.UIDValidity(7), + 6 //unseen + ), + new Imap.Capabilities(1) + ) + ); + + this.account.clone_folder_async.begin( + folder, + null, + (obj, ret) => { async_complete(ret); } + ); + this.account.clone_folder_async.end(async_result()); + + Geary.Db.Result result = this.account.db.query( + "SELECT * FROM FolderTable WHERE id != 1;" + ); + assert_false(result.finished, "Folder not created"); + assert_string("child", result.string_for("name"), "Folder name"); + assert_int(1, result.int_for("parent_id"), "Folder parent"); + assert_false(result.next(), "Multiple rows inserted"); + } + + public void list_folders() throws GLib.Error { + this.account.db.exec(""" + INSERT INTO FolderTable (id, name, parent_id) + VALUES + (1, 'test1', null), + (2, 'test2', 1), + (3, 'test3', 2); + """); + + this.account.list_folders_async.begin( + this.account.imap_folder_root, + null, + (obj, ret) => { async_complete(ret); } + ); + Gee.Collection result = + this.account.list_folders_async.end(async_result()); + + Folder test1 = traverse(result).first(); + assert_int(1, result.size, "Base folder not listed"); + assert_string("test1", test1.get_path().name, "Base folder name"); + + this.account.list_folders_async.begin( + test1.get_path(), + null, + (obj, ret) => { async_complete(ret); } + ); + result = this.account.list_folders_async.end(async_result()); + + Folder test2 = traverse(result).first(); + assert_int(1, result.size, "Child folder not listed"); + assert_string("test2", test2.get_path().name, "Child folder name"); + + this.account.list_folders_async.begin( + test2.get_path(), + null, + (obj, ret) => { async_complete(ret); } + ); + result = this.account.list_folders_async.end(async_result()); + + Folder test3 = traverse(result).first(); + assert_int(1, result.size, "Grandchild folder not listed"); + assert_string("test3", test3.get_path().name, "Grandchild folder name"); + } + + public void delete_folder() throws GLib.Error { + this.account.db.exec(""" + INSERT INTO FolderTable (id, name, parent_id) + VALUES + (1, 'test1', null), + (2, 'test2', 1); + """); + + this.account.delete_folder_async.begin( + this.root.get_child("test1").get_child("test2"), + null, + (obj, ret) => { async_complete(ret); } + ); + this.account.delete_folder_async.end(async_result()); + + this.account.delete_folder_async.begin( + this.root.get_child("test1"), + null, + (obj, ret) => { async_complete(ret); } + ); + this.account.delete_folder_async.end(async_result()); + } + + public void delete_folder_with_child() throws GLib.Error { + this.account.db.exec(""" + INSERT INTO FolderTable (id, name, parent_id) + VALUES + (1, 'test1', null), + (2, 'test2', 1); + """); + + this.account.delete_folder_async.begin( + this.root.get_child("test1"), + null, + (obj, ret) => { async_complete(ret); } + ); + try { + this.account.delete_folder_async.end(async_result()); + assert_not_reached(); + } catch (GLib.Error err) { + assert_error(new ImapError.NOT_SUPPORTED(""), err); + } + } + + public void delete_nonexistent_folder() throws GLib.Error { + this.account.db.exec(""" + INSERT INTO FolderTable (id, name, parent_id) + VALUES + (1, 'test1', null), + (2, 'test2', 1); + """); + + this.account.delete_folder_async.begin( + this.root.get_child("test3"), + null, + (obj, ret) => { async_complete(ret); } + ); + try { + this.account.delete_folder_async.end(async_result()); + assert_not_reached(); + } catch (GLib.Error err) { + assert_error(new EngineError.NOT_FOUND(""), err); + } + } + + public void fetch_base_folder() throws GLib.Error { + this.account.db.exec(""" + INSERT INTO FolderTable (id, name, parent_id) + VALUES + (1, 'test1', null), + (2, 'test2', 1); + """); + + this.account.fetch_folder_async.begin( + this.root.get_child("test1"), + null, + (obj, ret) => { async_complete(ret); } + ); + + Folder? result = this.account.fetch_folder_async.end(async_result()); + assert_non_null(result); + assert_string("test1", result.get_path().name); + } + + public void fetch_child_folder() throws GLib.Error { + this.account.db.exec(""" + INSERT INTO FolderTable (id, name, parent_id) + VALUES + (1, 'test1', null), + (2, 'test2', 1); + """); + + this.account.fetch_folder_async.begin( + this.root.get_child("test1").get_child("test2"), + null, + (obj, ret) => { async_complete(ret); } + ); + + Folder? result = this.account.fetch_folder_async.end(async_result()); + assert_non_null(result); + assert_string("test2", result.get_path().name); + } + + public void fetch_nonexistent_folder() throws GLib.Error { + this.account.db.exec(""" + INSERT INTO FolderTable (id, name, parent_id) + VALUES + (1, 'test1', null), + (2, 'test2', 1); + """); + + this.account.fetch_folder_async.begin( + this.root.get_child("test3"), + null, + (obj, ret) => { async_complete(ret); } + ); + try { + this.account.fetch_folder_async.end(async_result()); + assert_not_reached(); + } catch (GLib.Error err) { + assert_error(new EngineError.NOT_FOUND(""), err); + } + } + +} diff -Nru geary-0.12.4/test/engine/imap-db/imap-db-attachment-test.vala geary-3.32.0/test/engine/imap-db/imap-db-attachment-test.vala --- geary-0.12.4/test/engine/imap-db/imap-db-attachment-test.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/test/engine/imap-db/imap-db-attachment-test.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,363 @@ +/* + * Copyright 2018 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + + +class Geary.ImapDB.AttachmentTest : TestCase { + + + private const string ATTACHMENT_BODY = "This is an attachment.\r\n"; + + + public AttachmentTest() { + base("Geary.ImapDB.AttachmentTest"); + add_test("new_from_minimal_mime_part", new_from_minimal_mime_part); + add_test("new_from_complete_mime_part", new_from_complete_mime_part); + add_test("new_from_inline_mime_part", new_from_inline_mime_part); + } + + public void new_from_minimal_mime_part() throws Error { + GMime.Part part = new_part(null, ATTACHMENT_BODY.data); + part.set_header("Content-Type", ""); + + Attachment test = new Attachment.from_part( + 1, new Geary.RFC822.Part(part) + ); + assert_string( + Geary.Mime.ContentType.ATTACHMENT_DEFAULT.to_string(), + test.content_type.to_string() + ); + assert_null_string(test.content_id, "content_id"); + assert_null_string(test.content_description, "content_description"); + assert_int( + Geary.Mime.DispositionType.UNSPECIFIED, + test.content_disposition.disposition_type, + "content disposition type" + ); + assert_false(test.has_content_filename, "has_content_filename"); + assert_null_string(test.content_filename, "content_filename"); + } + + public void new_from_complete_mime_part() throws Error { + const string TYPE = "text/plain"; + const string ID = "test-id"; + const string DESC = "test description"; + const string NAME = "test.txt"; + + GMime.Part part = new_part(null, ATTACHMENT_BODY.data); + part.set_content_id(ID); + part.set_content_description(DESC); + part.set_content_disposition( + new GMime.ContentDisposition.from_string( + "attachment; filename=%s".printf(NAME) + ) + ); + + Attachment test = new Attachment.from_part( + 1, new Geary.RFC822.Part(part) + ); + + assert_string(TYPE, test.content_type.to_string()); + assert_string(ID, test.content_id); + assert_string(DESC, test.content_description); + assert_int( + Geary.Mime.DispositionType.ATTACHMENT, + test.content_disposition.disposition_type + ); + assert_true(test.has_content_filename, "has_content_filename"); + assert_string(test.content_filename, NAME, "content_filename"); + } + + public void new_from_inline_mime_part() throws Error { + GMime.Part part = new_part(null, ATTACHMENT_BODY.data); + part.set_content_disposition( + new GMime.ContentDisposition.from_string("inline") + ); + + Attachment test = new Attachment.from_part( + 1, new Geary.RFC822.Part(part) + ); + + assert_int( + Geary.Mime.DispositionType.INLINE, + test.content_disposition.disposition_type + ); + } + +} + +class Geary.ImapDB.AttachmentIoTest : TestCase { + + + private const string ENCODED_BODY = "This is an attachment.\r\n"; + private const string DECODED_BODY = "This is an attachment.\n"; + + private GLib.File? tmp_dir; + private Geary.Db.Database? db; + + public AttachmentIoTest() { + base("Geary.ImapDB.AttachmentIoTest"); + add_test("save_minimal_attachment", save_minimal_attachment); + add_test("save_complete_attachment", save_complete_attachment); + add_test("save_qp_attachment", save_qp_attachment); + add_test("list_attachments", list_attachments); + add_test("delete_attachments", delete_attachments); + } + + public override void set_up() throws Error { + this.tmp_dir = GLib.File.new_for_path( + GLib.DirUtils.make_tmp("geary-impadb-attachment-io-test-XXXXXX") + ); + + this.db = new Geary.Db.Database.transient(); + this.db.open.begin( + Geary.Db.DatabaseFlags.NONE, null, null, + (obj, res) => { async_complete(res); } + ); + this.db.open.end(async_result()); + this.db.exec(""" +CREATE TABLE MessageTable ( + id INTEGER PRIMARY KEY +); +"""); + this.db.exec("INSERT INTO MessageTable VALUES (1);"); + + this.db.exec(""" +CREATE TABLE MessageAttachmentTable ( + id INTEGER PRIMARY KEY, + message_id INTEGER REFERENCES MessageTable ON DELETE CASCADE, + filename TEXT, + mime_type TEXT, + filesize INTEGER, + disposition INTEGER, + content_id TEXT DEFAULT NULL, + description TEXT DEFAULT NULL +); +"""); + + } + + public override void tear_down() throws Error { + this.db.close(); + this.db = null; + + Files.recursive_delete_async.begin( + this.tmp_dir, GLib.Priority.DEFAULT, null, + (obj, res) => { async_complete(res); } + ); + Files.recursive_delete_async.end(async_result()); + this.tmp_dir = null; + } + + public void save_minimal_attachment() throws Error { + GMime.Part part = new_part(null, ENCODED_BODY.data); + + Gee.List attachments = Attachment.save_attachments( + this.db.get_master_connection(), + this.tmp_dir, + 1, + new Gee.ArrayList.wrap({ + new Geary.RFC822.Part(part) + }), + null + ); + + assert_int(1, attachments.size, "No attachment provided"); + + Geary.Attachment attachment = attachments[0]; + assert_non_null(attachment.file, "Attachment file"); + assert_int( + DECODED_BODY.data.length, + (int) attachment.filesize, + "Attachment file size" + ); + + uint8[] buf = new uint8[4096]; + size_t len = 0; + attachments[0].file.read().read_all(buf, out len); + assert_string(DECODED_BODY, (string) buf[0:len]); + + Geary.Db.Result result = this.db.query( + "SELECT * FROM MessageAttachmentTable;" + ); + assert_false(result.finished, "Row not inserted"); + assert_int(1, result.int_for("message_id"), "Row message id"); + assert_int( + DECODED_BODY.data.length, + result.int_for("filesize"), + "Row file size" + ); + assert_false(result.next(), "Multiple rows inserted"); + } + + public void save_complete_attachment() throws Error { + const string TYPE = "text/plain"; + const string ID = "test-id"; + const string DESCRIPTION = "test description"; + const Geary.Mime.DispositionType DISPOSITION_TYPE = + Geary.Mime.DispositionType.INLINE; + const string FILENAME = "test.txt"; + + GMime.Part part = new_part(TYPE, ENCODED_BODY.data); + part.set_content_id(ID); + part.set_content_description(DESCRIPTION); + part.set_content_disposition( + new GMime.ContentDisposition.from_string( + "inline; filename=%s;".printf(FILENAME) + )); + + Gee.List attachments = Attachment.save_attachments( + this.db.get_master_connection(), + this.tmp_dir, + 1, + new Gee.ArrayList.wrap({ + new Geary.RFC822.Part(part) + }), + null + ); + + assert_int(1, attachments.size, "No attachment provided"); + + Geary.Attachment attachment = attachments[0]; + assert_string(TYPE, attachment.content_type.to_string()); + assert_string(ID, attachment.content_id); + assert_string(DESCRIPTION, attachment.content_description); + assert_string(FILENAME, attachment.content_filename); + assert_int( + DISPOSITION_TYPE, + attachment.content_disposition.disposition_type, + "Attachment disposition type" + ); + + uint8[] buf = new uint8[4096]; + size_t len = 0; + attachment.file.read().read_all(buf, out len); + assert_string(DECODED_BODY, (string) buf[0:len]); + + Geary.Db.Result result = this.db.query( + "SELECT * FROM MessageAttachmentTable;" + ); + assert_false(result.finished, "Row not inserted"); + assert_int(1, result.int_for("message_id"), "Row message id"); + assert_string(TYPE, result.string_for("mime_type")); + assert_string(ID, result.string_for("content_id")); + assert_string(DESCRIPTION, result.string_for("description")); + assert_int( + DISPOSITION_TYPE, + result.int_for("disposition"), + "Row disposition type" + ); + assert_string(FILENAME, result.string_for("filename")); + assert_false(result.next(), "Multiple rows inserted"); + } + + public void save_qp_attachment() throws Error { + // Example courtesy https://en.wikipedia.org/wiki/Quoted-printable + const string QP_ENCODED = +"""J'interdis aux marchands de vanter trop leur marchandises. Car ils se font = +vite p=C3=A9dagogues et t'enseignent comme but ce qui n'est par essence qu'= +un moyen, et te trompant ainsi sur la route =C3=A0 suivre les voil=C3=A0 bi= +ent=C3=B4t qui te d=C3=A9gradent, car si leur musique est vulgaire ils te f= +abriquent pour te la vendre une =C3=A2me vulgaire."""; + const string QP_DECODED = +"""J'interdis aux marchands de vanter trop leur marchandises. Car ils se font vite pédagogues et t'enseignent comme but ce qui n'est par essence qu'un moyen, et te trompant ainsi sur la route à suivre les voilà bientôt qui te dégradent, car si leur musique est vulgaire ils te fabriquent pour te la vendre une âme vulgaire."""; + GMime.Part part = new_part( + "text/plain; charset=utf-8", + QP_ENCODED.data, + GMime.ContentEncoding.QUOTEDPRINTABLE + ); + + Gee.List attachments = Attachment.save_attachments( + this.db.get_master_connection(), + this.tmp_dir, + 1, + new Gee.ArrayList.wrap({ + new Geary.RFC822.Part(part) + }), + null + ); + + assert_int(1, attachments.size, "No attachment provided"); + + uint8[] buf = new uint8[4096]; + size_t len = 0; + attachments[0].file.read().read_all(buf, out len); + assert_string(QP_DECODED, (string) buf[0:len]); + } + + public void list_attachments() throws Error { + this.db.exec(""" +INSERT INTO MessageAttachmentTable ( message_id, mime_type ) +VALUES (1, 'text/plain'); +"""); + this.db.exec(""" +INSERT INTO MessageAttachmentTable ( message_id, mime_type ) +VALUES (2, 'text/plain'); +"""); + + Gee.List loaded = Attachment.list_attachments( + this.db.get_master_connection(), + GLib.File.new_for_path("/tmp"), + 1, + null + ); + + assert_int(1, loaded.size, "Expected one row loaded"); + assert_int(1, (int) loaded[0].message_id, "Unexpected message id"); + } + + public void delete_attachments() throws Error { + GMime.Part part = new_part(null, ENCODED_BODY.data); + + Gee.List attachments = Attachment.save_attachments( + this.db.get_master_connection(), + this.tmp_dir, + 1, + new Gee.ArrayList.wrap({ + new Geary.RFC822.Part(part) + }), + null + ); + + assert_true(attachments[0].file.query_exists(null), + "Attachment not saved to disk"); + + this.db.exec(""" +INSERT INTO MessageAttachmentTable ( message_id, mime_type ) +VALUES (2, 'text/plain'); +"""); + + Attachment.delete_attachments( + this.db.get_master_connection(), this.tmp_dir, 1, null + ); + + Geary.Db.Result result = this.db.query( + "SELECT * FROM MessageAttachmentTable;" + ); + assert_false(result.finished); + assert_int(2, result.int_for("message_id"), "Unexpected message_id"); + assert_false(result.next(), "Attachment not deleted from db"); + + assert_false(attachments[0].file.query_exists(null), + "Attachment not deleted from disk"); + } + +} + +private GMime.Part new_part(string? mime_type, + uint8[] body, + GMime.ContentEncoding encoding = GMime.ContentEncoding.DEFAULT) { + GMime.Part part = new GMime.Part(); + if (mime_type != null) { + part.set_content_type(new GMime.ContentType.from_string(mime_type)); + } + GMime.DataWrapper body_wrapper = new GMime.DataWrapper.with_stream( + new GMime.StreamMem.with_buffer(body), + encoding + ); + part.set_content_object(body_wrapper); + return part; +} diff -Nru geary-0.12.4/test/engine/imap-db/imap-db-database-test.vala geary-3.32.0/test/engine/imap-db/imap-db-database-test.vala --- geary-0.12.4/test/engine/imap-db/imap-db-database-test.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/test/engine/imap-db/imap-db-database-test.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,155 @@ +/* + * Copyright 2018 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + + +class Geary.ImapDB.DatabaseTest : TestCase { + + + private GLib.File? tmp_dir = null; + + + public DatabaseTest() { + base("Geary.ImapDb.DatabaseTest"); + add_test("open_new", open_new); + add_test("upgrade_0_6", upgrade_0_6); + } + + public override void set_up() throws GLib.Error { + this.tmp_dir = GLib.File.new_for_path( + GLib.DirUtils.make_tmp("geary-imap-db-database-test-XXXXXX") + ); + } + + public override void tear_down() throws GLib.Error { + delete_file(this.tmp_dir); + this.tmp_dir = null; + } + + public void open_new() throws Error { + Database db = new Database( + this.tmp_dir.get_child("test.db"), + GLib.File.new_for_path(_SOURCE_ROOT_DIR).get_child("sql"), + this.tmp_dir.get_child("attachments"), + new Geary.SimpleProgressMonitor(Geary.ProgressType.DB_UPGRADE), + new Geary.SimpleProgressMonitor(Geary.ProgressType.DB_VACUUM), + "test@example.com" + ); + + db.open.begin( + Geary.Db.DatabaseFlags.CREATE_FILE, null, + (obj, ret) => { async_complete(ret); } + ); + db.open.end(async_result()); + + // Need to get a connection since the database doesn't + // actually get created until then + db.get_master_connection(); + + // Need to close it again to stop the GC process running + db.close(); + } + + public void upgrade_0_6() throws Error { + // Since the upgrade process also messes around with + // attachments on disk which we want to be able to test, we + // need to have a complete-ish database and attachments + // directory hierarchy. For convenience, these are included as + // a single compressed archive, but that means we need to + // un-compress and unpack the archive as part of the test + // fixture. + const string DB_0_6_RESOURCE = "geary-0.6-db.tar.xz"; + const string DB_0_6_DIR = "geary-0.6-db"; + const string ATTACHMENT_12 = "capitalism.jpeg"; + + GLib.File db_archive = GLib.File + .new_for_uri(RESOURCE_URI) + .resolve_relative_path(DB_0_6_RESOURCE); + GLib.File db_dir = this.tmp_dir.get_child(DB_0_6_DIR); + GLib.File db_file = db_dir.get_child("geary.db"); + GLib.File attachments_dir = db_dir.get_child("attachments"); + + unpack_archive(db_archive, this.tmp_dir); + + // This number is the id of the last known message in the + // database + GLib.File message_dir = attachments_dir.get_child("43"); + + // Ensure one of the expected attachments exists up + // front. Since there are 12 known attachments, 12 should be + // the last one in the table and exist on the file system, + // while 13 should not. + assert_true( + message_dir.get_child("12").get_child(ATTACHMENT_12).query_exists(), + "Expected attachment file" + ); + assert_false( + message_dir.get_child("13").query_exists(), + "Unexpected attachment file" + ); + + Database db = new Database( + db_file, + GLib.File.new_for_path(_SOURCE_ROOT_DIR).get_child("sql"), + attachments_dir, + new Geary.SimpleProgressMonitor(Geary.ProgressType.DB_UPGRADE), + new Geary.SimpleProgressMonitor(Geary.ProgressType.DB_VACUUM), + "test@example.com" + ); + + db.open.begin( + Geary.Db.DatabaseFlags.CREATE_FILE, null, + (obj, ret) => { async_complete(ret); } + ); + db.open.end(async_result()); + + assert_int(25, db.get_schema_version(), "Post-upgrade version"); + + // Since schema v22 deletes the re-creates all attachments, + // attachment 12 should no longer exist on the file system and + // there should be an attachment with id 24. + assert_false( + message_dir.get_child("12").get_child(ATTACHMENT_12).query_exists(), + "Old attachment file not deleted" + ); + assert_true( + message_dir.get_child("24").get_child(ATTACHMENT_12).query_exists(), + "New attachment dir/file not created" + ); + + + // Need to close it again to stop the GC process running + db.close(); + } + + + private void unpack_archive(GLib.File archive, GLib.File dest) + throws Error { + // GLib doesn't seem to have native support for unpacking + // multi-file archives however, so use this fun kludge + // instead. + + GLib.InputStream bytes = archive.read(); + + GLib.Subprocess untar = new GLib.Subprocess( + GLib.SubprocessFlags.STDIN_PIPE, + "tar", "-xJf", "-", "-C", dest.get_path() + ); + GLib.OutputStream stdin = untar.get_stdin_pipe(); + + uint8[] buf = new uint8[4096]; + ssize_t len = 0; + do { + len = bytes.read(buf); + stdin.write(buf[0:len]); + } while (len > 0); + + stdin.close(); + + untar.wait(); + } + +} diff -Nru geary-0.12.4/test/engine/imap-db/imap-db-folder-test.vala geary-3.32.0/test/engine/imap-db/imap-db-folder-test.vala --- geary-0.12.4/test/engine/imap-db/imap-db-folder-test.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/test/engine/imap-db/imap-db-folder-test.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,357 @@ +/* + * Copyright 2019 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + + +class Geary.ImapDB.FolderTest : TestCase { + + + private GLib.File? tmp_dir = null; + private Geary.AccountInformation? config = null; + private Account? account = null; + private Folder? folder = null; + + + public FolderTest() { + base("Geary.ImapDB.FolderTest"); + add_test("create_read_email", create_read_email); + add_test("create_unread_email", create_unread_email); + add_test("create_no_unread_update", create_no_unread_update); + add_test("merge_email", merge_email); + add_test("merge_add_flags", merge_add_flags); + add_test("merge_remove_flags", merge_remove_flags); + //add_test("merge_existing_preview", merge_existing_preview); + add_test("set_flags", set_flags); + add_test("set_flags_on_deleted", set_flags_on_deleted); + } + + public override void set_up() throws GLib.Error { + this.tmp_dir = GLib.File.new_for_path( + GLib.DirUtils.make_tmp("geary-imap-db-account-test-XXXXXX") + ); + + this.config = new Geary.AccountInformation( + "test", + ServiceProvider.OTHER, + new MockCredentialsMediator(), + new Geary.RFC822.MailboxAddress(null, "test@example.com") + ); + + this.account = new Account(config); + this.account.open_async.begin( + this.tmp_dir, + GLib.File.new_for_path(_SOURCE_ROOT_DIR).get_child("sql"), + null, + (obj, ret) => { async_complete(ret); } + ); + this.account.open_async.end(async_result()); + + this.account.db.exec( + "INSERT INTO FolderTable (id, name) VALUES (1, 'test');" + ); + + this.account.list_folders_async.begin( + this.account.imap_folder_root, + null, + (obj, ret) => { async_complete(ret); } + ); + this.folder = traverse( + this.account.list_folders_async.end(async_result()) + ).first(); + } + + public override void tear_down() throws GLib.Error { + this.folder = null; + this.account.close_async.begin( + null, + (obj, ret) => { async_complete(ret); } + ); + this.account.close_async.end(async_result()); + this.account = null; + this.config = null; + + delete_file(this.tmp_dir); + this.tmp_dir = null; + } + + public void create_read_email() throws GLib.Error { + Email mock = new_mock_remote_email(1, "test"); + + this.folder.create_or_merge_email_async.begin( + Collection.single(mock), + true, + null, + (obj, ret) => { async_complete(ret); } + ); + Gee.Map results = + this.folder.create_or_merge_email_async.end(async_result()); + + assert_int(1, results.size); + assert(results.get(mock)); + assert_int(0, this.folder.get_properties().email_unread); + } + + public void create_unread_email() throws GLib.Error { + Email mock = new_mock_remote_email( + 1, "test", new EmailFlags.with(EmailFlags.UNREAD) + ); + + this.folder.create_or_merge_email_async.begin( + Collection.single(mock), + true, + null, + (obj, ret) => { async_complete(ret); } + ); + Gee.Map results = + this.folder.create_or_merge_email_async.end(async_result()); + + assert_int(1, results.size); + assert(results.get(mock)); + assert_int(1, this.folder.get_properties().email_unread); + } + + public void create_no_unread_update() throws GLib.Error { + Email mock = new_mock_remote_email( + 1, "test", new EmailFlags.with(EmailFlags.UNREAD) + ); + + this.folder.create_or_merge_email_async.begin( + Collection.single(mock), + false, + null, + (obj, ret) => { async_complete(ret); } + ); + Gee.Map results = + this.folder.create_or_merge_email_async.end(async_result()); + + assert_int(1, results.size); + assert(results.get(mock)); + assert_int(0, this.folder.get_properties().email_unread); + } + + public void merge_email() throws GLib.Error { + Email.Field fixture_fields = Email.Field.RECEIVERS; + string fixture_to = "test@example.com"; + this.account.db.exec( + "INSERT INTO MessageTable (id, fields, to_field) " + + "VALUES (1, %d, '%s');".printf(fixture_fields, fixture_to) + ); + this.account.db.exec(""" + INSERT INTO MessageLocationTable (id, message_id, folder_id, ordering) + VALUES (1, 1, 1, 1); + """); + + string mock_subject = "test subject"; + Email mock = new_mock_remote_email(1, mock_subject); + + this.folder.create_or_merge_email_async.begin( + Collection.single(mock), + true, + null, + (obj, ret) => { async_complete(ret); } + ); + Gee.Map results = + this.folder.create_or_merge_email_async.end(async_result()); + + assert_int(1, results.size); + assert(!results.get(mock)); + + // Fetch it again to make sure it's been merged using required + // fields to check + this.folder.fetch_email_async.begin( + (EmailIdentifier) mock.id, + fixture_fields | mock.fields, + Folder.ListFlags.NONE, + null, + (obj, ret) => { async_complete(ret); } + ); + Email? merged = null; + try { + merged = this.folder.fetch_email_async.end(async_result()); + } catch (EngineError.INCOMPLETE_MESSAGE err) { + assert_no_error(err); + } + + assert_string(fixture_to, merged.to.to_string()); + assert_string(mock_subject, merged.subject.to_string()); + } + + public void merge_add_flags() throws GLib.Error { + // Flags in the DB are expected to be Imap.MessageFlags + Email.Field fixture_fields = Email.Field.FLAGS; + Imap.MessageFlags fixture_flags = + new Imap.MessageFlags(Collection.single(Imap.MessageFlag.SEEN)); + this.account.db.exec( + "INSERT INTO MessageTable (id, fields, flags) " + + "VALUES (1, %d, '%s');".printf( + fixture_fields, fixture_flags.serialize() + ) + ); + this.account.db.exec(""" + INSERT INTO MessageLocationTable (id, message_id, folder_id, ordering) + VALUES (1, 1, 1, 1); + """); + + EmailFlags test_flags = new EmailFlags.with(EmailFlags.UNREAD); + Email test = new_mock_remote_email(1, null, test_flags); + this.folder.create_or_merge_email_async.begin( + Collection.single(test), + true, + null, + (obj, ret) => { async_complete(ret); } + ); + Gee.Map results = + this.folder.create_or_merge_email_async.end(async_result()); + + assert_int(1, results.size); + assert(!results.get(test)); + + assert_flags((EmailIdentifier) test.id, test_flags); + } + + public void merge_remove_flags() throws GLib.Error { + // Flags in the DB are expected to be Imap.MessageFlags + Email.Field fixture_fields = Email.Field.FLAGS; + Imap.MessageFlags fixture_flags = + new Imap.MessageFlags(Gee.Collection.empty()); + this.account.db.exec( + "INSERT INTO MessageTable (id, fields, flags) " + + "VALUES (1, %d, '%s');".printf( + fixture_fields, fixture_flags.serialize() + ) + ); + this.account.db.exec(""" + INSERT INTO MessageLocationTable (id, message_id, folder_id, ordering) + VALUES (1, 1, 1, 1); + """); + + EmailFlags test_flags = new EmailFlags(); + Email test = new_mock_remote_email(1, null, test_flags); + this.folder.create_or_merge_email_async.begin( + Collection.single(test), + true, + null, + (obj, ret) => { async_complete(ret); } + ); + Gee.Map results = + this.folder.create_or_merge_email_async.end(async_result()); + + assert_int(1, results.size); + assert(!results.get(test)); + + assert_flags((EmailIdentifier) test.id, test_flags); + } + + public void set_flags() throws GLib.Error { + // Note: Flags in the DB are expected to be Imap.MessageFlags, + // and flags passed in to ImapDB.Folder are expected to be + // Imap.EmailFlags + + Email.Field fixture_fields = Email.Field.FLAGS; + Imap.MessageFlags fixture_flags = + new Imap.MessageFlags(Collection.single(Imap.MessageFlag.SEEN)); + this.account.db.exec( + "INSERT INTO MessageTable (id, fields, flags) " + + "VALUES (1, %d, '%s');".printf( + fixture_fields, fixture_flags.serialize() + ) + ); + this.account.db.exec(""" + INSERT INTO MessageLocationTable (id, message_id, folder_id, ordering) + VALUES (1, 1, 1, 1); + """); + + Imap.EmailFlags test_flags = Imap.EmailFlags.from_api_email_flags( + new EmailFlags.with(EmailFlags.UNREAD) + ); + EmailIdentifier test = new EmailIdentifier(1, new Imap.UID(1)); + + this.folder.set_email_flags_async.begin( + Collection.single_map(test, test_flags), + null, + (obj, ret) => { async_complete(ret); } + ); + this.folder.set_email_flags_async.end(async_result()); + + assert_flags(test, test_flags); + } + + public void set_flags_on_deleted() throws GLib.Error { + // Note: Flags in the DB are expected to be Imap.MessageFlags, + // and flags passed in to ImapDB.Folder are expected to be + // Imap.EmailFlags + + Email.Field fixture_fields = Email.Field.FLAGS; + Imap.MessageFlags fixture_flags = + new Imap.MessageFlags(Collection.single(Imap.MessageFlag.SEEN)); + this.account.db.exec( + "INSERT INTO MessageTable (id, fields, flags) " + + "VALUES (1, %d, '%s');".printf( + fixture_fields, fixture_flags.serialize() + ) + ); + this.account.db.exec(""" + INSERT INTO MessageLocationTable + (id, message_id, folder_id, ordering, remove_marker) + VALUES + (1, 1, 1, 1, 1); + """); + + Imap.EmailFlags test_flags = Imap.EmailFlags.from_api_email_flags( + new EmailFlags.with(EmailFlags.UNREAD) + ); + EmailIdentifier test = new EmailIdentifier(1, new Imap.UID(1)); + + this.folder.set_email_flags_async.begin( + Collection.single_map(test, test_flags), + null, + (obj, ret) => { async_complete(ret); } + ); + this.folder.set_email_flags_async.end(async_result()); + + assert_flags(test, test_flags); + } + + private Email new_mock_remote_email(int64 uid, + string? subject = null, + Geary.EmailFlags? flags = null) { + Email mock = new Email( + new EmailIdentifier.no_message_id(new Imap.UID(uid)) + ); + if (subject != null) { + mock.set_message_subject(new RFC822.Subject(subject)); + } + // Flags passed in to ImapDB.Folder are expected to be + // Imap.EmailFlags + if (flags != null) { + mock.set_flags(Imap.EmailFlags.from_api_email_flags(flags)); + } + return mock; + } + + private void assert_flags(EmailIdentifier id, EmailFlags expected) + throws GLib.Error { + this.folder.fetch_email_async.begin( + id, + Email.Field.FLAGS, + Folder.ListFlags.INCLUDE_MARKED_FOR_REMOVE, + null, + (obj, ret) => { async_complete(ret); } + ); + Email? merged = null; + try { + merged = this.folder.fetch_email_async.end(async_result()); + } catch (EngineError.INCOMPLETE_MESSAGE err) { + assert_no_error(err); + } + + assert_true( + expected.equal_to(merged.email_flags), + "Unexpected merged flags: %s".printf(merged.to_string()) + ); + } + +} diff -Nru geary-0.12.4/test/engine/imap-engine/account-processor-test.vala geary-3.32.0/test/engine/imap-engine/account-processor-test.vala --- geary-0.12.4/test/engine/imap-engine/account-processor-test.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/test/engine/imap-engine/account-processor-test.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,176 @@ +/* + * Copyright 2017 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +errordomain AccountProcessorTestError { + TEST; +} + +public class Geary.ImapEngine.AccountProcessorTest : TestCase { + + + public class TestOperation : AccountOperation { + + public bool throw_error = false; + public bool wait_for_cancel = false; + public bool execute_called = false; + + private Nonblocking.Spinlock spinlock = new Nonblocking.Spinlock(); + + internal TestOperation(Geary.Account account) { + base(account); + } + + public override async void execute(Cancellable cancellable) + throws Error { + print("Test op/"); + this.execute_called = true; + if (this.wait_for_cancel) { + yield this.spinlock.wait_async(cancellable); + } + if (this.throw_error) { + throw new AccountProcessorTestError.TEST("Failed"); + } + } + + } + + + public class OtherOperation : TestOperation { + + internal OtherOperation(Geary.Account account) { + base(account); + } + + } + + + private AccountProcessor? processor = null; + private Geary.Account? account = null; + private Geary.AccountInformation? info = null; + private uint succeeded; + private uint failed; + private uint completed; + + + public AccountProcessorTest() { + base("Geary.ImapEngine.AccountProcessorTest"); + add_test("success", success); + add_test("failure", failure); + add_test("duplicate", duplicate); + add_test("stop", stop); + + // XXX this has to be here instead of in set_up for some + // reason... + this.processor = new AccountProcessor("processor"); + } + + public override void set_up() { + this.info = new Geary.AccountInformation( + "test-info", + ServiceProvider.OTHER, + new MockCredentialsMediator(), + new RFC822.MailboxAddress(null, "test1@example.com") + ); + this.account = new Geary.MockAccount(this.info); + + this.succeeded = 0; + this.failed = 0; + this.completed = 0; + } + + public void success() throws Error { + TestOperation op = setup_operation(new TestOperation(this.account)); + + this.processor.enqueue(op); + assert(this.processor.waiting == 1); + + execute_all(); + + assert(op.execute_called); + assert(this.succeeded == 1); + assert(this.failed == 0); + assert(this.completed == 1); + } + + public void failure() throws Error { + TestOperation op = setup_operation(new TestOperation(this.account)); + op.throw_error = true; + + AccountOperation? error_op = null; + Error? error = null; + this.processor.operation_error.connect((proc, op, err) => { + error_op = op; + error = err; + }); + + this.processor.enqueue(op); + execute_all(); + + assert(this.succeeded == 0); + assert(this.failed == 1); + assert(this.completed == 1); + assert(error_op == op); + assert(error is AccountProcessorTestError.TEST); + } + + public void duplicate() throws Error { + TestOperation op1 = setup_operation(new TestOperation(this.account)); + TestOperation op2 = setup_operation(new TestOperation(this.account)); + TestOperation op3 = setup_operation(new OtherOperation(this.account)); + + this.processor.enqueue(op1); + this.processor.enqueue(op2); + assert(this.processor.waiting == 1); + + this.processor.enqueue(op3); + assert(this.processor.waiting == 2); + } + + public void stop() throws Error { + TestOperation op1 = setup_operation(new TestOperation(this.account)); + op1.wait_for_cancel = true; + TestOperation op2 = setup_operation(new OtherOperation(this.account)); + + this.processor.enqueue(op1); + this.processor.enqueue(op2); + + while (!this.processor.is_executing) { + this.main_loop.iteration(true); + } + + this.processor.stop(); + + while (this.main_loop.pending()) { + this.main_loop.iteration(true); + } + + assert(!this.processor.is_executing); + assert(this.processor.waiting == 0); + assert(this.succeeded == 0); + assert(this.failed == 1); + assert(this.completed == 1); + } + + private TestOperation setup_operation(TestOperation op) { + op.succeeded.connect(() => { + this.succeeded++; + }); + op.failed.connect(() => { + this.failed++; + }); + op.completed.connect(() => { + this.completed++; + }); + return op; + } + + private void execute_all() { + while (this.processor.is_executing || this.processor.waiting > 0) { + this.main_loop.iteration(true); + } + } +} diff -Nru geary-0.12.4/test/engine/mime-content-type-test.vala geary-3.32.0/test/engine/mime-content-type-test.vala --- geary-0.12.4/test/engine/mime-content-type-test.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/test/engine/mime-content-type-test.vala 2019-03-17 13:39:29.000000000 +0000 @@ -5,56 +5,59 @@ * (version 2.1 or later). See the COPYING file in this distribution. */ -class Geary.Mime.ContentTypeTest : Gee.TestCase { +class Geary.Mime.ContentTypeTest : TestCase { public ContentTypeTest() { base("Geary.Mime.ContentTypeTest"); - add_test("is_default", is_default); + add_test("static_defaults", static_defaults); add_test("get_file_name_extension", get_file_name_extension); add_test("guess_type_from_name", guess_type_from_name); add_test("guess_type_from_buf", guess_type_from_buf); } - public void is_default() { - assert(new ContentType("application", "octet-stream", null).is_default()); + public void static_defaults() throws Error { + assert_string( + "text/plain; charset=us-ascii", + ContentType.DISPLAY_DEFAULT.to_string() + ); + assert_string( + "application/octet-stream", + ContentType.ATTACHMENT_DEFAULT.to_string() + ); } - public void get_file_name_extension() { + public void get_file_name_extension() throws Error { assert(new ContentType("image", "jpeg", null).get_file_name_extension() == ".jpeg"); assert(new ContentType("test", "unknown", null).get_file_name_extension() == null); } - public void guess_type_from_name() { - try { - assert(ContentType.guess_type("test.png", null).is_type("image", "png")); - } catch (Error err) { - assert_not_reached(); - } - - try { - assert(ContentType.guess_type("foo.test", null).get_mime_type() == ContentType.DEFAULT_CONTENT_TYPE); - } catch (Error err) { - assert_not_reached(); - } + public void guess_type_from_name() throws Error { + assert_true( + ContentType.guess_type("test.png", null).is_type("image", "png"), + "Expected image/png" + ); + assert_true( + ContentType.guess_type("foo.test", null) + .is_same(ContentType.ATTACHMENT_DEFAULT), + "Expected ContentType.ATTACHMENT_DEFAULT" + ); } - public void guess_type_from_buf() { + public void guess_type_from_buf() throws Error { Memory.ByteBuffer png = new Memory.ByteBuffer( {0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a}, 8 // PNG magic ); Memory.ByteBuffer empty = new Memory.ByteBuffer({0x0}, 1); - try { - assert(ContentType.guess_type(null, png).is_type("image", "png")); - } catch (Error err) { - assert_not_reached(); - } - - try { - assert(ContentType.guess_type(null, empty).get_mime_type() == ContentType.DEFAULT_CONTENT_TYPE); - } catch (Error err) { - assert_not_reached(); - } + assert_true( + ContentType.guess_type(null, png).is_type("image", "png"), + "Expected image/png" + ); + assert_true( + ContentType.guess_type(null, empty) + .is_same(ContentType.ATTACHMENT_DEFAULT), + "Expected ContentType.ATTACHMENT_DEFAULT" + ); } } diff -Nru geary-0.12.4/test/engine/rfc822-mailbox-addresses-test.vala geary-3.32.0/test/engine/rfc822-mailbox-addresses-test.vala --- geary-0.12.4/test/engine/rfc822-mailbox-addresses-test.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/test/engine/rfc822-mailbox-addresses-test.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,59 @@ +/* + * Copyright 2018-2019 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +class Geary.RFC822.MailboxAddressesTest : TestCase { + + public MailboxAddressesTest() { + base("Geary.RFC822.MailboxAddressesTest"); + add_test("from_rfc822_string_encoded", from_rfc822_string_encoded); + add_test("from_rfc822_string_quoted", from_rfc822_string_quoted); + add_test("to_rfc822_string", to_rfc822_string); + } + + public void from_rfc822_string_encoded() throws Error { + MailboxAddresses addrs = new MailboxAddresses.from_rfc822_string("test@example.com"); + assert(addrs.size == 1); + + addrs = new MailboxAddresses.from_rfc822_string("test1@example.com, test2@example.com"); + assert(addrs.size == 2); + + // Courtesy Mailsploit https://www.mailsploit.com + addrs = new MailboxAddresses.from_rfc822_string("\"=?utf-8?b?dGVzdCIgPHBvdHVzQHdoaXRlaG91c2UuZ292Pg==?==?utf-8?Q?=00=0A?=\" "); + assert(addrs.size == 1); + + // Courtesy Mailsploit https://www.mailsploit.com + addrs = new MailboxAddresses.from_rfc822_string("\"=?utf-8?Q?=42=45=47=49=4E=20=2F=20=28=7C=29=7C=3C=7C=3E=7C=40=7C=2C=7C=3B=7C=3A=7C=5C=7C=22=7C=2F=7C=5B=7C=5D=7C=3F=7C=2E=7C=3D=20=2F=20=00=20=50=41=53=53=45=44=20=4E=55=4C=4C=20=42=59=54=45=20=2F=20=0D=0A=20=50=41=53=53=45=44=20=43=52=4C=46=20=2F=20?==?utf-8?b?RU5E=?=\", "); + assert(addrs.size == 2); + } + + public void from_rfc822_string_quoted() throws GLib.Error { + MailboxAddresses addrs = new MailboxAddresses.from_rfc822_string( + "\"Surname, Name\" " + ) ; + assert_int(1, addrs.size); + assert_string("Surname, Name", addrs[0].name); + assert_string("mail@example.com", addrs[0].address); + + assert_string("\"Surname, Name\" ", addrs.to_rfc822_string()); + } + + public void to_rfc822_string() throws Error { + assert(new MailboxAddresses().to_rfc822_string() == ""); + assert(new_addreses({ "test1@example.com" }) + .to_rfc822_string() == "test1@example.com"); + assert(new_addreses({ "test1@example.com", "test2@example.com" }) + .to_rfc822_string() == "test1@example.com, test2@example.com"); + } + + private MailboxAddresses new_addreses(string[] address_strings) { + Gee.List addresses = new Gee.LinkedList(); + foreach (string address in address_strings) { + addresses.add(new MailboxAddress(null, address)); + } + return new MailboxAddresses(addresses); + } +} diff -Nru geary-0.12.4/test/engine/rfc822-mailbox-address-test.vala geary-3.32.0/test/engine/rfc822-mailbox-address-test.vala --- geary-0.12.4/test/engine/rfc822-mailbox-address-test.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/test/engine/rfc822-mailbox-address-test.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,24 +1,36 @@ /* - * Copyright 2016 Michael Gratton + * Copyright 2016-2019 Michael Gratton * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ -class Geary.RFC822.MailboxAddressTest : Gee.TestCase { +class Geary.RFC822.MailboxAddressTest : TestCase { public MailboxAddressTest() { base("Geary.RFC822.MailboxAddressTest"); add_test("is_valid_address", is_valid_address); + add_test("unescaped_constructor", unescaped_constructor); + add_test("from_rfc822_string_encoded", from_rfc822_string_encoded); + add_test("is_spoofed", is_spoofed); + add_test("has_distinct_name", has_distinct_name); + add_test("to_full_display", to_full_display); + add_test("to_short_display", to_short_display); + // latter depends on the former, so test that first + add_test("to_rfc822_address", to_rfc822_address); + add_test("to_rfc822_string", to_rfc822_string); + add_test("equal_to", equal_to); } - public void is_valid_address() { + public void is_valid_address() throws Error { assert(Geary.RFC822.MailboxAddress.is_valid_address("john@dep.aol.museum") == true); assert(Geary.RFC822.MailboxAddress.is_valid_address("test@example.com") == true); - // This is Bug 714299 - //assert(Geary.RFC822.MailboxAddress.is_valid_address("test@example") == true); + assert(Geary.RFC822.MailboxAddress.is_valid_address("test.other@example.com") == true); + assert(Geary.RFC822.MailboxAddress.is_valid_address("test@localhost") == true); + assert(Geary.RFC822.MailboxAddress.is_valid_address("test2@localhost") == true); assert(Geary.RFC822.MailboxAddress.is_valid_address("some context test@example.com text") == true); + assert(Geary.RFC822.MailboxAddress.is_valid_address("test@example") == false); assert(Geary.RFC822.MailboxAddress.is_valid_address("john@aol...com") == false); assert(Geary.RFC822.MailboxAddress.is_valid_address("@example.com") == false); assert(Geary.RFC822.MailboxAddress.is_valid_address("@example") == false); @@ -26,6 +38,266 @@ assert(Geary.RFC822.MailboxAddress.is_valid_address("test@") == false); assert(Geary.RFC822.MailboxAddress.is_valid_address("@") == false); assert(Geary.RFC822.MailboxAddress.is_valid_address("") == false); + + assert(Geary.RFC822.MailboxAddress.is_valid_address("\"Surname, Name\" ") == true); + } + + public void unescaped_constructor() throws Error { + MailboxAddress addr1 = new MailboxAddress("test1", "test2@example.com"); + assert(addr1.name == "test1"); + assert(addr1.address == "test2@example.com"); + assert(addr1.mailbox == "test2"); + assert(addr1.domain == "example.com"); + + MailboxAddress addr2 = new MailboxAddress(null, "test1@test2@example.com"); + assert(addr2.address == "test1@test2@example.com"); + assert(addr2.mailbox == "test1@test2"); + assert(addr2.domain == "example.com"); + + MailboxAddress addr3 = new MailboxAddress(null, "©@example.com"); + assert(addr3.address == "©@example.com"); + assert(addr3.mailbox == "©"); + assert(addr3.domain == "example.com"); + + MailboxAddress addr4 = new MailboxAddress(null, "😸@example.com"); + assert(addr4.address == "😸@example.com"); + assert(addr4.mailbox == "😸"); + assert(addr4.domain == "example.com"); + + MailboxAddress addr5 = new MailboxAddress(null, "example.com"); + assert(addr5.address == "example.com"); + assert(addr5.mailbox == ""); + assert(addr5.domain == ""); + } + + public void from_rfc822_string_encoded() throws Error { + try { + MailboxAddress addr = new MailboxAddress.from_rfc822_string("test@example.com"); + assert(addr.name == null); + assert(addr.mailbox == "test"); + assert(addr.domain == "example.com"); + + addr = new MailboxAddress.from_rfc822_string("\"test\"@example.com"); + assert(addr.name == null); + assert(addr.address == "test@example.com"); + assert(addr.mailbox == "test"); + assert(addr.domain == "example.com"); + + addr = new MailboxAddress.from_rfc822_string("=?UTF-8?b?dGVzdA==?=@example.com"); + assert(addr.name == null); + assert(addr.address == "test@example.com"); + assert(addr.mailbox == "test"); + assert(addr.domain == "example.com"); + + addr = new MailboxAddress.from_rfc822_string("\"=?UTF-8?b?dGVzdA==?=\"@example.com"); + assert(addr.name == null); + assert(addr.address == "test@example.com"); + assert(addr.mailbox == "test"); + assert(addr.domain == "example.com"); + + addr = new MailboxAddress.from_rfc822_string(""); + assert(addr.name == null); + assert(addr.address == "test@example.com"); + assert(addr.mailbox == "test"); + assert(addr.domain == "example.com"); + + addr = new MailboxAddress.from_rfc822_string("<\"test\"@example.com>"); + assert(addr.name == null); + assert(addr.address == "test@example.com"); + assert(addr.mailbox == "test"); + assert(addr.domain == "example.com"); + + addr = new MailboxAddress.from_rfc822_string("Test 1 "); + assert(addr.name == "Test 1"); + assert(addr.address == "test2@example.com"); + assert(addr.mailbox == "test2"); + assert(addr.domain == "example.com"); + + addr = new MailboxAddress.from_rfc822_string("\"Test 1\" "); + assert(addr.name == "Test 1"); + assert(addr.address == "test2@example.com"); + assert(addr.mailbox == "test2"); + assert(addr.domain == "example.com"); + + addr = new MailboxAddress.from_rfc822_string("Test 1 <\"test2\"@example.com>"); + assert(addr.name == "Test 1"); + assert(addr.address == "test2@example.com"); + assert(addr.mailbox == "test2"); + assert(addr.domain == "example.com"); + + addr = new MailboxAddress.from_rfc822_string("=?UTF-8?b?VGVzdCAx?= "); + assert(addr.name == "Test 1"); + assert(addr.address == "test2@example.com"); + assert(addr.mailbox == "test2"); + assert(addr.domain == "example.com"); + + addr = new MailboxAddress.from_rfc822_string("\"=?UTF-8?b?VGVzdCAx?=\" "); + assert(addr.name == "Test 1"); + assert(addr.address == "test2@example.com"); + assert(addr.mailbox == "test2"); + assert(addr.domain == "example.com"); + + // Courtesy Mailsploit https://www.mailsploit.com + addr = new MailboxAddress.from_rfc822_string("\"=?utf-8?b?dGVzdCIgPHBvdHVzQHdoaXRlaG91c2UuZ292Pg==?==?utf-8?Q?=00=0A?=\" "); + assert(addr.name == "test ?\n"); + assert(addr.address == "demo@mailsploit.com"); + + // Courtesy Mailsploit https://www.mailsploit.com + addr = new MailboxAddress.from_rfc822_string("\"=?utf-8?Q?=42=45=47=49=4E=20=2F=20=28=7C=29=7C=3C=7C=3E=7C=40=7C=2C=7C=3B=7C=3A=7C=5C=7C=22=7C=2F=7C=5B=7C=5D=7C=3F=7C=2E=7C=3D=20=2F=20=00=20=50=41=53=53=45=44=20=4E=55=4C=4C=20=42=59=54=45=20=2F=20=0D=0A=20=50=41=53=53=45=44=20=43=52=4C=46=20=2F=20?==?utf-8?b?RU5E=?=\""); + assert(addr.name == null); + assert(addr.address == "BEGIN / (|)|<|>|@|,|;|:|\\|\"|/|[|]|?|.|= / ? PASSED NULL BYTE / \r\n PASSED CRLF / END"); + } catch (Error err) { + assert_not_reached(); + } + } + + public void is_spoofed() throws Error { + assert(new MailboxAddress(null, "example@example.com").is_spoofed() == false); + assert(new MailboxAddress("", "example@example.com").is_spoofed() == false); + assert(new MailboxAddress("", "example@example.com").is_spoofed() == false); + assert(new MailboxAddress("test", "example@example.com").is_spoofed() == false); + assert(new MailboxAddress("test test", "example@example.com").is_spoofed() == false); + assert(new MailboxAddress("test test", "example@example.com").is_spoofed() == false); + assert(new MailboxAddress("test?", "example@example.com").is_spoofed() == false); + assert(new MailboxAddress("test@example.com", "test@example.com").is_spoofed() == false); + + assert(new MailboxAddress("test@example.com", "example@example.com").is_spoofed() == true); + assert(new MailboxAddress("test @ example . com", "example@example.com").is_spoofed() == true); + assert(new MailboxAddress("\n", "example@example.com").is_spoofed() == true); + assert(new MailboxAddress("\n", "example@example.com").is_spoofed() == true); + assert(new MailboxAddress("test", "example@\nexample@example.com").is_spoofed() == true); + assert(new MailboxAddress("test", "example@example@example.com").is_spoofed() == true); + + try { + assert(new MailboxAddress.from_rfc822_string("\"=?utf-8?b?dGVzdCIgPHBvdHVzQHdoaXRlaG91c2UuZ292Pg==?==?utf-8?Q?=00=0A?=\" ") + .is_spoofed() == true); + } catch (Error err) { + assert_no_error(err); + } + } + + public void has_distinct_name() throws Error { + assert(new MailboxAddress("example", "example@example.com").has_distinct_name() == true); + + assert(new MailboxAddress("", "example@example.com").has_distinct_name() == false); + assert(new MailboxAddress(" ", "example@example.com").has_distinct_name() == false); + assert(new MailboxAddress("example@example.com", "example@example.com").has_distinct_name() == false); + assert(new MailboxAddress(" example@example.com ", "example@example.com").has_distinct_name() == false); + assert(new MailboxAddress(" example@example.com ", "example@example.com").has_distinct_name() == false); + } + + public void to_full_display() throws Error { + assert(new MailboxAddress("", "example@example.com").to_full_display() == + "example@example.com"); + assert(new MailboxAddress("Test", "example@example.com").to_full_display() == + "Test "); + assert(new MailboxAddress("example@example.com", "example@example.com").to_full_display() == + "example@example.com"); + assert(new MailboxAddress("Test", "example@example@example.com").to_full_display() == + "example@example@example.com"); + + assert_string( + "\"Testerson, Test\" ", + new MailboxAddress("Testerson, Test", "test@example.com").to_full_display() + ); + } + + public void to_short_display() throws Error { + assert(new MailboxAddress("", "example@example.com").to_short_display() == + "example@example.com"); + assert(new MailboxAddress("Test", "example@example.com").to_short_display() == + "Test"); + assert(new MailboxAddress("example@example.com", "example@example.com").to_short_display() == + "example@example.com"); + assert(new MailboxAddress("Test", "example@example@example.com").to_short_display() == + "example@example@example.com"); + } + + public void to_rfc822_address() throws GLib.Error { + assert_string( + "example@example.com", + new MailboxAddress(null, "example@example.com").to_rfc822_address() + ); + assert_string( + "test.account@example.com", + new MailboxAddress(null, "test.account@example.com").to_rfc822_address() + ); + //assert(new MailboxAddress(null, "test test@example.com").to_rfc822_address() == + // "\"test test\"@example.com"); + //assert(new MailboxAddress(null, "test\" test@example.com").to_rfc822_address() == + // "\"test\" test\"@example.com"); + //assert(new MailboxAddress(null, "test\"test@example.com").to_rfc822_address() == + // "\"test\"test\"@example.com"); + assert_string( + "$test@example.com", + new MailboxAddress(null, "$test@example.com").to_rfc822_address() + ); + assert_string( + "\"test@test\"@example.com", + new MailboxAddress(null, "test@test@example.com").to_rfc822_address() + ); + assert_string( + "=?iso-8859-1?b?qQ==?=@example.com", + new MailboxAddress(null, "©@example.com").to_rfc822_address() + ); + assert_string( + "=?UTF-8?b?8J+YuA==?=@example.com", + new MailboxAddress(null, "😸@example.com").to_rfc822_address() + ); } + public void to_rfc822_string() throws Error { + assert(new MailboxAddress("", "example@example.com").to_rfc822_string() == + "example@example.com"); + assert(new MailboxAddress(" ", "example@example.com").to_rfc822_string() == + "example@example.com"); + assert(new MailboxAddress("test", "example@example.com").to_rfc822_string() == + "test "); + assert(new MailboxAddress("test test", "example@example.com").to_rfc822_string() == + "test test "); + assert(new MailboxAddress("example@example.com", "example@example.com").to_rfc822_string() == + "example@example.com"); + assert(new MailboxAddress("test?", "example@example.com").to_rfc822_string() == + "test? "); + assert(new MailboxAddress("test@test", "example@example.com").to_rfc822_string() == + "\"test@test\" "); + assert(new MailboxAddress(";", "example@example.com").to_rfc822_string() == + "\";\" "); + assert(new MailboxAddress("©", "example@example.com").to_rfc822_string() == + "=?iso-8859-1?b?qQ==?= "); + assert(new MailboxAddress("😸", "example@example.com").to_rfc822_string() == + "=?UTF-8?b?8J+YuA==?= "); + + assert_string( + "\"Surname, Name\" ", + new MailboxAddress("Surname, Name", "example@example.com").to_rfc822_string() + ); + assert_string( + "\"Surname, Name\" ", + new MailboxAddress + .from_rfc822_string("\"Surname, Name\" ") + .to_rfc822_string() + ); + } + + public void equal_to() throws GLib.Error { + MailboxAddress test = new MailboxAddress("test", "example@example.com"); + + assert_true( + test.equal_to(test), + "Object identity equality" + ); + assert_true( + test.equal_to(new MailboxAddress("test", "example@example.com")), + "Mailbox identity equality" + ); + assert_true( + test.equal_to(new MailboxAddress(null, "example@example.com")), + "Address equality" + ); + assert_false( + test.equal_to(new MailboxAddress(null, "blarg@example.com")), + "Address inequality" + ); + } } diff -Nru geary-0.12.4/test/engine/rfc822-message-data-test.vala geary-3.32.0/test/engine/rfc822-message-data-test.vala --- geary-0.12.4/test/engine/rfc822-message-data-test.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/test/engine/rfc822-message-data-test.vala 2019-03-17 13:39:29.000000000 +0000 @@ -5,39 +5,39 @@ * (version 2.1 or later). See the COPYING file in this distribution. */ -class Geary.RFC822.MessageDataTest : Gee.TestCase { +class Geary.RFC822.MessageDataTest : TestCase { public MessageDataTest() { base("Geary.RFC822.MessageDataTest"); add_test("PreviewText.with_header", preview_text_with_header); } - public void preview_text_with_header() { + public void preview_text_with_header() throws Error { PreviewText plain_preview1 = new PreviewText.with_header( - new Geary.Memory.StringBuffer(PLAIN_BODY1_ENCODED), - new Geary.Memory.StringBuffer(PLAIN_BODY1_HEADERS) + new Geary.Memory.StringBuffer(PLAIN_BODY1_HEADERS), + new Geary.Memory.StringBuffer(PLAIN_BODY1_ENCODED) ); - assert(plain_preview1.buffer.to_string() == PLAIN_BODY1_EXPECTED); + assert_string(PLAIN_BODY1_EXPECTED, plain_preview1.buffer.to_string()); PreviewText base64_preview = new PreviewText.with_header( - new Geary.Memory.StringBuffer(BASE64_BODY_ENCODED), - new Geary.Memory.StringBuffer(BASE64_BODY_HEADERS) + new Geary.Memory.StringBuffer(BASE64_BODY_HEADERS), + new Geary.Memory.StringBuffer(BASE64_BODY_ENCODED) ); - assert(base64_preview.buffer.to_string() == BASE64_BODY_EXPECTED); + assert_string(BASE64_BODY_EXPECTED, base64_preview.buffer.to_string()); string html_part_headers = "Content-Type: text/html; charset=utf-8\r\nContent-Transfer-Encoding: quoted-printable\r\n\r\n"; PreviewText html_preview1 = new PreviewText.with_header( - new Geary.Memory.StringBuffer(HTML_BODY1_ENCODED), - new Geary.Memory.StringBuffer(html_part_headers) + new Geary.Memory.StringBuffer(html_part_headers), + new Geary.Memory.StringBuffer(HTML_BODY1_ENCODED) ); - assert(html_preview1.buffer.to_string() == HTML_BODY1_EXPECTED); + assert_string(HTML_BODY1_EXPECTED, html_preview1.buffer.to_string()); PreviewText html_preview2 = new PreviewText.with_header( - new Geary.Memory.StringBuffer(HTML_BODY2_ENCODED), - new Geary.Memory.StringBuffer(html_part_headers) + new Geary.Memory.StringBuffer(html_part_headers), + new Geary.Memory.StringBuffer(HTML_BODY2_ENCODED) ); - assert(html_preview2.buffer.to_string() == HTML_BODY2_EXPECTED); + assert_string(HTML_BODY2_EXPECTED, html_preview2.buffer.to_string()); } public static string PLAIN_BODY1_HEADERS = "Content-Type: text/plain; charset=\"us-ascii\"\r\nContent-Transfer-Encoding: 7bit\r\n"; @@ -70,7 +70,7 @@ >

"""; - public static string HTML_BODY1_EXPECTED = "Hi Kenneth, We xxxxx xxxx xx xxx xxx xx xxxx x xxxxxxxx xxxxxxxx.  Thank you, XXXXXX XXXXXX You can reply directly to this message or click the following link: https://app.foobar.com/xxxxxxxxxxxxxxxx1641966deff6c48623aba You can change your email preferences at: https://app.foobar.com/xxxxxxxxxxx"; + public static string HTML_BODY1_EXPECTED = "Hi Kenneth, We xxxxx xxxx xx xxx xxx xx xxxx x xxxxxxxx xxxxxxxx. Thank you, XXXXXX XXXXXX You can reply directly to this message or click the following link: https://app.foobar.com/xxxxxxxxxxxxxxxx1641966deff6c48623aba You can change your email preferences at: https://app.foobar.com/xxxxxxxxxxx"; public static string HTML_BODY2_ENCODED = """ @@ -618,5 +618,5 @@ """; - public static string HTML_BODY2_EXPECTED = "Buy It Now from US $1,750.00 to US $5,950.00. eBay Daccordi, Worldwide: 2 new matches today Daccordi 50th anniversary edition with... Buy it now: US $5,950.00 100% positive feedback Daccordi Griffe Campagnolo Croce D'Aune... Buy it now: US $1,750.00 100% positive feedback View all results Refine this search Disable emails for this search   Email reference id: [#d9f42b5e860b4eabb98195c2888cba9e#] We don't check this mailbox, so please don't reply to this message. If you have a question, go to Help & Contact. ©2016 eBay Inc., eBay International AG Helvetiastrasse 15/17 - P.O. Box 133, 3000 Bern 6, Switzerland"; + public static string HTML_BODY2_EXPECTED = "Buy It Now from US $1,750.00 to US $5,950.00. eBay Daccordi, Worldwide: 2 new matches today Daccordi 50th anniversary edition with... Buy it now: US $5,950.00 100% positive feedback Daccordi Griffe Campagnolo Croce D'Aune... Buy it now: US $1,750.00 100% positive feedback View all results Refine this search Disable emails for this search Email reference id: [#d9f42b5e860b4eabb98195c2888cba9e#] We don't check this mailbox, so please don't reply to this message. If you have a question, go to Help & Contact. ©2016 eBay Inc., eBay International AG Helvetiastrasse 15/17 - P.O. Box 133, 3000 Bern 6, Switzerland"; } diff -Nru geary-0.12.4/test/engine/rfc822-message-test.vala geary-3.32.0/test/engine/rfc822-message-test.vala --- geary-0.12.4/test/engine/rfc822-message-test.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/test/engine/rfc822-message-test.vala 2019-03-17 13:39:29.000000000 +0000 @@ -1,24 +1,172 @@ /* - * Copyright 2016 Michael Gratton + * Copyright 2016-2018 Michael Gratton * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ -class Geary.RFC822.MessageTest : Gee.TestCase { +class Geary.RFC822.MessageTest : TestCase { + + + private const string BASIC_TEXT_PLAIN = "basic-text-plain.eml"; + private const string BASIC_TEXT_HTML = "basic-text-html.eml"; + private const string BASIC_MULTIPART_ALTERNATIVE = + "basic-multipart-alternative.eml"; + + private const string HTML_CONVERSION_TEMPLATE = + "
%s
"; + + private const string BASIC_PLAIN_BODY = """This is the first line. + +This is the second line. + +"""; + + private const string BASIC_HTML_BODY = """

This is the first line. + +

This is the second line. + +"""; public MessageTest() { base("Geary.RFC822.MessageTest"); - add_test("Message::get_preview", get_preview); + add_test("basic_message_from_buffer", basic_message_from_buffer); + add_test("encoded_recipient", encoded_recipient); + add_test("duplicate_mailbox", duplicate_mailbox); + add_test("duplicate_message_id", duplicate_message_id); + add_test("text_plain_as_plain", text_plain_as_plain); + add_test("text_plain_as_html", text_plain_as_html); + add_test("text_html_as_html", text_html_as_html); + add_test("text_html_as_plain", text_html_as_plain); + add_test("multipart_alternative_as_plain", + multipart_alternative_as_plain); + add_test("multipart_alternative_as_converted_html", + multipart_alternative_as_converted_html); + add_test("multipart_alternative_as_html", + multipart_alternative_as_html); + add_test("get_preview", get_preview); + } + + public void basic_message_from_buffer() throws Error { + Message basic = resource_to_message(BASIC_TEXT_PLAIN); + + assert_data(basic.subject, "Re: Basic text/plain message"); + assert_addresses(basic.from, "Alice "); + assert_address(basic.sender, "Bob "); + assert_addresses(basic.reply_to, "\"Alice: Personal Account\" "); + assert_addresses(basic.to, "Charlie "); + assert_addresses(basic.cc, "Dave "); + assert_addresses(basic.bcc, "Eve "); + //assert_data(basic.message_id, "<3456@example.net>"); + assert_message_id_list(basic.in_reply_to, "<1234@local.machine.example>"); + assert_message_id_list(basic.references, "<1234@local.machine.example>"); + assert_data(basic.date, "Fri, 21 Nov 1997 10:01:10 -0600"); + assert(basic.mailer == "Geary Test Suite 1.0"); + } + + public void encoded_recipient() throws Error { + Message enc = string_to_message(ENCODED_TO); + + // Courtesy Mailsploit https://www.mailsploit.com + assert(enc.to[0].name == "potus@whitehouse.gov "); + } + + public void duplicate_mailbox() throws Error { + Message dup = string_to_message(DUPLICATE_TO); + + assert(dup.to.size == 2); + assert_addresses( + dup.to, "John Doe 1 , John Doe 2 " + ); + } + + public void duplicate_message_id() throws Error { + Message dup = string_to_message(DUPLICATE_REFERENCES); + + assert(dup.references.list.size == 2); + assert_message_id_list( + dup.references, "<1234@local.machine.example> <5678@local.machine.example>" + ); + } + + public void text_plain_as_plain() throws Error { + Message test = resource_to_message(BASIC_TEXT_PLAIN); + + assert_true(test.has_plain_body(), "Expected plain body"); + assert_false(test.has_html_body(), "Expected non-html body"); + assert_string(BASIC_PLAIN_BODY, test.get_plain_body(false, null)); + } + + public void text_plain_as_html() throws Error { + Message test = resource_to_message(BASIC_TEXT_PLAIN); + + assert_true(test.has_plain_body(), "Expected plain body"); + assert_false(test.has_html_body(), "Expected non-html body"); + assert_string( + HTML_CONVERSION_TEMPLATE.printf(BASIC_PLAIN_BODY), + test.get_plain_body(true, null) + ); + } + + public void text_html_as_html() throws Error { + Message test = resource_to_message(BASIC_TEXT_HTML); + + assert_true(test.has_html_body(), "Expected html body"); + assert_false(test.has_plain_body(), "Expected non-plain body"); + assert_string(BASIC_HTML_BODY, test.get_html_body(null)); + } + + public void text_html_as_plain() throws Error { + Message test = resource_to_message(BASIC_TEXT_HTML); + + assert_true(test.has_html_body(), "Expected html body"); + assert_false(test.has_plain_body(), "Expected non-plain body"); + assert_string(BASIC_HTML_BODY, test.get_html_body(null)); + } + + public void multipart_alternative_as_plain() throws Error { + Message test = resource_to_message(BASIC_MULTIPART_ALTERNATIVE); + + assert_true(test.has_plain_body(), "Expected plain body"); + assert_true(test.has_html_body(), "Expected html body"); + assert_string(BASIC_PLAIN_BODY, test.get_plain_body(false, null)); + } + + public void multipart_alternative_as_converted_html() throws Error { + Message test = resource_to_message(BASIC_MULTIPART_ALTERNATIVE); + + assert_true(test.has_plain_body(), "Expected plain body"); + assert_true(test.has_html_body(), "Expected html body"); + assert_string( + HTML_CONVERSION_TEMPLATE.printf(BASIC_PLAIN_BODY), + test.get_plain_body(true, null) + ); + } + + public void multipart_alternative_as_html() throws Error { + Message test = resource_to_message(BASIC_MULTIPART_ALTERNATIVE); + + assert_true(test.has_plain_body(), "Expected plain body"); + assert_true(test.has_html_body(), "Expected html body"); + assert_string(BASIC_HTML_BODY, test.get_html_body(null)); + } + + public void get_preview() throws Error { + Message multipart_signed = string_to_message(MULTIPART_SIGNED_MESSAGE_TEXT); + + assert(multipart_signed.get_preview() == MULTIPART_SIGNED_MESSAGE_PREVIEW); } - public void get_preview() { - try { - Message multipart_signed = string_to_message(MULTIPART_SIGNED_MESSAGE_TEXT); - assert(multipart_signed.get_preview() == MULTIPART_SIGNED_MESSAGE_PREVIEW); - } catch (Error err) { - assert_no_error(err); - } + private Message resource_to_message(string path) throws Error { + GLib.File resource = + GLib.File.new_for_uri(RESOURCE_URI).resolve_relative_path(path); + + uint8[] contents; + resource.load_contents(null, out contents, null); + + return new Message.from_buffer( + new Geary.Memory.ByteBuffer(contents, contents.length) + ); } private Message string_to_message(string message_text) throws Error { @@ -27,6 +175,41 @@ ); } + private void assert_data(Geary.MessageData.AbstractMessageData? actual, + string expected) + throws Error { + assert_non_null(actual, expected); + assert_string(expected, actual.to_string()); + } + + private void assert_address(Geary.RFC822.MailboxAddress? address, + string expected) + throws Error { + assert_non_null(address, expected); + assert_string(expected, address.to_rfc822_string()); + } + + private void assert_addresses(Geary.RFC822.MailboxAddresses? addresses, + string expected) + throws Error { + assert_non_null(addresses, expected); + assert_string(expected, addresses.to_rfc822_string()); + } + + private void assert_message_id_list(Geary.RFC822.MessageIDList? ids, + string expected) + throws Error { + assert_non_null(ids, expected); + assert(ids.to_rfc822_string() == expected); + } + + // Courtesy Mailsploit https://www.mailsploit.com + private static string ENCODED_TO = "From: Mary Smith \r\nTo: =?utf-8?b?cG90dXNAd2hpdGVob3VzZS5nb3YiIDx0ZXN0Pg==?= \r\nSubject: Re: Saying Hello\r\nDate: Fri, 21 Nov 1997 10:01:10 -0600\r\n\r\nThis is a reply to your hello.\r\n\r\n"; + + private static string DUPLICATE_TO = "From: Mary Smith \r\nTo: John Doe 1 \r\nTo: John Doe 2 \r\nSubject: Re: Saying Hello\r\nDate: Fri, 21 Nov 1997 10:01:10 -0600\r\n\r\nThis is a reply to your hello.\r\n\r\n"; + + private static string DUPLICATE_REFERENCES = "From: Mary Smith \r\nTo: John Doe \r\nReferences: <1234@local.machine.example>\r\nReferences: <5678@local.machine.example>\r\nSubject: Re: Saying Hello\r\nDate: Fri, 21 Nov 1997 10:01:10 -0600\r\n\r\nThis is a reply to your hello.\r\n\r\n"; + private static string MULTIPART_SIGNED_MESSAGE_TEXT = "Return-Path: \r\nReceived: from mogul.quuxo.net ([unix socket])\r\n by mogul (Cyrus v2.4.12-Debian-2.4.12-2) with LMTPA;\r\n Wed, 21 Dec 2016 06:54:03 +1030\r\nX-Sieve: CMU Sieve 2.4\r\nReceived: from huckleberry.canonical.com (huckleberry.canonical.com [91.189.94.19])\r\n by mogul.quuxo.net (8.14.4/8.14.4/Debian-2ubuntu2.1) with ESMTP id uBKKNtpt026727\r\n for ; Wed, 21 Dec 2016 06:53:57 +1030\r\nReceived: from localhost ([127.0.0.1] helo=huckleberry.canonical.com)\r\n by huckleberry.canonical.com with esmtp (Exim 4.76)\r\n (envelope-from )\r\n id 1cJQwM-0003Xk-IO; Tue, 20 Dec 2016 20:23:14 +0000\r\nReceived: from 208-151-246-43.dq1sn.easystreet.com ([208.151.246.43]\r\n helo=lizaveta.nxnw.org)\r\n by huckleberry.canonical.com with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32)\r\n (Exim 4.76) (envelope-from )\r\n id 1cJQin-0000t2-G6\r\n for ubuntu-security-announce@lists.ubuntu.com; Tue, 20 Dec 2016 20:09:14 +0000\r\nReceived: from kryten.nxnw.org (kryten.nxnw.org [10.19.96.254])\r\n (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits))\r\n (Client CN \"kryten.int.wirex.com\", Issuer \"nxnw.org\" (not verified))\r\n by lizaveta.nxnw.org (Postfix) with ESMTPS id DD8C360941\r\n for ;\r\n Tue, 20 Dec 2016 12:09:06 -0800 (PST)\r\nReceived: by kryten.nxnw.org (Postfix, from userid 1000)\r\n id 84341342F6C; Tue, 20 Dec 2016 12:09:06 -0800 (PST)\r\nDate: Tue, 20 Dec 2016 12:09:06 -0800\r\nFrom: Steve Beattie \r\nTo: ubuntu-security-announce@lists.ubuntu.com\r\nSubject: [USN-3159-1] Linux kernel vulnerability\r\nMessage-ID: <20161220200906.GF8251@nxnw.org>\r\nMail-Followup-To: Ubuntu Security \r\nMIME-Version: 1.0\r\nUser-Agent: Mutt/1.5.24 (2015-08-30)\r\nX-Mailman-Approved-At: Tue, 20 Dec 2016 20:23:12 +0000\r\nX-BeenThere: ubuntu-security-announce@lists.ubuntu.com\r\nX-Mailman-Version: 2.1.14\r\nPrecedence: list\r\nReply-To: ubuntu-users@lists.ubuntu.com, Ubuntu Security \r\nList-Id: Ubuntu Security Announcements\r\n \r\nList-Unsubscribe: , \r\n \r\nList-Archive: \r\nList-Post: \r\nList-Help: \r\nList-Subscribe: , \r\n \r\nContent-Type: multipart/mixed; boundary=\"===============7564301068935298617==\"\r\nErrors-To: ubuntu-security-announce-bounces@lists.ubuntu.com\r\nSender: ubuntu-security-announce-bounces@lists.ubuntu.com\r\nX-Greylist: Sender IP whitelisted by DNSRBL, not delayed by milter-greylist-4.3.9 (mogul.quuxo.net [203.18.245.241]); Wed, 21 Dec 2016 06:53:57 +1030 (ACDT)\r\nX-Virus-Scanned: clamav-milter 0.99.2 at mogul\r\nX-Virus-Status: Clean\r\nX-Spam-Status: No, score=-4.2 required=5.0 tests=BAYES_00,RCVD_IN_DNSWL_MED\r\n autolearn=ham version=3.3.2\r\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on mogul.quuxo.net\r\n\r\n\r\n--===============7564301068935298617==\r\nContent-Type: multipart/signed; micalg=pgp-sha512;\r\n protocol=\"application/pgp-signature\"; boundary=\"O98KdSgI27dgYlM5\"\r\nContent-Disposition: inline\r\n\r\n\r\n--O98KdSgI27dgYlM5\r\nContent-Type: text/plain; charset=us-ascii\r\nContent-Disposition: inline\r\n\r\n==========================================================================\r\nUbuntu Security Notice USN-3159-1\r\nDecember 20, 2016\r\n\r\nlinux vulnerability\r\n==========================================================================\r\n\r\nA security issue affects these releases of Ubuntu and its derivatives:\r\n\r\n- Ubuntu 12.04 LTS\r\n\r\nSummary:\r\n\r\nThe system could be made to expose sensitive information.\r\n\r\nSoftware Description:\r\n- linux: Linux kernel\r\n\r\nDetails:\r\n\r\nIt was discovered that a race condition existed in the procfs\r\nenviron_read function in the Linux kernel, leading to an integer\r\nunderflow. A local attacker could use this to expose sensitive\r\ninformation (kernel memory).\r\n\r\nUpdate instructions:\r\n\r\nThe problem can be corrected by updating your system to the following\r\npackage versions:\r\n\r\nUbuntu 12.04 LTS:\r\n linux-image-3.2.0-119-generic 3.2.0-119.162\r\n linux-image-3.2.0-119-generic-pae 3.2.0-119.162\r\n linux-image-3.2.0-119-highbank 3.2.0-119.162\r\n linux-image-3.2.0-119-omap 3.2.0-119.162\r\n linux-image-3.2.0-119-powerpc-smp 3.2.0-119.162\r\n linux-image-3.2.0-119-powerpc64-smp 3.2.0-119.162\r\n linux-image-3.2.0-119-virtual 3.2.0-119.162\r\n linux-image-generic 3.2.0.119.134\r\n linux-image-generic-pae 3.2.0.119.134\r\n linux-image-highbank 3.2.0.119.134\r\n linux-image-omap 3.2.0.119.134\r\n linux-image-powerpc-smp 3.2.0.119.134\r\n linux-image-powerpc64-smp 3.2.0.119.134\r\n linux-image-virtual 3.2.0.119.134\r\n\r\nAfter a standard system update you need to reboot your computer to make\r\nall the necessary changes.\r\n\r\nATTENTION: Due to an unavoidable ABI change the kernel updates have\r\nbeen given a new version number, which requires you to recompile and\r\nreinstall all third party kernel modules you might have installed.\r\nUnless you manually uninstalled the standard kernel metapackages\r\n(e.g. linux-generic, linux-generic-lts-RELEASE, linux-virtual,\r\nlinux-powerpc), a standard system upgrade will automatically perform\r\nthis as well.\r\n\r\nReferences:\r\n http://www.ubuntu.com/usn/usn-3159-1\r\n CVE-2016-7916\r\n\r\nPackage Information:\r\n https://launchpad.net/ubuntu/+source/linux/3.2.0-119.162\r\n\r\n\r\n--O98KdSgI27dgYlM5\r\nContent-Type: application/pgp-signature; name=\"signature.asc\"\r\n\r\n-----BEGIN PGP SIGNATURE-----\r\nVersion: GnuPG v1\r\n\r\niQIcBAEBCgAGBQJYWY/iAAoJEC8Jno0AXoH0gKUQAJ7UOWV591M8K+HGXHI3BVJi\r\n75LCUSBRrV2NZTpc32ZMCsssb4TSqQinzczQfWSNtlLsgucKTLdCYGJvbXYxd32z\r\nBzHHHH9D8EDC6X4Olx0byiDBTX76kVBVUjxsKJ1zkYBFeMZ6tx9Tmgsl7Rdr26lP\r\n9oe3nBadkP0vM7j/dG1913MdzOlFc/2YOnGRK6QKzy1HhM74XMQTzvj9Nsbgs8ea\r\nZFTzWgDiUXi9SbBDLmwkY2uFJ+zreIH/vRjZHZ5ofJz9ed91HDhMB7CmRzn4JG/b\r\nSPAmTk0IRzWVBWglb0hPA8NN194ijeQFa6OJt94+EIMYuIasjsi8zGr+o1yxM5aY\r\ngTiDLzrQVWfddcZWmoCw8WWVbHAjMW60ehAs+y6ly0tBAn7wailXFRDFir1Vt4i2\r\n1WRTnJR2JebfQN4YeJ7CAiw34+PO8+vi8qHcRqMGkRu5IYdBy8AvBucVO923jIIy\r\nJBRTVkZqacRVp4PLx7vrOXX02z7y38iQcP2QSeapMoQjViYOVSMYhycO9zqGe3Tj\r\nAHMqp2HGj1uPp+3mM/yRBaE1X1j7lzjsKO1XZwjMUIYcFmAAsg2Gwi5S0FhhS+cD\r\nulCZ0A+r4wZ/1K6cZ2ZCEQoAZyMovwiVLNP+4q7pHhcQGTYAvCEgPksktQwD3YOe\r\nnSj5HG2dTMTOHDjVGSVV\r\n=qUGf\r\n-----END PGP SIGNATURE-----\r\n\r\n--O98KdSgI27dgYlM5--\r\n\r\n\r\n--===============7564301068935298617==\r\nContent-Type: text/plain; charset=\"us-ascii\"\r\nMIME-Version: 1.0\r\nContent-Transfer-Encoding: 7bit\r\nContent-Disposition: inline\r\n\r\n-- \r\nubuntu-security-announce mailing list\r\nubuntu-security-announce@lists.ubuntu.com\r\nModify settings or unsubscribe at: https://lists.ubuntu.com/mailman/listinfo/ubuntu-security-announce\r\n\r\n--===============7564301068935298617==--\r\n"; private static string MULTIPART_SIGNED_MESSAGE_PREVIEW = "Ubuntu Security Notice USN-3159-1 December 20, 2016 linux vulnerability A security issue affects these releases of Ubuntu and its derivatives: - Ubuntu 12.04 LTS Summary: The system could be made to expose sensitive information. Software Description: - linux: Linux kernel Details: It was discovered that a race condition existed in the procfs environ_read function in the Linux kernel, leading to an integer underflow. A local attacker could use this to expose sensitive information (kernel memory). Update instructions: The problem can be corrected by updating your system to the following package versions: Ubuntu 12.04 LTS: linux-image-3.2.0-119-generic 3.2.0-119.162 linux-image-3.2.0-119-generic-pae 3.2.0-119.162 linux-image-3.2.0-119-highbank 3.2.0-119.162 linux-image-3.2.0-119-omap 3.2.0-119.162 linux-image-3.2.0-119-powerpc-smp 3.2.0-119.162 linux-image-3.2.0-119-powerpc64-smp 3.2.0-119.162 linux-image-3.2.0-119-virtual 3.2.0-119.162 linux-image-generic 3.2.0.119.134 linux-image-generic-pae 3.2.0.119.134 linux-image-highbank 3.2.0.119.134 linux-image-omap 3.2.0.119.134 linux-image-powerpc-smp 3.2.0.119.134 linux-image-powerpc64-smp 3.2.0.119.134 linux-image-virtual 3.2.0.119.134 After a standard system update you need to reboot your computer to make all the necessary changes. ATTENTION: Due to an unavoidable ABI change the kernel updates have been given a new version number, which requires you to recompile and reinstall all third party kernel modules you might have installed. Unless you manually uninstalled the standard kernel metapackages (e.g. linux-generic, linux-generic-lts-RELEASE, linux-virtual, linux-powerpc), a standard system upgrade will automatically perform this as well. References: http://www.ubuntu.com/usn/usn-3159-1 CVE-2016-7916 Package Information: https://launchpad.net/ubuntu/+source/linux/3.2.0-119.162 ubuntu-security-announce mailing list ubuntu-security-announce@lists.ubuntu.com Modify settings or unsubscribe at: https://lists.ubuntu.com/mailman/listinfo/ubuntu-security-announce"; } diff -Nru geary-0.12.4/test/engine/rfc822-part-test.vala geary-3.32.0/test/engine/rfc822-part-test.vala --- geary-0.12.4/test/engine/rfc822-part-test.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/test/engine/rfc822-part-test.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,102 @@ +/* + * Copyright 2018 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +class Geary.RFC822.PartTest : TestCase { + + private const string CR_BODY = "This is an attachment.\n"; + private const string CRLF_BODY = "This is an attachment.\r\n"; + private const string ICAL_BODY = "BEGIN:VCALENDAR\r\nEND:VCALENDAR\r\n"; + + + public PartTest() { + base("Geary.RFC822.PartTest"); + add_test("new_from_empty_mime_part", new_from_empty_mime_part); + add_test("new_from_complete_mime_part", new_from_complete_mime_part); + add_test("write_to_buffer_plain", write_to_buffer_plain); + add_test("write_to_buffer_plain_crlf", write_to_buffer_plain_crlf); + add_test("write_to_buffer_plain_ical", write_to_buffer_plain_ical); + } + + public void new_from_empty_mime_part() throws Error { + GMime.Part part = new_part(null, CR_BODY.data); + part.set_header("Content-Type", ""); + + Part test = new Part(part); + + assert_null(test.content_type, "content_type"); + assert_null_string(test.content_id, "content_id"); + assert_null_string(test.content_description, "content_description"); + assert_null(test.content_disposition, "content_disposition"); + } + + public void new_from_complete_mime_part() throws Error { + const string TYPE = "text/plain"; + const string ID = "test-id"; + const string DESC = "test description"; + + GMime.Part part = new_part(TYPE, CR_BODY.data); + part.set_content_id(ID); + part.set_content_description(DESC); + part.set_content_disposition( + new GMime.ContentDisposition.from_string("inline") + ); + + Part test = new Part(part); + + assert_string(TYPE, test.content_type.to_string()); + assert_string(ID, test.content_id); + assert_string(DESC, test.content_description); + assert_non_null(test.content_disposition, "content_disposition"); + assert_int( + Geary.Mime.DispositionType.INLINE, + test.content_disposition.disposition_type + ); + } + + public void write_to_buffer_plain() throws Error { + Part test = new Part(new_part("text/plain", CR_BODY.data)); + + Memory.Buffer buf = test.write_to_buffer(); + + assert_string(CR_BODY, buf.to_string()); + } + + public void write_to_buffer_plain_crlf() throws Error { + Part test = new Part(new_part("text/plain", CRLF_BODY.data)); + + Memory.Buffer buf = test.write_to_buffer(); + + // CRLF should be stripped + assert_string(CR_BODY, buf.to_string()); + } + + public void write_to_buffer_plain_ical() throws Error { + Part test = new Part(new_part("text/calendar", ICAL_BODY.data)); + + Memory.Buffer buf = test.write_to_buffer(); + + // CRLF should not be stripped + assert_string(ICAL_BODY, buf.to_string()); + } + + private GMime.Part new_part(string? mime_type, + uint8[] body, + GMime.ContentEncoding encoding = GMime.ContentEncoding.DEFAULT) { + GMime.Part part = new GMime.Part(); + if (mime_type != null) { + part.set_content_type(new GMime.ContentType.from_string(mime_type)); + } + GMime.DataWrapper body_wrapper = new GMime.DataWrapper.with_stream( + new GMime.StreamMem.with_buffer(body), + encoding + ); + part.set_content_object(body_wrapper); + part.encode(GMime.EncodingConstraint.7BIT); + return part; + } + +} diff -Nru geary-0.12.4/test/engine/rfc822-utils-test.vala geary-3.32.0/test/engine/rfc822-utils-test.vala --- geary-0.12.4/test/engine/rfc822-utils-test.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/test/engine/rfc822-utils-test.vala 2019-03-17 13:39:29.000000000 +0000 @@ -5,14 +5,14 @@ * (version 2.1 or later). See the COPYING file in this distribution. */ -class Geary.RFC822.Utils.Test : Gee.TestCase { +class Geary.RFC822.Utils.Test : TestCase { public Test() { base("Geary.RFC822.Utils.Test"); add_test("to_preview_text", to_preview_text); } - public void to_preview_text() { + public void to_preview_text() throws Error { assert(Geary.RFC822.Utils.to_preview_text(PLAIN_BODY_ENCODED, Geary.RFC822.TextFormat.PLAIN) == PLAIN_BODY_EXPECTED); assert(Geary.RFC822.Utils.to_preview_text(HTML_BODY_ENCODED, Geary.RFC822.TextFormat.HTML) == diff -Nru geary-0.12.4/test/engine/util-ascii-test.vala geary-3.32.0/test/engine/util-ascii-test.vala --- geary-0.12.4/test/engine/util-ascii-test.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/test/engine/util-ascii-test.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,44 @@ +/* + * Copyright 2018 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +class Geary.Ascii.Test : TestCase { + + public Test() { + base("Geary.Ascii.Test"); + add_test("index_of", index_of); + add_test("last_index_of", last_index_of); + } + + public void index_of() throws Error { + assert_int(-1, Ascii.index_of("", 'a')); + assert_int(0, Ascii.index_of("a", 'a')); + assert_int(0, Ascii.index_of("aa", 'a')); + + assert_int(0, Ascii.index_of("abcabc", 'a')); + assert_int(1, Ascii.index_of("abcabc", 'b')); + assert_int(2, Ascii.index_of("abcabc", 'c')); + + assert_int(0, Ascii.index_of("@", '@')); + + assert_int(-1, Ascii.index_of("abc", 'd')); + } + + public void last_index_of() throws Error { + assert_int(-1, Ascii.last_index_of("", 'a')); + assert_int(0, Ascii.last_index_of("a", 'a')); + assert_int(1, Ascii.last_index_of("aa", 'a')); + + assert_int(3, Ascii.last_index_of("abcabc", 'a')); + assert_int(4, Ascii.last_index_of("abcabc", 'b')); + assert_int(5, Ascii.last_index_of("abcabc", 'c')); + + assert_int(0, Ascii.last_index_of("@", '@')); + + assert_int(-1, Ascii.last_index_of("abc", 'd')); + } + +} diff -Nru geary-0.12.4/test/engine/util-config-file-test.vala geary-3.32.0/test/engine/util-config-file-test.vala --- geary-0.12.4/test/engine/util-config-file-test.vala 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/test/engine/util-config-file-test.vala 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,129 @@ +/* + * Copyright 2018 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +class Geary.ConfigFileTest : TestCase { + + + private const string TEST_KEY = "test-key"; + private const string TEST_KEY_MISSING = "test-key-missing"; + + private ConfigFile? test_config = null; + private ConfigFile.Group? test_group = null; + + + public ConfigFileTest() { + base("Geary.ConfigFileTest"); + add_test("test_string", test_string); + add_test("test_string_fallback", test_string_fallback); + add_test("test_string_list", test_string_list); + add_test("test_string_list", test_string_list); + add_test("test_bool", test_bool); + add_test("test_int", test_int); + add_test("test_uint16", test_uint16); + add_test("test_has_key", test_has_key); + add_test("test_key_remove", test_key_remove); + add_test("test_group_exists", test_group_exists); + add_test("test_group_remove", test_group_remove); + } + + public override void set_up() throws GLib.Error { + this.test_config = new ConfigFile(GLib.File.new_for_path("/tmp/config.ini")); + this.test_group = this.test_config.get_group("Test"); + } + + public override void tear_down() throws GLib.Error { + this.test_group = null; + this.test_config = null; + } + + public void test_string() throws Error { + this.test_group.set_string(TEST_KEY, "a string"); + assert_string("a string", this.test_group.get_string(TEST_KEY)); + assert_string("default", this.test_group.get_string(TEST_KEY_MISSING, "default")); + } + + public void test_string_fallback() throws Error { + ConfigFile.Group fallback = this.test_config.get_group("fallback"); + fallback.set_string("fallback-test-key", "a string"); + + this.test_group.set_fallback("fallback", "fallback-"); + assert_string("a string", this.test_group.get_string(TEST_KEY)); + } + + public void test_string_list() throws Error { + this.test_group.set_string_list( + TEST_KEY, new Gee.ArrayList.wrap({ "a", "b"}) + ); + + Gee.List saved = this.test_group.get_string_list(TEST_KEY); + assert_int(2, saved.size, "Saved string list"); + assert_string("a", saved[0]); + assert_string("b", saved[1]); + + Gee.List def = this.test_group.get_string_list(TEST_KEY_MISSING); + assert_int(0, def.size, "Default string list"); + } + + public void test_bool() throws Error { + this.test_group.set_bool(TEST_KEY, true); + assert_true(this.test_group.get_bool(TEST_KEY)); + assert_true(this.test_group.get_bool(TEST_KEY_MISSING, true)); + assert_false(this.test_group.get_bool(TEST_KEY_MISSING, false)); + } + + public void test_int() throws Error { + this.test_group.set_int(TEST_KEY, 42); + assert_int(42, this.test_group.get_int(TEST_KEY)); + assert_int(42, this.test_group.get_int(TEST_KEY_MISSING, 42)); + } + + public void test_uint16() throws Error { + this.test_group.set_uint16(TEST_KEY, 42); + assert_int(42, this.test_group.get_uint16(TEST_KEY)); + assert_int(42, this.test_group.get_uint16(TEST_KEY_MISSING, 42)); + } + + public void test_has_key() throws Error { + assert_false( + this.test_group.has_key(TEST_KEY), + "Should not already exist" + ); + this.test_group.set_string(TEST_KEY, "a string"); + assert_true( + this.test_group.has_key(TEST_KEY), "Should now exist" + ); + } + + public void test_key_remove() throws Error { + // create the key + this.test_group.set_string(TEST_KEY, "a string"); + assert_true( + this.test_group.has_key(TEST_KEY), "Should exist" + ); + + this.test_group.remove_key(TEST_KEY); + assert_false( + this.test_group.has_key(TEST_KEY), "Should no longer exist" + ); + } + + public void test_group_exists() throws Error { + assert_false(this.test_group.exists, "Should not already exist"); + this.test_group.set_string(TEST_KEY, "a string"); + assert_true(this.test_group.exists, "Should now exist"); + } + + public void test_group_remove() throws Error { + // create the group + this.test_group.set_string(TEST_KEY, "a string"); + assert_true(this.test_group.exists, "Should exist"); + + this.test_group.remove(); + assert_false(this.test_group.exists, "Should no longer exist"); + } + +} diff -Nru geary-0.12.4/test/engine/util-html-test.vala geary-3.32.0/test/engine/util-html-test.vala --- geary-0.12.4/test/engine/util-html-test.vala 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/test/engine/util-html-test.vala 2019-03-17 13:39:29.000000000 +0000 @@ -5,14 +5,80 @@ * (version 2.1 or later). See the COPYING file in this distribution. */ -class Geary.HTML.UtilTest : Gee.TestCase { +class Geary.HTML.UtilTest : TestCase { public UtilTest() { base("Geary.HTML.Util"); + add_test("preserve_whitespace", preserve_whitespace); + add_test("smart_escape_div", smart_escape_div); + add_test("smart_escape_no_closing_tag", smart_escape_no_closing_tag); + add_test("smart_escape_img", smart_escape_img); + add_test("smart_escape_xhtml_img", smart_escape_xhtml_img); + add_test("smart_escape_mixed", smart_escape_mixed); + add_test("smart_escape_text", smart_escape_text); + add_test("smart_escape_text_url", smart_escape_text_url); add_test("remove_html_tags", remove_html_tags); } - public void remove_html_tags() { + public void preserve_whitespace() throws GLib.Error { + assert_string("some text", Geary.HTML.smart_escape("some text")); + assert_string("some  text", Geary.HTML.smart_escape("some text")); + assert_string("some   text", Geary.HTML.smart_escape("some text")); + assert_string("some    text", Geary.HTML.smart_escape("some\ttext")); + + assert_string("some
text", Geary.HTML.smart_escape("some\ntext")); + assert_string("some
text", Geary.HTML.smart_escape("some\rtext")); + assert_string("some
text", Geary.HTML.smart_escape("some\r\ntext")); + + assert_string("some

text", Geary.HTML.smart_escape("some\n\ntext")); + assert_string("some

text", Geary.HTML.smart_escape("some\r\rtext")); + assert_string("some

text", Geary.HTML.smart_escape("some\n\rtext")); + assert_string("some

text", Geary.HTML.smart_escape("some\r\n\r\ntext")); + } + + public void smart_escape_div() throws Error { + string html = "

ohhai
"; + assert(Geary.HTML.smart_escape(html) == html); + } + + public void smart_escape_no_closing_tag() throws Error { + string html = "
ohhai"; + assert(Geary.HTML.smart_escape(html) == html); + } + + public void smart_escape_img() throws Error { + string html = ""; + assert(Geary.HTML.smart_escape(html) == html); + } + + public void smart_escape_xhtml_img() throws Error { + string html = ""; + assert(Geary.HTML.smart_escape(html) == html); + } + + public void smart_escape_mixed() throws Error { + string html = "mixed
ohhai
text"; + assert(Geary.HTML.smart_escape(html) == html); + } + + public void smart_escape_text() throws GLib.Error { + assert_string("some text", Geary.HTML.smart_escape("some text")); + assert_string("<some text", Geary.HTML.smart_escape("")); + } + + public void smart_escape_text_url() throws GLib.Error { + assert_string( + "<http://example.com>", + Geary.HTML.smart_escape("") + ); + assert_string( + "<http://example.com>", + Geary.HTML.smart_escape("") + ); + } + + public void remove_html_tags() throws Error { string blockquote_body = """
hello

there

"""; string style_complete = """ - - - False - True - 1 - - - - diff -Nru geary-0.12.4/ui/accounts_editor_add_pane.ui geary-3.32.0/ui/accounts_editor_add_pane.ui --- geary-0.12.4/ui/accounts_editor_add_pane.ui 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/ui/accounts_editor_add_pane.ui 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,234 @@ + + + + + + True + False + Add an account + False + + + True + False + + + True + True + True + + + + True + False + True + go-previous-symbolic + + + + + 0 + 0 + + + + + + + True + False + 12 + + + True + False + + + 0 + 0 + + + + + Create + True + False + True + True + + + + + 1 + 0 + + + + + end + 1 + + + + + 100 + 1 + 10 + + + diff -Nru geary-0.12.4/ui/accounts_editor_edit_pane.ui geary-3.32.0/ui/accounts_editor_edit_pane.ui --- geary-0.12.4/ui/accounts_editor_edit_pane.ui 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/ui/accounts_editor_edit_pane.ui 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,302 @@ + + + + + + True + False + Edit Account + Account Name + False + True + + + True + False + + + True + True + True + + + + True + False + True + go-previous-symbolic + + + + + 0 + 0 + + + + + + + True + False + + + True + True + True + win.undo + + + True + False + True + edit-undo-symbolic + + + + + 0 + 0 + + + + + end + 1 + + + + + 100 + 1 + 10 + + + diff -Nru geary-0.12.4/ui/accounts_editor_list_pane.ui geary-3.32.0/ui/accounts_editor_list_pane.ui --- geary-0.12.4/ui/accounts_editor_list_pane.ui 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/ui/accounts_editor_list_pane.ui 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,186 @@ + + + + + + True + False + Accounts + False + True + + + 100 + 1 + 10 + + + diff -Nru geary-0.12.4/ui/accounts_editor_remove_pane.ui geary-3.32.0/ui/accounts_editor_remove_pane.ui --- geary-0.12.4/ui/accounts_editor_remove_pane.ui 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/ui/accounts_editor_remove_pane.ui 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,152 @@ + + + + + + + True + False + Remove account + Account name + True + + + True + False + + + True + True + True + + + + True + False + True + go-previous-symbolic + + + + + 0 + 0 + + + + + + diff -Nru geary-0.12.4/ui/accounts_editor_servers_pane.ui geary-3.32.0/ui/accounts_editor_servers_pane.ui --- geary-0.12.4/ui/accounts_editor_servers_pane.ui 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/ui/accounts_editor_servers_pane.ui 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,217 @@ + + + + + + True + False + Server Settings + Account Name + True + + + True + False + + + Cancel + True + True + True + + + + 0 + 0 + + + + + + + True + False + 12 + + + True + False + + + 0 + 0 + + + + + Apply + True + False + True + True + + + + + 1 + 0 + + + + + end + 1 + + + + + 100 + 1 + 10 + + + diff -Nru geary-0.12.4/ui/accounts_editor.ui geary-3.32.0/ui/accounts_editor.ui --- geary-0.12.4/ui/accounts_editor.ui 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/ui/accounts_editor.ui 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,64 @@ + + + + + + diff -Nru geary-0.12.4/ui/account_spinner.glade geary-3.32.0/ui/account_spinner.glade --- geary-0.12.4/ui/account_spinner.glade 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/ui/account_spinner.glade 1970-01-01 00:00:00.000000000 +0000 @@ -1,64 +0,0 @@ - - - - - True - False - vertical - 15 - - - True - False - - - - - - True - True - 0 - - - - - 45 - 45 - True - False - True - - - False - True - 1 - - - - - True - False - Please wait while Geary validates your account. - - - False - True - 2 - - - - - True - False - - - - - - True - True - 3 - - - - diff -Nru geary-0.12.4/ui/client-web-view.js geary-3.32.0/ui/client-web-view.js --- geary-0.12.4/ui/client-web-view.js 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/ui/client-web-view.js 2019-03-17 13:39:29.000000000 +0000 @@ -16,11 +16,30 @@ init: function() { this.allowRemoteImages = false; this.isLoaded = false; + this.undoEnabled = false; + this.redoEnabled = false; this.hasSelection = false; this.lastPreferredHeight = 0; let state = this; + // Set up an observer to keep track of modifications made to + // the document when editing. + let modifiedId = null; + this.bodyObserver = new MutationObserver(function(records) { + if (modifiedId == null) { + modifiedId = window.setTimeout(function() { + state.documentModified(); + state.checkCommandStack(); + modifiedId = null; + }, 1000); + } + }); + + document.addEventListener("DOMContentLoaded", function(e) { + state.loaded(); + }); + // Coalesce multiple calls to updatePreferredHeight using a // timeout to avoid the overhead of multiple JS messages sent // to the app and hence view multiple resizes being queued. @@ -30,17 +49,10 @@ clearTimeout(queueTimeout); } queueTimeout = setTimeout( - function() { state.updatePreferredHeight(); }, 10 + function() { state.updatePreferredHeight(); }, 100 ); }; - // Queues an update after the DOM has been initially loaded - // and had any changes made to it by derived classes. - document.addEventListener("DOMContentLoaded", function(e) { - state.loaded(); - queuePreferredHeightUpdate(); - }); - // Queues an update when the complete document is loaded. // // Note also that the delay introduced here by this last call @@ -57,22 +69,31 @@ document.addEventListener("load", function(e) { queuePreferredHeightUpdate(); }, true); // load does not bubble + + // Queues an update if the window changes size, e.g. if the + // user resized the window + window.addEventListener("resize", function(e) { + queuePreferredHeightUpdate(); + }, false); // load does not bubble + + // Queues an update when a transition has completed, e.g. if the + // user resized the window + window.addEventListener("transitionend", function(e) { + queuePreferredHeightUpdate(); + }, false); // load does not bubble }, getPreferredHeight: function() { - let html = window.document.documentElement; - let height = html.offsetHeight; - let computed = window.getComputedStyle(html); - let top = computed.getPropertyValue('margin-top'); - let bot = computed.getPropertyValue('margin-bottom'); - - return ( - height - + parseInt(top.substring(0, top.length - 2)) - + parseInt(bot.substring(0, bot.length - 2)) - ); + return window.document.body.scrollHeight; + }, + getHtml: function() { + return document.body.innerHTML; }, loaded: function() { this.isLoaded = true; + // Always fire a prefered height update first so that it will + // be vaguegly correct when notifying of the HTML load + // completing. + this.updatePreferredHeight(); window.webkit.messageHandlers.contentLoaded.postMessage(null); }, loadRemoteImages: function() { @@ -85,6 +106,29 @@ img.src = src; } }, + setEditable: function(enabled) { + if (!enabled) { + this.stopBodyObserver(); + } + document.body.contentEditable = enabled; + if (enabled) { + // Enable modification observation only after the document + // has been set editable as WebKit will alter some attrs + this.startBodyObserver(); + } + }, + startBodyObserver: function() { + let config = { + attributes: true, + childList: true, + characterData: true, + subtree: true + }; + this.bodyObserver.observe(document.body, config); + }, + stopBodyObserver: function() { + this.bodyObserver.disconnect(); + }, remoteImageLoadBlocked: function() { window.webkit.messageHandlers.remoteImageLoadBlocked.postMessage(null); }, @@ -96,15 +140,33 @@ height = this.getPreferredHeight(); } - let updated = false; - if (height > 0 && height != this.lastPreferredHeight) { - updated = true; + // Don't send the message until after the DOM has been fully + // loaded and processed by any derived classes. Since + // ConversationPageState may collapse any quotes, sending the + // current preferred height before then may send a value that + // is too large, causing the message body view to grow then + // shrink again, leading to visual flicker. + if (this.isLoaded && height > 0 && height != this.lastPreferredHeight) { this.lastPreferredHeight = height; window.webkit.messageHandlers.preferredHeightChanged.postMessage( height ); } - return updated; + }, + checkCommandStack: function() { + let canUndo = document.queryCommandEnabled("undo"); + let canRedo = document.queryCommandEnabled("redo"); + + if (canUndo != this.undoEnabled || canRedo != this.redoEnabled) { + this.undoEnabled = canUndo; + this.redoEnabled = canRedo; + window.webkit.messageHandlers.commandStackChanged.postMessage( + this.undoEnabled + "," + this.redoEnabled + ); + } + }, + documentModified: function(element) { + window.webkit.messageHandlers.documentModified.postMessage(null); }, selectionChanged: function() { let hasSelection = !window.getSelection().isCollapsed; diff -Nru geary-0.12.4/ui/CMakeLists.txt geary-3.32.0/ui/CMakeLists.txt --- geary-0.12.4/ui/CMakeLists.txt 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/ui/CMakeLists.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,68 +0,0 @@ - -set(RESOURCE_LIST - STRIPBLANKS "accelerators.ui" - STRIPBLANKS "account_cannot_remove.glade" - STRIPBLANKS "account_list.glade" - STRIPBLANKS "account_spinner.glade" - STRIPBLANKS "certificate_warning_dialog.glade" - "client-web-view.js" - "client-web-view-allow-remote-images.js" - STRIPBLANKS "composer-headerbar.ui" - STRIPBLANKS "composer-link-popover.ui" - STRIPBLANKS "composer-menus.ui" - STRIPBLANKS "composer-widget.ui" - "composer-web-view.css" - "composer-web-view.js" - STRIPBLANKS "conversation-email.ui" - STRIPBLANKS "conversation-email-attachment-view.ui" - STRIPBLANKS "conversation-email-menus.ui" - STRIPBLANKS "conversation-message.ui" - STRIPBLANKS "conversation-message-menus.ui" - STRIPBLANKS "conversation-viewer.ui" - "conversation-web-view.css" - "conversation-web-view.js" - STRIPBLANKS "edit_alternate_emails.glade" - STRIPBLANKS "empty-placeholder.ui" - STRIPBLANKS "find_bar.glade" - STRIPBLANKS "folder-popover.ui" - STRIPBLANKS "gtk/help-overlay.ui" - STRIPBLANKS "gtk/menus.ui" - STRIPBLANKS "login.glade" - STRIPBLANKS "main-toolbar.ui" - STRIPBLANKS "main-window.ui" - STRIPBLANKS "password-dialog.glade" - STRIPBLANKS "preferences-dialog.ui" - STRIPBLANKS "remove_confirm.glade" - STRIPBLANKS "toolbar_empty_menu.ui" - STRIPBLANKS "toolbar_mark_menu.ui" - STRIPBLANKS "upgrade_dialog.glade" - "geary.css" -) - -compile_gresources( - RESOURCES_C - RESOURCES_XML - TYPE EMBED_C - SOURCE_DIR "${CMAKE_SOURCE_DIR}/ui" - PREFIX "/org/gnome/Geary" - RESOURCES ${RESOURCE_LIST} -) - -add_custom_target(resource_c DEPENDS ${RESOURCES_C}) - -# Work around valac wanting the resource files to be in the same -# directory as the XML file. -add_custom_target(resource_copy ALL - mkdir -p ${CMAKE_BINARY_DIR}/ui - COMMAND - cp ${RESOURCES_XML} ${CMAKE_BINARY_DIR}/ui - COMMAND - cp ${CMAKE_SOURCE_DIR}/ui/*.glade ${CMAKE_SOURCE_DIR}/ui/*.ui ${CMAKE_BINARY_DIR}/ui - COMMAND - cp ${CMAKE_SOURCE_DIR}/ui/*.css ${CMAKE_BINARY_DIR}/ui -) -add_dependencies(resource_copy resource_c) - -# Export the file names so they can be used in the source build -set(RESOURCES_C ${RESOURCES_C} PARENT_SCOPE) -set(RESOURCES_XML "${CMAKE_BINARY_DIR}/ui/.gresource.xml" PARENT_SCOPE) diff -Nru geary-0.12.4/ui/components-placeholder-pane.ui geary-3.32.0/ui/components-placeholder-pane.ui --- geary-0.12.4/ui/components-placeholder-pane.ui 1970-01-01 00:00:00.000000000 +0000 +++ geary-3.32.0/ui/components-placeholder-pane.ui 2019-03-17 13:39:29.000000000 +0000 @@ -0,0 +1,59 @@ + + + + + + diff -Nru geary-0.12.4/ui/composer-headerbar.ui geary-3.32.0/ui/composer-headerbar.ui --- geary-0.12.4/ui/composer-headerbar.ui 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/ui/composer-headerbar.ui 2019-03-17 13:39:29.000000000 +0000 @@ -1,15 +1,9 @@ - - - True - False - mail-send-symbolic - + diff -Nru geary-0.12.4/ui/composer-link-popover.ui geary-3.32.0/ui/composer-link-popover.ui --- geary-0.12.4/ui/composer-link-popover.ui 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/ui/composer-link-popover.ui 2019-03-17 13:39:29.000000000 +0000 @@ -23,7 +23,7 @@ 40 False False - http:// + https:// url diff -Nru geary-0.12.4/ui/composer-menus.ui geary-3.32.0/ui/composer-menus.ui --- geary-0.12.4/ui/composer-menus.ui 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/ui/composer-menus.ui 2019-03-17 13:39:29.000000000 +0000 @@ -5,53 +5,53 @@
S_ans Serif - cmp.font-family + win.font-family sans S_erif - cmp.font-family + win.font-family serif _Fixed Width - cmp.font-family + win.font-family monospace
_Small - cmp.font-size + win.font-size small _Medium - cmp.font-size + win.font-size medium Lar_ge - cmp.font-size + win.font-size large
C_olor - cmp.color + win.color
_Rich Text - cmp.compose-as-html + win.compose-as-html
Show Extended Fields - cmp.show-extended + win.show-extended
@@ -60,13 +60,13 @@
_Rich Text - cmp.compose-as-html + win.compose-as-html
Show Extended Fields - cmp.show-extended + win.show-extended
@@ -76,56 +76,56 @@
_Undo - cmp.undo + win.undo _Redo - cmp.redo + win.redo
Cu_t - cmp.cut + win.cut _Copy - cmp.copy + win.copy _Paste - cmp.paste + win.paste - Paste _With Formatting - cmp.paste-with-formatting + Paste _Without Formatting + win.paste-without-formatting
Cu_t - cmp.cut + win.cut _Copy - cmp.copy + win.copy _Paste - cmp.paste + win.paste
Select _All - cmp.select-all + win.select-all
_Inspect… - cmp.open_inspector + win.open_inspector
diff -Nru geary-0.12.4/ui/composer-web-view.css geary-3.32.0/ui/composer-web-view.css --- geary-0.12.4/ui/composer-web-view.css 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/ui/composer-web-view.css 2019-03-17 13:39:29.000000000 +0000 @@ -24,6 +24,10 @@ cursor: text; } +body > *.geary-no-display { + display: none !important; +} + body > div#geary-body { margin: 0 !important; border: 0 !important; diff -Nru geary-0.12.4/ui/composer-web-view.js geary-3.32.0/ui/composer-web-view.js --- geary-0.12.4/ui/composer-web-view.js 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/ui/composer-web-view.js 2019-03-17 13:39:29.000000000 +0000 @@ -13,8 +13,6 @@ this.init.apply(this, arguments); }; ComposerPageState.KEYWORD_SPLIT_REGEX = /[\s]+/g; -ComposerPageState.QUOTE_START = "\x91"; // private use one -ComposerPageState.QUOTE_END = "\x92"; // private use two ComposerPageState.QUOTE_MARKER = "\x7f"; // delete ComposerPageState.PROTOCOL_REGEX = /^(aim|apt|bitcoin|cvs|ed2k|ftp|file|finger|git|gtalk|http|https|irc|ircs|irc6|lastfm|ldap|ldaps|magnet|news|nntp|rsync|sftp|skype|smb|sms|svn|telnet|tftp|ssh|webcal|xmpp):/i; // Taken from Geary.HTML.URL_REGEX, without the inline modifier (?x) @@ -30,8 +28,6 @@ this.quotePart = null; this.focusedPart = null; - this.undoEnabled = false; - this.redoEnabled = false; this.selections = new Map(); this.nextSelectionId = 0; this.cursorContext = null; @@ -43,17 +39,6 @@ e.preventDefault(); } }, true); - - let modifiedId = null; - this.bodyObserver = new MutationObserver(function() { - if (modifiedId == null) { - modifiedId = window.setTimeout(function() { - state.documentModified(); - state.checkCommandStack(); - modifiedId = null; - }, 1000); - } - }); }, loaded: function() { let state = this; @@ -138,8 +123,17 @@ cursor.parentNode.removeChild(cursor); } - // Enable editing and observation machinery only after - // modifying the body above. + + // Enable editing only after modifying the body above. + this.setEditable(true); + + PageState.prototype.loaded.apply(this, []); + }, + setEditable: function(enabled) { + if (!enabled) { + this.stopBodyObserver(); + } + this.bodyPart.contentEditable = true; if (this.signaturePart != null) { this.signaturePart.contentEditable = true; @@ -147,16 +141,12 @@ if (this.quotePart != null) { this.quotePart.contentEditable = true; } - let config = { - attributes: true, - childList: true, - characterData: true, - subtree: true - }; - this.bodyObserver.observe(document.body, config); - // Chain up - PageState.prototype.loaded.apply(this, []); + if (enabled) { + // Enable modification observation only after the document + // has been set editable as WebKit will alter some attrs + this.startBodyObserver(); + } }, undo: function() { document.execCommand("undo", false, null); @@ -212,10 +202,21 @@ element.setAttribute("type", "cite"); } }, + insertOrderedList: function() { + document.execCommand("insertOrderedList", false, null); + }, + insertUnorderedList: function() { + document.execCommand("insertUnorderedList", false, null); + }, updateSignature: function(signature) { if (this.signaturePart != null) { - console.log(signature); - this.signaturePart.innerHTML = signature; + if (signature.trim()) { + this.signaturePart.innerHTML = signature; + this.signaturePart.classList.remove("geary-no-display"); + } else { + this.signaturePart.innerHTML = ""; + this.signaturePart.classList.add("geary-no-display"); + } } }, deleteQuotedMessage: function() { @@ -299,6 +300,10 @@ } }, cleanContent: function() { + // Prevent any modification signals being sent when mutating + // the document below. + this.stopBodyObserver(); + ComposerPageState.cleanPart(this.bodyPart, false); ComposerPageState.linkify(this.bodyPart); @@ -317,20 +322,25 @@ if (this.signaturePart != null) { parent.appendChild( - ComposerPageState.cleanPart(this.signaturePart.cloneNode(true), false) + ComposerPageState.cleanPart( + this.signaturePart.cloneNode(true), false + ) ); } if (this.quotePart != null) { parent.appendChild( - ComposerPageState.cleanPart(this.quotePart.cloneNode(true), false) + ComposerPageState.cleanPart( + this.quotePart.cloneNode(true), false + ) ); } return parent.innerHTML; }, getText: function() { - return ComposerPageState.htmlToQuotedText(document.body); + let text = ComposerPageState.htmlToText(document.body); + return ComposerPageState.replaceNonBreakingSpace(text); }, setRichText: function(enabled) { if (enabled) { @@ -339,21 +349,6 @@ document.body.classList.add("plain"); } }, - checkCommandStack: function() { - let canUndo = document.queryCommandEnabled("undo"); - let canRedo = document.queryCommandEnabled("redo"); - - if (canUndo != this.undoEnabled || canRedo != this.redoEnabled) { - this.undoEnabled = canUndo; - this.redoEnabled = canRedo; - window.webkit.messageHandlers.commandStackChanged.postMessage( - this.undoEnabled + "," + this.redoEnabled - ); - } - }, - documentModified: function(element) { - window.webkit.messageHandlers.documentModified.postMessage(null); - }, selectionChanged: function() { PageState.prototype.selectionChanged.apply(this, []); @@ -456,66 +451,122 @@ }; /** - * Convert a HTML DOM tree to plain text with delineated quotes. + * Gets plain text that adequately represents the information in the HTML * - * Lines are delinated using LF. Quoted lines are prefixed with - * `ComposerPageState.QUOTE_MARKER`, where the number of markers - * indicates the depth of nesting of the quote. + * Asterisks are inserted around bold text, slashes around italic text, and + * underscores around underlined text. Link URLs are inserted after the link + * text. * - * This will modify/reset the DOM, since it ultimately requires - * stuffing `QUOTE_MARKER` into existing paragraphs and getting it - * back out in a way that preserves the visual presentation. + * Each line of a blockquote is prefixed with + * `ComposerPageState.QUOTE_MARKER`, where the number of markers indicates + * the depth of nesting of the quote. */ -ComposerPageState.htmlToQuotedText = function(root) { - // XXX It would be nice to just clone the root and modify that, or - // see if we can implement this some other way so as to not modify - // the DOM at all, but currently unit test show that the results - // are not the same if we work on a clone, likely because of the - // use of HTMLElement::innerText. Need to look into it more. - - let savedDoc = root.innerHTML; - let blockquotes = root.querySelectorAll("blockquote"); - let nbq = blockquotes.length; - let bqtexts = new Array(nbq); - - // Get text of blockquotes and pull them out of DOM. They are - // replaced with tokens deliminated with the characters - // QUOTE_START and QUOTE_END (from a unicode private use block). - // We need to get the text while they're still in the DOM to get - // newlines at appropriate places. We go through the list of - // blockquotes from the end so that we get the innermost ones - // first. - for (let i = nbq - 1; i >= 0; i--) { - let bq = blockquotes.item(i); - let text = bq.innerText; - if (text.substr(-1, 1) == "\n") { - text = text.slice(0, -1); - } else { - console.debug( - " no newline at end of quote: " + - text.length > 0 - ? "0x" + text.codePointAt(text.length - 1).toString(16) - : "empty line" - ); - } - bqtexts[i] = text; - - bq.innerText = ( - ComposerPageState.QUOTE_START - + i.toString() - + ComposerPageState.QUOTE_END +ComposerPageState.htmlToText = function(root) { + let parentStyle = window.getComputedStyle(root); + let text = ""; + + for (let node of (root.childNodes || [])) { + let isBlock = ( + node instanceof Element + && window.getComputedStyle(node).display == "block" + && node.innerText ); + if (isBlock) { + // Make sure there's a newline before the element + if (text != "" && text.substr(-1) != "\n") { + text += "\n"; + } + } + switch (node.nodeName.toLowerCase()) { + case "#text": + let nodeText = node.nodeValue; + switch (parentStyle.whiteSpace) { + case 'normal': + case 'nowrap': + case 'pre-line': + // Only space, tab, carriage return, and newline collapse + // https://www.w3.org/TR/2011/REC-CSS2-20110607/text.html#white-space-model + nodeText = nodeText.replace(/[ \t\r\n]+/g, " "); + if (nodeText == " " && " \t\r\n".includes(text.substr(-1))) + break; // There's already whitespace here + if (node == root.firstChild) + nodeText = nodeText.replace(/^ /, ""); + if (node == root.lastChild) + nodeText = nodeText.replace(/ $/, ""); + // Fall through + default: + text += nodeText; + break; + } + break; + case "a": + if (node.closest("body.plain")) { + text += ComposerPageState.htmlToText(node); + } else if (node.textContent == node.href) { + text += "<" + node.href + ">"; + } else { + text += ComposerPageState.htmlToText(node); + text += " <" + node.href + ">"; + } + break; + case "b": + case "strong": + if (node.closest("body.plain")) { + text += ComposerPageState.htmlToText(node); + } else { + text += "*" + ComposerPageState.htmlToText(node) + "*"; + } + break; + case "blockquote": + let bqText = ComposerPageState.htmlToText(node); + // If there is a newline at the end of the quote, remove it + // After this switch we ensure that there is a newline after the quote + bqText = bqText.replace(/\n$/, ""); + let lines = bqText.split("\n"); + for (let i = 0; i < lines.length; i++) + lines[i] = ComposerPageState.QUOTE_MARKER + lines[i]; + text += lines.join("\n"); + break; + case "br": + text += "\n"; + break; + case "i": + case "em": + if (node.closest("body.plain")) { + text += ComposerPageState.htmlToText(node); + } else { + text += "/" + ComposerPageState.htmlToText(node) + "/"; + } + break; + case "u": + if (node.closest("body.plain")) { + text += ComposerPageState.htmlToText(node); + } else { + text += "_" + ComposerPageState.htmlToText(node) + "_"; + } + break; + case "#comment": + break; + default: + text += ComposerPageState.htmlToText(node); + break; + } + if (isBlock) { + // Ensure that the last character is a newline + if (text.substr(-1) != "\n") { + text += "\n"; + } + if (node.nodeName.toLowerCase() == "p") { + // Ensure that the last two characters are newlines + if (text.substr(-2, 1) != "\n") { + text += "\n"; + } + } + } } - // Reassemble plain text out of parts, and replace non-breaking - // space with regular space. - let text = ComposerPageState.resolveNesting(root.innerText, bqtexts); - - // Reassemble DOM now we have the plain text - root.innerHTML = savedDoc; - - return ComposerPageState.replaceNonBreakingSpace(text); -}; + return text; +} // Linkifies "plain text" link ComposerPageState.linkify = function(node) { @@ -565,46 +616,6 @@ } }; -ComposerPageState.resolveNesting = function(text, values) { - let tokenregex = new RegExp( - "(.?)" + - ComposerPageState.QUOTE_START + - "([0-9]*)" + - ComposerPageState.QUOTE_END + - "(?=(.?))", "g" - ); - return text.replace(tokenregex, function(match, p1, p2, p3, offset, str) { - let key = new Number(p2); - let prevChars = p1; - let nextChars = p3; - let insertNext = ""; - // Make sure there's a newline before and after the quote. - if (prevChars != "" && prevChars != "\n") - prevChars = prevChars + "\n"; - if (nextChars != "" && nextChars != "\n") - insertNext = "\n"; - - let value = ""; - if (key >= 0 && key < values.length) { - let nested = ComposerPageState.resolveNesting(values[key], values); - value = prevChars + ComposerPageState.quoteLines(nested) + insertNext; - } else { - console.error("Regex error in denesting blockquotes: Invalid key"); - } - return value; - }); -}; - -/** - * Prefixes each NL-delineated line with `ComposerPageState.QUOTE_MARKER`. - */ -ComposerPageState.quoteLines = function(text) { - let lines = text.split("\n"); - for (let i = 0; i < lines.length; i++) - lines[i] = ComposerPageState.QUOTE_MARKER + lines[i]; - return lines.join("\n"); -}; - /** * Converts all non-breaking space chars to plain spaces. */ diff -Nru geary-0.12.4/ui/composer-widget.ui geary-3.32.0/ui/composer-widget.ui --- geary-0.12.4/ui/composer-widget.ui 2018-08-29 13:57:20.000000000 +0000 +++ geary-3.32.0/ui/composer-widget.ui 2019-03-17 13:39:29.000000000 +0000 @@ -1,13 +1,14 @@ - + - +