Add lockfile attribute

This commit is contained in:
Eric Torres 2019-04-15 23:29:26 -07:00
parent f2bf042549
commit b21dbd8890
2 changed files with 57 additions and 3 deletions

View File

@ -11,7 +11,8 @@ from pathlib import Path
from tempfile import NamedTemporaryFile
# ========== Constants ==========
VALID_DB_COMPRESS_MODES = ["bzip2", "gz", "lzma", "xz"]
LOCKFILE_MODE = 0o0000
VALID_DB_COMPRESS_MODES = [None, "bzip2", "gz", "lzma", "xz"]
# ========== Logging Setup ==========
syslog = logging.getLogger(__name__)
@ -19,20 +20,62 @@ syslog = logging.getLogger(__name__)
# ========== Classes ==========
class PackageManager:
def __init__(self, cachedir, db_path, pkglist_cmd):
"""Class for abstracting package manager-based operations.
The package manager can be used in conjunction with a ``Snapshot`` for backups.
Lockfile Management
^^^^^^^^^^^^^^^^^^^
This class can be used as a context manager for creating a lockfile for the
specific package manager. This is to prevent transactions from occurring during
backup operations which would most likely leave the package manager's database in
an inconsistent state on the backup.
.. note:: Subclasses can override the context manager and implement i.e. blocking until
the process is complete with a timeout.
"""
def __init__(self, cachedir=None, db_path=None, lockfile=None, pkglist_cmd=None):
"""Default constructor for the PackageManager class.
:param cachedir: path to the package manager cache directory
:type cachedir: str or path-like object
:param db_path: path to the package manager database
:type db_path: str or path-like object
:param lockfile: path to this package manager's lockfile
:type lockfile: str
:param pkglist_cmd: command to list installed packages to stdout
:type pkglist_cmd: str or iterable of str
"""
self._cachedir = Path(cachedir)
self._db_path = Path(db_path)
self._lockfile = Path(lockfile)
self._pkglist_cmd = pkglist_cmd
def __enter__(self):
"""Create the package manager's lockfile. This prevents transactions
from occurring during the backup which could leave the database backup
in an inconsistent state.
The existence of this lockfile is an error, and its meaning is up to
the package manager. For example, pacman's db.lck indicates
either there is an ongoing transaction in progress or a previous transaction
failed and the database is in an inconsistent state.
:returns: self
:rtype: ``PackageManager`` object
:raises FileExistsError: if lockfile exists when this method is called
"""
self._lockfile.touch(mode=0o000)
yield self
def __exit__(self):
"""Remove the package manager's lockfile. After this lockfile is closed,
the package manager this class abstracts can perform transactions once again.
"""
self._lockfile.unlink()
def gen_pkglist(self):
"""Generate a text file listing installed packages
on the system and return the path to that file.
@ -109,6 +152,14 @@ class PackageManager:
"""
return self._db_path
@property
def lockfile(self):
"""
:returns: the lockfile path of this package manager
:rtype: path-like object
"""
return self._lockfile
@property
def pkglist_cmd(self):
"""

View File

@ -60,8 +60,11 @@ class TestPackageManagerMethods(unittest.TestCase):
self.cachedir = "/var/cache/pacman/"
self.db_path = "/var/lib/pacman"
self.lockfile = "/var/lib/pacman/db.lck"
self.pkglist_cmd = ["pacman", "-Qqe"]
self.p = PackageManager(self.cachedir, self.db_path, self.pkglist_cmd)
self.p = PackageManager(
self.cachedir, self.db_path, self.lockfile, self.pkglist_cmd
)
def test_pkglist(self):
self.mocked_run.return_value.stdout = "packages"