def set_task(self, task): """ Propagate a change in local tasks into server """ if self._parameters["state"] != "online": return sync_tags = self._xmpp.get_synchronized_tags() tags = task.get_tags_name() tag_overlap = set(sync_tags) & set(tags) if not tag_overlap: return if task.get_id() in self._changed_remotely: self._changed_remotely.remove(task.get_id()) return Log.info("GTG --set--> PubSub: [%s] '%s'" % (task.get_id(), task.get_title())) doc = xml.dom.minidom.parseString("<task></task>") task_id = task.get_id() task_xml = task_to_xml(doc, task).toxml() tags = task.get_tags_name() self._xmpp.set_task(task_id, tags, task_xml) self._sync_tasks.add(task_id) self._changed_locally.add(task_id)
def on_remote_set_task(self, task_id, raw_xml): """ Task was set on server """ if self._parameters["state"] != "online": return if task_id in self._changed_locally: self._changed_locally.remove(task_id) return Log.info("PubSub --set--> GTG: [%s]" % task_id) # Parse XML string into <task> node xmldoc = xml.dom.minidom.parseString(raw_xml) task_xml = xmldoc.getElementsByTagName("task")[0] self._changed_remotely.add(task_id) task = self.datastore.get_task(task_id) if task: # Already exists task = task_from_xml(task, task_xml) else: # New task task = self.datastore.task_factory(task_id) task = task_from_xml(task, task_xml) self.datastore.push_task(task)
def on_connected(self): """ Get the initial set of the tasks from the XMPP """ if self._parameters["state"] != "onine": self._parameters["state"] = "online" # Ensure all teams tag_tree = self.datastore.get_tagstore().get_main_view() for tag_id in tag_tree.get_all_nodes(): tag = self.datastore.get_tag(tag_id) team = tag.get_people_shared_with() if len(team) > 0: self._xmpp.ensure_team(tag_id, team) # Fetch initial tasks for task_id, tag, raw_xml in self._xmpp.get_tasks(): # Parse the raw_xml by DOM doc = xml.dom.minidom.parseString(raw_xml) task_xml = doc.getElementsByTagName("task")[0] # Create a new task or return the existing one with the same id task = self.datastore.task_factory(task_id) task = task_from_xml(task, task_xml) task.add_tag(tag) self.datastore.push_task(task) self._sync_tasks.add(task_id) Log.info("(init) PubSub --set--> GTG: [%s] '%s'" % (task_id, task.get_title()))
def save_state(self): """ The last function before shutting this backend down. Disconnect XMPP and store picked_file """ Log.info("Quitting backend") self._xmpp.disconnect() Log.info("Backend is shut down")
def _get_avatar(self, jid): """ Return avatar for jid if it is possible. The avatar is cached for the future runs. """ if not os.path.exists(self.AVATARS_DIR): os.makedirs(self.AVATARS_DIR) # If avatar was cached, return it avatars = glob.glob(os.path.join(self.AVATARS_DIR, jid + ".*")) if len(avatars) > 0: return avatars[0] # Download vCard and avatar in it vcard = self['xep_0054'].get_vcard(jid) img_type = vcard['vcard_temp']['PHOTO']['TYPE'] photo = vcard['vcard_temp']['PHOTO']['BINVAL'] # Determine a name for the file if not img_type.startswith("image/") or " " in img_type: return None suffix = img_type[len("image/"):] name = os.path.join(self.AVATARS_DIR, "%s.%s" % (jid, suffix)) Log.info("Saving avatar for '%s'" % jid) with open(name, 'wb') as avatar_file: avatar_file.write(photo) return name
def _discover_nodes(self): """ Discover all nodes user can access """ subscriptions = self._get_subscribed_nodes() self._nodes = {} affiliations = self['xep_0060'].get_affiliations(self._pubsub) affiliations = affiliations['pubsub']['affiliations'] if 'substanzas' not in affiliations.values: # No nodes available return for affiliation in affiliations.values['substanzas']: affiliation, node = affiliation['affiliation'], affiliation['node'] if affiliation == 'owner' and node.startswith('GTG_'): # Check node config config = self['xep_0060'].get_node_config(self._pubsub, node) values = config['pubsub_owner']['configure']['form']['values'] form = xep_0004.Form() form.add_field( var='FORM_TYPE', type='hidden', value='http://jabber.org/protocol/pubsub#node_config') if int(values['pubsub#max_items']) < self.MAX_ITEMS: Log.info("Max items is set only to %s" % values['pubsub#max_items']) form.add_field(var='pubsub#max_items', value=str(self.MAX_ITEMS)) if values['pubsub#access_model'] != 'whitelist': form.add_field(var='pubsub#access_model', value='whitelist') if not values['pubsub#notify_delete']: form.add_field(var='pubsub#notify_delete', value='1') if not values['pubsub#notify_config']: form.add_field(var='pubsub#notify_config', value='1') m = re.match('Project (@\w+)', values['pubsub#title']) if not m: Log.warning("Malformed node name '%s'" % values['pubsub#title']) continue project_name = m.group(1) self._nodes[node] = project_name Log.info("Discovered project '%s'" % project_name) if len(form.field) > 1: form['type'] = 'submit' self['xep_0060'].set_node_config(self._pubsub, node, form) if node not in subscriptions: self['xep_0060'].subscribe(self._pubsub, node) # Find teammates for cache self._teams[node] = self._get_teammates(node)
def openxmlfile(zefile, root): """ Open an XML file in a robust way If file could not be opened, try: - file__ - file.bak.0 - file.bak.1 - .... until BACKUP_NBR If file doesn't exist, create a new file """ tmpfile = zefile + '__' try: if os.path.exists(zefile): return _try_openxmlfile(zefile, root) elif os.path.exists(tmpfile): Log.warning("Something happened to %s. Using backup" % zefile) os.rename(tmpfile, zefile) return _try_openxmlfile(zefile, root) else: # Creating empty file doc, xmlproject = emptydoc(root) newfile = savexml(zefile, doc) if not newfile: Log.error("Could not create a new file %s" % zefile) sys.exit(1) return _try_openxmlfile(zefile, root) except IOError as msg: print(msg) sys.exit(1) except xml.parsers.expat.ExpatError as msg: errormsg = "Error parsing XML file %s: %s" % (zefile, msg) Log.error(errormsg) if os.path.exists(tmpfile): Log.warning("Something happened to %s. Using backup" % zefile) os.rename(tmpfile, zefile) # Ok, try one more time now try: return _try_openxmlfile(zefile, root) except Exception as msg: Log.warning('Failed with reason: %s' % msg) # Try to revert to backup backup_name = _get_backup_name(zefile) for i in range(BACKUP_NBR): backup_file = "%s.bak.%d" % (backup_name, i) if os.path.exists(backup_file): Log.info("Trying to restore backup file %s" % backup_file) try: return _try_openxmlfile(backup_file, root) except Exception as msg: Log.warning('Failed with reason: %s' % msg) Log.info("No suitable backup was found") sys.exit(1)
def _discover_nodes(self): """ Discover all nodes user can access """ subscriptions = self._get_subscribed_nodes() self._nodes = {} affiliations = self['xep_0060'].get_affiliations(self._pubsub) affiliations = affiliations['pubsub']['affiliations'] if 'substanzas' not in affiliations.values: # No nodes available return for affiliation in affiliations.values['substanzas']: affiliation, node = affiliation['affiliation'], affiliation['node'] if affiliation == 'owner' and node.startswith('GTG_'): # Check node config config = self['xep_0060'].get_node_config(self._pubsub, node) values = config['pubsub_owner']['configure']['form']['values'] form = xep_0004.Form() form.add_field(var='FORM_TYPE', type='hidden', value='http://jabber.org/protocol/pubsub#node_config') if int(values['pubsub#max_items']) < self.MAX_ITEMS: Log.info("Max items is set only to %s" % values['pubsub#max_items']) form.add_field(var='pubsub#max_items', value=str(self.MAX_ITEMS)) if values['pubsub#access_model'] != 'whitelist': form.add_field(var='pubsub#access_model', value='whitelist') if not values['pubsub#notify_delete']: form.add_field(var='pubsub#notify_delete', value='1') if not values['pubsub#notify_config']: form.add_field(var='pubsub#notify_config', value='1') m = re.match('Project (@\w+)', values['pubsub#title']) if not m: Log.warning("Malformed node name '%s'" % values['pubsub#title']) continue project_name = m.group(1) self._nodes[node] = project_name Log.info("Discovered project '%s'" % project_name) if len(form.field) > 1: form['type'] = 'submit' self['xep_0060'].set_node_config(self._pubsub, node, form) if node not in subscriptions: self['xep_0060'].subscribe(self._pubsub, node) # Find teammates for cache self._teams[node] = self._get_teammates(node)
def get_tasks(self): """ Return list of all available tasks """ Log.info("Looking for available tasks") for node in self._nodes: project_tag = self._nodes[node] result_items = self['xep_0060'].get_items(self._pubsub, node, max_items=self.MAX_ITEMS, block=True) items = result_items['pubsub']['items']['substanzas'] for item in items: yield item['id'], project_tag, tostring(item['payload'])
def remove_task(self, task_id): """ After removing local task remove tasks from the server """ if self._parameters["state"] != "online": return if task_id in self._sync_tasks: Log.info("GTG --del--> PubSub: [%s]" % task_id) self._xmpp.delete_task(task_id) self._sync_tasks.remove(task_id) if task_id in self._changed_locally: self._changed_locally.remove(task_id) if task_id in self._changed_remotely: self._changed_remotely.remove(task_id)
def on_new_remote_project(self, tag_id, team_jids): """ New project was added on server """ if self._parameters["state"] != "online": return team = [unicode(member) for member in team_jids] Log.info("Pubsub --new project--> GTG: %s, teammates: %s" % (tag_id, team)) tag = self.datastore.get_tag(tag_id) if tag is None: tag = self.datastore.new_tag(tag_id) tag.set_people_shared_with(team)
def on_remote_rm_task(self, task_id): """ Task was removed on server """ if task_id not in self._sync_tasks: # This task is not synchronized via this synchronization service return if task_id in self._changed_locally: self._changed_locally.remove(task_id) if task_id in self._changed_remotely: self._changed_remotely.remove(task_id) Log.info("PubSub --del--> GTG: [%s]" % task_id) self.datastore.request_task_deletion(task_id) if task_id in self._sync_tasks: self._sync_tasks.remove(task_id)
def ensure_team(self, tag, team): """ Set the team members of the tag If a node for the tag doesn't exists, create it. """ Log.info("Set team for tag '%s' to '%s'" % (tag, team)) team_node = None for node, associated_tag in self._nodes.items(): if associated_tag == tag: team_node = node break if team_node is None: team_node = self._create_node(tag) self._set_teammates(team_node, team)
def _create_node(self, tag): """ Create a new node for tag """ name = 'GTG_%s' % uuid.uuid4() form = xep_0004.Form() form.add_field(var='pubsub#max_items', value=str(self.MAX_ITEMS)) form.add_field(var='pubsub#access_model', value='whitelist') form.add_field(var='pubsub#notify_delete', value='1') form.add_field(var='pubsub#notify_config', value='1') title = "Project %s" % tag form.add_field(var='pubsub#title', value=title) Log.info("Creating node '%s' for tag %s" % (name, tag)) self['xep_0060'].create_node(self._pubsub, name, config=form) self['xep_0060'].subscribe(self._pubsub, name) self._nodes[name] = tag return name
def _on_start(self, event): """ Do stuff after connection Fetch a list of assigned nodes, create a home node if needed. Get avatars if there are not available. """ Log.info("Connected to PubSub") # Get roster notifications self.get_roster() self.send_presence() # Discover nodes and notify GTG about project nodes self._discover_nodes() for node, name in self._nodes.items(): self._callback("project_tag", name, self._get_teammates(node)) self._callback("connected")
def _load_pickled_file(self, path, default_value=None): ''' A helper function to load some object from a file. @param path: the relative path of the file @param default_value: the value to return if the file is missing or corrupt @returns object: the needed object, or default_value ''' path = os.path.join(CoreConfig().get_data_dir(), path) if not os.path.exists(path): return default_value with open(path, 'r') as file: try: return pickle.load(file) except Exception: Log.error("Pickle file for backend '%s' is damaged" % self.get_name()) # Loading file failed, trying backups for i in range(1, PICKLE_BACKUP_NBR + 1): backup_file = "%s.bak.%d" % (path, i) if os.path.exists(backup_file): with open(backup_file, 'r') as file: try: data = pickle.load(file) Log.info("Succesfully restored backup #%d for '%s'" % (i, self.get_name())) return data except Exception: Log.error("Backup #%d for '%s' is damaged as well" % (i, self.get_name())) # Data could not be loaded, degrade to default data Log.error("There is no suitable backup for '%s', " "loading default data" % self.get_name()) return default_value
def openxmlfile(zefile, root): """ Open an XML file in a robust way If file could not be opened, try: - file__ - file.bak.0 - file.bak.1 - .... until BACKUP_NBR If file doesn't exist, create a new file """ # reset _USED_BACKUP and _BACKUP_FILE_INFO global _USED_BACKUP global _BACKUP_FILE_INFO _USED_BACKUP = False _BACKUP_FILE_INFO = "" tmpfile = zefile + '__' try: if os.path.exists(zefile): return _try_openxmlfile(zefile, root) elif os.path.exists(tmpfile): Log.warning("Something happened to %s. Using backup" % zefile) os.rename(tmpfile, zefile) _USED_BACKUP = True _BACKUP_FILE_INFO = "Recovered from backup made on: " + \ datetime.datetime.fromtimestamp( os.path.getmtime(tmpfile)).strftime('%Y-%m-%d') return _try_openxmlfile(zefile, root) except IOError as msg: print(msg) sys.exit(1) except xml.parsers.expat.ExpatError as msg: errormsg = "Error parsing XML file %s: %s" % (zefile, msg) Log.error(errormsg) if os.path.exists(tmpfile): Log.warning("Something happened to %s. Using backup" % zefile) os.rename(tmpfile, zefile) _USED_BACKUP = True _BACKUP_FILE_INFO = "Recovered from backup made on: " + \ datetime.datetime.fromtimestamp( os.path.getmtime(tmpfile)).strftime('%Y-%m-%d') # Ok, try one more time now try: return _try_openxmlfile(zefile, root) except Exception as msg: Log.warning('Failed with reason: %s' % msg) # Try to revert to backup backup_name = _get_backup_name(zefile) for i in range(BACKUP_NBR): backup_file = "%s.bak.%d" % (backup_name, i) if os.path.exists(backup_file): Log.info("Trying to restore backup file %s" % backup_file) _USED_BACKUP = True _BACKUP_FILE_INFO = "Recovered from backup made on: " + \ datetime.datetime.fromtimestamp( os.path.getmtime(backup_file)).strftime('%Y-%m-%d') try: return _try_openxmlfile(backup_file, root) except Exception as msg: Log.warning('Failed with reason: %s' % msg) Log.info("No suitable backup was found") # Creating empty file doc, xmlproject = emptydoc(root) newfile = savexml(zefile, doc) if not newfile: Log.error("Could not create a new file %s" % zefile) sys.exit(1) # set _USED_BACKUP even if there's a failure to notify about the same _USED_BACKUP = True _BACKUP_FILE_INFO = "No backups found. Created a new file" return _try_openxmlfile(zefile, root) # exit if execution reached this statement sys.exit(1)
Log.error(errormsg) if os.path.exists(tmpfile): Log.warning("Something happened to %s. Using backup" % zefile) os.rename(tmpfile, zefile) # Ok, try one more time now try: return _try_openxmlfile(zefile, root) except Exception, msg: Log.warning('Failed with reason: %s' % msg) # Try to revert to backup backup_name = _get_backup_name(zefile) for i in range(BACKUP_NBR): backup_file = "%s.bak.%d" % (backup_name, i) if os.path.exists(backup_file): Log.info("Trying to restore backup file %s" % backup_file) try: return _try_openxmlfile(backup_file, root) except Exception, msg: Log.warning('Failed with reason: %s' % msg) Log.info("No suitable backup was found") sys.exit(1) # Return a doc element with only one root element of the name "root" def emptydoc(root): doc = xml.dom.minidom.Document() rootproject = doc.createElement(root)
class FallbackKeyring(Borg): def __init__(self): super(Keyring, self).__init__() if not hasattr(self, "keyring"): self.keyring = {} self.max_key = 1 def set_password(self, name, password, userid=""): """ This implementation does nto need name and userid. It is there because of GNOMEKeyring """ # Find unused key while self.max_key in self.keyring: self.max_key += 1 self.keyring[self.max_key] = password return self.max_key def get_password(self, key): return self.keyring.get(key, "") if gnomekeyring is not None: Keyring = GNOMEKeyring else: Log.info("GNOME keyring was not found, passwords will be not stored after\ restart of GTG") Keyring = FallbackKeyring
Log.error(errormsg) if os.path.exists(tmpfile): Log.warning("Something happened to %s. Using backup" % zefile) os.rename(tmpfile, zefile) # Ok, try one more time now try: return _try_openxmlfile(zefile, root) except Exception, msg: Log.warning('Failed with reason: %s' % msg) # Try to revert to backup backup_name = _get_backup_name(zefile) for i in range(BACKUP_NBR): backup_file = "%s.bak.%d" % (backup_name, i) if os.path.exists(backup_file): Log.info("Trying to restore backup file %s" % backup_file) try: return _try_openxmlfile(backup_file, root) except Exception, msg: Log.warning('Failed with reason: %s' % msg) Log.info("No suitable backup was found") sys.exit(1) # Return a doc element with only one root element of the name "root" def emptydoc(root): doc = xml.dom.minidom.Document() rootproject = doc.createElement(root) doc.appendChild(rootproject)
def on_task_deleted(self, task_id, path): """ Stop tracking a deleted task if it is being tracked """ Log.info('Hamster: task deleted %s', task_id) self.stop_task(task_id)
class FallbackKeyring(Borg): def __init__(self): super(Keyring, self).__init__() if not hasattr(self, "keyring"): self.keyring = {} self.max_key = 1 def set_password(self, name, password, userid=""): """ This implementation does nto need name and userid. It is there because of GNOMEKeyring """ # Find unused key while self.max_key in self.keyring: self.max_key += 1 self.keyring[self.max_key] = password return self.max_key def get_password(self, key): return self.keyring.get(key, "") if GnomeKeyring is not None: Keyring = GNOMEKeyring else: Log.info("GNOME keyring was not found, passwords will be not stored after\ restart of GTG") Keyring = FallbackKeyring