diff --git a/bin/isrepo b/bin/isrepo index ccc0c41f7a0e35f0fc7858aa1238708d97670dfa..e625d4baec23ddfd20a236dfe35807e728e40f61 100755 --- a/bin/isrepo +++ b/bin/isrepo @@ -81,10 +81,10 @@ p_add = subparsers.add_parser("add", help = add.__doc__.lower()) p_add.add_argument("path") p_add.set_defaults(func = add) # Del command parser -#p_del = subparsers.add_parser("del", help=delete.__doc__.lower()) -#p_del.add_argument("image_name", type=str) -#p_del.add_argument("image_version", type=str) -#p_del.set_defaults(func=delete) +p_del = subparsers.add_parser("del", help = delete.__doc__.lower()) +p_del.add_argument("image_name") +p_del.add_argument("image_version") +p_del.set_defaults(func = delete) try: # Parse and run args = p_main.parse_args() diff --git a/installsystems/database.py b/installsystems/database.py index cd78da9357d71cad2f6a4ca7e0480553da8b908b..e7e8f111016ae3684a88756d35172d6b4d317dfb 100644 --- a/installsystems/database.py +++ b/installsystems/database.py @@ -50,26 +50,23 @@ class Database(object): self.conn = sqlite3.connect(self.path, isolation_level=None) self.conn.execute("PRAGMA foreign_keys = ON") - def get(self, name, version): - '''Return a description dict from a image name''' - # parse tarball - try: - self.file.seek(0) - tarball = Tarball.open(fileobj=self.file, mode="r:gz") - rdata = tarball.get_str("%s-%s" % (name, version)) - tarball.close() - except KeyError: - raise Exception("No image %s version %s in metadata" % (name, version)) - except Exception as e: - raise Exception("Unable to read db %s version %s: %s" % (name, version, e)) - # convert loaded data into dict (json parser) - try: - return json.loads(rdata) - except Exception as e: - raise Exception("Invalid metadata in image %s version %s: e" % (name, version, e)) + def begin(self): + ''' + Start a db transaction + ''' + self.conn.execute("BEGIN TRANSACTION") + + def commit(self): + ''' + Commit current db transaction + ''' + self.conn.execute("COMMIT TRANSACTION") + def ask(self, sql, args=()): - '''Ask question to db''' + ''' + Ask question to db + ''' return self.conn.execute(sql, args) def add(self, image): @@ -81,7 +78,7 @@ class Database(object): self.conn.execute("BEGIN TRANSACTION") # insert image information arrow("Add image metadata") - self.conn.execute("INSERT OR REPLACE INTO image values (?,?,?,?,?,?,?)", + self.conn.execute("INSERT INTO image values (?,?,?,?,?,?,?)", (image.md5, image.name, image.version, @@ -93,7 +90,7 @@ class Database(object): # insert data informations arrow("Add payload metadata") for name, obj in image.payload.items(): - self.conn.execute("INSERT OR REPLACE INTO payload values (?,?,?,?,?)", + self.conn.execute("INSERT INTO payload values (?,?,?,?,?)", (obj.md5, image.md5, name, @@ -106,25 +103,3 @@ class Database(object): arrowlevel(-1) except Exception as e: raise Exception("Adding metadata fail: %s" % e) - - def delete(self, name, version): - '''Delete a packaged image''' - arrow("Removing metadata from db") - # check locality - if istools.pathtype(self.path) != "file": - raise NotImplementedError("Database deletion must be local") - newdb_path = "%s.new" % self.path - fname = "%s-%s.json" % (name, version) - try: - db = Tarball.open(self.path, mode='r:gz') - newdb = Tarball.open(newdb_path, mode='w:gz') - for ti in db.getmembers(): - if ti.name != fname: - newdb.addfile(ti, db.extractfile(ti)) - db.close() - newdb.close() - # preserve permission and stats when moving - shutil.copystat(self.path, newdb_path) - os.rename(newdb_path, self.path) - except Exception as e: - raise Exception("Removing metadata fail: %s" % e) diff --git a/installsystems/printer.py b/installsystems/printer.py index 8f40c1afdb67a41b78431f81023635f8d14719c4..1ee21a62addc2fa602da5edbb99dea4f919e8dec 100644 --- a/installsystems/printer.py +++ b/installsystems/printer.py @@ -70,7 +70,7 @@ def debug(message, fd=sys.stderr, endl=os.linesep): if installsystems.debug: out("#light##black#%s#reset#" % message, fd, endl) -def arrowlevel(inc=None,level=None): +def arrowlevel(inc=None, level=None): global _arrow_level old_level = _arrow_level if level is not None: @@ -79,12 +79,11 @@ def arrowlevel(inc=None,level=None): _arrow_level = max(1, min(4, _arrow_level + inc)) return old_level -def arrow(message, level=None, fd=sys.stdout, endl=os.linesep): +def arrow(message, inclevel=None, level=None, fd=sys.stdout, endl=os.linesep): if installsystems.quiet: return - # set a one shot level - if level is not None: - old_level = arrowlevel(level=level) + # define new level + old_level = arrowlevel(inc=inclevel, level=level) if _arrow_level == 1: out("#light##red#=>#reset# %s" % message) elif _arrow_level == 2: @@ -94,5 +93,4 @@ def arrow(message, level=None, fd=sys.stdout, endl=os.linesep): elif _arrow_level == 4: out(" #light##green#=>#reset# %s" % message) # restore old on one shot level - if level is not None: - arrowlevel(level=old_level) + arrowlevel(level = old_level) diff --git a/installsystems/repository.py b/installsystems/repository.py index e6ee69e34cd25115edf9f68f56a1f16aaa2e10fb..7baa8a506e15c3aaa983064b8e5b757e2e5f4269 100644 --- a/installsystems/repository.py +++ b/installsystems/repository.py @@ -31,10 +31,12 @@ class Repository(object): @classmethod def create(cls, config): - '''Create an empty base repository''' + ''' + Create an empty base repository + ''' # check local repository if istools.pathtype(config.path) != "file": - raise NotImplementedError("Repository creation must be local") + raise Exception("Repository creation must be local") # create base directories arrow("Creating base directories") arrowlevel(1) @@ -58,10 +60,12 @@ class Repository(object): return self def update_last(self): - '''Update last file to current time''' + ''' + Update last file to current time + ''' # check local repository if istools.pathtype(self.config.path) != "file": - raise NotImplementedError("Repository addition must be local") + raise Exception("Repository addition must be local") try: arrow("Updating last file") last_path = os.path.join(self.config.path, self.config.lastname) @@ -71,7 +75,9 @@ class Repository(object): raise Exception("Update last file failed: %s" % e) def last(self): - '''Return the last value''' + ''' + Return the last value + ''' try: last_path = os.path.join(config.path, config.lastname) return int(istools.uopen(last_path, "r").read().rstrip()) @@ -80,10 +86,15 @@ class Repository(object): return 0 def add(self, image): - '''Add a packaged image to repository''' + ''' + Add a packaged image to repository + ''' # check local repository if istools.pathtype(self.config.path) != "file": - raise NotImplementedError("Repository addition must be local") + raise Exception("Repository addition must be local") + # cannot add already existant image + if self.has(image.name, image.version): + raise Exception("Image already in database, delete first!") # checking data tarballs md5 before copy image.check("Check image and payload before copy") # adding file to repository @@ -112,43 +123,60 @@ class Repository(object): self.update_last() def delete(self, name, version): - '''Delete an image from repository''' - raise NotImplementedError() + ''' + Delete an image from repository + ''' # check local repository if istools.pathtype(self.config.path) != "file": - raise NotImplementedError("Repository deletion must be local") - desc = self.db.find(name, version) - if desc is None: - error("Unable to find %s version %s in database" % (name, version)) - # removing script tarballs - arrow("Removing script tarball") - arrowlevel(1) - tpath = os.path.join(self.config.path, - "%s-%s%s" % (name, version, Image.extension)) - if os.path.exists(tpath): - os.unlink(tpath) - arrow("%s removed" % os.path.basename(tpath)) + raise Exception("Repository deletion must be local") + # get md5 of files related to images (exception is raised if not exists + md5s = self.getmd5(name, version) + # cleaning db (must be done before cleaning) + arrow("Cleaning database") + arrow("Remove payloads from database", 1) + self.db.begin() + for md5 in md5s[1:]: + self.db.ask("DELETE FROM payload WHERE md5 = ? AND image_md5 = ?", + (md5, md5s[0])).fetchone() + arrow("Remove image from database", 1) + self.db.ask("DELETE FROM image WHERE md5 = ?", + (md5s[0],)).fetchone() + self.db.commit() + # Removing script image + arrow("Removing files from pool") arrowlevel(1) - # removing data tarballs - arrow("Removing data tarballs") - arrowlevel(1) - for tb in self.db.databalls(name, version): - tpath = os.path.join(self.config.data, tb) - if os.path.exists(tpath): - os.unlink(tpath) - arrow("%s removed" % tb) + for md5 in md5s: + self._remove_file(md5) arrowlevel(-1) - # removing metadata - self.db.delete(name, version) # update last file - arrow("Updating last file") self.update_last() + def _remove_file(self, filename): + ''' + Remove a filename from pool. Check if it's not needed by db before + ''' + # check existance in table image + have = False + for table in ("image", "payload"): + have = have or self.db.ask("SELECT md5 FROM %s WHERE md5 = ? LIMIT 1" % table, + (filename,)).fetchone() is not None + # if no reference, delete! + if not have: + arrow("%s, deleted" % filename) + os.unlink(os.path.join(self.config.path, filename)) + else: + arrow("%s, skipped" % filename) + def has(self, name, version): - return self.db.ask("select name,version from image where name = ? and version = ? limit 1", (name,version)).fetchone() is not None + ''' + Return the existance of a package + ''' + return self.db.ask("SELECT name,version FROM image WHERE name = ? AND version = ? LIMIT 1", (name,version)).fetchone() is not None def get(self, name, version): - '''return a image from a name and version of pakage''' + ''' + return a image from a name and version + ''' # get file md5 from db r = self.db.ask("select md5 from image where name = ? and version = ? limit 1", (name,version)).fetchone() @@ -156,11 +184,29 @@ class Repository(object): raise Exception("No such image %s version %s" % name, version) path = os.path.join(self.config.path, r[0]) debug("Getting %s v%s from %s" % (name, version, path)) - return PackageImage(path, md5name=True) + pkg = PackageImage(path, md5name=True) + pkg.md5 = r[0] + return pkg + + def getmd5(self, name, version): + ''' + return a image md5 and payload md5 from name and version. Order matter ! + image md5 will still be the first + ''' + # get file md5 from db + a = self.db.ask("SELECT md5 FROM image WHERE name = ? AND version = ? LIMIT 1", + (name,version)).fetchone() + if a is None: + raise Exception("No such image %s version %s" % name, version) + b = self.db.ask("SELECT md5 FROM payload WHERE image_md5 = ?", + (a[0],)).fetchall() + return [ a[0] ] + [ x[0] for x in b ] def last(self, name): - '''Return last version of name in repo or -1 if not found''' - r = self.db.ask("select version from image where name = ? order by version desc limit 1", (name,)).fetchone() + ''' + Return last version of name in repo or -1 if not found + ''' + r = self.db.ask("SELECT version FROM image WHERE name = ? ORDER BY version DESC LIMIT 1", (name,)).fetchone() # no row => no way if r is None: return -1