Commit 47615a42 authored by Martin's avatar Martin

Merge tag 'upstream/1.1.1' into debian/master

parents eb9e8cd0 3b649bba
before_script:
- sudo apt-get update -qq && sudo apt-get install -y -qq libtool pkg-config python3-openssl gir1.2-gtk-3.0 python3-gi python3-nbxmpp-nightly python3-cairo python3-pip
- sudo apt-get update -qq && sudo apt-get install -y -qq libtool pkg-config gir1.2-gtk-3.0 python3-gi python3-cairo
- sudo apt-get build-dep -y -qq gajim-default-nightly
- sudo pip3 install pylint==2.1.1
stages:
- test
- build
run-test:
stage: test
script:
- python3 setup.py test_nogui
run-mypy:
run-tests:
stage: test
script:
- rm -rf civenv-master
- virtualenv --system-site-packages civenv
- . ./civenv/bin/activate
- pip3 install mypy
- pip3 install nbxmpp
- pip3 install pylint==2.1.1
- mypy gajim
run-pylint:
stage: test
script:
- pylint3 --version
- pylint --version
- scripts/dev/pylint-ci.sh --jobs=2 gajim
- python3 setup.py test -s test.no_gui
- deactivate
- rm -rf civenv-master
run-appdata:
stage: test
......@@ -57,4 +54,4 @@ run-build:
name: "gajim-default-$CI_COMMIT_REF_NAME-$CI_COMMIT_SHA"
expire_in: 1 week
paths:
- gajim-default-2???-??-??.tar.gz
- gajim-default-$CI_COMMIT_REF_NAME-$CI_COMMIT_SHA.tar.gz
Gajim 1.1.1 (23 December 2018)
Bug fixes
* 8362 DBus: Incorrect unread message count
* 9427 Placeholder not cleared if pasting text into message input
* 9444 Determine the delay timestamp correctly when using mam:1
* 9453 Fix opening links inside the group chat subject (MacOS/Windows)
* 9465 Allow the full range of possible nicknames in group chats
* 9067 Gajim crashes when receiving xhtml messages
* 9096 Error when clicking on a subscription notification
* 9446 Chatstate error in MUC conversation
* 9471 Conversation Textview: Error on key press
* 9472 Handle presences without from attr correctly
* 9473 Error when creating a new group chat
* 9491 Identify group chat subject changes correctly
* 9496 Error on MUC roster selection change
* Determine soundplayer correctly on unix systems
* In some circumstances plugins could not be deleted
* Show correct contact status on tabs
* Dont answer group chat receipt requests
* Fix receipts for private messages
* Pressing the back button in the Accounts window leads to an error
* Better handle not available keyring backends
* Dont show incorrect contact on private messages
* Join group chat menu is disabled when there are no bookmarks
* Error on start chat menu action
* Error when opening sign-in/out notification
* Copying text does not work with different keyboard layouts
Gajim 1.1.0 (06 November 2018)
New
......
......@@ -32,8 +32,8 @@ build_script:
bash "git clone C:/projects/gajim C:/msys64/home/appveyor/gajim"
bash "C:/msys64/home/appveyor/gajim/win/build.sh $($env:MSYS_ARCH)"
Push-AppveyorArtifact "$($env:BUILDROOT)/Gajim.exe" -FileName "Gajim-1.1.0-$($env:ARCH)-$($env:TIME_STRING).exe"
Push-AppveyorArtifact "$($env:BUILDROOT)/Gajim-Portable.exe" -FileName "Gajim-Portable-1.1.0-$($env:ARCH)-$($env:TIME_STRING).exe"
Push-AppveyorArtifact "$($env:BUILDROOT)/Gajim.exe" -FileName "Gajim-1.1.1-$($env:ARCH)-$($env:TIME_STRING).exe"
Push-AppveyorArtifact "$($env:BUILDROOT)/Gajim-Portable.exe" -FileName "Gajim-Portable-1.1.1-$($env:ARCH)-$($env:TIME_STRING).exe"
# on_finish:
# - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
......
......@@ -8,7 +8,7 @@ desktop-file-name-prefix: '(Nightly) '
finish-args:
- --share=ipc
- --share=network
- --socket=x11
- --socket=fallback-x11
- --socket=wayland
- --socket=pulseaudio
- --system-talk-name=org.freedesktop.GeoClue2
......@@ -33,10 +33,6 @@ add-extensions:
no-autodownload: true
autodelete: true
build-options:
cflags: -O2 -g
cxxflags: -O2 -g
cleanup:
- '/bin/easy*'
- /include
......@@ -54,7 +50,7 @@ modules:
- name: python3-pycparser
buildsystem: simple
build-commands:
- python3 setup.py install --prefix=/app
- pip3 install --prefix=/app .
sources:
- type: archive
url: https://files.pythonhosted.org/packages/68/9e/49196946aee219aead1290e00d1e7fdeab8567783e83e1b9ab5585e6206a/pycparser-2.19.tar.gz
......@@ -63,7 +59,7 @@ modules:
- name: python3-cffi
buildsystem: simple
build-commands:
- python3 setup.py install --prefix=/app
- pip3 install --prefix=/app .
sources:
- type: archive
url: https://files.pythonhosted.org/packages/e7/a7/4cd50e57cc6f436f1cc3a7e8fa700ff9b8b4d471620629074913e3735fb2/cffi-1.11.5.tar.gz
......@@ -88,11 +84,9 @@ modules:
sha256: 156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e
- name: python3-cryptography
ensure-writable:
- easy-install.pth
buildsystem: simple
build-commands:
- python3 setup.py install --prefix=/app
- pip3 install --prefix=/app .
sources:
- type: archive
url: https://files.pythonhosted.org/packages/22/21/233e38f74188db94e8451ef6385754a98f3cad9b59bedf3a8e8b14988be4/cryptography-2.3.1.tar.gz
......@@ -146,13 +140,13 @@ modules:
- name: python3-keyring
buildsystem: simple
build-commands:
- pip3 install --prefix=/app keyring-15.1.0-py2.py3-none-any.whl
- pip3 install --prefix=/app keyring-16.0.2-py2.py3-none-any.whl
cleanup:
- /bin
sources:
- type: file
url: https://files.pythonhosted.org/packages/6c/3f/7a3f780dfa9ee5708507090ce15d6707aded2865f9e6999f48fa88b65bf3/keyring-15.1.0-py2.py3-none-any.whl
sha256: 16dddc3edaeb2703aaf5588a0b488b62f162e26f1877b6faf3a3db4b7712df61
url: https://files.pythonhosted.org/packages/5f/cb/dc7b2215cd82b77e7b8b48abd8989c1b09990d4c91a3ccfdc18a61157b36/keyring-16.0.2-py2.py3-none-any.whl
sha256: 2a5cf5e596cbf8b66b98b8df2c214adfe21e6e18baa82006b2c482bd0c4be94c
- name: python3-cssutils
buildsystem: simple
......@@ -185,7 +179,7 @@ modules:
- name: python3-nbxmpp
buildsystem: simple
build-commands:
- python3 setup.py install --prefix=/app
- pip3 install --prefix=/app .
sources:
- type: archive
url: https://files.pythonhosted.org/packages/24/54/23a475a0d7d3664ea21b14ce907245dc390496f31d229a9aac2ae20c7c28/nbxmpp-0.6.8.tar.gz
......
import os
import subprocess
__version__ = "1.1.0"
__version__ = "1.1.1"
IS_FLATPAK = False
if os.path.exists('/app/share/run-as-flatpak'):
......
......@@ -42,6 +42,7 @@ from gajim.common import helpers
from gajim.common import ged
from gajim.common import i18n
from gajim.common.i18n import _
from gajim.common.helpers import AdditionalDataDict
from gajim.common.contacts import GC_Contact
from gajim.common.const import AvatarSize
from gajim.common.const import KindConstant
......@@ -933,7 +934,7 @@ class ChatControl(ChatControlBase):
contact = self.contact
if additional_data is None:
additional_data = {}
additional_data = AdditionalDataDict()
if frm == 'status':
if not app.config.get('print_status_in_chats'):
......
......@@ -38,6 +38,7 @@ from gajim.common import helpers
from gajim.common import ged
from gajim.common import i18n
from gajim.common.i18n import _
from gajim.common.helpers import AdditionalDataDict
from gajim.common.contacts import GC_Contact
from gajim.common.connection_handlers_events import MessageOutgoingEvent
from gajim.common.const import StyleAttr
......@@ -53,6 +54,9 @@ from gajim.message_textview import MessageTextView
from gajim.gtk.dialogs import NonModalConfirmationDialog
from gajim.gtk.util import convert_rgb_to_hex
from gajim.gtk.util import get_primary_accel_mod
from gajim.gtk.util import get_hardware_key_codes
from gajim.gtk.emoji_chooser import emoji_chooser
from gajim.command_system.implementation.middleware import ChatCommandProcessor
......@@ -77,23 +81,12 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
A base class containing a banner, ConversationTextview, MessageTextView
"""
keymap = Gdk.Keymap.get_for_display(Gdk.Display.get_default())
try:
keycode_c = keymap.get_entries_for_keyval(Gdk.KEY_c)[1][0].keycode
except TypeError:
keycode_c = 54
except IndexError:
# FIXME
# On some keyboard layouts there is no keyval for KEY_c
keycode_c = None
try:
keycode_ins = keymap.get_entries_for_keyval(Gdk.KEY_Insert)[1][0].keycode
except TypeError:
keycode_ins = 118
except IndexError:
# There is no KEY_Insert (MacOS)
keycode_ins = None
# This is needed so copying text from the conversation textview
# works with different language layouts. Pressing the key c on a russian
# layout yields another keyval than with the english layout.
# So we match hardware keycodes instead of keyvals.
# Multiple hardware keycodes can trigger a keyval like Gdk.KEY_c.
keycodes_c = get_hardware_key_codes(Gdk.KEY_c)
def make_href(self, match):
url_color = app.css_config.get_value('.gajim-url', StyleAttr.COLOR)
......@@ -583,20 +576,18 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
if event.button == 3: # right click
self.parent_win.popup_menu(event)
def _conv_textview_key_press_event(self, widget, event):
# translate any layout to latin_layout
_valid, entries = self.keymap.get_entries_for_keyval(event.keyval)
keycode = entries[0].keycode
if (event.get_state() & Gdk.ModifierType.CONTROL_MASK and keycode in (
self.keycode_c, self.keycode_ins)):
return False
def _conv_textview_key_press_event(self, _widget, event):
if (event.get_state() & get_primary_accel_mod() and
event.hardware_keycode in self.keycodes_c):
return Gdk.EVENT_PROPAGATE
if event.get_state() & Gdk.ModifierType.SHIFT_MASK and \
event.keyval in (Gdk.KEY_Page_Down, Gdk.KEY_Page_Up):
if (event.get_state() & Gdk.ModifierType.SHIFT_MASK and
event.keyval in (Gdk.KEY_Page_Down, Gdk.KEY_Page_Up)):
self._on_scroll(None, event.keyval)
return False
return Gdk.EVENT_PROPAGATE
self.parent_win.notebook.event(event)
return True
return Gdk.EVENT_STOP
def _on_message_textview_key_press_event(self, widget, event):
if event.keyval == Gdk.KEY_space:
......@@ -905,7 +896,7 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
if other_tags_for_text is None:
other_tags_for_text = []
if additional_data is None:
additional_data = {}
additional_data = AdditionalDataDict()
textview.print_conversation_line(text, jid, kind, name, tim,
other_tags_for_name, other_tags_for_time, other_tags_for_text,
......
......@@ -69,7 +69,7 @@ def set_profile(profile: str) -> None:
def set_config_root(config_root: str) -> None:
_paths.custom_config_root = config_root
_paths.custom_config_root = str(Path(config_root).resolve())
def init() -> None:
......
......@@ -146,6 +146,7 @@ class ConnectionHandlersBase:
obj.contact_list.append(obj.contact)
obj.contact.resource = resource
obj.need_redraw = True
obj.need_add_in_roster = True
if not app.jid_is_transport(jid) and len(obj.contact_list) == 1:
......@@ -206,6 +207,9 @@ class ConnectionHandlersBase:
if obj.conn.name != self.name:
return
if obj.stanza.getType() == 'error':
return
self._check_for_mam_compliance(obj.jid, obj.stanza_id)
if (app.config.should_log(obj.conn.name, obj.jid) and
......
......@@ -28,6 +28,7 @@ from gajim.common import helpers
from gajim.common import app
from gajim.common import i18n
from gajim.common.i18n import _
from gajim.common.helpers import AdditionalDataDict
from gajim.common.modules import dataforms
from gajim.common.modules.misc import parse_idle
from gajim.common.modules.misc import parse_delay
......@@ -91,14 +92,12 @@ class HelperEvent:
def get_oob_data(self, stanza):
oob_node = stanza.getTag('x', namespace=nbxmpp.NS_X_OOB)
if oob_node is not None:
if 'gajim' not in self.additional_data:
self.additional_data['gajim'] = {}
oob_url = oob_node.getTagData('url')
if oob_url is not None:
self.additional_data['gajim']['oob_url'] = oob_url
self.additional_data.set_value('gajim', 'oob_url', oob_url)
oob_desc = oob_node.getTagData('desc')
if oob_desc is not None:
self.additional_data['gajim']['oob_desc'] = oob_desc
self.additional_data.set_value('gajim', 'oob_desc', oob_desc)
def get_stanza_id(self, stanza, query=False):
if query:
......@@ -407,7 +406,7 @@ class GcMessageReceivedEvent(nec.NetworkIncomingEvent):
def generate(self):
self.stanza = self.msg_obj.stanza
if not hasattr(self.msg_obj, 'additional_data'):
self.additional_data = {}
self.additional_data = AdditionalDataDict()
else:
self.additional_data = self.msg_obj.additional_data
self.id_ = self.msg_obj.stanza.getID()
......@@ -433,15 +432,6 @@ class GcMessageReceivedEvent(nec.NetworkIncomingEvent):
# message from server
self.nick = ''
self.subject = self.stanza.getSubject()
if self.subject is not None:
app.nec.push_incoming_event(
nec.NetworkEvent('gc-subject-received',
nickname=self.msg_obj.resource,
**vars(self.msg_obj)))
return
conditions = self.stanza.getStatusConditions()
if conditions:
self.status_code = []
......@@ -626,18 +616,9 @@ class ConnectionTypeEvent(nec.NetworkIncomingEvent):
class StanzaReceivedEvent(nec.NetworkIncomingEvent):
name = 'stanza-received'
def init(self):
self.additional_data = {}
def generate(self):
return True
class StanzaSentEvent(nec.NetworkIncomingEvent):
name = 'stanza-sent'
def init(self):
self.additional_data = {}
class AgentRemovedEvent(nec.NetworkIncomingEvent):
name = 'agent-removed'
......@@ -1158,7 +1139,7 @@ class MessageOutgoingEvent(nec.NetworkOutgoingEvent):
name = 'message-outgoing'
def init(self):
self.additional_data = {}
self.additional_data = AdditionalDataDict()
self.message = None
self.keyID = None
self.type_ = 'chat'
......@@ -1211,7 +1192,7 @@ class GcMessageOutgoingEvent(nec.NetworkOutgoingEvent):
name = 'gc-message-outgoing'
def init(self):
self.additional_data = {}
self.additional_data = AdditionalDataDict()
self.message = ''
self.chatstate = None
self.xhtml = None
......
......@@ -36,7 +36,7 @@ try:
DBusGMainLoop(set_as_default=True)
except ImportError:
supported = False
if sys.platform == 'linux': # windows and mac have no dbus
if sys.platform not in ('win32', 'darwin'):
print(_('D-Bus python bindings are missing in this computer'))
print(_('D-Bus capabilities of Gajim cannot be used'))
else:
......@@ -47,7 +47,7 @@ else:
supported = True # does user have D-Bus bindings?
except dbus.DBusException:
supported = False
if sys.platform == 'linux': # windows and mac have no dbus
if sys.platform not in ('win32', 'darwin'):
print(_('D-Bus does not run correctly on this machine'))
print(_('D-Bus capabilities of Gajim cannot be used'))
except exceptions.SystemBusNotPresent:
......
......@@ -81,7 +81,8 @@ class ChatEvent(Event):
self.displaymarking = displaymarking
self.sent_forwarded = sent_forwarded
if additional_data is None:
additional_data = {}
from gajim.common.helpers import AdditionalDataDict
additional_data = AdditionalDataDict()
self.additional_data = additional_data
class NormalEvent(ChatEvent):
......
......@@ -24,6 +24,7 @@ Global Events Dispatcher module.
import logging
import traceback
import inspect
from nbxmpp import NodeProcessed
......@@ -84,11 +85,19 @@ class GlobalEventsDispatcher:
Error: %s''', handler, priority, event_name, error)
def raise_event(self, event_name, *args, **kwargs):
log.debug('%s Args: %s', event_name, str(args))
log.debug('Raise event: %s', event_name)
if event_name in self.handlers:
node_processed = False
for _priority, handler in self.handlers[event_name]:
# Iterate over a copy of the handlers list, so while iterating
# the original handlers list can be modified
for _priority, handler in list(self.handlers[event_name]):
try:
if inspect.ismethod(handler):
log.debug('Call handler %s on %s',
handler.__name__,
handler.__self__)
else:
log.debug('Call handler %s', handler.__name__)
if handler(*args, **kwargs):
return True
except NodeProcessed:
......
......@@ -40,48 +40,22 @@ import socket
import time
import logging
import json
import shutil
import collections
from datetime import datetime, timedelta
from distutils.version import LooseVersion as V
from encodings.punycode import punycode_encode
from string import Template
import nbxmpp
from nbxmpp.stringprepare import nameprep
import precis_i18n.codec # pylint: disable=unused-import
from gajim.common import caps_cache
from gajim.common import configpaths
from gajim.common.i18n import Q_
from gajim.common.i18n import _
from gajim.common.i18n import ngettext
from gajim.common.caps_cache import muc_caps_cache
try:
import precis_i18n.codec # pylint: disable=unused-import
HAS_PRECIS_I18N = True
except ImportError:
HAS_PRECIS_I18N = False
HAS_SOUND = True
if sys.platform == 'win32':
try:
import winsound # windows-only built-in module for playing wav
except ImportError:
HAS_SOUND = False
print('Gajim is not able to playback sound because'
'pywin32 is missing', file=sys.stderr)
elif sys.platform == 'darwin':
try:
from AppKit import NSSound
except ImportError:
HAS_SOUND = False
print('Gajim is not able to playback sound because'
'pyobjc is missing', file=sys.stderr)
try:
import wave # posix-only fallback wav playback
import ossaudiodev as oss
except Exception:
pass
log = logging.getLogger('gajim.c.helpers')
......@@ -180,10 +154,7 @@ def parse_resource(resource):
"""
if resource:
try:
if HAS_PRECIS_I18N:
return resource.encode('Nickname').decode('utf-8')
from nbxmpp.stringprepare import resourceprep
return resourceprep.prepare(resource)
return resource.encode('OpaqueString').decode('utf-8')
except UnicodeError:
raise InvalidFormat('Invalid character in resource.')
......@@ -217,7 +188,6 @@ def prep(user, server, resource):
if not server or len(server.encode('utf-8')) > 1023:
raise InvalidFormat(_('Server must be between 1 and 1023 bytes'))
try:
from nbxmpp.stringprepare import nameprep
server = nameprep.prepare(server)
except UnicodeError:
raise InvalidFormat(_('Invalid character in hostname.'))
......@@ -228,11 +198,7 @@ def prep(user, server, resource):
if not user or len(user.encode('utf-8')) > 1023:
raise InvalidFormat(_('Username must be between 1 and 1023 bytes'))
try:
if HAS_PRECIS_I18N:
user = user.encode('UsernameCaseMapped').decode('utf-8')
else:
from nbxmpp.stringprepare import nodeprep
user = nodeprep.prepare(user)
user = user.encode('UsernameCaseMapped').decode('utf-8')
except UnicodeError:
raise InvalidFormat(_('Invalid character in username.'))
else:
......@@ -242,11 +208,7 @@ def prep(user, server, resource):
if not resource or len(resource.encode('utf-8')) > 1023:
raise InvalidFormat(_('Resource must be between 1 and 1023 bytes'))
try:
if HAS_PRECIS_I18N:
resource = resource.encode('OpaqueString').decode('utf-8')
else:
from nbxmpp.stringprepare import resourceprep
resource = resourceprep.prepare(resource)
resource = resource.encode('OpaqueString').decode('utf-8')
except UnicodeError:
raise InvalidFormat(_('Invalid character in resource.'))
else:
......@@ -444,22 +406,26 @@ def get_uf_chatstate(chatstate):
return _('has closed the chat window or tab')
return ''
def is_in_path(command, return_abs_path=False):
"""
Return True if 'command' is found in one of the directories in the user's
path. If 'return_abs_path' is True, return the absolute path of the first
found command instead. Return False otherwise and on errors
"""
for directory in os.getenv('PATH').split(os.pathsep):
try:
if command in os.listdir(directory):
if return_abs_path:
return os.path.join(directory, command)
return True
except OSError:
# If the user has non directories in his path
pass
return False
def find_soundplayer():
if sys.platform in ('win32', 'darwin'):
return
if app.config.get('soundplayer') != '':
return
commands = ('aucat', 'paplay', 'aplay', 'play', 'ossplay')
for command in commands:
if shutil.which(command) is not None:
if command == 'paplay':
command += ' -n gajim --property=media.role=event'
elif command in ('aplay', 'play'):
command += ' -q'
elif command == 'ossplay':
command += ' -qq'
elif command == 'aucat':
command += ' -i'
app.config.set('soundplayer', command)
break
def exec_command(command, use_shell=False, posix=True):
"""
......@@ -758,37 +724,51 @@ def strip_soundfile_path(file_, dirs=None, abs_=True):
return file_
def play_sound_file(path_to_soundfile):
if path_to_soundfile == 'beep':
exec_command('beep')
return
path_to_soundfile = check_soundfile_path(path_to_soundfile)
if path_to_soundfile is None:
return
if sys.platform == 'win32' and HAS_SOUND:
if sys.platform == 'win32':
import winsound
try:
winsound.PlaySound(path_to_soundfile,
winsound.SND_FILENAME|winsound.SND_ASYNC)
winsound.SND_FILENAME|winsound.SND_ASYNC)
except Exception:
log.exception('Sound Playback Error')
elif sys.platform == 'darwin':
try:
from AppKit import NSSound
except ImportError:
log.exception('Sound Playback Error')
return
sound = NSSound.alloc()
sound.initWithContentsOfFile_byReference_(path_to_soundfile, True)
sound.play()
elif app.config.get('soundplayer') == '':
try:
import wave
import ossaudiodev
except Exception:
log.exception('Sound Playback Error')
elif sys.platform == 'linux':
if app.config.get('soundplayer') == '':
def _oss_play():
sndfile = wave.open(path_to_soundfile, 'rb')
nc, sw, fr, nf, _comptype, _compname = sndfile.getparams()
dev = oss.open('/dev/dsp', 'w')
dev.setparameters(sw * 8, nc, fr)
dev.write(sndfile.readframes(nf))
sndfile.close()
dev.close()
app.thread_interface(_oss_play)
return
def _oss_play():
sndfile = wave.open(path_to_soundfile, 'rb')
nc, sw, fr, nf, _comptype, _compname = sndfile.getparams()
dev = ossaudiodev.open('/dev/dsp', 'w')
dev.setparameters(sw * 8, nc, fr)
dev.write(sndfile.readframes(nf))
sndfile.close()
dev.close()
app.thread_interface(_oss_play)
else:
player = app.config.get('soundplayer')
command = build_command(player, path_to_soundfile)
exec_command(command)
elif sys.platform == 'darwin' and HAS_SOUND:
sound = NSSound.alloc()
sound.initWithContentsOfFile_byReference_(path_to_soundfile, True)
sound.play()
def get_global_show():
maxi = 0
......@@ -1485,8 +1465,9 @@ def call_counter(func):
return helper
def get_sync_threshold(jid, archive_info):
cache = caps_cache.muc_caps_cache
if archive_info is None or archive_info.sync_threshold is None:
if muc_caps_cache.supports(jid, 'muc#roomconfig_membersonly'):
if cache.supports(jid, 'muc#roomconfig_membersonly'):
threshold = app.config.get('private_room_sync_threshold')
else:
threshold = app.config.get('public_room_sync_threshold')
......@@ -1505,3 +1486,51 @@ def load_json(path, key=None, default=None):
if key is None:
return json_dict
return json_dict.get(key, default)
class AdditionalDataDict(collections.UserDict):
def __init__(self, initialdata=None):
collections.UserDict.__init__(self, initialdata)
@staticmethod
def _get_path_childs(full_path):
path_childs = [full_path]
if ':' in full_path:
path_childs = full_path.split(':')
return path_childs
def set_value(self, full_path, key, value):
path_childs = self._get_path_childs(full_path)
_dict = self.data
for path in path_childs:
try:
_dict = _dict[path]
except KeyError:
_dict[path] = {}
_dict = _dict[path]