def from_conf(cls, path=None, **overrides): """Initialize instance from YAML configuration file, writing updates (only to keys, specified by "conf_update_keys") back to it.""" from onedrive import portalocker import yaml if path is None: path = cls.conf_path_default log.debug("Using default state-file path: %r", path) path = os.path.expanduser(path) with open(path, "rb") as src: portalocker.lock(src, portalocker.LOCK_SH) conf = yaml.load(src.read()) portalocker.unlock(src) conf.setdefault("conf_save", path) conf_cls = dict() for ns, keys in cls.conf_update_keys.viewitems(): for k in keys: try: v = conf.get(ns, dict()).get(k) except AttributeError: if not cls.conf_raise_structure_errors: raise raise KeyError( "Unable to get value for configuration parameter" ' "{k}" in section "{ns}", check configuration file (path: {path}) syntax' " near the aforementioned section/value.".format(ns=ns, k=k, path=path) ) if v is not None: conf_cls["{}_{}".format(ns, k)] = conf[ns][k] conf_cls.update(overrides) self = cls(**conf_cls) self.conf_save = conf["conf_save"] return self
def sync(self): if not self.conf_save: return from onedrive import portalocker import yaml retry = False with open(self.conf_save, "r+b") as src: portalocker.lock(src, portalocker.LOCK_SH) conf_raw = src.read() conf = yaml.load(io.BytesIO(conf_raw)) if conf_raw else dict() portalocker.unlock(src) conf_updated = False for ns, keys in self.conf_update_keys.viewitems(): for k in keys: v = getattr(self, "{}_{}".format(ns, k), None) if isinstance(v, unicode): v = v.encode("utf-8") if v != conf.get(ns, dict()).get(k): # log.debug( # 'Different val ({}.{}): {!r} != {!r}'\ # .format(ns, k, v, conf.get(ns, dict()).get(k)) ) conf.setdefault(ns, dict())[k] = v conf_updated = True if conf_updated: log.debug("Updating configuration file (%r)", src.name) conf_new = yaml.safe_dump(conf, default_flow_style=False) if os.name == "nt": # lockf + tempfile + rename doesn't work on windows due to # "[Error 32] ... being used by another process", # so this update can potentially leave broken file there # Should probably be fixed by someone who uses/knows about windows portalocker.lock(src, portalocker.LOCK_EX) src.seek(0) if src.read() != conf_raw: retry = True else: src.seek(0) src.truncate() src.write(conf_new) src.flush() portalocker.unlock(src) else: with tempfile.NamedTemporaryFile( prefix="{}.".format(basename(self.conf_save)), dir=dirname(self.conf_save), delete=False ) as tmp: try: portalocker.lock(src, portalocker.LOCK_EX) src.seek(0) if src.read() != conf_raw: retry = True else: portalocker.lock(tmp, portalocker.LOCK_EX) tmp.write(conf_new) os.fchmod(tmp.fileno(), stat.S_IMODE(os.fstat(src.fileno()).st_mode)) os.rename(tmp.name, src.name) # Non-atomic update for pids that already have fd to old file, # but (presumably) are waiting for the write-lock to be released src.seek(0) src.truncate() src.write(conf_new) finally: try: os.unlink(tmp.name) except OSError: pass if retry: log.debug("Configuration file (%r) was changed" " during merge, restarting merge", self.conf_save) return self.sync()