summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTomas Touceda <chiiph@gentoo.org>2011-06-23 00:10:30 -0300
committerTomas Touceda <chiiph@gentoo.org>2011-06-23 00:10:30 -0300
commit2bb0d22585d1b75c48c28ffb11743e8de44cf08a (patch)
treec84bba124f8ffc54f216aaa37af727ce8f6d80e7
parentcf45099e2196a239feedebbb2f2c6ec2f1989894 (diff)
Validate files in a thp and add LockFile bundle dep
-rw-r--r--lib/thandy/lockfile/__init__.py286
-rw-r--r--lib/thandy/lockfile/linklockfile.py69
-rw-r--r--lib/thandy/lockfile/mkdirlockfile.py79
-rw-r--r--lib/thandy/lockfile/pidlockfile.py189
-rw-r--r--lib/thandy/lockfile/sqlitelockfile.py146
-rw-r--r--lib/thandy/packagesys/ThpPackages.py36
6 files changed, 801 insertions, 4 deletions
diff --git a/lib/thandy/lockfile/__init__.py b/lib/thandy/lockfile/__init__.py
new file mode 100644
index 0000000..a167cd8
--- /dev/null
+++ b/lib/thandy/lockfile/__init__.py
@@ -0,0 +1,286 @@
+
+"""
+lockfile.py - Platform-independent advisory file locks.
+
+Requires Python 2.5 unless you apply 2.4.diff
+Locking is done on a per-thread basis instead of a per-process basis.
+
+Usage:
+
+>>> lock = LockFile('somefile')
+>>> try:
+... lock.acquire()
+... except AlreadyLocked:
+... print 'somefile', 'is locked already.'
+... except LockFailed:
+... print 'somefile', 'can\\'t be locked.'
+... else:
+... print 'got lock'
+got lock
+>>> print lock.is_locked()
+True
+>>> lock.release()
+
+>>> lock = LockFile('somefile')
+>>> print lock.is_locked()
+False
+>>> with lock:
+... print lock.is_locked()
+True
+>>> print lock.is_locked()
+False
+
+>>> lock = LockFile('somefile')
+>>> # It is okay to lock twice from the same thread...
+>>> with lock:
+... lock.acquire()
+...
+>>> # Though no counter is kept, so you can't unlock multiple times...
+>>> print lock.is_locked()
+False
+
+Exceptions:
+
+ Error - base class for other exceptions
+ LockError - base class for all locking exceptions
+ AlreadyLocked - Another thread or process already holds the lock
+ LockFailed - Lock failed for some other reason
+ UnlockError - base class for all unlocking exceptions
+ AlreadyUnlocked - File was not locked.
+ NotMyLock - File was locked but not by the current thread/process
+"""
+
+import sys
+import socket
+import os
+import threading
+import time
+import urllib
+import warnings
+
+# Work with PEP8 and non-PEP8 versions of threading module.
+if not hasattr(threading, "current_thread"):
+ threading.current_thread = threading.currentThread
+if not hasattr(threading.Thread, "get_name"):
+ threading.Thread.get_name = threading.Thread.getName
+
+__all__ = ['Error', 'LockError', 'LockTimeout', 'AlreadyLocked',
+ 'LockFailed', 'UnlockError', 'NotLocked', 'NotMyLock',
+ 'LinkLockFile', 'MkdirLockFile', 'SQLiteLockFile',
+ 'LockBase']
+
+class Error(Exception):
+ """
+ Base class for other exceptions.
+
+ >>> try:
+ ... raise Error
+ ... except Exception:
+ ... pass
+ """
+ pass
+
+class LockError(Error):
+ """
+ Base class for error arising from attempts to acquire the lock.
+
+ >>> try:
+ ... raise LockError
+ ... except Error:
+ ... pass
+ """
+ pass
+
+class LockTimeout(LockError):
+ """Raised when lock creation fails within a user-defined period of time.
+
+ >>> try:
+ ... raise LockTimeout
+ ... except LockError:
+ ... pass
+ """
+ pass
+
+class AlreadyLocked(LockError):
+ """Some other thread/process is locking the file.
+
+ >>> try:
+ ... raise AlreadyLocked
+ ... except LockError:
+ ... pass
+ """
+ pass
+
+class LockFailed(LockError):
+ """Lock file creation failed for some other reason.
+
+ >>> try:
+ ... raise LockFailed
+ ... except LockError:
+ ... pass
+ """
+ pass
+
+class UnlockError(Error):
+ """
+ Base class for errors arising from attempts to release the lock.
+
+ >>> try:
+ ... raise UnlockError
+ ... except Error:
+ ... pass
+ """
+ pass
+
+class NotLocked(UnlockError):
+ """Raised when an attempt is made to unlock an unlocked file.
+
+ >>> try:
+ ... raise NotLocked
+ ... except UnlockError:
+ ... pass
+ """
+ pass
+
+class NotMyLock(UnlockError):
+ """Raised when an attempt is made to unlock a file someone else locked.
+
+ >>> try:
+ ... raise NotMyLock
+ ... except UnlockError:
+ ... pass
+ """
+ pass
+
+class LockBase:
+ """Base class for platform-specific lock classes."""
+ def __init__(self, path, threaded=True):
+ """
+ >>> lock = LockBase('somefile')
+ >>> lock = LockBase('somefile', threaded=False)
+ """
+ self.path = path
+ self.lock_file = os.path.abspath(path) + ".lock"
+ self.hostname = socket.gethostname()
+ self.pid = os.getpid()
+ if threaded:
+ t = threading.current_thread()
+ # Thread objects in Python 2.4 and earlier do not have ident
+ # attrs. Worm around that.
+ ident = getattr(t, "ident", hash(t))
+ self.tname = "-%x" % (ident & 0xffffffff)
+ else:
+ self.tname = ""
+ dirname = os.path.dirname(self.lock_file)
+ self.unique_name = os.path.join(dirname,
+ "%s%s.%s" % (self.hostname,
+ self.tname,
+ self.pid))
+
+ def acquire(self, timeout=None):
+ """
+ Acquire the lock.
+
+ * If timeout is omitted (or None), wait forever trying to lock the
+ file.
+
+ * If timeout > 0, try to acquire the lock for that many seconds. If
+ the lock period expires and the file is still locked, raise
+ LockTimeout.
+
+ * If timeout <= 0, raise AlreadyLocked immediately if the file is
+ already locked.
+ """
+ raise NotImplemented("implement in subclass")
+
+ def release(self):
+ """
+ Release the lock.
+
+ If the file is not locked, raise NotLocked.
+ """
+ raise NotImplemented("implement in subclass")
+
+ def is_locked(self):
+ """
+ Tell whether or not the file is locked.
+ """
+ raise NotImplemented("implement in subclass")
+
+ def i_am_locking(self):
+ """
+ Return True if this object is locking the file.
+ """
+ raise NotImplemented("implement in subclass")
+
+ def break_lock(self):
+ """
+ Remove a lock. Useful if a locking thread failed to unlock.
+ """
+ raise NotImplemented("implement in subclass")
+
+ def __enter__(self):
+ """
+ Context manager support.
+ """
+ self.acquire()
+ return self
+
+ def __exit__(self, *_exc):
+ """
+ Context manager support.
+ """
+ self.release()
+
+def _fl_helper(cls, mod, *args, **kwds):
+ warnings.warn("Import from %s module instead of lockfile package" % mod,
+ DeprecationWarning, stacklevel=2)
+ # This is a bit funky, but it's only for awhile. The way the unit tests
+ # are constructed this function winds up as an unbound method, so it
+ # actually takes three args, not two. We want to toss out self.
+ if not isinstance(args[0], str):
+ # We are testing, avoid the first arg
+ args = args[1:]
+ if len(args) == 1 and not kwds:
+ kwds["threaded"] = True
+ return cls(*args, **kwds)
+
+def LinkFileLock(*args, **kwds):
+ """Factory function provided for backwards compatibility.
+
+ Do not use in new code. Instead, import LinkLockFile from the
+ lockfile.linklockfile module.
+ """
+ import linklockfile
+ return _fl_helper(linklockfile.LinkLockFile, "lockfile.linklockfile",
+ *args, **kwds)
+
+def MkdirFileLock(*args, **kwds):
+ """Factory function provided for backwards compatibility.
+
+ Do not use in new code. Instead, import MkdirLockFile from the
+ lockfile.mkdirlockfile module.
+ """
+ import mkdirlockfile
+ return _fl_helper(mkdirlockfile.MkdirLockFile, "lockfile.mkdirlockfile",
+ *args, **kwds)
+
+def SQLiteFileLock(*args, **kwds):
+ """Factory function provided for backwards compatibility.
+
+ Do not use in new code. Instead, import SQLiteLockFile from the
+ lockfile.mkdirlockfile module.
+ """
+ import sqlitelockfile
+ return _fl_helper(sqlitelockfile.SQLiteLockFile, "lockfile.sqlitelockfile",
+ *args, **kwds)
+
+if hasattr(os, "link"):
+ import linklockfile as _llf
+ LockFile = _llf.LinkLockFile
+else:
+ import mkdirlockfile as _mlf
+ LockFile = _mlf.MkdirLockFile
+
+FileLock = LockFile
+
diff --git a/lib/thandy/lockfile/linklockfile.py b/lib/thandy/lockfile/linklockfile.py
new file mode 100644
index 0000000..737fd08
--- /dev/null
+++ b/lib/thandy/lockfile/linklockfile.py
@@ -0,0 +1,69 @@
+from __future__ import absolute_import
+
+import time
+import os
+
+from . import (LockBase, LockFailed, NotLocked, NotMyLock, LockTimeout,
+ AlreadyLocked)
+
+class LinkLockFile(LockBase):
+ """Lock access to a file using atomic property of link(2).
+
+ >>> lock = LinkLockFile('somefile')
+ >>> lock = LinkLockFile('somefile', threaded=False)
+ """
+
+ def acquire(self, timeout=None):
+ try:
+ open(self.unique_name, "wb").close()
+ except IOError:
+ raise LockFailed("failed to create %s" % self.unique_name)
+
+ end_time = time.time()
+ if timeout is not None and timeout > 0:
+ end_time += timeout
+
+ while True:
+ # Try and create a hard link to it.
+ try:
+ os.link(self.unique_name, self.lock_file)
+ except OSError:
+ # Link creation failed. Maybe we've double-locked?
+ nlinks = os.stat(self.unique_name).st_nlink
+ if nlinks == 2:
+ # The original link plus the one I created == 2. We're
+ # good to go.
+ return
+ else:
+ # Otherwise the lock creation failed.
+ if timeout is not None and time.time() > end_time:
+ os.unlink(self.unique_name)
+ if timeout > 0:
+ raise LockTimeout
+ else:
+ raise AlreadyLocked
+ time.sleep(timeout is not None and timeout/10 or 0.1)
+ else:
+ # Link creation succeeded. We're good to go.
+ return
+
+ def release(self):
+ if not self.is_locked():
+ raise NotLocked
+ elif not os.path.exists(self.unique_name):
+ raise NotMyLock
+ os.unlink(self.unique_name)
+ os.unlink(self.lock_file)
+
+ def is_locked(self):
+ return os.path.exists(self.lock_file)
+
+ def i_am_locking(self):
+ return (self.is_locked() and
+ os.path.exists(self.unique_name) and
+ os.stat(self.unique_name).st_nlink == 2)
+
+ def break_lock(self):
+ if os.path.exists(self.lock_file):
+ os.unlink(self.lock_file)
+
diff --git a/lib/thandy/lockfile/mkdirlockfile.py b/lib/thandy/lockfile/mkdirlockfile.py
new file mode 100644
index 0000000..fb78902
--- /dev/null
+++ b/lib/thandy/lockfile/mkdirlockfile.py
@@ -0,0 +1,79 @@
+from __future__ import absolute_import, division
+
+import time
+import os
+import sys
+import errno
+
+from . import (LockBase, LockFailed, NotLocked, NotMyLock, LockTimeout,
+ AlreadyLocked)
+
+class MkdirLockFile(LockBase):
+ """Lock file by creating a directory."""
+ def __init__(self, path, threaded=True):
+ """
+ >>> lock = MkdirLockFile('somefile')
+ >>> lock = MkdirLockFile('somefile', threaded=False)
+ """
+ LockBase.__init__(self, path, threaded)
+ # Lock file itself is a directory. Place the unique file name into
+ # it.
+ self.unique_name = os.path.join(self.lock_file,
+ "%s.%s%s" % (self.hostname,
+ self.tname,
+ self.pid))
+
+ def acquire(self, timeout=None):
+ end_time = time.time()
+ if timeout is not None and timeout > 0:
+ end_time += timeout
+
+ if timeout is None:
+ wait = 0.1
+ else:
+ wait = max(0, timeout / 10)
+
+ while True:
+ try:
+ os.mkdir(self.lock_file)
+ except OSError:
+ err = sys.exc_info()[1]
+ if err.errno == errno.EEXIST:
+ # Already locked.
+ if os.path.exists(self.unique_name):
+ # Already locked by me.
+ return
+ if timeout is not None and time.time() > end_time:
+ if timeout > 0:
+ raise LockTimeout
+ else:
+ # Someone else has the lock.
+ raise AlreadyLocked
+ time.sleep(wait)
+ else:
+ # Couldn't create the lock for some other reason
+ raise LockFailed("failed to create %s" % self.lock_file)
+ else:
+ open(self.unique_name, "wb").close()
+ return
+
+ def release(self):
+ if not self.is_locked():
+ raise NotLocked
+ elif not os.path.exists(self.unique_name):
+ raise NotMyLock
+ os.unlink(self.unique_name)
+ os.rmdir(self.lock_file)
+
+ def is_locked(self):
+ return os.path.exists(self.lock_file)
+
+ def i_am_locking(self):
+ return (self.is_locked() and
+ os.path.exists(self.unique_name))
+
+ def break_lock(self):
+ if os.path.exists(self.lock_file):
+ for name in os.listdir(self.lock_file):
+ os.unlink(os.path.join(self.lock_file, name))
+ os.rmdir(self.lock_file)
diff --git a/lib/thandy/lockfile/pidlockfile.py b/lib/thandy/lockfile/pidlockfile.py
new file mode 100644
index 0000000..10dcb31
--- /dev/null
+++ b/lib/thandy/lockfile/pidlockfile.py
@@ -0,0 +1,189 @@
+# -*- coding: utf-8 -*-
+
+# pidlockfile.py
+#
+# Copyright © 2008–2009 Ben Finney <ben+python@benfinney.id.au>
+#
+# This is free software: you may copy, modify, and/or distribute this work
+# under the terms of the Python Software Foundation License, version 2 or
+# later as published by the Python Software Foundation.
+# No warranty expressed or implied. See the file LICENSE.PSF-2 for details.
+
+""" Lockfile behaviour implemented via Unix PID files.
+ """
+
+from __future__ import absolute_import
+
+import os
+import sys
+import errno
+import time
+
+from . import (LockBase, AlreadyLocked, LockFailed, NotLocked, NotMyLock,
+ LockTimeout)
+
+
+class PIDLockFile(LockBase):
+ """ Lockfile implemented as a Unix PID file.
+
+ The lock file is a normal file named by the attribute `path`.
+ A lock's PID file contains a single line of text, containing
+ the process ID (PID) of the process that acquired the lock.
+
+ >>> lock = PIDLockFile('somefile')
+ >>> lock = PIDLockFile('somefile')
+ """
+
+ def __init__(self, path, threaded=False):
+ # pid lockfiles don't support threaded operation, so always force
+ # False as the threaded arg.
+ LockBase.__init__(self, path, False)
+ dirname = os.path.dirname(self.lock_file)
+ basename = os.path.split(self.path)[-1]
+ self.unique_name = self.path
+
+ def read_pid(self):
+ """ Get the PID from the lock file.
+ """
+ return read_pid_from_pidfile(self.path)
+
+ def is_locked(self):
+ """ Test if the lock is currently held.
+
+ The lock is held if the PID file for this lock exists.
+
+ """
+ return os.path.exists(self.path)
+
+ def i_am_locking(self):
+ """ Test if the lock is held by the current process.
+
+ Returns ``True`` if the current process ID matches the
+ number stored in the PID file.
+ """
+ return self.is_locked() and os.getpid() == self.read_pid()
+
+ def acquire(self, timeout=None):
+ """ Acquire the lock.
+
+ Creates the PID file for this lock, or raises an error if
+ the lock could not be acquired.
+ """
+
+ end_time = time.time()
+ if timeout is not None and timeout > 0:
+ end_time += timeout
+
+ while True:
+ try:
+ write_pid_to_pidfile(self.path)
+ except OSError, exc:
+ if exc.errno == errno.EEXIST:
+ # The lock creation failed. Maybe sleep a bit.
+ if timeout is not None and time.time() > end_time:
+ if timeout > 0:
+ raise LockTimeout
+ else:
+ raise AlreadyLocked
+ time.sleep(timeout is not None and timeout/10 or 0.1)
+ else:
+ raise LockFailed
+ else:
+ return
+
+ def release(self):
+ """ Release the lock.
+
+ Removes the PID file to release the lock, or raises an
+ error if the current process does not hold the lock.
+
+ """
+ if not self.is_locked():
+ raise NotLocked
+ if not self.i_am_locking():
+ raise NotMyLock
+ remove_existing_pidfile(self.path)
+
+ def break_lock(self):
+ """ Break an existing lock.
+
+ Removes the PID file if it already exists, otherwise does
+ nothing.
+
+ """
+ remove_existing_pidfile(self.path)
+
+def read_pid_from_pidfile(pidfile_path):
+ """ Read the PID recorded in the named PID file.
+
+ Read and return the numeric PID recorded as text in the named
+ PID file. If the PID file cannot be read, or if the content is
+ not a valid PID, return ``None``.
+
+ """
+ pid = None
+ try:
+ pidfile = open(pidfile_path, 'r')
+ except IOError:
+ pass
+ else:
+ # According to the FHS 2.3 section on PID files in /var/run:
+ #
+ # The file must consist of the process identifier in
+ # ASCII-encoded decimal, followed by a newline character.
+ #
+ # Programs that read PID files should be somewhat flexible
+ # in what they accept; i.e., they should ignore extra
+ # whitespace, leading zeroes, absence of the trailing
+ # newline, or additional lines in the PID file.
+
+ line = pidfile.readline().strip()
+ try:
+ pid = int(line)
+ except ValueError:
+ pass
+ pidfile.close()
+
+ return pid
+
+
+def write_pid_to_pidfile(pidfile_path):
+ """ Write the PID in the named PID file.
+
+ Get the numeric process ID (“PID”) of the current process
+ and write it to the named file as a line of text.
+
+ """
+ open_flags = (os.O_CREAT | os.O_EXCL | os.O_WRONLY)
+ open_mode = 0644
+ pidfile_fd = os.open(pidfile_path, open_flags, open_mode)
+ pidfile = os.fdopen(pidfile_fd, 'w')
+
+ # According to the FHS 2.3 section on PID files in /var/run:
+ #
+ # The file must consist of the process identifier in
+ # ASCII-encoded decimal, followed by a newline character. For
+ # example, if crond was process number 25, /var/run/crond.pid
+ # would contain three characters: two, five, and newline.
+
+ pid = os.getpid()
+ line = "%(pid)d\n" % vars()
+ pidfile.write(line)
+ pidfile.close()
+
+
+def remove_existing_pidfile(pidfile_path):
+ """ Remove the named PID file if it exists.
+
+ Removing a PID file that doesn't already exist puts us in the
+ desired state, so we ignore the condition if the file does not
+ exist.
+
+ """
+ try:
+ os.remove(pidfile_path)
+ except OSError, exc:
+ if exc.errno == errno.ENOENT:
+ pass
+ else:
+ raise
diff --git a/lib/thandy/lockfile/sqlitelockfile.py b/lib/thandy/lockfile/sqlitelockfile.py
new file mode 100644
index 0000000..d044d2a
--- /dev/null
+++ b/lib/thandy/lockfile/sqlitelockfile.py
@@ -0,0 +1,146 @@
+from __future__ import absolute_import, division
+
+import time
+import os
+
+from . import LockBase, NotLocked, NotMyLock, LockTimeout, AlreadyLocked
+
+class SQLiteLockFile(LockBase):
+ "Demonstrate SQL-based locking."
+
+ testdb = None
+
+ def __init__(self, path, threaded=True):
+ """
+ >>> lock = SQLiteLockFile('somefile')
+ >>> lock = SQLiteLockFile('somefile', threaded=False)
+ """
+ LockBase.__init__(self, path, threaded)
+ self.lock_file = unicode(self.lock_file)
+ self.unique_name = unicode(self.unique_name)
+
+ if SQLiteLockFile.testdb is None:
+ import tempfile
+ _fd, testdb = tempfile.mkstemp()
+ os.close(_fd)
+ os.unlink(testdb)
+ del _fd, tempfile
+ SQLiteLockFile.testdb = testdb
+
+ import sqlite3
+ self.connection = sqlite3.connect(SQLiteLockFile.testdb)
+
+ c = self.connection.cursor()
+ try:
+ c.execute("create table locks"
+ "("
+ " lock_file varchar(32),"
+ " unique_name varchar(32)"
+ ")")
+ except sqlite3.OperationalError:
+ pass
+ else:
+ self.connection.commit()
+ import atexit
+ atexit.register(os.unlink, SQLiteLockFile.testdb)
+
+ def acquire(self, timeout=None):
+ end_time = time.time()
+ if timeout is not None and timeout > 0:
+ end_time += timeout
+
+ if timeout is None:
+ wait = 0.1
+ elif timeout <= 0:
+ wait = 0
+ else:
+ wait = timeout / 10
+
+ cursor = self.connection.cursor()
+
+ while True:
+ if not self.is_locked():
+ # Not locked. Try to lock it.
+ cursor.execute("insert into locks"
+ " (lock_file, unique_name)"
+ " values"
+ " (?, ?)",
+ (self.lock_file, self.unique_name))
+ self.connection.commit()
+
+ # Check to see if we are the only lock holder.
+ cursor.execute("select * from locks"
+ " where unique_name = ?",
+ (self.unique_name,))
+ rows = cursor.fetchall()
+ if len(rows) > 1:
+ # Nope. Someone else got there. Remove our lock.
+ cursor.execute("delete from locks"
+ " where unique_name = ?",
+ (self.unique_name,))
+ self.connection.commit()
+ else:
+ # Yup. We're done, so go home.
+ return
+ else:
+ # Check to see if we are the only lock holder.
+ cursor.execute("select * from locks"
+ " where unique_name = ?",
+ (self.unique_name,))
+ rows = cursor.fetchall()
+ if len(rows) == 1:
+ # We're the locker, so go home.
+ return
+
+ # Maybe we should wait a bit longer.
+ if timeout is not None and time.time() > end_time:
+ if timeout > 0:
+ # No more waiting.
+ raise LockTimeout
+ else:
+ # Someone else has the lock and we are impatient..
+ raise AlreadyLocked
+
+ # Well, okay. We'll give it a bit longer.
+ time.sleep(wait)
+
+ def release(self):
+ if not self.is_locked():
+ raise NotLocked
+ if not self.i_am_locking():
+ raise NotMyLock((self._who_is_locking(), self.unique_name))
+ cursor = self.connection.cursor()
+ cursor.execute("delete from locks"
+ " where unique_name = ?",
+ (self.unique_name,))
+ self.connection.commit()
+
+ def _who_is_locking(self):
+ cursor = self.connection.cursor()
+ cursor.execute("select unique_name from locks"
+ " where lock_file = ?",
+ (self.lock_file,))
+ return cursor.fetchone()[0]
+
+ def is_locked(self):
+ cursor = self.connection.cursor()
+ cursor.execute("select * from locks"
+ " where lock_file = ?",
+ (self.lock_file,))
+ rows = cursor.fetchall()
+ return not not rows
+
+ def i_am_locking(self):
+ cursor = self.connection.cursor()
+ cursor.execute("select * from locks"
+ " where lock_file = ?"
+ " and unique_name = ?",
+ (self.lock_file, self.unique_name))
+ return not not cursor.fetchall()
+
+ def break_lock(self):
+ cursor = self.connection.cursor()
+ cursor.execute("delete from locks"
+ " where lock_file = ?",
+ (self.lock_file,))
+ self.connection.commit()
diff --git a/lib/thandy/packagesys/ThpPackages.py b/lib/thandy/packagesys/ThpPackages.py
index b87c7b4..3ca9d52 100644
--- a/lib/thandy/packagesys/ThpPackages.py
+++ b/lib/thandy/packagesys/ThpPackages.py
@@ -6,7 +6,10 @@ import zipfile
import tempfile
import time
+from lockfile import LockFile
+
import thandy.util
+import thandy.formats
import thandy.packagesys.PackageSystem as PS
import thandy.packagesys.PackageDB as PDB
@@ -90,12 +93,21 @@ class ThpInstaller(PS.Installer):
if self._thp_root is None:
raise Exception("There is no THP_INSTALL_ROOT variable set")
- pkg = ThpPackage(os.path.join(self._cacheRoot, self._relPath[1:]))
- self._db.insert(pkg.getAll())
+ lockfile = os.path.join(self._thp_db_root, ".lock")
+ lock = LockFile(lockfile)
+ try:
+ lock.acquire()
+ pkg = ThpPackage(os.path.join(self._cacheRoot, self._relPath[1:]))
+ shutil.copytree()
+ except AlreadyLocked:
+ print "You can't run more than one instance of Thandy"
+ except LockFailed:
+ print "Can't acquire lock on %s" % lockfile
+# self._db.insert(pkg.getAll())
+# self._db.statusInstalled(pkg.getAll())
# self._db.delete(pkg.getAll())
- print self._db.exists(pkg.get("package_name"))
+# print self._db.exists(pkg.get("package_name"))
-# shutil.copytree()
def remove(self):
print "Running thp remover"
@@ -104,6 +116,7 @@ class ThpPackage(object):
def __init__(self, thp_path):
self._thp_path = thp_path
self._metadata = None
+ self._valid = False
self._process()
@@ -118,6 +131,7 @@ class ThpPackage(object):
thpFile.extractall(tmpPath)
contents = open(os.path.join(tmpPath, "meta", "package.json")).read()
self._metadata = json.loads(contents)
+ print self._validateFiles(tmpPath)
thandy.util.deltree(tmpPath)
@@ -127,3 +141,17 @@ class ThpPackage(object):
def getAll(self):
return self._metadata
+
+ def isValid(self):
+ return self._valid
+
+ def _validateFiles(self, tmpPath):
+ for manifest in self._metadata['manifest']:
+ name = manifest['name']
+ digest = manifest['digest']
+ is_config = manifest['is_config']
+ f = open(os.path.join(tmpPath, "content", name), "rb")
+ newdigest = thandy.formats.formatHash(thandy.formats.getFileDigest(f))
+ f.close()
+ if newdigest != digest:
+ return (False, [name, digest, newdigest])