tg-sync

git clone git://archive.git.mtrnord.blog/Nordgedanken/tg-sync.git
Log | Files | Refs | README

commit 326d293707557609967ddc616fac1e1d7c0a8a72
parent fd9f41806efd73682621fc0643c29061aab2c832
Author: Marcel <mtrnord1@gmail.com>
Date:   Fri, 27 Jan 2017 09:07:50 +0100

fix sync, configs and memory

Diffstat:
M__init__.py | 211++++++++++++++++++++++++++++---------------------------------------------------
A__pycache__/config.cpython-35.pyc | 0
Aconfig.py | 331+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adefaults/config.json | 12++++++++++++
Adefaults/memory.json | 3+++
5 files changed, 421 insertions(+), 136 deletions(-)

diff --git a/__init__.py b/__init__.py @@ -1,94 +1,28 @@ -#Import Modules that are needed +#Import external Modules that are needed import telegram.ext from telegram.ext import Updater, Filters from slackclient import SlackClient -import json -import os.path +import json, os.path, sys, traceback, functools, time, shutil from multiprocessing import Pool -import sys, traceback, functools, time - -class Config: - filename = 'config/config.json' - def generate(self): - data = {"TELEGRAM_API_KEY": "INSERT YOUR TELEGRAM API KEY HERE", "SLACK_API_KEY": "INSERT YOUR SLACK API KEY HERE", "plugins": []} - with open(self.filename, 'w') as config_file: - outfile.write(json.dumps(data, indent=4)) - - def get_by_path(self, keys_list): - with open(self.filename) as config_file: - data = json.load(config_file) - return functools.reduce(lambda d, k: d[int(k) if isinstance(d, list) else k], keys_list, data) - - def set_by_path(self, keys_list, value): - with open(self.filename, "r+") as config_file: - config_data = json.load(config_file) - config_data[keys_list[-1]] = value - - config_file.seek(0) # rewind - config_file.write(json.dumps(config_data)) - config_file.truncate() - - def remove(self, json_object): - with open(self.filename) as config_file: - config_data = json.load(config_file) - for item in config_data: - item.pop(config_data, None) - with open(self.filename, mode='w') as f: - f.write(json.dumps(item, indent=4)) - - def exists(self, keys_list): - _exists = True - try: - if self.get_by_path(keys_list) is None: - _exists = False - except (KeyError, TypeError): - _exists = False - - return _exists - -class Memory: - filename = 'config/memory.json' - def generate(self): - data = {} - with open(self.filename, 'w') as memory_file: - memory_file.write(json.dumps(data, indent=4)) - - def get_by_path(self, keys_list): - with open(self.filename) as memory_file: - data = json.load(memory_file) - return functools.reduce(lambda d, k: d[int(k) if isinstance(d, list) else k], keys_list, data) - - def set_by_path(self, keys_list, value): - with open(self.filename, "r+") as memory_file: - memory_data = json.load(memory_file) - memory_data[keys_list[:-1]][keys_list[-1]] = value - memory_file.seek(0) # rewind - memory_file.write(json.dumps(memory_data, indent=4)) - memory_file.truncate() - - def remove(self, json_object): - with open(self.filename) as memory_file: - memory_data = json.load(memory_file) - for item in memory_data: - item.pop(json_object, None) - with open(self.filename, mode='w') as f: - f.write(json.dumps(item, indent=4)) - - def exists(self, keys_list): - _exists = True +#Import internal Modules that are needed +import config +class Core: + def __init__(self): try: - if self.get_by_path(keys_list) is None: - _exists = False - except (KeyError, TypeError): - _exists = False + self.config = config.Config("config/config.json") + except ValueError: + logging.exception("failed to load config, malformed json") + sys.exit() - return _exists - -class Core: + try: + self.memory = config.Memory("config/memory.json") + except ValueError: + logging.exception("failed to load config, malformed json") + sys.exit() def slack_init(self): - api_key = Config().get_by_path(['SLACK_API_KEY']) + api_key = self.config.get_by_path(['SLACK_API_KEY']) sc = SlackClient(api_key) print("slack starting...") @@ -121,41 +55,48 @@ class Core: def slacksync(bot, update): params = update.message.text.split() - - if Memory().exists(['tg_sl-sync']['tg2sl']): - if Memory().exists(['tg_sl-sync']['sl2tg']): - tg2sl = Memory().get_by_path(['tg_sl-sync']['tg2sl']) - sl2tg = Memory().get_by_path(['tg_sl-sync']['sl2tg']) - - if update.message.chat_id in tg2sl: - print("tretre") - else: - try: - tg2sl[str(update.message.chat_id)] = params[1] - sl2tg[str(params[1])] = str(update.message.chat_id) - new_memory = {'tg2sl': tg2sl, 'sl2tg': sl2tg} - Memory().set_by_path(['tg_sl-sync'], new_memory) - update.message.reply_text('Saved sync!') - except Exception as e: - #print(e) - exc_type, exc_obj, exc_tb = sys.exc_info() - fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1] - print(exc_type, fname, exc_tb.tb_lineno) - traceback.print_exc() - update.message.reply_text('Failed to save sync!') - print("sync saved") + print(params[1]) + + try: + tg2sl = self.memory.get_by_path(['tg_sl-sync'])['tg2sl'] + sl2tg = self.memory.get_by_path(['tg_sl-sync'])['sl2tg'] + except Exception as e: + #print(e) + exc_type, exc_obj, exc_tb = sys.exc_info() + fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1] + print(exc_type, fname, exc_tb.tb_lineno) + traceback.print_exc() + update.message.reply_text('Failed to get memory. Please contact Admin!') + + if tg2sl: + print("works") + + if update.message.chat_id in tg2sl: + print("tretre") + else: + print("got sync request...") + try: + tg2sl[str(update.message.chat_id)] = params[1] + sl2tg[str(params[1])] = str(update.message.chat_id) + new_memory = {'tg2sl': tg2sl, 'sl2tg': sl2tg} + self.memory.set_by_path(['tg_sl-sync'], new_memory) + self.memory.save() + update.message.reply_text('Saved sync!') + except Exception as e: + #print(e) + exc_type, exc_obj, exc_tb = sys.exc_info() + fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1] + print(exc_type, fname, exc_tb.tb_lineno) + traceback.print_exc() + update.message.reply_text('Failed to save sync!') + print("sync saved") print("Telegram starting...") - api_key = Config().get_by_path(['TELEGRAM_API_KEY']) - if not Memory().exists(['tg_sl-sync']): + api_key = self.config.get_by_path(['TELEGRAM_API_KEY']) + if not self.memory.exists(['tg_sl-sync']): print('tg_sl-sync missing...') - Memory().set_by_path(['tg_sl-sync'], {}) - # if not Memory().exists(['tg_sl-sync']['sl2tg']): - # print("sl2tg missing...") - # Memory().set_by_path(['tg_sl-sync']['sl2tg'], {}) - # if not Memory().exists(['tg_sl-sync']['tg2sl']): - # print("tg2sl missing...") - # Memory().set_by_path(['tg_sl-sync']['tg2sl'], {}) + self.memory.set_by_path(['tg_sl-sync'], {'sl2tg':{}, 'tg2sl': {}}) + self.memory.save() updater = Updater(api_key) updater.dispatcher.add_handler(telegram.ext.MessageHandler(Filters.text, sync_handler)) updater.dispatcher.add_handler(telegram.ext.CommandHandler('slacksync', slacksync)) @@ -173,28 +114,26 @@ class Core: slack = pool.apply_async(self.slack_init(), []) # evaluate "solve2(B)" asynchronously if __name__ == "__main__": - - check_pass = 0 - if os.path.isdir("config"): - if os.path.isfile("config/config.json"): - check_pass = check_pass+1 - else: - check_pass = check_pass-1 - Config().generate() - - if os.path.isfile("config/memory.json"): - check_pass = check_pass+1 - else: - check_pass = check_pass-1 - Memory().generate() - - if check_pass == 2: - Core().init() - else: - print("Configs are regenrated. Please check the API Keys and restart the Bot.") + if not os.path.isfile('config/config.json'): + try: + shutil.copy('defaults/config.json', "config/config.json") + sys.exit('Please set Api Keys') + except (OSError, IOError) as e: + sys.exit('Failed to copy default config file: {}'.format(e)) + if not os.path.isfile('config/memory.json'): + try: + shutil.copy('defaults/memory.json', "config/memory.json") + except (OSError, IOError) as e: + sys.exit('Failed to copy default memory file: {}'.format(e)) else: os.mkdir("config") - Config().generate() - Memory().generate() - print("First run... Configs are genrated. Please add the API Keys and restart the Bot.") + if not os.path.isfile('config/config.json'): + try: + shutil.copy('defaults/config.json', "config/config.json") + sys.exit('Please set Api Keys') + except (OSError, IOError) as e: + sys.exit('Failed to copy default config file: {}'.format(e)) + + core = Core() + core.init() diff --git a/__pycache__/config.cpython-35.pyc b/__pycache__/config.cpython-35.pyc Binary files differ. diff --git a/config.py b/config.py @@ -0,0 +1,331 @@ +import collections, datetime, functools, json, glob, logging, os, shutil, sys, time + +from threading import Timer + + +logger = logging.getLogger(__name__) + + +class Config(collections.MutableMapping): + """Configuration JSON storage class""" + def __init__(self, filename, default=None, failsafe_backups=0, save_delay=0): + self.filename = filename + self.default = None + self.config = {} + self.changed = False + self.failsafe_backups = failsafe_backups + self.save_delay = save_delay + self.load() + + self._timer_save = False + + def _make_failsafe_backup(self): + try: + json.load(open(self.filename)) + except IOError: + return False + except ValueError: + logger.warning("{} is corrupted, aborting backup".format(self.filename)) + return False + + existing = sorted(glob.glob(self.filename + ".*.bak")) + while len(existing) > (self.failsafe_backups - 1): + os.remove(existing.pop(0)) + + backup_file = self.filename + "." + datetime.datetime.now().strftime("%Y%m%d%H%M%S") + ".bak" + shutil.copy2(self.filename, backup_file) + + return True + + def _recover_from_failsafe(self): + existing = sorted(glob.glob(self.filename + ".*.bak")) + while len(existing) > 0: + try: + recovery_filename = existing.pop() + json.load(open(recovery_filename)) + shutil.copy2(recovery_filename, self.filename) + self.load(recovery=True) + logger.info("recovery successful: {}".format(recovery_filename)) + return True + except IOError: + pass + except ValueError: + logger.error("corrupted recovery: {}".format(self.filename)) + return False + + def load(self, recovery=False): + """Load config from file""" + try: + self.config = json.load(open(self.filename)) + logger.info("{} read".format(self.filename)) + + except IOError: + self.config = {} + + except ValueError: + if not recovery and self.failsafe_backups > 0 and self._recover_from_failsafe(): + return + + raise + + self.changed = False + + def force_taint(self): + self.changed = True + + def loads(self, json_str): + """Load config from JSON string""" + self.config = json.loads(json_str) + self.changed = True + + def save(self, delay=True): + if self.save_delay: + if delay: + if self._timer_save and self._timer_save.is_alive(): + self._timer_save.cancel() + self._timer_save = Timer(self.save_delay, self.save, [], {"delay": False}) + self._timer_save.start() + return False + + """Save config to file (only if config has changed)""" + if self.changed: + start_time = time.time() + + if self.failsafe_backups: + self._make_failsafe_backup() + + with open(self.filename, 'w') as f: + json.dump(self.config, f, indent=2, sort_keys=True) + self.changed = False + interval = time.time() - start_time + + logger.info("{} write {}".format(self.filename, interval)) + + return self.changed + + def flush(self): + if self._timer_save and self._timer_save.is_alive(): + logger.info("flushing {}".format(self.filename)) + self._timer_save.cancel() + self.save(delay=False) + + def get_by_path(self, keys_list): + """Get item from config by path (list of keys)""" + return functools.reduce(lambda d, k: d[int(k) if isinstance(d, list) else k], keys_list, self) + + def set_by_path(self, keys_list, value): + """Set item in config by path (list of keys)""" + self.get_by_path(keys_list[:-1])[keys_list[-1]] = value + self.changed = True + + def pop_by_path(self, keys_list): + popped_value = self.get_by_path(keys_list[:-1]).pop(keys_list[-1]) + self.changed = True + return popped_value + + def get_option(self, keyname): + try: + value = self.config[keyname] + except KeyError: + value = None + return value + + def get_suboption(self, grouping, groupname, keyname): + try: + value = self.config[grouping][groupname][keyname] + except KeyError: + value = self.get_option(keyname) + return value + + def exists(self, keys_list): + _exists = True + + try: + if self.get_by_path(keys_list) is None: + _exists = False + except (KeyError, TypeError): + _exists = False + + return _exists + + def __getitem__(self, key): + try: + return self.config[key] + except KeyError: + return self.default + + def __setitem__(self, key, value): + self.config[key] = value + self.changed = True + + def __delitem__(self, key): + del self.config[key] + self.changed = True + + def __iter__(self): + return iter(self.config) + + def __len__(self): + return len(self.config) + +class Memory(collections.MutableMapping): + """Configuration JSON storage class""" + def __init__(self, filename, default=None, failsafe_backups=0, save_delay=0): + self.filename = filename + self.default = None + self.config = {} + self.changed = False + self.failsafe_backups = failsafe_backups + self.save_delay = save_delay + self.load() + + self._timer_save = False + + def _make_failsafe_backup(self): + try: + json.load(open(self.filename)) + except IOError: + return False + except ValueError: + logger.warning("{} is corrupted, aborting backup".format(self.filename)) + return False + + existing = sorted(glob.glob(self.filename + ".*.bak")) + while len(existing) > (self.failsafe_backups - 1): + os.remove(existing.pop(0)) + + backup_file = self.filename + "." + datetime.datetime.now().strftime("%Y%m%d%H%M%S") + ".bak" + shutil.copy2(self.filename, backup_file) + + return True + + def _recover_from_failsafe(self): + existing = sorted(glob.glob(self.filename + ".*.bak")) + while len(existing) > 0: + try: + recovery_filename = existing.pop() + json.load(open(recovery_filename)) + shutil.copy2(recovery_filename, self.filename) + self.load(recovery=True) + logger.info("recovery successful: {}".format(recovery_filename)) + return True + except IOError: + pass + except ValueError: + logger.error("corrupted recovery: {}".format(self.filename)) + return False + + def load(self, recovery=False): + """Load config from file""" + try: + self.config = json.load(open(self.filename)) + logger.info("{} read".format(self.filename)) + + except IOError: + self.config = {} + + except ValueError: + if not recovery and self.failsafe_backups > 0 and self._recover_from_failsafe(): + return + + raise + + self.changed = False + + def force_taint(self): + self.changed = True + + def loads(self, json_str): + """Load config from JSON string""" + self.config = json.loads(json_str) + self.changed = True + + def save(self, delay=True): + if self.save_delay: + if delay: + if self._timer_save and self._timer_save.is_alive(): + self._timer_save.cancel() + self._timer_save = Timer(self.save_delay, self.save, [], {"delay": False}) + self._timer_save.start() + return False + + """Save config to file (only if config has changed)""" + if self.changed: + start_time = time.time() + + if self.failsafe_backups: + self._make_failsafe_backup() + + with open(self.filename, 'w') as f: + json.dump(self.config, f, indent=2, sort_keys=True) + self.changed = False + interval = time.time() - start_time + + logger.info("{} write {}".format(self.filename, interval)) + + return self.changed + + def flush(self): + if self._timer_save and self._timer_save.is_alive(): + logger.info("flushing {}".format(self.filename)) + self._timer_save.cancel() + self.save(delay=False) + + def get_by_path(self, keys_list): + """Get item from config by path (list of keys)""" + return functools.reduce(lambda d, k: d[int(k) if isinstance(d, list) else k], keys_list, self) + + def set_by_path(self, keys_list, value): + """Set item in config by path (list of keys)""" + self.get_by_path(keys_list[:-1])[keys_list[-1]] = value + self.changed = True + + def pop_by_path(self, keys_list): + popped_value = self.get_by_path(keys_list[:-1]).pop(keys_list[-1]) + self.changed = True + return popped_value + + def get_option(self, keyname): + try: + value = self.config[keyname] + except KeyError: + value = None + return value + + def get_suboption(self, grouping, groupname, keyname): + try: + value = self.config[grouping][groupname][keyname] + except KeyError: + value = self.get_option(keyname) + return value + + def exists(self, keys_list): + _exists = True + + try: + if self.get_by_path(keys_list) is None: + _exists = False + except (KeyError, TypeError): + _exists = False + + return _exists + + def __getitem__(self, key): + try: + return self.config[key] + except KeyError: + return self.default + + def __setitem__(self, key, value): + self.config[key] = value + self.changed = True + + def __delitem__(self, key): + del self.config[key] + self.changed = True + + def __iter__(self): + return iter(self.config) + + def __len__(self): + return len(self.config) diff --git a/defaults/config.json b/defaults/config.json @@ -0,0 +1,12 @@ +{ + "admins": [ ], + "commands_user": [ ], + "commands_enabled": true, + "link_to_guide": "https://github.com/hangoutsbot/hangoutsbot/wiki/User-Guide", + "silentmode": false, + "plugins": [ + + ], + "TELEGRAM_API_KEY": "SET TELEGRAM API KEY HERE", + "SLACK_API_KEY": "SET SLACK API KEY HERE" +} diff --git a/defaults/memory.json b/defaults/memory.json @@ -0,0 +1,3 @@ +{ + +}