Commit aaeeff41 authored by Martin's avatar Martin

Imported Upstream version 1.0.0-beta1

parent 5f648fb6
Pipeline #1391 failed with stages
in 37 seconds
Gajim 0.99.1 (08 February 2018)
* Integrate HTTPUpload
* Add Navigation buttons in History Window
* Added XEP-0368 (SRV records for XMPP over TLS)
* Improvements for HiDPI Screens
* Move Chat Menu button so we are not forced to use CSD
* Depend on the python keyring package for password storage
* Bug fixes
Gajim 0.98.2 (17 December 2017)
* Fix DB migration
......
import subprocess
__version__ = "0.98.3"
__version__ = "0.99.1"
try:
node = subprocess.Popen('git rev-parse --short=12 HEAD', shell=True,
......
......@@ -24,8 +24,6 @@
from calendar import timegm
import datetime
import hashlib
import binascii
import base64
import hmac
import logging
import sys
......@@ -604,48 +602,6 @@ class PubsubBookmarksReceivedEvent(nec.NetworkIncomingEvent, BookmarksHelper):
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.debug(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.debug(self.stanza)
return
self.data = data_tag.getData()
if self.data is None:
log.warning('Received malformed avatar data via pubsub')
log.debug(self.stanza)
return
try:
self.data = base64.b64decode(self.data.encode('utf-8'))
except binascii.Error as err:
log.debug('Received malformed avatar data via pubsub: %s' % err)
return
return True
class SearchFormReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
name = 'search-form-received'
base_network_events = []
......@@ -1159,7 +1115,12 @@ class MamGcMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
if self.msg_.getType() != 'groupchat':
return False
self.room_jid = self.stanza.getFrom().getStripped()
try:
self.room_jid = self.stanza.getFrom().getStripped()
except AttributeError:
log.warning('Received GC MAM message '
'without from attribute\n%s', self.stanza)
return False
self.unique_id = self.get_stanza_id(self.result, query=True)
......
......@@ -147,3 +147,13 @@ class PluginsystemError(Exception):
def __str__(self):
return self.text
class StanzaMalformed(Exception):
"""
Malfromed Stanza
"""
def __init__(self, message, stanza=''):
Exception.__init__(self, message, stanza)
self._msg = '{}\n{}'.format(message, stanza)
def __str__(self):
return self._msg
......@@ -732,6 +732,64 @@ class Logger:
# attributes set to None because of the MAX() function
return self.con.execute(sql, tuple(jids)).fetchone().time
def get_first_date_that_has_logs(self, account, jid):
"""
Get the timestamp of the first message we received for the jid.
:param account: The account
:param jid: The jid for which we request the first timestamp
returns a timestamp or None
"""
jids = self._get_family_jids(account, jid)
kinds = map(str, [KindConstant.STATUS,
KindConstant.GCSTATUS])
sql = '''
SELECT MIN(time) as time FROM logs
NATURAL JOIN jids WHERE jid IN ({jids})
AND kind NOT IN ({kinds})
'''.format(jids=', '.join('?' * len(jids)),
kinds=', '.join(kinds))
# fetchone() returns always at least one Row with all
# attributes set to None because of the MIN() function
return self.con.execute(sql, tuple(jids)).fetchone().time
def get_date_has_logs(self, account, jid, date):
"""
Get single timestamp of a message we received for the jid
in the time range of one day.
:param account: The account
:param jid: The jid for which we request the first timestamp
:param date: datetime.datetime instance
example: datetime.datetime(year, month, day)
returns a timestamp or None
"""
jids = self._get_family_jids(account, jid)
kinds = map(str, [KindConstant.STATUS,
KindConstant.GCSTATUS])
delta = datetime.timedelta(
hours=23, minutes=59, seconds=59, microseconds=999999)
sql = '''
SELECT time
FROM logs NATURAL JOIN jids WHERE jid IN ({jids})
AND time BETWEEN ? AND ?
'''.format(jids=', '.join('?' * len(jids)))
return self.con.execute(sql, tuple(jids) +
(date.timestamp(),
(date + delta).timestamp())).fetchone()
def get_room_last_message_time(self, account, jid):
"""
Get the timestamp of the last message we received in a room.
......
......@@ -509,8 +509,8 @@ class AvatarNotificationPEP(AbstractPEP):
jid, self.avatar['id'])
return
app.log('avatar').info('Request (Pubsub): %s', jid)
con.send_pb_retrieve(jid, 'urn:xmpp:avatar:data',
self.avatar['id'])
con.get_pubsub_avatar(jid, 'urn:xmpp:avatar:data',
self.avatar['id'])
SUPPORTED_PERSONAL_USER_EVENTS = [
......
......@@ -21,16 +21,18 @@
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
##
import base64
import binascii
import nbxmpp
from gajim.common import app
#TODO: Doesn't work
#from common.connection_handlers import PEP_CONFIG
PEP_CONFIG = 'pep_config'
from gajim.common import ged
from gajim.common.nec import NetworkEvent
from gajim.common.connection_handlers_events import PubsubReceivedEvent
from gajim.common.connection_handlers_events import PubsubBookmarksReceivedEvent
from gajim.common.connection_handlers_events import PubsubAvatarReceivedEvent
from gajim.common.exceptions import StanzaMalformed
import logging
log = logging.getLogger('gajim.c.pubsub')
......@@ -39,11 +41,8 @@ class ConnectionPubSub:
def __init__(self):
self.__callbacks = {}
app.nec.register_incoming_event(PubsubBookmarksReceivedEvent)
app.nec.register_incoming_event(PubsubAvatarReceivedEvent)
app.ged.register_event_handler('pubsub-bookmarks-received',
ged.CORE, self._nec_pubsub_bookmarks_received)
app.ged.register_event_handler('pubsub-avatar-received',
ged.CORE, self._nec_pubsub_avatar_received)
def cleanup(self):
app.ged.remove_event_handler('pubsub-bookmarks-received',
......@@ -103,22 +102,35 @@ class ConnectionPubSub:
self.connection.send(query)
def send_pb_retrieve(self, jid, node, item_id=None, cb=None, *args, **kwargs):
@staticmethod
def get_pb_retrieve_iq(jid, node, item_id=None):
"""
Get items from a node
Get IQ to query items from a node
"""
if not self.connection or self.connected < 2:
return
query = nbxmpp.Iq('get', to=jid)
r = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB)
r = r.addChild('items', {'node': node})
if item_id is not None:
r.addChild('item', {'id': item_id})
return query
def send_pb_retrieve(self, jid, node, item_id=None, cb=None, *args, **kwargs):
"""
Get items from a node
"""
if not self.connection or self.connected < 2:
return
query = self.get_pb_retrieve_iq(jid, node, item_id)
id_ = self.connection.send(query)
if cb:
self.__callbacks[id_] = (cb, args, kwargs)
def get_pubsub_avatar(self, jid, node, item_id):
query = self.get_pb_retrieve_iq(jid, node, item_id)
self.connection.SendAndCallForResponse(
query, self._nec_pubsub_avatar_received, {'jid': jid})
def send_pb_retract(self, jid, node, id_):
"""
Delete item from a node
......@@ -211,25 +223,62 @@ class ConnectionPubSub:
# We got bookmarks from pubsub, now get those from xml to merge them
self.get_bookmarks(storage_type='xml')
def _nec_pubsub_avatar_received(self, obj):
if obj.conn.name != self.name:
return
if obj.jid is None:
def _validate_avatar_node(self, stanza):
jid = stanza.getFrom()
if jid is None:
jid = self.get_own_jid().getStripped()
else:
jid = obj.jid.getStripped()
jid = jid.getStripped()
if nbxmpp.isErrorNode(stanza):
raise StanzaMalformed(stanza.getErrorMsg())
pubsub_node = stanza.getTag('pubsub')
if pubsub_node is None:
raise StanzaMalformed('No pubsub node', stanza)
items_node = pubsub_node.getTag('items')
if items_node is None:
raise StanzaMalformed('No items node', stanza)
if items_node.getAttr('node') != 'urn:xmpp:avatar:data':
raise StanzaMalformed('Wrong namespace', stanza)
item = items_node.getTag('item')
if item is None:
raise StanzaMalformed('No item node', stanza)
sha = item.getAttr('id')
data_tag = item.getTag('data', namespace='urn:xmpp:avatar:data')
if sha is None or data_tag is None:
raise StanzaMalformed('No id attr or data node found', stanza)
data = data_tag.getData()
if data is None:
raise StanzaMalformed('Data node empty', stanza)
data = base64.b64decode(data.encode('utf-8'))
return jid, sha, data
def _nec_pubsub_avatar_received(self, conn, stanza, jid):
try:
jid, sha, data = self._validate_avatar_node(stanza)
except (StanzaMalformed, binascii.Error) as error:
app.log('avatar').warning(
'Error loading Avatar (Pubsub): %s %s', jid, error)
return
app.log('avatar').info(
'Received Avatar (Pubsub): %s %s', jid, obj.sha)
app.interface.save_avatar(obj.data)
'Received Avatar (Pubsub): %s %s', jid, sha)
app.interface.save_avatar(data)
if self.get_own_jid().bareMatch(jid):
app.config.set_per('accounts', self.name, 'avatar_sha', obj.sha)
app.config.set_per('accounts', self.name, 'avatar_sha', sha)
else:
own_jid = self.get_own_jid().getStripped()
app.logger.set_avatar_sha(own_jid, jid, obj.sha)
app.contacts.set_avatar(self.name, jid, obj.sha)
app.logger.set_avatar_sha(own_jid, jid, sha)
app.contacts.set_avatar(self.name, jid, sha)
app.interface.update_avatar(self.name, jid)
......
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.0 -->
<!-- Generated with glade 3.20.2 -->
<interface>
<requires lib="gtk+" version="3.20"/>
<object class="GtkApplicationWindow" id="history_window">
......@@ -35,7 +35,7 @@
<object class="GtkEntry" id="query_entry">
<property name="can_focus">True</property>
<property name="invisible_char"></property>
<property name="placeholder_text">Enter name / JID of contact or groupchat</property>
<property name="placeholder_text" translatable="yes">Enter name / JID of contact or groupchat</property>
</object>
<packing>
<property name="expand">True</property>
......@@ -102,11 +102,13 @@
<object class="GtkPaned" id="hpaned">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="margin_bottom">5</property>
<property name="position">165</property>
<child>
<object class="GtkBox" id="vbox2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_right">5</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
......@@ -123,7 +125,98 @@
</packing>
</child>
<child>
<placeholder/>
<object class="GtkButtonBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="spacing">5</property>
<property name="layout_style">expand</property>
<child>
<object class="GtkButton" id="button_first_day">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<signal name="clicked" handler="_change_date" swapped="no"/>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">go-first-symbolic</property>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button_previous_day">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<signal name="clicked" handler="_change_date" swapped="no"/>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">go-previous-symbolic</property>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button_next_day">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<signal name="clicked" handler="_change_date" swapped="no"/>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">go-next-symbolic</property>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button_last_day">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<signal name="clicked" handler="_change_date" swapped="no"/>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">go-last-symbolic</property>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
......
......@@ -53,7 +53,7 @@ from gajim.common import i18n
from gajim.common import logging_helpers
from gajim.common import crypto
MIN_NBXMPP_VER = "0.6.2"
MIN_NBXMPP_VER = "0.6.3"
MIN_GTK_VER = "3.20.0"
......
......@@ -71,6 +71,10 @@ class HistoryWindow:
self.window = xml.get_object('history_window')
self.window.set_application(app.app)
self.calendar = xml.get_object('calendar')
self.button_first_day = xml.get_object('button_first_day')
self.button_previous_day = xml.get_object('button_previous_day')
self.button_next_day = xml.get_object('button_next_day')
self.button_last_day = xml.get_object('button_last_day')
scrolledwindow = xml.get_object('scrolledwindow')
self.history_textview = conversation_textview.ConversationTextview(
account, used_in_history_window = True)
......@@ -309,17 +313,25 @@ class HistoryWindow:
self.jids_to_search = [info_jid]
# select logs for last date we have logs with contact
self.calendar.set_sensitive(True)
last_log = \
app.logger.get_last_date_that_has_logs(self.account, self.jid)
# Get first/last date we have logs with contact (for log navigation)
self.first_log = app.logger.get_first_date_that_has_logs(
self.account, self.jid)
self.first_day = self._get_date_from_timestamp(self.first_log)
self.last_log = app.logger.get_last_date_that_has_logs(
self.account, self.jid)
self.last_day = self._get_date_from_timestamp(self.last_log)
date = time.localtime(last_log)
# Select logs for last date we have logs with contact
self.calendar.set_sensitive(True)
gtk_month = gtkgui_helpers.make_python_month_gtk_month(
self.last_day.month)
self.calendar.select_month(gtk_month, self.last_day.year)
self.calendar.select_day(self.last_day.day)
y, m, d = date[0], date[1], date[2]
gtk_month = gtkgui_helpers.make_python_month_gtk_month(m)
self.calendar.select_month(gtk_month, y)
self.calendar.select_day(d)
self.button_previous_day.set_sensitive(True)
self.button_next_day.set_sensitive(True)
self.button_first_day.set_sensitive(True)
self.button_last_day.set_sensitive(True)
self.search_entry.set_sensitive(True)
self.search_entry.grab_focus()
......@@ -339,6 +351,10 @@ class HistoryWindow:
self.checkbutton.set_sensitive(False)
self.calendar.set_sensitive(False)
self.calendar.clear_marks()
self.button_previous_day.set_sensitive(False)
self.button_next_day.set_sensitive(False)
self.button_first_day.set_sensitive(False)
self.button_last_day.set_sensitive(False)
self.results_window.set_property('visible', False)
......@@ -377,6 +393,60 @@ class HistoryWindow:
for date in log_days:
widget.mark_day(date.day)
def _get_date_from_timestamp(self, timestamp):
# Conversion from timestamp to date
log = time.localtime(timestamp)
y, m, d = log[0], log[1], log[2]
date = datetime.datetime(y, m, d)
return(date)
def _change_date(self, widget):
# Get day selected in calendar
y, m, d = self.calendar.get_date()
py_m = gtkgui_helpers.make_gtk_month_python_month(m)
_date = datetime.datetime(y, py_m, d)
if widget is self.button_first_day:
gtk_m = gtkgui_helpers.make_python_month_gtk_month(
self.first_day.month)
self.calendar.select_month(gtk_m, self.first_day.year)
self.calendar.select_day(self.first_day.day)
return
elif widget is self.button_last_day:
gtk_m = gtkgui_helpers.make_python_month_gtk_month(
self.last_day.month)
self.calendar.select_month(gtk_m, self.last_day.year)
self.calendar.select_day(self.last_day.day)
return
elif widget is self.button_previous_day:
end_date = self.first_day
timedelta = datetime.timedelta(days=-1)
if end_date >= _date:
return
elif widget is self.button_next_day:
end_date = self.last_day
timedelta = datetime.timedelta(days=1)
if end_date <= _date:
return
# Iterate through days until log entry found or
# supplied end_date (first_log / last_log) reached
logs = None
while logs is None:
_date = _date + timedelta
if _date == end_date:
break
try:
logs = app.logger.get_date_has_logs(
self.account, self.jid, _date)
except exceptions.PysqliteOperationalError as e:
dialogs.ErrorDialog(_('Disk Error'), str(e))
return
gtk_month = gtkgui_helpers.make_python_month_gtk_month(_date.month)
self.calendar.select_month(gtk_month, _date.year)
self.calendar.select_day(_date.day)
def _get_string_show_from_constant_int(self, show):
if show == ShowConstant.ONLINE:
show = 'online'
......
......@@ -281,7 +281,7 @@
{
"type": "git",
"url": "https://dev.gajim.org/gajim/python-nbxmpp.git",
"branch": "nbxmpp-0.6.2"
"branch": "nbxmpp-0.6.3"
}
]
},
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment