Commit b10ffc99 authored by Martin's avatar Martin

Imported upstream master from 2017-10-10

parent 67973b5a
......@@ -12,12 +12,16 @@ data/org.gajim.Gajim.appdata.xml
data/org.gajim.Gajim.desktop
libtool
po/.intltool-merge-cache
data/gajim-remote.desktop
po/gajim.pot
po/POTFILES
po/stamp-it
stamp-h1
Makefile
__pycache__/
build/
dist/
gajim.egg-info/
src/gtk/
win/_build_root
......
......@@ -14,10 +14,10 @@
### Optional Runtime Requirements
- python-pillow for support of webp avatars
- python3-crypto to enable End to end encryption
- python3-gnupg to enable GPG encryption
- For zeroconf (bonjour), the "enable link-local messaging" checkbox, you need dbus-glib, python-avahi
- dnsutils (or whatever package provides the nslookup binary) for SRV support
- For zeroconf (bonjour) you need dbus-glib, python-avahi
- gir1.2-gtkspell3-3.0 and aspell-LANG where lang is your locale eg. en, fr etc
- gir1.2-secret-1 for GNOME Keyring or KDE support as password storage
- D-Bus running to have gajim-remote working. Some distributions split dbus-x11, which is needed for dbus to work with Gajim. Version >= 0.80 is required.
......@@ -34,7 +34,17 @@
### Installation Procedure
##### Linux / Mac
#### Packages
- [Arch](https://aur.archlinux.org/packages/gajim-git/)
- [Debian](https://packages.debian.org/source/experimental/gajim) (tested with Debian ``testing`` and ``unstable``)
#### Snapshots
- [Daily Linux](https://www.gajim.org/downloads/snap/?M=D)
- [Daily Windows](https://gajim.org/downloads/snap/win)
#### Linux / Mac
``./setup.py install --root=/``
......@@ -42,23 +52,29 @@ or
``pip install .`` (python-pip is required)
##### Windows
#### Developing
see [README](./win/README.md)
For developing you dont have to install Gajim.
### Miscellaneous
After installing all dependencys execute
##### Snapshots
``./launch.py``
- [Daily Linux](https://www.gajim.org/downloads/snap/?M=D)
- [Daily Windows](https://gajim.org/downloads/snap/win)
#### Flatpak
see [README](./flatpak/README.md)
#### Windows
see [README](./win/README.md)
### Miscellaneous
##### Debugging
#### Debugging
Execute gajim with --verbose
##### Links
#### Links
- [FAQ](https://dev.gajim.org/gajim/gajim/wikis/help/gajimfaq)
- [Wiki](https://dev.gajim.org/gajim/gajim/wikis/home)
......
From 8dbe0dc3eea5c689d4f76b37b93fe216cf1f00d4 Mon Sep 17 00:00:00 2001
From: Legrandin <helderijs@gmail.com>
Date: Sun, 22 Dec 2013 22:24:46 +0100
Subject: [PATCH] Throw exception when IV is used with ECB or CTR
The IV parameter is currently ignored when initializing
a cipher in ECB or CTR mode.
For CTR mode, it is confusing: it takes some time to see
that a different parameter is needed (the counter).
For ECB mode, it is outright dangerous.
This patch forces an exception to be raised.
---
lib/Crypto/SelfTest/Cipher/common.py | 31 +++++++++++++++++++++++--------
src/block_template.c | 11 +++++++++++
2 files changed, 34 insertions(+), 8 deletions(-)
diff --git a/lib/Crypto/SelfTest/Cipher/common.py b/lib/Crypto/SelfTest/Cipher/common.py
index 420b6ff..a5f8a88 100644
--- a/lib/Crypto/SelfTest/Cipher/common.py
+++ b/lib/Crypto/SelfTest/Cipher/common.py
@@ -239,19 +239,34 @@ def shortDescription(self):
return """%s .decrypt() output of .encrypt() should not be garbled""" % (self.module_name,)
def runTest(self):
- for mode in (self.module.MODE_ECB, self.module.MODE_CBC, self.module.MODE_CFB, self.module.MODE_OFB, self.module.MODE_OPENPGP):
+
+ ## ECB mode
+ mode = self.module.MODE_ECB
+ encryption_cipher = self.module.new(a2b_hex(self.key), mode)
+ ciphertext = encryption_cipher.encrypt(self.plaintext)
+ decryption_cipher = self.module.new(a2b_hex(self.key), mode)
+ decrypted_plaintext = decryption_cipher.decrypt(ciphertext)
+ self.assertEqual(self.plaintext, decrypted_plaintext)
+
+ ## OPENPGP mode
+ mode = self.module.MODE_OPENPGP
+ encryption_cipher = self.module.new(a2b_hex(self.key), mode, self.iv)
+ eiv_ciphertext = encryption_cipher.encrypt(self.plaintext)
+ eiv = eiv_ciphertext[:self.module.block_size+2]
+ ciphertext = eiv_ciphertext[self.module.block_size+2:]
+ decryption_cipher = self.module.new(a2b_hex(self.key), mode, eiv)
+ decrypted_plaintext = decryption_cipher.decrypt(ciphertext)
+ self.assertEqual(self.plaintext, decrypted_plaintext)
+
+ ## All other non-AEAD modes (but CTR)
+ for mode in (self.module.MODE_CBC, self.module.MODE_CFB, self.module.MODE_OFB):
encryption_cipher = self.module.new(a2b_hex(self.key), mode, self.iv)
ciphertext = encryption_cipher.encrypt(self.plaintext)
-
- if mode != self.module.MODE_OPENPGP:
- decryption_cipher = self.module.new(a2b_hex(self.key), mode, self.iv)
- else:
- eiv = ciphertext[:self.module.block_size+2]
- ciphertext = ciphertext[self.module.block_size+2:]
- decryption_cipher = self.module.new(a2b_hex(self.key), mode, eiv)
+ decryption_cipher = self.module.new(a2b_hex(self.key), mode, self.iv)
decrypted_plaintext = decryption_cipher.decrypt(ciphertext)
self.assertEqual(self.plaintext, decrypted_plaintext)
+
class PGPTest(unittest.TestCase):
def __init__(self, module, params):
unittest.TestCase.__init__(self)
diff --git a/src/block_template.c b/src/block_template.c
index f940e0e..d555ceb 100644
--- a/src/block_template.c
+++ b/src/block_template.c
@@ -170,6 +170,17 @@ ALGnew(PyObject *self, PyObject *args, PyObject *kwdict)
"Key cannot be the null string");
return NULL;
}
+ if (IVlen != 0 && mode == MODE_ECB)
+ {
+ PyErr_Format(PyExc_ValueError, "ECB mode does not use IV");
+ return NULL;
+ }
+ if (IVlen != 0 && mode == MODE_CTR)
+ {
+ PyErr_Format(PyExc_ValueError,
+ "CTR mode needs counter parameter, not IV");
+ return NULL;
+ }
if (IVlen != BLOCK_SIZE && mode != MODE_ECB && mode != MODE_CTR)
{
PyErr_Format(PyExc_ValueError,
Run the following steps from a directory containing the gajim source dir.
Install gajim flatpak repo
--------------------------
1. `flatpak --user remote-add --from gnome https://sdk.gnome.org/gnome.flatpakrepo`
1. `flatpak --user install gnome org.gnome.Platform//3.24`
1. `flatpak --user install gnome org.gnome.Sdk//3.24`
1. `flatpak-builder --repo=repo directory gajim/org.gajim.Gajim.json`
1. `flatpak --user remote-add --no-gpg-verify repo repo`
1. `flatpak --user install repo org.gajim.Gajim`
1. `flatpak run org.gajim.Gajim`
Update gajim flatpak repo
-------------------------
1. update your gajim source repository
1. `rm -r directory`
1. `flatpak-builder --repo=repo directory gajim/org.gajim.Gajim.json`
1. `flatpak --user update`
Note: remove `--user` if you want a system-wide installation
import subprocess
__version__ = "0.16.11"
__version__ = "0.16.11.1"
try:
node = subprocess.Popen('git rev-parse --short=12 HEAD', shell=True,
......
This diff is collapsed.
......@@ -34,8 +34,11 @@ import uuid
from distutils.version import LooseVersion as V
import gi
import nbxmpp
import hashlib
from gajim.common import config
from gi.repository import GLib
from gajim.common import config as c_config
from gajim.common import configpaths
from gajim.common import ged as ged_module
from gajim.common.contacts import LegacyContactsAPI
......@@ -43,9 +46,10 @@ from gajim.common.events import Events
interface = None # The actual interface (the gtk one for the moment)
thread_interface = lambda *args: None # Interface to run a thread and then a callback
config = config.Config()
config = c_config.Config()
version = config.get('version')
connections = {} # 'account name': 'account (connection.Connection) instance'
avatar_cache = {}
ipython_window = None
app = None # Gtk.Application
......@@ -260,7 +264,7 @@ gajim_common_features = [nbxmpp.NS_BYTESTREAM, nbxmpp.NS_SI, nbxmpp.NS_FILE,
nbxmpp.NS_ROSTERX, nbxmpp.NS_SECLABEL, nbxmpp.NS_HASHES_2,
nbxmpp.NS_HASHES_MD5, nbxmpp.NS_HASHES_SHA1, nbxmpp.NS_HASHES_SHA256,
nbxmpp.NS_HASHES_SHA512, nbxmpp.NS_CONFERENCE, nbxmpp.NS_CORRECT,
nbxmpp.NS_EME]
nbxmpp.NS_EME, 'urn:xmpp:avatar:metadata+notify']
# Optional features gajim supports per account
gajim_optional_features = {}
......
This diff is collapsed.
......@@ -116,6 +116,7 @@ def create_cache_db():
name TEXT,
subscription INTEGER,
ask BOOLEAN,
avatar_sha TEXT,
PRIMARY KEY (account_jid_id, jid_id)
);
......
......@@ -50,7 +50,7 @@ class Option(IntEnum):
opt_int = [ 'integer', 0 ]
opt_str = [ 'string', 0 ]
opt_bool = [ 'boolean', 0 ]
opt_color = [ 'color', '^(#[0-9a-fA-F]{6})|()$' ]
opt_color = [ 'color', '^(#[0-9a-fA-F]{6})|()|rgb\(\d+,\d+,\d+\)|rgba\(\d+,\d+,\d+,[01]\.?\d*\)$' ]
opt_one_window_types = ['never', 'always', 'always_with_roster', 'peracct', 'pertype']
opt_show_roster_on_startup = ['always', 'never', 'last_state']
opt_treat_incoming_messages = ['', 'chat', 'normal']
......@@ -220,12 +220,6 @@ class Config:
'tabs_border': [opt_bool, False, _('Show tabbed notebook border in chat windows?')],
'tabs_close_button': [opt_bool, True, _('Show close button in tab?')],
'esession_modp': [opt_str, '15,16,14', _('A list of modp groups to use in a Diffie-Hellman, highest preference first, separated by commas. Valid groups are 1, 2, 5, 14, 15, 16, 17 and 18. Higher numbers are more secure, but take longer to calculate when you start a session.')],
'chat_avatar_width': [opt_int, 52],
'chat_avatar_height': [opt_int, 52],
'roster_avatar_width': [opt_int, 32],
'roster_avatar_height': [opt_int, 32],
'tooltip_avatar_width': [opt_int, 125],
'tooltip_avatar_height': [opt_int, 125],
'tooltip_status_online_color': [opt_color, '#73D216'],
'tooltip_status_free_for_chat_color': [opt_color, '#3465A4'],
'tooltip_status_away_color': [opt_color, '#EDD400'],
......@@ -238,13 +232,9 @@ class Config:
'tooltip_affiliation_owner_color': [opt_color, '#CC0000'],
'tooltip_account_name_color': [opt_color, '#888A85'],
'tooltip_idle_color': [opt_color, '#888A85'],
'vcard_avatar_width': [opt_int, 200],
'vcard_avatar_height': [opt_int, 200],
'notification_preview_message': [opt_bool, True, _('Preview new messages in notification popup?')],
'notification_position_x': [opt_int, -1],
'notification_position_y': [opt_int, -1],
'notification_avatar_width': [opt_int, 48],
'notification_avatar_height': [opt_int, 48],
'muc_highlight_words': [opt_str, '', _('A semicolon-separated list of words that will be highlighted in group chats.')],
'quit_on_roster_x_button': [opt_bool, False, _('If True, quits Gajim when X button of Window Manager is clicked. This setting is taken into account only if notification icon is used.')],
'show_unread_tab_icon': [opt_bool, False, _('If True, Gajim will display an icon on each tab containing unread messages. Depending on the theme, this icon may be animated.')],
......@@ -255,7 +245,6 @@ class Config:
'show_tunes_in_roster': [opt_bool, True, '', True],
'show_location_in_roster': [opt_bool, True, '', True],
'avatar_position_in_roster': [opt_str, 'right', _('Define the position of the avatar in roster. Can be left or right'), True],
'ask_avatars_on_startup': [opt_bool, True, _('If True, Gajim will ask for avatar each contact that did not have an avatar last time or has one cached that is too old.')],
'print_status_in_chats': [opt_bool, False, _('If False, Gajim will no longer print status line in chats when a contact changes his or her status and/or his or her status message.')],
'print_status_in_muc': [opt_str, 'none', _('Can be "none", "all" or "in_and_out". If "none", Gajim will no longer print status line in groupchats when a member changes his or her status and/or his or her status message. If "all" Gajim will print all status messages. If "in_and_out", Gajim will only print FOO enters/leaves group chat.')],
'log_contact_status_changes': [opt_bool, False],
......@@ -325,6 +314,7 @@ class Config:
'account_label': [ opt_str, '', '', False ],
'hostname': [ opt_str, '', '', True ],
'anonymous_auth': [ opt_bool, False ],
'avatar_sha': [opt_str, '', '', False],
'client_cert': [ opt_str, '', '', True ],
'client_cert_encrypted': [ opt_bool, False, '', False ],
'savepass': [ opt_bool, False ],
......
......@@ -779,6 +779,7 @@ class Connection(CommonConnection, ConnectionHandlers):
self.connected = 0
self.time_to_reconnect = None
self.privacy_rules_supported = False
self.avatar_presence_sent = False
if on_purpose:
self.sm = Smacks(self)
if self.connection:
......@@ -1778,7 +1779,7 @@ class Connection(CommonConnection, ConnectionHandlers):
show='invisible'))
if initial:
# ask our VCard
self.request_vcard(None)
self.request_vcard(self._on_own_avatar_received)
# Get bookmarks from private namespace
self.get_bookmarks()
......
This diff is collapsed.
......@@ -24,6 +24,7 @@
from calendar import timegm
import datetime
import hashlib
import base64
import hmac
import logging
import sys
......@@ -324,6 +325,7 @@ class RosterReceivedEvent(nec.NetworkIncomingEvent):
self.conn.connection.getRoster().delItem(jid)
elif jid != our_jid: # don't add our jid
self.roster[j] = raw_roster[jid]
self.roster[j]['avatar_sha'] = None
else:
# Roster comes from DB
self.received_from_server = False
......@@ -376,6 +378,9 @@ class RosterInfoEvent(nec.NetworkIncomingEvent):
name = 'roster-info'
base_network_events = []
def init(self):
self.avatar_sha = None
class MucOwnerReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
name = 'muc-owner-received'
base_network_events = []
......@@ -532,19 +537,13 @@ class PubsubReceivedEvent(nec.NetworkIncomingEvent):
base_network_events = []
def generate(self):
self.jid = self.stanza.getFrom()
self.pubsub_node = self.stanza.getTag('pubsub')
if not self.pubsub_node:
return
self.items_node = self.pubsub_node.getTag('items')
if not self.items_node:
return
self.item_node = self.items_node.getTag('item')
if not self.item_node:
return
children = self.item_node.getChildren()
if not children:
return
self.node = children[0]
return True
class PubsubBookmarksReceivedEvent(nec.NetworkIncomingEvent, BookmarksHelper):
......@@ -553,13 +552,57 @@ class PubsubBookmarksReceivedEvent(nec.NetworkIncomingEvent, BookmarksHelper):
def generate(self):
self.conn = self.base_event.conn
self.storage_node = self.base_event.node
self.item_node = self.base_event.items_node.getTag('item')
if not self.item_node:
return
children = self.item_node.getChildren()
if not children:
return
self.storage_node = children[0]
ns = self.storage_node.getNamespace()
if ns != nbxmpp.NS_BOOKMARKS:
return
self.parse_bookmarks()
return True
class PubsubAvatarReceivedEvent(nec.NetworkIncomingEvent):
name = 'pubsub-avatar-received'
base_network_events = ['pubsub-received']
def __init__(self, name, base_event):
'''
Pre-Generated attributes on self:
:conn: Connection instance
:jid: The from jid
:pubsub_node: The 'pubsub' node
:items_node: The 'items' node
'''
self._set_base_event_vars_as_attributes(base_event)
def generate(self):
if self.items_node.getAttr('node') != 'urn:xmpp:avatar:data':
return
item = self.items_node.getTag('item')
if not item:
log.warning('Received malformed avatar data via pubsub')
log.warning(self.stanza)
return
self.sha = item.getAttr('id')
data_tag = item.getTag('data', namespace='urn:xmpp:avatar:data')
if self.sha is None or data_tag is None:
log.warning('Received malformed avatar data via pubsub')
log.warning(self.stanza)
return
self.data = data_tag.getData()
if self.data is None:
log.warning('Received malformed avatar data via pubsub')
log.warning(self.stanza)
return
self.data = base64.b64decode(self.data.encode('utf-8'))
return True
class SearchFormReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
name = 'search-form-received'
base_network_events = []
......@@ -874,10 +917,8 @@ class GcPresenceReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
contact_name=fjid.getResource(),
message=st,
show=show)
if self.avatar_sha == '':
# contact has no avatar
puny_nick = helpers.sanitize_filename(self.nick)
app.interface.remove_avatar_files(self.room_jid, puny_nick)
# NOTE: if it's a gc presence, don't ask vcard here.
# We may ask it to real jid in gui part.
self.status_code = []
......@@ -2004,16 +2045,20 @@ class VcardReceivedEvent(nec.NetworkIncomingEvent):
base_network_events = []
def generate(self):
self.nickname = None
if 'NICKNAME' in self.vcard_dict:
self.nickname = self.vcard_dict['NICKNAME']
elif 'FN' in self.vcard_dict:
self.nickname = self.vcard_dict['FN']
self.jid = self.vcard_dict['jid']
self.resource = self.vcard_dict['resource']
self.fjid = self.jid
if self.resource:
self.fjid += '/' + self.resource
return True
class UpdateGCAvatarEvent(nec.NetworkIncomingEvent):
name = 'update-gc-avatar'
base_network_events = []
def generate(self):
return True
class UpdateRosterAvatarEvent(nec.NetworkIncomingEvent):
name = 'update-roster-avatar'
base_network_events = []
def generate(self):
return True
class PEPConfigReceivedEvent(nec.NetworkIncomingEvent):
......
......@@ -28,6 +28,14 @@ class OptionType(IntEnum):
ACTION = 3
DIALOG = 4
class AvatarSize(IntEnum):
ROSTER = 32
NOTIFICATION = 48
CHAT = 52
PROFILE = 64
TOOLTIP = 125
VCARD = 200
THANKS = u"""\
Alexander Futász
......
......@@ -94,7 +94,7 @@ class Contact(CommonContact):
"""
def __init__(self, jid, account, name='', groups=None, show='', status='',
sub='', ask='', resource='', priority=0, keyID='', client_caps=None,
our_chatstate=None, chatstate=None, idle_time=None):
our_chatstate=None, chatstate=None, idle_time=None, avatar_sha=None):
if not isinstance(jid, str):
print('no str')
if groups is None:
......@@ -105,6 +105,7 @@ class Contact(CommonContact):
self.contact_name = '' # nick choosen by contact
self.groups = [i if i else _('General') for i in set(groups)] # filter duplicate values
self.avatar_sha = avatar_sha
self.sub = sub
self.ask = ask
......@@ -182,7 +183,7 @@ class GC_Contact(CommonContact):
def __init__(self, room_jid, account, name='', show='', status='', role='',
affiliation='', jid='', resource='', our_chatstate=None,
chatstate=None):
chatstate=None, avatar_sha=None):
CommonContact.__init__(self, jid, account, resource, show, status, name,
our_chatstate, chatstate)
......@@ -190,6 +191,7 @@ class GC_Contact(CommonContact):
self.room_jid = room_jid
self.role = role
self.affiliation = affiliation
self.avatar_sha = avatar_sha
def get_full_jid(self):
return self.room_jid + '/' + self.name
......@@ -197,13 +199,16 @@ class GC_Contact(CommonContact):
def get_shown_name(self):
return self.name
def get_avatar(self, size=None):
return common.app.interface.get_avatar(self.avatar_sha, size)
def as_contact(self):
"""
Create a Contact instance from this GC_Contact instance
"""
return Contact(jid=self.get_full_jid(), account=self.account,
name=self.name, groups=[], show=self.show, status=self.status,
sub='none', client_caps=self.client_caps)
sub='none', client_caps=self.client_caps, avatar_sha=self.avatar_sha)
class LegacyContactsAPI:
......@@ -245,7 +250,8 @@ class LegacyContactsAPI:
def create_contact(self, jid, account, name='', groups=None, show='',
status='', sub='', ask='', resource='', priority=0, keyID='',
client_caps=None, our_chatstate=None, chatstate=None, idle_time=None):
client_caps=None, our_chatstate=None, chatstate=None, idle_time=None,
avatar_sha=None):
if groups is None:
groups = []
# Use Account object if available
......@@ -254,7 +260,7 @@ class LegacyContactsAPI:
show=show, status=status, sub=sub, ask=ask, resource=resource,
priority=priority, keyID=keyID, client_caps=client_caps,
our_chatstate=our_chatstate, chatstate=chatstate,
idle_time=idle_time)
idle_time=idle_time, avatar_sha=avatar_sha)
def create_self_contact(self, jid, account, resource, show, status, priority,
name='', keyID=''):
......@@ -283,7 +289,7 @@ class LegacyContactsAPI:
resource=contact.resource, priority=contact.priority,
keyID=contact.keyID, client_caps=contact.client_caps,
our_chatstate=contact.our_chatstate, chatstate=contact.chatstate,
idle_time=contact.idle_time)
idle_time=contact.idle_time, avatar_sha=contact.avatar_sha)
def add_contact(self, account, contact):
if account not in self._accounts:
......@@ -306,6 +312,15 @@ class LegacyContactsAPI:
def get_contact(self, account, jid, resource=None):
return self._accounts[account].contacts.get_contact(jid, resource=resource)
def get_avatar(self, account, jid, size=None):
return self._accounts[account].contacts.get_avatar(jid, size)
def get_avatar_sha(self, account, jid):
return self._accounts[account].contacts.get_avatar_sha(jid)
def set_avatar(self, account, jid, sha):
self._accounts[account].contacts.set_avatar(jid, sha)
def iter_contacts(self, account):
for contact in self._accounts[account].contacts.iter_contacts():
yield contact
......@@ -395,10 +410,10 @@ class LegacyContactsAPI:
raise AttributeError(attr_name)
def create_gc_contact(self, room_jid, account, name='', show='', status='',
role='', affiliation='', jid='', resource=''):
role='', affiliation='', jid='', resource='', avatar_sha=None):
account = self._accounts.get(account, account) # Use Account object if available
return GC_Contact(room_jid, account, name, show, status, role, affiliation, jid,
resource)
resource, avatar_sha=avatar_sha)
def add_gc_contact(self, account, gc_contact):
return self._accounts[account].gc_contacts.add_gc_contact(gc_contact)
......@@ -424,6 +439,12 @@ class LegacyContactsAPI:
def get_nb_role_total_gc_contacts(self, account, room_jid, role):
return self._accounts[account].gc_contacts.get_nb_role_total_gc_contacts(room_jid, role)
def set_gc_avatar(self, account, room_jid, nick, sha):
contact = self.get_gc_contact(account, room_jid, nick)
if contact is None:
return
contact.avatar_sha = sha
class Contacts():
"""
......@@ -490,6 +511,33 @@ class Contacts():
return c
return self._contacts[jid][0]
def get_avatar(self, jid, size=None):
if jid not in self._contacts:
return None
for resource in self._contacts[jid]:
if resource.avatar_sha is None:
continue
avatar = common.app.interface.get_avatar(resource.avatar_sha, size)
if avatar is None:
self.set_avatar(jid, None)
return avatar
def get_avatar_sha(self, jid):
if jid not in self._contacts:
return None
for resource in self._contacts[jid]:
if resource.avatar_sha is not None:
return resource.avatar_sha
return None
def set_avatar(self, jid, sha):
if jid not in self._contacts:
return
for resource in self._contacts[jid]:
resource.avatar_sha = sha
def iter_contacts(self):
for jid in list(self._contacts.keys()):
for contact in self._contacts[jid][:]:
......
......@@ -633,24 +633,6 @@ def get_account_status(account):
status = reduce_chars_newlines(account['status_line'], 100, 1)
return status
def get_avatar_path(prefix):
"""
Return the filename of the avatar, distinguishes between user- and contact-
provided one. Return None if no avatar was found at all. prefix is the path
to the requested avatar just before the ".png" or ".jpeg"
"""
# First, scan for a local, user-set avatar
for type_ in ('jpeg', 'png'):
file_ = prefix + '_local.' + type_
if os.path.exists(file_):
return file_
# If none available, scan for a contact-provided avatar
for type_ in ('jpeg', 'png'):
file_ = prefix + '.' + type_
if os.path.exists(file_):
return file_
return None
def datetime_tuple(timestamp):
"""
Convert timestamp using strptime and the format: %Y%m%dT%H:%M:%S
......@@ -1396,7 +1378,7 @@ def get_subscription_request_msg(account=None):
if account:
s = _('Hello, I am $name.') + ' ' + s
our_jid = app.get_jid_from_account(account)
vcard = app.connections[account].get_cached_vcard(our_jid)
vcard = app.connections[account].own_vcard
name = ''
if vcard:
if 'N' in vcard:
......
......@@ -42,7 +42,6 @@ from enum import IntEnum, unique
from gajim.common import exceptions
from gajim.common import app
from gajim.common import ged
import sqlite3 as sqlite
......@@ -979,8 +978,11 @@ class Logger:
if name is None:
name = ''
self.cur.execute('REPLACE INTO roster_entry VALUES(?, ?, ?, ?, ?)',
(account_jid_id, jid_id, name,
self.cur.execute('''
REPLACE INTO roster_entry
(account_jid_id, jid_id, name, subscription, ask)
VALUES(?, ?, ?, ?, ?)''', (
account_jid_id, jid_id, name,
self.convert_human_subscription_values_to_db_api_values(sub),
bool(ask)))
if commit:
......@@ -995,7 +997,7 @@ class Logger:
# First we fill data with roster_entry informations
self.cur.execute('''
SELECT j.jid, re.jid_id, re.name, re.subscription, re.ask
SELECT j.jid, re.jid_id, re.name, re.subscription, re.ask, re.avatar_sha
FROM roster_entry re, jids j
WHERE re.account_jid_id=? AND j.jid_id=re.jid_id''', (account_jid_id,))
for row in self.cur:
......@@ -1003,6 +1005,7 @@ class Logger:
jid = row.jid
name = row.name
data[jid] = {}
data[jid]['avatar_sha'] = row.avatar_sha
if name:
data[jid]['name'] = name
else:
......@@ -1132,3 +1135,25 @@ class Logger:
self._timeout_commit()
return lastrowid
def set_avatar_sha(self, account_jid, jid, sha=None):
"""
Set the avatar sha of a jid on an account
:param account_jid: The jid of the account