2008-08-15 19:31:51 +02:00
|
|
|
# -*- coding:utf-8 -*-
|
2008-08-15 05:20:23 +02:00
|
|
|
## src/common/optparser.py
|
2003-10-22 20:45:13 +02:00
|
|
|
##
|
2008-08-15 05:20:23 +02:00
|
|
|
## Copyright (C) 2003-2005 Vincent Hanquez <tab AT snarc.org>
|
2014-01-02 09:33:54 +01:00
|
|
|
## Copyright (C) 2003-2014 Yann Leboulanger <asterix AT lagaule.org>
|
2008-08-15 05:20:23 +02:00
|
|
|
## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com>
|
|
|
|
## Nikos Kouremenos <kourem AT gmail.com>
|
|
|
|
## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
|
|
|
|
## Copyright (C) 2007 James Newton <redshodan AT gmail.com>
|
|
|
|
## Brendan Taylor <whateley AT gmail.com>
|
|
|
|
## Tomasz Melcer <liori AT exroot.org>
|
|
|
|
## Stephan Erb <steve-e AT h3c.de>
|
2003-10-22 20:45:13 +02:00
|
|
|
##
|
2007-10-22 13:13:13 +02:00
|
|
|
## This file is part of Gajim.
|
|
|
|
##
|
|
|
|
## Gajim is free software; you can redistribute it and/or modify
|
2003-10-22 20:45:13 +02:00
|
|
|
## it under the terms of the GNU General Public License as published
|
2007-10-22 13:13:13 +02:00
|
|
|
## by the Free Software Foundation; version 3 only.
|
2003-10-22 20:45:13 +02:00
|
|
|
##
|
2007-10-22 13:13:13 +02:00
|
|
|
## Gajim is distributed in the hope that it will be useful,
|
2003-10-22 20:45:13 +02:00
|
|
|
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
2008-08-15 05:20:23 +02:00
|
|
|
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
2003-10-22 20:45:13 +02:00
|
|
|
## GNU General Public License for more details.
|
|
|
|
##
|
2007-10-22 13:13:13 +02:00
|
|
|
## You should have received a copy of the GNU General Public License
|
2008-08-15 05:20:23 +02:00
|
|
|
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
2007-10-22 13:13:13 +02:00
|
|
|
##
|
2003-10-22 20:45:13 +02:00
|
|
|
|
2005-04-16 19:03:21 +02:00
|
|
|
import os
|
2010-01-29 15:20:23 +01:00
|
|
|
import sys
|
2008-11-05 19:21:47 +01:00
|
|
|
import re
|
2009-11-11 23:14:51 +01:00
|
|
|
from time import time
|
2017-08-13 13:18:56 +02:00
|
|
|
from gajim.common import app
|
2017-06-13 23:58:06 +02:00
|
|
|
from gajim.common import helpers
|
|
|
|
from gajim.common import caps_cache
|
2003-10-22 20:45:13 +02:00
|
|
|
|
2009-11-30 16:36:47 +01:00
|
|
|
import sqlite3 as sqlite
|
2017-06-13 23:58:06 +02:00
|
|
|
from gajim.common import logger
|
2006-10-12 00:45:33 +02:00
|
|
|
|
2015-03-26 20:58:51 +01:00
|
|
|
import logging
|
|
|
|
log = logging.getLogger('gajim.c.optparser')
|
|
|
|
|
2005-01-22 21:26:43 +01:00
|
|
|
class OptionsParser:
|
2010-02-08 15:08:40 +01:00
|
|
|
def __init__(self, filename):
|
2015-09-26 15:58:33 +02:00
|
|
|
self.__filename = os.path.realpath(filename)
|
2010-02-08 15:08:40 +01:00
|
|
|
self.old_values = {} # values that are saved in the file and maybe
|
|
|
|
# no longer valid
|
|
|
|
|
|
|
|
def read(self):
|
|
|
|
try:
|
2018-02-10 18:59:34 +01:00
|
|
|
fd = open(self.__filename, encoding='utf-8')
|
2010-02-08 15:08:40 +01:00
|
|
|
except Exception:
|
|
|
|
if os.path.exists(self.__filename):
|
|
|
|
#we talk about a file
|
2013-01-01 19:36:56 +01:00
|
|
|
print(_('Error: cannot open %s for reading') % self.__filename,
|
|
|
|
file=sys.stderr)
|
2010-02-08 15:08:40 +01:00
|
|
|
return False
|
|
|
|
|
2017-08-13 13:18:56 +02:00
|
|
|
new_version = app.config.get('version')
|
2017-11-24 16:49:30 +01:00
|
|
|
new_version = new_version.split('+', 1)[0]
|
2010-02-08 15:08:40 +01:00
|
|
|
seen = set()
|
2014-09-14 14:47:03 +02:00
|
|
|
regex = re.compile(r"(?P<optname>[^.=]+)(?:(?:\.(?P<key>.+))?\.(?P<subname>[^.=]+))?\s=\s(?P<value>.*)")
|
2010-02-08 15:08:40 +01:00
|
|
|
|
|
|
|
for line in fd:
|
2015-03-26 20:58:51 +01:00
|
|
|
match = regex.match(line)
|
|
|
|
if match is None:
|
|
|
|
log.warn('Invalid configuration line, ignoring it: %s', line)
|
|
|
|
continue
|
|
|
|
optname, key, subname, value = match.groups()
|
2010-02-08 15:08:40 +01:00
|
|
|
if key is None:
|
|
|
|
self.old_values[optname] = value
|
2017-08-13 13:18:56 +02:00
|
|
|
app.config.set(optname, value)
|
2010-02-08 15:08:40 +01:00
|
|
|
else:
|
|
|
|
if (optname, key) not in seen:
|
2015-11-22 20:09:54 +01:00
|
|
|
if optname in self.old_values:
|
|
|
|
self.old_values[optname][key] = {}
|
|
|
|
else:
|
|
|
|
self.old_values[optname] = {key: {}}
|
2017-08-13 13:18:56 +02:00
|
|
|
app.config.add_per(optname, key)
|
2010-02-08 15:08:40 +01:00
|
|
|
seen.add((optname, key))
|
2015-11-22 20:09:54 +01:00
|
|
|
self.old_values[optname][key][subname] = value
|
2017-08-13 13:18:56 +02:00
|
|
|
app.config.set_per(optname, key, subname, value)
|
2010-02-08 15:08:40 +01:00
|
|
|
|
2017-08-13 13:18:56 +02:00
|
|
|
old_version = app.config.get('version')
|
2017-11-30 17:19:17 +01:00
|
|
|
if '+' in old_version:
|
|
|
|
old_version = old_version.split('+', 1)[0]
|
|
|
|
elif '-' in old_version:
|
|
|
|
old_version = old_version.split('-', 1)[0]
|
2010-02-08 15:08:40 +01:00
|
|
|
|
|
|
|
self.update_config(old_version, new_version)
|
|
|
|
self.old_values = {} # clean mem
|
|
|
|
|
|
|
|
fd.close()
|
|
|
|
return True
|
|
|
|
|
|
|
|
def write_line(self, fd, opt, parents, value):
|
|
|
|
if value is None:
|
|
|
|
return
|
|
|
|
# convert to utf8 before writing to file if needed
|
2013-01-01 21:06:16 +01:00
|
|
|
value = str(value)
|
2010-02-08 15:08:40 +01:00
|
|
|
s = ''
|
|
|
|
if parents:
|
|
|
|
if len(parents) == 1:
|
|
|
|
return
|
|
|
|
for p in parents:
|
|
|
|
s += p + '.'
|
|
|
|
s += opt
|
|
|
|
fd.write(s + ' = ' + value + '\n')
|
|
|
|
|
|
|
|
def write(self):
|
|
|
|
(base_dir, filename) = os.path.split(self.__filename)
|
|
|
|
self.__tempfile = os.path.join(base_dir, '.' + filename)
|
|
|
|
try:
|
2018-02-12 23:39:50 +01:00
|
|
|
with open(self.__tempfile, 'w', encoding='utf-8') as f:
|
|
|
|
app.config.foreach(self.write_line, f)
|
2013-01-01 23:18:36 +01:00
|
|
|
except IOError as e:
|
2010-02-08 15:08:40 +01:00
|
|
|
return str(e)
|
2018-02-12 23:39:50 +01:00
|
|
|
|
2010-02-08 15:08:40 +01:00
|
|
|
if os.path.exists(self.__filename):
|
2010-06-02 22:14:57 +02:00
|
|
|
if os.name == 'nt':
|
|
|
|
# win32 needs this
|
|
|
|
try:
|
|
|
|
os.remove(self.__filename)
|
2018-02-12 23:39:50 +01:00
|
|
|
except Exception as e:
|
|
|
|
return str(e)
|
2010-02-08 15:08:40 +01:00
|
|
|
try:
|
|
|
|
os.rename(self.__tempfile, self.__filename)
|
2013-01-01 23:18:36 +01:00
|
|
|
except IOError as e:
|
2010-02-08 15:08:40 +01:00
|
|
|
return str(e)
|
|
|
|
|
|
|
|
def update_config(self, old_version, new_version):
|
|
|
|
old_version_list = old_version.split('.') # convert '0.x.y' to (0, x, y)
|
|
|
|
old = []
|
|
|
|
while len(old_version_list):
|
|
|
|
old.append(int(old_version_list.pop(0)))
|
|
|
|
new_version_list = new_version.split('.')
|
|
|
|
new = []
|
|
|
|
while len(new_version_list):
|
|
|
|
new.append(int(new_version_list.pop(0)))
|
|
|
|
|
2010-09-08 15:25:36 +02:00
|
|
|
if old < [0, 14, 0, 1] and new >= [0, 14, 0, 1]:
|
|
|
|
self.update_config_to_01401()
|
2011-08-02 10:38:51 +02:00
|
|
|
if old < [0, 14, 90, 0] and new >= [0, 14, 90, 0]:
|
|
|
|
self.update_config_to_014900()
|
2014-12-27 16:44:05 +01:00
|
|
|
if old < [0, 16, 0, 1] and new >= [0, 16, 0, 1]:
|
|
|
|
self.update_config_to_01601()
|
2015-11-22 20:09:54 +01:00
|
|
|
if old < [0, 16, 4, 1] and new >= [0, 16, 4, 1]:
|
|
|
|
self.update_config_to_01641()
|
2016-03-30 21:19:13 +02:00
|
|
|
if old < [0, 16, 10, 1] and new >= [0, 16, 10, 1]:
|
|
|
|
self.update_config_to_016101()
|
2016-09-06 21:48:41 +02:00
|
|
|
if old < [0, 16, 10, 2] and new >= [0, 16, 10, 2]:
|
|
|
|
self.update_config_to_016102()
|
2017-03-13 19:33:16 +01:00
|
|
|
if old < [0, 16, 10, 3] and new >= [0, 16, 10, 3]:
|
|
|
|
self.update_config_to_016103()
|
2017-07-02 19:42:42 +02:00
|
|
|
if old < [0, 16, 10, 4] and new >= [0, 16, 10, 4]:
|
|
|
|
self.update_config_to_016104()
|
2017-07-10 20:47:49 +02:00
|
|
|
if old < [0, 16, 10, 5] and new >= [0, 16, 10, 5]:
|
|
|
|
self.update_config_to_016105()
|
2017-10-08 14:03:37 +02:00
|
|
|
if old < [0, 16, 11, 1] and new >= [0, 16, 11, 1]:
|
2017-09-16 17:29:34 +02:00
|
|
|
self.update_config_to_016111()
|
2017-11-13 19:20:11 +01:00
|
|
|
if old < [0, 16, 11, 2] and new >= [0, 16, 11, 2]:
|
|
|
|
self.update_config_to_016112()
|
2017-12-17 11:55:28 +01:00
|
|
|
if old < [0, 98, 2] and new >= [0, 98, 2]:
|
|
|
|
self.update_config_to_0982()
|
2018-02-04 17:02:02 +01:00
|
|
|
if old < [0, 98, 3] and new >= [0, 98, 3]:
|
|
|
|
self.update_config_to_0983()
|
2018-02-17 18:37:22 +01:00
|
|
|
if old < [0, 99, 2] and new >= [0, 99, 2]:
|
|
|
|
self.update_config_to_0992()
|
2010-02-08 15:08:40 +01:00
|
|
|
|
2017-08-13 13:18:56 +02:00
|
|
|
app.logger.init_vars()
|
|
|
|
app.logger.attach_cache_database()
|
|
|
|
app.config.set('version', new_version)
|
2010-02-08 15:08:40 +01:00
|
|
|
|
|
|
|
caps_cache.capscache.initialize_from_db()
|
|
|
|
|
2017-02-08 03:12:41 +01:00
|
|
|
@staticmethod
|
|
|
|
def assert_unread_msgs_table_exists():
|
2010-02-08 15:08:40 +01:00
|
|
|
"""
|
|
|
|
Create table unread_messages if there is no such table
|
|
|
|
"""
|
|
|
|
back = os.getcwd()
|
|
|
|
os.chdir(logger.LOG_DB_FOLDER)
|
|
|
|
con = sqlite.connect(logger.LOG_DB_FILE)
|
|
|
|
os.chdir(back)
|
|
|
|
cur = con.cursor()
|
|
|
|
try:
|
|
|
|
cur.executescript(
|
|
|
|
'''
|
2017-11-30 21:36:29 +01:00
|
|
|
CREATE TABLE IF NOT EXISTS unread_messages (
|
2010-02-08 15:08:40 +01:00
|
|
|
message_id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
|
|
|
|
jid_id INTEGER
|
|
|
|
);
|
|
|
|
'''
|
|
|
|
)
|
|
|
|
con.commit()
|
2017-08-13 13:18:56 +02:00
|
|
|
app.logger.init_vars()
|
2010-02-08 15:08:40 +01:00
|
|
|
except sqlite.OperationalError:
|
|
|
|
pass
|
|
|
|
con.close()
|
|
|
|
|
2017-02-08 03:12:41 +01:00
|
|
|
@staticmethod
|
|
|
|
def update_ft_proxies(to_remove=None, to_add=None):
|
|
|
|
if to_remove is None:
|
|
|
|
to_remove = []
|
|
|
|
if to_add is None:
|
|
|
|
to_add = []
|
2017-08-13 13:18:56 +02:00
|
|
|
for account in app.config.get_per('accounts'):
|
|
|
|
proxies_str = app.config.get_per('accounts', account,
|
2010-02-08 15:08:40 +01:00
|
|
|
'file_transfer_proxies')
|
|
|
|
proxies = [p.strip() for p in proxies_str.split(',')]
|
|
|
|
for wrong_proxy in to_remove:
|
|
|
|
if wrong_proxy in proxies:
|
|
|
|
proxies.remove(wrong_proxy)
|
|
|
|
for new_proxy in to_add:
|
|
|
|
if new_proxy not in proxies:
|
|
|
|
proxies.append(new_proxy)
|
|
|
|
proxies_str = ', '.join(proxies)
|
2017-08-13 13:18:56 +02:00
|
|
|
app.config.set_per('accounts', account, 'file_transfer_proxies',
|
2010-02-08 15:08:40 +01:00
|
|
|
proxies_str)
|
|
|
|
|
2017-11-30 21:36:29 +01:00
|
|
|
@staticmethod
|
|
|
|
def call_sql(db_path, sql):
|
|
|
|
con = sqlite.connect(db_path)
|
|
|
|
cur = con.cursor()
|
|
|
|
try:
|
|
|
|
cur.executescript(sql)
|
|
|
|
con.commit()
|
|
|
|
except sqlite.OperationalError as e:
|
|
|
|
if str(e).startswith('duplicate column name:'):
|
|
|
|
log.info(str(e))
|
|
|
|
else:
|
|
|
|
log.exception('Error')
|
|
|
|
con.close()
|
|
|
|
|
2010-09-08 15:25:36 +02:00
|
|
|
def update_config_to_01401(self):
|
|
|
|
if 'autodetect_browser_mailer' not in self.old_values or 'openwith' \
|
|
|
|
not in self.old_values or \
|
2011-09-02 07:23:31 +02:00
|
|
|
(self.old_values['autodetect_browser_mailer'] == False and \
|
2010-09-08 15:25:36 +02:00
|
|
|
self.old_values['openwith'] != 'custom'):
|
2017-08-13 13:18:56 +02:00
|
|
|
app.config.set('autodetect_browser_mailer', True)
|
|
|
|
app.config.set('openwith', app.config.DEFAULT_OPENWITH)
|
|
|
|
app.config.set('version', '0.14.0.1')
|
2011-08-02 10:38:51 +02:00
|
|
|
|
|
|
|
def update_config_to_014900(self):
|
|
|
|
if 'use_stun_server' in self.old_values and self.old_values[
|
|
|
|
'use_stun_server'] and not self.old_values['stun_server']:
|
2017-08-13 13:18:56 +02:00
|
|
|
app.config.set('use_stun_server', False)
|
2011-09-02 07:23:31 +02:00
|
|
|
if os.name == 'nt':
|
2017-08-13 13:18:56 +02:00
|
|
|
app.config.set('autodetect_browser_mailer', True)
|
2014-12-27 16:44:05 +01:00
|
|
|
|
|
|
|
def update_config_to_01601(self):
|
|
|
|
if 'last_mam_id' in self.old_values:
|
|
|
|
last_mam_id = self.old_values['last_mam_id']
|
2017-08-13 13:18:56 +02:00
|
|
|
for account in app.config.get_per('accounts'):
|
|
|
|
app.config.set_per('accounts', account, 'last_mam_id',
|
2014-12-27 16:44:05 +01:00
|
|
|
last_mam_id)
|
2017-08-13 13:18:56 +02:00
|
|
|
app.config.set('version', '0.16.0.1')
|
2015-11-22 20:09:54 +01:00
|
|
|
|
|
|
|
def update_config_to_01641(self):
|
|
|
|
for account in self.old_values['accounts'].keys():
|
|
|
|
connection_types = self.old_values['accounts'][account][
|
|
|
|
'connection_types'].split()
|
|
|
|
if 'plain' in connection_types and len(connection_types) > 1:
|
|
|
|
connection_types.remove('plain')
|
2017-08-13 13:18:56 +02:00
|
|
|
app.config.set_per('accounts', account, 'connection_types',
|
2015-11-22 20:09:54 +01:00
|
|
|
' '.join(connection_types))
|
2017-08-13 13:18:56 +02:00
|
|
|
app.config.set('version', '0.16.4.1')
|
2016-03-30 21:19:13 +02:00
|
|
|
|
|
|
|
def update_config_to_016101(self):
|
|
|
|
if 'video_input_device' in self.old_values:
|
|
|
|
if self.old_values['video_input_device'] == 'autovideosrc ! videoscale ! ffmpegcolorspace':
|
2017-08-13 13:18:56 +02:00
|
|
|
app.config.set('video_input_device', 'autovideosrc')
|
2016-03-30 21:19:13 +02:00
|
|
|
if self.old_values['video_input_device'] == 'videotestsrc is-live=true ! video/x-raw-yuv,framerate=10/1':
|
2017-08-13 13:18:56 +02:00
|
|
|
app.config.set('video_input_device', 'videotestsrc is-live=true ! video/x-raw,framerate=10/1')
|
|
|
|
app.config.set('version', '0.16.10.1')
|
2016-09-06 21:48:41 +02:00
|
|
|
|
|
|
|
def update_config_to_016102(self):
|
2016-09-05 19:02:43 +02:00
|
|
|
for account in self.old_values['accounts'].keys():
|
2017-08-13 13:18:56 +02:00
|
|
|
app.config.del_per('accounts', account, 'minimized_gc')
|
2016-09-06 21:48:41 +02:00
|
|
|
|
2017-11-30 21:36:29 +01:00
|
|
|
self.call_sql(logger.LOG_DB_PATH,
|
|
|
|
'''ALTER TABLE logs
|
2017-12-17 17:10:00 +01:00
|
|
|
ADD COLUMN 'additional_data' TEXT;'''
|
2017-11-30 21:36:29 +01:00
|
|
|
)
|
2017-08-13 13:18:56 +02:00
|
|
|
app.config.set('version', '0.16.10.2')
|
2017-03-13 19:33:16 +01:00
|
|
|
|
|
|
|
def update_config_to_016103(self):
|
2017-11-30 21:36:29 +01:00
|
|
|
self.call_sql(logger.LOG_DB_PATH,
|
|
|
|
'''ALTER TABLE logs ADD COLUMN 'stanza_id' TEXT;
|
|
|
|
ALTER TABLE logs ADD COLUMN 'encryption' TEXT;
|
|
|
|
ALTER TABLE logs ADD COLUMN 'encryption_state' TEXT;
|
|
|
|
ALTER TABLE logs ADD COLUMN 'marker' INTEGER;
|
|
|
|
'''
|
|
|
|
)
|
2017-08-13 13:18:56 +02:00
|
|
|
app.config.set('version', '0.16.10.3')
|
2017-07-02 19:42:42 +02:00
|
|
|
|
|
|
|
def update_config_to_016104(self):
|
2017-08-13 13:18:56 +02:00
|
|
|
app.config.set('emoticons_theme', 'noto-emoticons')
|
|
|
|
app.config.set('version', '0.16.10.4')
|
2017-07-10 20:47:49 +02:00
|
|
|
|
|
|
|
def update_config_to_016105(self):
|
2017-08-13 13:18:56 +02:00
|
|
|
app.config.set('muc_restore_timeout', -1)
|
|
|
|
app.config.set('restore_timeout', -1)
|
|
|
|
app.config.set('version', '0.16.10.5')
|
2017-09-16 17:29:34 +02:00
|
|
|
|
|
|
|
def update_config_to_016111(self):
|
2017-11-30 21:36:29 +01:00
|
|
|
self.call_sql(logger.CACHE_DB_PATH,
|
|
|
|
'''ALTER TABLE roster_entry ADD COLUMN 'avatar_sha' TEXT;
|
|
|
|
'''
|
|
|
|
)
|
2017-09-16 17:29:34 +02:00
|
|
|
app.config.set('version', '0.16.11.1')
|
2017-11-13 19:20:11 +01:00
|
|
|
|
|
|
|
def update_config_to_016112(self):
|
2017-11-30 21:36:29 +01:00
|
|
|
self.call_sql(logger.LOG_DB_PATH,
|
|
|
|
'''
|
|
|
|
CREATE TABLE IF NOT EXISTS last_archive_message(
|
|
|
|
jid_id INTEGER PRIMARY KEY UNIQUE,
|
|
|
|
last_mam_id TEXT,
|
|
|
|
oldest_mam_timestamp TEXT,
|
|
|
|
last_muc_timestamp TEXT
|
|
|
|
);
|
2017-12-17 11:34:54 +01:00
|
|
|
ALTER TABLE logs ADD COLUMN 'account_id' INTEGER;
|
2017-11-30 21:36:29 +01:00
|
|
|
'''
|
|
|
|
)
|
2017-11-13 19:20:11 +01:00
|
|
|
app.config.set('version', '0.16.11.2')
|
2017-12-17 11:55:28 +01:00
|
|
|
|
|
|
|
def update_config_to_0982(self):
|
|
|
|
# This fixes a typo in update_config_to_016112()
|
|
|
|
self.call_sql(logger.LOG_DB_PATH,
|
|
|
|
'''
|
|
|
|
ALTER TABLE logs ADD COLUMN 'account_id' INTEGER;
|
2017-12-17 17:10:00 +01:00
|
|
|
ALTER TABLE logs ADD COLUMN 'additional_data' TEXT;
|
2017-12-17 11:55:28 +01:00
|
|
|
'''
|
|
|
|
)
|
|
|
|
app.config.set('version', '0.98.2')
|
2018-02-04 17:02:02 +01:00
|
|
|
|
|
|
|
def update_config_to_0983(self):
|
|
|
|
for account in self.old_values['accounts'].keys():
|
|
|
|
password = self.old_values['accounts'][account]['password']
|
|
|
|
if password == "winvault:":
|
|
|
|
app.config.set_per('accounts', account, 'password', 'keyring:')
|
|
|
|
elif password == "libsecret:":
|
|
|
|
app.config.set_per('accounts', account, 'password', '')
|
|
|
|
app.config.set('version', '0.98.3')
|
2018-02-17 18:37:22 +01:00
|
|
|
|
|
|
|
def update_config_to_0992(self):
|
|
|
|
self.call_sql(logger.LOG_DB_PATH,
|
|
|
|
'''
|
|
|
|
CREATE INDEX IF NOT EXISTS
|
|
|
|
idx_logs_stanza_id ON logs (stanza_id);
|
|
|
|
'''
|
|
|
|
)
|
|
|
|
app.config.set('version', '0.99.2')
|