Byron Clark avatar Byron Clark committed 320f3ec

Added filters for mount options.

Implemented basic filters for mount options. Right now it only reads
filters out of a file called filters.conf in the directory where
udiskie is run. The format looks like this:
[mount_options]
fstype.vfat: sync,nodev
uuid.9d53-13ba: noexec

refs #15

Comments (0)

Files changed (3)

udiskie/device.py

     def id_type(self):
         return self._get_property('IdType')
 
+    def id_uuid(self):
+        return self._get_property('IdUuid')
+
     def mount(self, filesystem, options):
         self.device.FilesystemMount(filesystem, options,
                                     dbus_interface=UDISKS_DEVICE_INTERFACE)
+from ConfigParser import SafeConfigParser
+import logging
+import re
+
+class InvalidFilter(Exception):
+    def __init__(self, value):
+        self.value = value
+
+    def __str__(self):
+        return 'Invalid Filter: %s' % (self.value,)
+
+
+class OptionFilter:
+    # This list also defines the order in which the filters are
+    # processed. Should go from least specific to most specific.
+    VALID_PARAMETERS = (
+        'fstype',
+        'uuid',
+    )
+
+    MATCH_PATTERN = re.compile(r'(\w+)\.(\S+)')
+
+    def __init__(self, parameter, value, options):
+        self.parameter = parameter
+        self.value = value
+        self.options = options
+
+    def __str__(self):
+        return '<OptionFilter: %s=%s, options=%s>' % (self.parameter,
+                                                      self.value,
+                                                      self.options)
+
+    def __repr__(self):
+        return str(self)
+
+
+class Filters:
+    def __init__(self):
+        self.option_filters = []
+        self.log = logging.getLogger('udiskie.match.Filters')
+
+    def _parse_option_match(self, match_expression):
+        match = OptionFilter.MATCH_PATTERN.match(match_expression)
+        if not match:
+            raise InvalidFilter('format is parameter=value')
+        parameter, value = match.groups()
+        if parameter not in OptionFilter.VALID_PARAMETERS:
+            raise InvalidFilter('parameter "%s" is not allowed' % (parameter,))
+        return parameter, value
+
+    def add_option_filter(self, match_expression, options):
+        parameter, value = self._parse_option_match(match_expression)
+        options = [S.strip() for S in options.split(',')]
+        filt = OptionFilter(parameter, value, options)
+        self.log.debug('loaded filter: %s' % (filt,))
+        self.option_filters.append(filt)
+
+    def get_option_filters(self, parameter):
+        return [F for F in self.option_filters if F.parameter == parameter]
+
+
+class FilterMatcher:
+    MOUNT_OPTIONS_SECTION = 'mount_options'
+
+    def __init__(self, config_files):
+        self.log = logging.getLogger('udiskie.match.FilterMatcher')
+        self.filters = self._load_filters_from_config_files(config_files)
+
+    def _load_filters_from_config_files(self, config_files):
+        filters = Filters()
+
+        parser = SafeConfigParser()
+        parser.read(config_files)
+
+        if parser.has_section(self.MOUNT_OPTIONS_SECTION):
+            for (match, options) in parser.items(self.MOUNT_OPTIONS_SECTION):
+                filters.add_option_filter(match, options)
+
+        return filters
+
+    def get_mount_options(self, device):
+        device_info = {
+            'fstype' : device.id_type(),
+            'uuid' : device.id_uuid().lower()
+        }
+
+        mount_options = set()
+        for match_type in OptionFilter.VALID_PARAMETERS:
+            device_value = device_info.get(match_type)
+            for filt in self.filters.get_option_filters(match_type):
+                if device_value == filt.value:
+                    self.log.info('filter matched: %s' % (filt,))
+                    for option in filt.options:
+                        mount_options.add(option)
+
+        return list(mount_options)
 import pynotify
 
 import udiskie.device
+import udiskie.match
 
 class DeviceState:
     def __init__(self, mounted, has_media):
 class AutoMounter:
     def __init__(self, bus=None):
         self.log = logging.getLogger('udiskie.mount.AutoMounter')
+        self.filters = udiskie.match.FilterMatcher(('filters.conf',))
         self.last_device_state = {}
 
         if not bus:
         if device.is_handleable():
             try:
                 if not device.is_mounted():
-                    filesystem = str(device.id_type())
-                    options = []
+                    fstype = str(device.id_type())
+                    options = self.filters.get_mount_options(device)
+
+                    S = 'attempting to mount device %s (%s:%s)'
+                    self.log.info(S % (device, fstype, options))
+
                     try:
-                        device.mount(filesystem, options)
+                        device.mount(fstype, options)
                         self.log.info('mounted device %s' % (device,))
                     except dbus.exceptions.DBusException, dbus_err:
                         self.log.error('failed to mount device %s: %s' % (device,
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.