From c3b6f7c9de1bd5723cd132e2ed66a9e8e557f22a Mon Sep 17 00:00:00 2001 From: Devan Franchini Date: Sun, 10 Aug 2014 21:00:49 -0400 Subject: Adds layman-mounter utility script api.py: Adds Mounter() initialization in the LaymanAPI. constants.py: Adds MOUNT_TYPES to constants to keep track of mountable overlay classes. archive.py: Adds check to unmount overlays if they are being synced. --- bin/layman-mounter | 42 ++++++ layman/api.py | 5 + layman/constants.py | 9 +- layman/mounter.py | 313 +++++++++++++++++++++++++++++++++++++++++++++ layman/overlays/archive.py | 5 + 5 files changed, 373 insertions(+), 1 deletion(-) create mode 100755 bin/layman-mounter create mode 100644 layman/mounter.py diff --git a/bin/layman-mounter b/bin/layman-mounter new file mode 100755 index 0000000..cd41078 --- /dev/null +++ b/bin/layman-mounter @@ -0,0 +1,42 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +################################################################################ +# LAYMAN - A UTILITY TO HANDLE MOUNTING OVERLAYS +################################################################################ +# Distributed under the terms of the GNU General Public License v2 +# +# Copyright: +# (c) 2014 Devan Franchini +# Distributed under the terms of the GNU General Public License v2 +# +# Author(s): +# Devan Franchini +# + +__version__ = "0.1" + +#=============================================================================== +# +# Dependencies +# +#------------------------------------------------------------------------------- + +from layman.api import LaymanAPI +from layman.config import OptionConfig +from layman.mounter import Interactive + +#=============================================================================== +# +# MAIN +# +#------------------------------------------------------------------------------- + +config = OptionConfig() +layman_api = LaymanAPI(config, + report_errors=True, + output=config['output']) + +main = Interactive(config=config, mounter=config['mounts']) +main() + diff --git a/layman/api.py b/layman/api.py index bae6972..0f43f28 100755 --- a/layman/api.py +++ b/layman/api.py @@ -27,6 +27,7 @@ from layman.overlays.source import require_supported #from layman.utils import path, delete_empty_directory from layman.compatibility import encode from layman.utils import verify_overlay_src +from layman.mounter import Mounter if sys.hexversion >= 0x30200f0: STR = str @@ -68,6 +69,10 @@ class LaymanAPI(object): self._error_messages = [] self.sync_results = [] + self.config.set_option('mounts', Mounter(self._get_installed_db, + self.get_installed, + config=self.config)) + def is_repo(self, ovl): """validates that the ovl given is a known repo id diff --git a/layman/constants.py b/layman/constants.py index c526cb6..7379429 100644 --- a/layman/constants.py +++ b/layman/constants.py @@ -67,7 +67,6 @@ COMPONENT_DEFAULTS = ['name', 'descriptions', 'owner', 'type', 'sources'] POSSIBLE_COMPONENTS = ['name', 'descriptions', 'homepage', 'owner', 'quality', 'priority', 'sources', 'branch', 'irc', 'feeds'] - ############################################################################### ## ## Archive overlay possible file extensions @@ -77,3 +76,11 @@ POSSIBLE_COMPONENTS = ['name', 'descriptions', 'homepage', 'owner', 'quality', FILE_EXTENSIONS = {'Tar': ('bz2', 'gz', 'lzma', 'xz', 'Z', 'tgz', 'tbz', 'taz', 'tlz', 'txz') } + +################################################################################### +## +## Overlay types mountable by script +## +#################################################################################### + +MOUNT_TYPES = [] diff --git a/layman/mounter.py b/layman/mounter.py new file mode 100644 index 0000000..f041a9a --- /dev/null +++ b/layman/mounter.py @@ -0,0 +1,313 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +############################################################################### +# LAYMAN MOUNTING OVERLAY HANDLER +############################################################################### +# File: mounter.py +# +# Controls all mountable overlay types +# +# Copyright: +# (c) 2014 Devan Franchini +# Distributed under the terms of the GNU General Public License v2 +# +# Author(s): +# Devan Franchini +# +''' +Controls all mountable overlay types. +''' +#============================================================================== +# +# Dependencies +# +#------------------------------------------------------------------------------ +from __future__ import unicode_literals + +import argparse +import copy +import os +import sys + +from layman.constants import MOUNT_TYPES +from layman.utils import path, run_command +from layman.version import VERSION + + +if sys.hexversion >= 0x30200f0: + STR = str +else: + STR = basestring + +MOUNT_ARGS = {'Squashfs': ['-o', 'loop', '-t', 'squashfs']} +_USAGE = 'layman-mounter [-h] [-l] [-L] [-m MOUNT [MOUNT ...]]\n'\ + ' [-u UMOUNT [UMOUNT ...]] [-V]' + +def is_mounted(mdir): + ''' + Determines whether or not an overlay is mounted at it's + installed overlay. + + @rtype bool + ''' + return os.path.ismount(mdir) + + +class Mounter(object): + ''' + Handles all mountable overlays. + ''' + def __init__(self, database, overlays, config=None): + self.config = config + self.database = database + self.output = self.config['output'] + self.overlays = overlays + self.storage = self.config['storage'] + + + @property + def installed(self): + ''' + Returns a dictionary of all installed overlays + and their overlay objects. + + @rtype dict {'ovl1', ,...} + ''' + installed_db = {} + + for overlay in self.overlays(): + ovl_db = self.database().select(overlay) + installed_db[overlay] = ovl_db + return installed_db + + + @property + def mountables(self): + ''' + Returns a dictionary of all mountable overlays and their + types. + + @rtype dict {'ovl1': 'Squashfs',...} + ''' + mountable_ovls = {} + + for key in sorted(self.installed): + for ovl_type in self.installed[key].source_types(): + if ovl_type in MOUNT_TYPES: + mountable_ovls[key] = ovl_type + return mountable_ovls + + + @property + def mounted(self): + ''' + Returns a dictionary of all mountable overlays and a + boolean reflecting their mounted status. + + @rtype dict {'ovl1': True, 'ovl2': False,...} + ''' + mounted_ovls = {} + + for ovl in self.mountables: + mdir = path([self.storage, ovl]) + mounted_ovls[ovl] = is_mounted(mdir) + return mounted_ovls + + + def _check_selection(self, repos): + ''' + Internal function to validate the repo parameter. + + @rtype tuple + ''' + if 'ALL' in repo: + repos = sorted(self.mountables) + elif isinstance(repos, STR): + repos = [repos] + + return repos + + + def mount(self, repo, dest=None, install=False, ovl_type=None, pkg=None): + ''' + Mounts an overlay to it's installation directory. + + @params repo: str of overlay name or "ALL". + @params dest: str of optional destination dir. + @params install: bool to reflect whether or not the overlay is being + installed. + @params ovl_type: str of optional overlay type. + @params pkg: str of optional location of package to mount. + @rtype int: reflects whether or not the overlay was mounted. + ''' + result = 1 + + selection = self._check_selection(repo) + + for i in selection: + name = {'ovl': i} + + if i not in self.mountables and not install: + self.output.error('Overlay "%(ovl)s" cannot be mounted!'\ + % name) + continue + if dest: + mdir = dest + else: + mdir = path([self.storage, i]) + + if not is_mounted(mdir): + if install: + args = copy.deepcopy(MOUNT_ARGS[ovl_type]) + else: + args = copy.deepcopy(MOUNT_ARGS[self.mountables[i]]) + + if not pkg: + source = self.installed[i].sources[0].src + + if 'file://' in source: + pkg = source.replace('file://', '') + else: + pkg = path([self.storage, i, source.get_extension()]) + + args.append(pkg) + args.append(mdir) + result = run_command(self.config, 'mount', args, cmd='mount') + else: + self.output.warn('Overlay "%(ovl)s" is already mounted!'\ + % name) + return result + + + def umount(self, repo, dest=None, sync=False): + ''' + Unmounts an overlay from it's installation directory. + + @params repo: str of overlay name or "ALL". + @params dest: str of optional path to unmount. + @params sync: bool to reflect whether or not the overlay is being + synced. + @rtype int: reflects whether or not it was a successful unmount. + ''' + result = 1 + + selection = self._check_selection(repo) + + for i in selection: + name = {'ovl': i} + + if i not in self.mountables and not sync: + self.output.error('Overlay "%(ovl)s" cannot be mounted!'\ + % name) + continue + if dest: + mdir = dest + else: + mdir = path([self.storage, i]) + + if is_mounted(mdir): + args = ['-l', mdir] + result = run_command(self.config, 'umount', args, cmd='umount') + else: + self.output.warn('Overlay "%(ovl)s" is already unmounted!'\ + % name) + + return result + + +class Interactive(object): + ''' + Interactive CLI session for the Mounter class + ''' + def __init__(self, config=None, mounter=None): + self.args = None + self.parser = None + self.output = config['output'] + self.storage = config['storage'] + self.mount = mounter + self.mountables = self.mount.mountables + + + def args_parser(self): + ''' + Parses all command line arguments. + + @rtype argparse.NameSpace object. + ''' + self.parser = argparse.ArgumentParser(prog='layman-mounter', + description='Layman\'s utility script to handle mountable '\ + 'overlays.') + self.parser.add_argument('-l', + '--list-mountables', + action='store_true', + help='Lists all available overlays that' + ' support mounting') + self.parser.add_argument('-L', + '--list-mounted', + action='store_true', + help='Lists all mounted overlays') + self.parser.add_argument('-m', + '--mount', + nargs='+', + help='Mounts the selected overlay. Specify '\ + '"ALL" to mount all possible overlays') + self.parser.add_argument('-u', + '--umount', + nargs='+', + help='Unmounts the selected overlay. Specify'\ + ' "ALL" to unmount all possible overlays') + self.parser.add_argument('-V', + '--version', + action='version', + version='%(prog)s ' + VERSION) + self.args = self.parser.parse_args() + + + def __call__(self): + self.args_parser() + if len(sys.argv) == 1: + self.output.notice('usage: %(USAGE)s' % {'USAGE':_USAGE}) + sys.exit(0) + options = {} + + for key in vars(self.args): + options[key] = vars(self.args)[key] + + for i in ('list_mountables', 'list_mounted'): + if options[i]: + getattr(self, i)() + self.output.notice('') + + for i in ('umount', 'mount'): + if options[i]: + getattr(self.mount, '%(action)s' % {'action': i})(options[i]) + + + def list_mountables(self): + ''' + Lists all overlays that can be mounted. + ''' + self.output.info('Mountable overlays:') + self.output.info('~~~~~~~~~~~~~~~~~~~') + if self.mountables: + for ovl in sorted(self.mountables): + self.output.info(ovl) + else: + self.output.warn('N/A') + + + def list_mounted(self): + ''' + Lists all mounted overlays. + ''' + mounted = self.mount.mounted + + self.output.info('Overlays:') + self.output.info('~~~~~~~~~') + for i in mounted: + if mounted[i]: + status = 'Mounted' + else: + status = 'Unmounted' + stat_dict = {'ovl': i, 'status': status} + self.output.info('Name: %(ovl)s, Status: %(status)s' % stat_dict) diff --git a/layman/overlays/archive.py b/layman/overlays/archive.py index 68c8b47..b1907ee 100644 --- a/layman/overlays/archive.py +++ b/layman/overlays/archive.py @@ -98,6 +98,11 @@ class ArchiveOverlay(OverlaySource): temp_path = final_path if not os.path.exists(temp_path): os.mkdir(temp_path) + else: + if os.path.ismount(temp_path): + self.config['mounts'].umount([self.parent.name], + dest=temp_path, + sync=True) pkg = self._fetch(base=base, archive_url=self.src, dest_dir=temp_path) result = self.post_fetch(pkg, temp_path) -- cgit v1.2.3-65-gdbad