class RepoWriteLock: def __init__(self, url): self.url = url self.transport = None self.client = None self.socket = socket() def __enter__(self): self.socket.connect((interpret_urlish(self.url)[1], 22)) self.transport = Transport(self.socket) self.transport.start_client() authenticate_transport(self.transport) self.client = self.transport.open_sftp_client() self.client.chdir(interpret_urlish(self.url)[2]) self._lock() def _lock(self): target_locks = self.client.listdir("locks") target_locks.sort() if target_locks: raise RuntimeError("repo is locked; try again later") else: self.client.mkdir("locks/write_lock") def _unlock(self): self.client.rmdir("locks/write_lock") def __exit__(self, *args): self._unlock() self.client.close() self.transport.close() self.socket.close()
class Repository: def __init__(self, url): self.url = url self.index = {} self.read_lock = RepoReadLock(url) self.write_lock = RepoWriteLock(url) self.client = None self.transport = None self.socket = socket() self.opened = False def open(self): """ Opens the connection """ self.socket.connect((interpret_urlish(self.url)[1], 22)) self.transport = Transport(self.socket) self.transport.start_client() authenticate_transport(self.transport) self.client = self.transport.open_sftp_client() target_path = interpret_urlish(self.url)[2] try: self.client.stat(target_path) except IOError: self.client.mkdir(target_path) self.client.chdir(target_path) self.opened = True def close(self): """ Closes the connection """ if not self.opened: return self.client.close() self.transport.close() self.socket.close() self.opened = False def update(self): """ Update the information in this class to match that of the remote. Raises exceptions if remote is invalid or in an invalid state """ if not self.opened: self.open() with self.read_lock: with self.client.open("index.json") as f: self.index = json.load(f) def get_script(self, hname=None): """ Get the script object """ if hname is None: hname = self.index["start"] return self.index["scripts"][hname] def download_script(self, hname=None): if hname is None: hname = self.index["start"] with self.read_lock: with self.client.open("scripts/" + hname + ".py", "r") as f: return f.read() def get_revision(self): return self.index["revision"] def append_script(self, script_obj, script_contents): h = sha512() h.update(script_contents.encode("utf-7")) hname = h.hexdigest() script_obj["prev"] = self.index["end"] with self.write_lock: self.index["revision"] += 1 if self.index["end"]: self.index["scripts"][self.index["end"]]["next"] = hname self.index["end"] = hname self.index["scripts"][hname] = script_obj if self.index["start"] == "": self.index["start"] = hname self._write() with self.client.open("scripts/" + hname + ".py", "w") as f: f.write(script_contents) def _write(self): with self.client.open("index.json", "w") as f: json.dump(self.index, f) def write(self): with self.write_lock: self._write() def __iter__(self): return FollowChainIterator(self, self.index["start"]) def iterate_from(self, pos): return FollowChainIterator(self, pos) def new(self): if not self.opened: self.open() try: self.client.stat("index.json") raise RuntimeError( "already init-ed, manually delete the folder to re-init") except IOError: pass # create the skeleton fs self.client.mkdir("locks") self.client.mkdir("scripts") # create a basic index.json self.index = { "version": 1, "revision": 0, "start": "", "end": "", "scripts": {} } self.write()