Thomas Gläßle avatar Thomas Gläßle committed 9bfdb91 Merge

Merge branch 'udisks-dependency-injection' into separate-mvc-components

Comments (0)

Files changed (7)

 
 import udiskie.match
 import udiskie.mount
-import udiskie.device
 import udiskie.prompt
 import udiskie.notify
 import udiskie.automount
 import udiskie.daemon
+import udiskie.common
 
 
 CONFIG_PATH = 'udiskie/filters.conf'
         DBusGMainLoop(set_as_default=True)
     bus = dbus.SystemBus()
 
+    # for now: just use the default udisks
+    udisks = udiskie.common.get_udisks()
+
     # create a mounter
     prompt = udiskie.prompt.password(options.password_prompt)
     filter = load_filter(options.filters)
-    mounter = udiskie.mount.Mounter(bus=bus, filter=filter, prompt=prompt)
+    mounter = udiskie.mount.Mounter(bus=bus, filter=filter, prompt=prompt, udisks=udisks)
 
     # run udiskie daemon if needed
     if run_daemon:
-        daemon = udiskie.daemon.Daemon(bus)
+        daemon = udiskie.daemon.Daemon(bus, udisks=udisks)
     if run_daemon and not options.suppress_notify:
         notify = udiskie.notify.Notify('udiskie.mount')
         notify.connect(daemon)
         parser.print_usage()
         return 1
 
+    # for now: use udisks v1 service
+    udisks = udiskie.common.get_udisks()
+
     if options.all:
-        unmounted = udiskie.mount.unmount_all(bus=bus)
+        unmounted = udiskie.mount.unmount_all(bus=bus, udisks=udisks)
     else:
         unmounted = []
         for path in posargs:
-            device = udiskie.mount.unmount(os.path.normpath(path), bus=bus)
+            device = udiskie.mount.unmount(os.path.normpath(path), bus=bus, udisks=udisks)
             if device:
                 unmounted.append(device)
 
     # automatically lock unused luks slaves of unmounted devices
     for device in unmounted:
-        udiskie.mount.lock_slave(device)
+        udiskie.mount.lock_slave(device, udisks=udisks)
 

udiskie/common.py

 """
 Common utilities.
 """
-__all__ = ['Properties']
+__all__ = ['Properties', 'get_udisks', 'default_udisks']
 import dbus
 
+DBUS_PROPS_INTERFACE = 'org.freedesktop.DBus.Properties'
+default_udisks = 'udiskie.udisks'
 
-DBUS_PROPS_INTERFACE = 'org.freedesktop.DBus.Properties'
+
+def get_udisks():
+    import importlib
+    return importlib.import_module(default_udisks)
 
 class Properties:
     """

udiskie/daemon.py

 import logging
 import dbus
 
-from udiskie.device import Device, get_all_handleable
+import sys
 
 
 class DeviceState:
     `disconnect` can be used to add or remove event handlers.
 
     """
-    def __init__(self, bus):
+    def __init__(self, bus, udisks):
         """
         Initialize object and start listening to udisks events.
         """
         self.log = logging.getLogger('udiskie.daemon.Daemon')
         self.bus = bus
         self.state = {}
+        self.udisks = udisks
 
         self.event_handlers = {
             'device_added': [],
             'device_changed': [self.on_device_changed]
         }
 
-        for device in get_all_handleable(bus):
+        for device in self.udisks.get_all_handleable(bus):
             self._store_device_state(device)
 
         self.bus.add_signal_receiver(
     # udisks event listeners
     def _device_added(self, device_name):
         try:
-            udevice = Device(self.bus, device_name)
+            udevice = self.udisks.Device(self.bus, device_name)
             if not udevice.is_handleable:
                 return
             self._store_device_state(udevice)
             self.trigger('device_added', udevice)
-        except dbus.exceptions.DBusException, err:
+        except dbus.exceptions.DBusException:
+            err = sys.exc_info()[1]
             self.log.error('%s(%s): %s' % ('device_added', device_name, err))
 
     def _device_removed(self, device_name):
         try:
             self.trigger('device_removed', device_name)
             self._remove_device_state(device_name)
-        except dbus.exceptions.DBusException, err:
+        except dbus.exceptions.DBusException:
+            err = sys.exc_info()[1]
             self.log.error('%s(%s): %s' % ('device_removed', device_name, err))
 
     def _device_changed(self, device_name):
         try:
-            udevice = Device(self.bus, device_name)
+            udevice = self.udisks.Device(self.bus, device_name)
             if not udevice.is_handleable:
                 return
             old_state = self._get_device_state(udevice)
             new_state = self._store_device_state(udevice)
             self.trigger('device_changed', udevice, old_state, new_state)
-        except dbus.exceptions.DBusException, err:
+        except dbus.exceptions.DBusException:
+            err = sys.exc_info()[1]
             self.log.error('%s(%s): %s' % ('device_changed', device_name, err))
 
     # internal state keeping

udiskie/device.py

-"""
-Udisks wrapper utilities.
-
-These act as a convenience abstraction layer on the udisks dbus service.
-
-"""
-__all__ = ['Device', 'get_all', 'get_all_handleable', 'get_device']
-
-import logging
-import os
-import dbus
-
-from udiskie.common import Properties as DbusProperties
-
-
-UDISKS_INTERFACE = 'org.freedesktop.UDisks'
-UDISKS_DEVICE_INTERFACE = 'org.freedesktop.UDisks.Device'
-
-UDISKS_OBJECT = 'org.freedesktop.UDisks'
-UDISKS_OBJECT_PATH = '/org/freedesktop/UDisks'
-
-
-class Device:
-    """
-    Wrapper class for org.freedesktop.UDisks.Device proxy objects.
-    """
-    def __init__(self, bus, device_path):
-        self.log = logging.getLogger('udiskie.device.Device')
-        self.bus = bus
-        self.device_path = device_path
-        device_object = self.bus.get_object(UDISKS_OBJECT, device_path)
-        self.property = DbusProperties(device_object, UDISKS_DEVICE_INTERFACE)
-        self.method = dbus.Interface(device_object, UDISKS_DEVICE_INTERFACE)
-
-    def __str__(self):
-        return self.device_path
-
-    # properties
-    @property
-    def partition_slave(self):
-        """Get the partition slave."""
-        return self.property.PartitionSlave
-
-    @property
-    def is_partition_table(self):
-        """Check if the device is a partition table."""
-        return self.property.DeviceIsPartitionTable
-
-    @property
-    def is_systeminternal(self):
-        """Check if the device is internal."""
-        return self.property.DeviceIsSystemInternal
-
-    @property
-    def is_handleable(self):
-        """
-        Should this device be handled by udiskie?
-
-        Currently this just means that the device is removable and holds a
-        filesystem or the device is a LUKS encrypted volume.
-
-        """
-        return (self.is_filesystem or self.is_crypto) and not self.is_systeminternal
-
-    @property
-    def is_mounted(self):
-        """Check if the device is mounted."""
-        return self.property.DeviceIsMounted
-
-    @property
-    def is_unlocked(self):
-        """Check if device is already unlocked."""
-        return self.is_luks and self.luks_cleartext_holder
-
-    @property
-    def mount_paths(self):
-        raw_paths = self.property.DeviceMountPaths
-        return [os.path.normpath(path) for path in raw_paths]
-
-    @property
-    def device_file(self):
-        return os.path.normpath(self.property.DeviceFile)
-
-    @property
-    def is_filesystem(self):
-        return self.id_usage == 'filesystem'
-
-    @property
-    def is_crypto(self):
-        return self.id_usage == 'crypto'
-
-    @property
-    def is_luks(self):
-        return self.property.DeviceIsLuks
-
-    @property
-    def is_luks_cleartext(self):
-        """Check whether this is a luks cleartext device."""
-        return self.property.DeviceIsLuksCleartext
-
-    @property
-    def luks_cleartext_slave(self):
-        """Get luks crypto device."""
-        return self.property.LuksCleartextSlave
-
-    @property
-    def luks_cleartext_holder(self):
-        """Get unlocked luks cleartext device."""
-        return self.property.LuksHolder
-
-    @property
-    def is_luks_cleartext_slave(self):
-        """Check whether the luks device is currently in use."""
-        if not self.is_luks:
-            return False
-        for device in get_all(self.bus):
-            if (not device.is_filesystem or device.is_mounted) and (
-                    device.is_luks_cleartext and
-                    device.luks_cleartext_slave == self.device_path):
-                return True
-        return False
-
-    @property
-    def has_media(self):
-        return self.property.DeviceIsMediaAvailable
-
-    @property
-    def id_type(self):
-        return self.property.IdType
-
-    @property
-    def id_usage(self):
-        return self.property.IdUsage
-
-    @property
-    def id_uuid(self):
-        return self.property.IdUuid
-
-    # methods
-    def mount(self, filesystem, options):
-        self.method.FilesystemMount(filesystem, options)
-
-    def unmount(self):
-        self.method.FilesystemUnmount([])
-
-    def lock(self, options):
-        """Lock Luks device."""
-        return self.method.LuksLock(options)
-
-    def unlock(self, password, options):
-        """Unlock Luks device."""
-        return self.method.LuksUnlock(password, options)
-
-
-def get_all(bus):
-    """Enumerate all device objects currently known to udisks."""
-    udisks = bus.get_object(UDISKS_OBJECT, UDISKS_OBJECT_PATH)
-    for path in udisks.EnumerateDevices(dbus_interface=UDISKS_INTERFACE):
-        yield Device(bus, path)
-
-def get_all_handleable(bus):
-    """Enumerate all handleable devices currently known to udisks."""
-    for device in get_all(bus):
-        if device.is_handleable:
-            yield device
-
-def get_device(bus, path):
-    """Get a device proxy by device name or any mount path of the device."""
-    logger = logging.getLogger('udiskie.device.get_device')
-    for device in get_all(bus):
-        if os.path.samefile(path, device.device_file):
-            return device
-        for p in device.mount_paths:
-            if os.path.samefile(path, p):
-                return device
-    logger.warn('Device not found: %s' % path)
-    return None
-
 """
 __all__ = ['InvalidFilter', 'OptionFilter', 'Filters', 'FilterMatcher']
 
-from ConfigParser import SafeConfigParser
+try:
+    from ConfigParser import SafeConfigParser
+except ImportError:
+    from configparser import SafeConfigParser
+
 import logging
 import re
 
     'unlock_device', 'lock_device',
     'add_device', 'remove_device',
     'mount_all', 'unmount_all',
-    'unmount',
-    'lock_luks_slave',
+    'mount', 'unmount',
+    'mount_holder', 'lock_slave',
     'Mounter']
 
+import sys
 import logging
 import dbus
 
-import udiskie.device
+from udiskie.common import get_udisks
 
 
 # mount/unmount
     try:
         device.mount(fstype, options)
         log.info('mounted device %s' % (device,))
-    except dbus.exceptions.DBusException, dbus_err:
-        log.error('failed to mount device %s: %s' % (
-                                            device, dbus_err))
+    except dbus.exceptions.DBusException:
+        err = sys.exc_info()[1]
+        log.error('failed to mount device %s: %s' % (device, err))
         return None
 
     mount_paths = ', '.join(device.mount_paths)
     try:
         device.unmount()
         logger.info('unmounted device %s' % (device,))
-    except dbus.exceptions.DBusException, dbus_err:
-        logger.error('failed to unmount device %s: %s' % (device,
-                                                            dbus_err))
+    except dbus.exceptions.DBusException:
+        err = sys.exc_info()[1]
+        logger.error('failed to unmount device %s: %s' % (device, err))
         return None
     return True
 
 
 # unlock/lock (LUKS)
-def unlock_device(device, prompt):
+def unlock_device(device, prompt, udisks=None):
     """
     Unlock the device if not already unlocked.
 
     log.info('attempting to unlock device %s' % (device,))
     try:
         device.unlock(password, [])
-        holder_dev = udiskie.device.Device(
-                device.bus,
-                device.luks_cleartext_holder)
+        udisks = udisks or get_udisks()
+        holder_dev = udisks.Device(device.bus, device.luks_cleartext_holder)
         holder_path = holder_dev.device_file
         log.info('unlocked device %s on %s' % (device, holder_path))
-    except dbus.exceptions.DBusException, dbus_err:
-        log.error('failed to unlock device %s:\n%s'
-                                    % (device, dbus_err))
+    except dbus.exceptions.DBusException:
+        err = sys.exc_info()[1]
+        log.error('failed to unlock device %s:\n%s' % (device, err))
         return None
     return True
 
     try:
         device.lock([])
         logger.info('locked device %s' % (device,))
-    except dbus.exceptions.DBusException, dbus_err:
-        logger.error('failed to lock device %s: %s' % (device, dbus_err))
+    except dbus.exceptions.DBusException:
+        err = sys.exc_info()[1]
+        logger.error('failed to lock device %s: %s' % (device, err))
         return None
     return True
 
 
 # add/remove (unlock/lock or mount/unmount)
-def add_device(device, filter=None, prompt=None):
+def add_device(device, filter=None, prompt=None, udisks=None):
     """Mount or unlock the device depending on its type."""
     log = logging.getLogger('udiskie.mount.add_device')
     if not device.is_handleable:
     if device.is_filesystem:
         return mount_device(device, filter)
     elif device.is_crypto:
-        return unlock_device(device, prompt)
+        return unlock_device(device, prompt, udisks=udisks)
 
 def remove_device(device):
     """Unmount or lock the device depending on device type."""
         return lock_device(device)
 
 # mount_all/unmount_all
-def mount_all(bus=None, filter=None, prompt=None):
+def mount_all(bus=None, filter=None, prompt=None, udisks=None):
     """Mount handleable devices that are already present."""
     bus = bus or dbus.SystemBus()
-    for device in udiskie.device.get_all_handleable(bus):
-        add_device(device, filter, prompt)
+    udisks = udisks or get_udisks()
+    for device in udisks.get_all_handleable(bus):
+        add_device(device, filter, prompt, udisks=udisks)
 
-def unmount_all(bus=None):
+def unmount_all(bus=None, udisks=None):
     """Unmount all filesystems handleable by udiskie."""
     unmounted = []
     bus = bus or dbus.SystemBus()
-    for device in udiskie.device.get_all_handleable(bus):
+    udisks = udisks or get_udisks()
+    for device in udisks.get_all_handleable(bus):
         if unmount_device(device):
             unmounted.append(device)
     return unmounted
 
 
 # mount a holder/lock a slave
-def mount_holder(device, filter=None, prompt=None):
+def mount_holder(device, filter=None, prompt=None, udisks=None):
     """
     Mount or unlock the holder device of this unlocked LUKS device.
 
     if not device.is_unlocked:
         logger.debug('skipping locked or non-luks device %s' % (device,))
         return False
+    udisks = udisks or get_udisks()
     holder_path = device.luks_cleartext_holder
-    holder = udiskie.device.Device(device.bus, holder_path)
-    return add_device(device, filter=filter, prompt=prompt)
+    holder = udisks.Device(device.bus, holder_path)
+    return add_device(device, filter=filter, prompt=prompt, udisks=udisks)
 
-def lock_slave(device):
+def lock_slave(device, udisks=None):
     """
     Lock the luks slave of this device.
 
     if not device.is_luks_cleartext:
         logger.debug('skipping non-luks-cleartext device %s' % (device,))
         return False
+    udisks = udisks or get_udisks()
     slave_path = device.luks_cleartext_slave
-    slave = udiskie.device.Device(device.bus, slave_path)
+    slave = udisks.Device(device.bus, slave_path)
     if slave.is_luks_cleartext_slave:
         return False
     return lock_device(slave)
 
 
 # mount/unmount by path
-def mount(path, bus=None, filter=None, prompt=None):
+def mount(path, bus=None, filter=None, prompt=None, udisks=None):
     """
     Mount or unlock a device.
 
     """
     logger = logging.getLogger('udiskie.mount.unmount')
     bus = bus or dbus.SystemBus()
-    device = udiskie.device.get_device(bus, path)
+    udisks = udisks or get_udisks()
+    device = udisks.get_device(bus, path)
     if device:
         logger.debug('found device owning "%s": "%s"' % (path, device))
-        if add_device(device, filter=filter, prompt=prompt):
+        if add_device(device, filter=filter, prompt=prompt, udisks=udisks):
             return device
     return None
 
-def unmount(path, bus=None):
+def unmount(path, bus=None, udisks=None):
     """
     Unmount or lock a filesystem
 
     """
     logger = logging.getLogger('udiskie.mount.unmount')
     bus = bus or dbus.SystemBus()
-    device = udiskie.device.get_device(bus, path)
+    udisks = udisks or get_udisks()
+    device = udisks.get_device(bus, path)
     if device:
         logger.debug('found device owning "%s": "%s"' % (path, device))
         if remove_device(device):
     """
     Mount utility.
 
-    Calls the global functions and remembers bus, filter and prompt.
+    Calls the global functions and remembers bus, filter and prompt as well
+    as the desired udisks module.
 
     """
-    def __init__(self, bus, filter=None, prompt=None):
+    def __init__(self, bus, filter=None, prompt=None, udisks=None):
         self.bus = bus or dbus.SystemBus()
         self.filter = filter
         self.prompt = prompt
+        self.udisks = udisks
 
     # mount/unmount
     def mount_device(self, device, filter=None):
         return unmount_device(device)
 
     # unlock/lock (LUKS)
-    def unlock_device(self, device, prompt=None):
-        return mount_device(device, filter=prompt or self.prompt)
+    def unlock_device(self, device, prompt=None, udisks=None):
+        return mount_device(
+                device,
+                filter=prompt or self.prompt,
+                udisks=udisks or self.udisks)
     def lock_device(self, device):
         return lock_device(device)
 
     # add/remove (unlock/lock or mount/unmount)
-    def add_device(self, device, filter=None, prompt=None):
+    def add_device(self, device, filter=None, prompt=None, udisks=None):
         return add_device(
                 device,
                 filter=filter or self.filter,
-                prompt=prompt or self.prompt)
+                prompt=prompt or self.prompt,
+                udisks=udisks or self.udisks)
     def remove_device(self, device):
         return remove_device(device)
 
     # mount_all/unmount_all
-    def mount_all(self, filter=None, prompt=None):
+    def mount_all(self, filter=None, prompt=None, udisks=None):
         return mount_all(
                 self.bus,
                 filter=filter or self.filter,
-                prompt=prompt or self.prompt)
-    def unmount_all(self):
-        return unmount_all(self.bus)
+                prompt=prompt or self.prompt,
+                udisks=udisks or self.udisks)
+    def unmount_all(self, udisks=None):
+        return unmount_all(self.bus, udisks=udisks or self.udisks)
 
     # mount/unmount
     def mount(self, path, filter=None, prompt=None):
         return unmount(path, bus=self.bus)
 
     # mount_holder/lock_slave
-    def mount_holder(self, device, filter=None, prompt=None):
+    def mount_holder(self, device, filter=None, prompt=None, udisks=None):
         return mount_holder(
                 device,
                 filter=filter or self.filter,
-                prompt=prompt or self.prompt)
-    def lock_slave(self, device):
-        return lock_slave(device)
+                prompt=prompt or self.prompt,
+                udisks=udisks or self.udisks)
+    def lock_slave(self, device, udisks=None):
+        return lock_slave(device, udisks=udisks or self.udisks)
 

udiskie/udisks.py

+"""
+Udisks wrapper utilities.
+
+These act as a convenience abstraction layer on the udisks dbus service.
+Requires Udisks 1.0.5 as described here:
+
+    http://udisks.freedesktop.org/docs/1.0.5/
+
+Note that (as this completely wraps the udisks dbus API) replacing this
+module will let you support Udisks2 or maybe even other services.
+
+"""
+__all__ = ['Device', 'get_all', 'get_all_handleable', 'get_device']
+
+import logging
+import os
+import dbus
+
+from udiskie.common import Properties as DbusProperties
+
+
+UDISKS_INTERFACE = 'org.freedesktop.UDisks'
+UDISKS_DEVICE_INTERFACE = 'org.freedesktop.UDisks.Device'
+
+UDISKS_OBJECT = 'org.freedesktop.UDisks'
+UDISKS_OBJECT_PATH = '/org/freedesktop/UDisks'
+
+
+class Device:
+    """
+    Wrapper class for org.freedesktop.UDisks.Device proxy objects.
+    """
+    def __init__(self, bus, device_path):
+        self.log = logging.getLogger('udiskie.udisks.Device')
+        self.bus = bus
+        self.device_path = device_path
+        device_object = self.bus.get_object(UDISKS_OBJECT, device_path)
+        self.property = DbusProperties(device_object, UDISKS_DEVICE_INTERFACE)
+        self.method = dbus.Interface(device_object, UDISKS_DEVICE_INTERFACE)
+
+    def __str__(self):
+        return self.device_path
+
+    # properties
+    @property
+    def partition_slave(self):
+        """Get the partition slave."""
+        return self.property.PartitionSlave
+
+    @property
+    def is_partition_table(self):
+        """Check if the device is a partition table."""
+        return self.property.DeviceIsPartitionTable
+
+    @property
+    def is_systeminternal(self):
+        """Check if the device is internal."""
+        return self.property.DeviceIsSystemInternal
+
+    @property
+    def is_handleable(self):
+        """
+        Should this device be handled by udiskie?
+
+        Currently this just means that the device is removable and holds a
+        filesystem or the device is a LUKS encrypted volume.
+
+        """
+        return (self.is_filesystem or self.is_crypto) and not self.is_systeminternal
+
+    @property
+    def is_mounted(self):
+        """Check if the device is mounted."""
+        return self.property.DeviceIsMounted
+
+    @property
+    def is_unlocked(self):
+        """Check if device is already unlocked."""
+        return self.is_luks and self.luks_cleartext_holder
+
+    @property
+    def mount_paths(self):
+        raw_paths = self.property.DeviceMountPaths
+        return [os.path.normpath(path) for path in raw_paths]
+
+    @property
+    def device_file(self):
+        return os.path.normpath(self.property.DeviceFile)
+
+    @property
+    def is_filesystem(self):
+        return self.id_usage == 'filesystem'
+
+    @property
+    def is_crypto(self):
+        return self.id_usage == 'crypto'
+
+    @property
+    def is_luks(self):
+        return self.property.DeviceIsLuks
+
+    @property
+    def is_luks_cleartext(self):
+        """Check whether this is a luks cleartext device."""
+        return self.property.DeviceIsLuksCleartext
+
+    @property
+    def luks_cleartext_slave(self):
+        """Get luks crypto device."""
+        return self.property.LuksCleartextSlave
+
+    @property
+    def luks_cleartext_holder(self):
+        """Get unlocked luks cleartext device."""
+        return self.property.LuksHolder
+
+    @property
+    def is_luks_cleartext_slave(self):
+        """Check whether the luks device is currently in use."""
+        if not self.is_luks:
+            return False
+        for device in get_all(self.bus):
+            if (not device.is_filesystem or device.is_mounted) and (
+                    device.is_luks_cleartext and
+                    device.luks_cleartext_slave == self.device_path):
+                return True
+        return False
+
+    @property
+    def has_media(self):
+        return self.property.DeviceIsMediaAvailable
+
+    @property
+    def id_type(self):
+        return self.property.IdType
+
+    @property
+    def id_usage(self):
+        return self.property.IdUsage
+
+    @property
+    def id_uuid(self):
+        return self.property.IdUuid
+
+    # methods
+    def mount(self, filesystem, options):
+        self.method.FilesystemMount(filesystem, options)
+
+    def unmount(self):
+        self.method.FilesystemUnmount([])
+
+    def lock(self, options):
+        """Lock Luks device."""
+        return self.method.LuksLock(options)
+
+    def unlock(self, password, options):
+        """Unlock Luks device."""
+        return self.method.LuksUnlock(password, options)
+
+
+def get_all(bus):
+    """Enumerate all device objects currently known to udisks."""
+    udisks = bus.get_object(UDISKS_OBJECT, UDISKS_OBJECT_PATH)
+    for path in udisks.EnumerateDevices(dbus_interface=UDISKS_INTERFACE):
+        yield Device(bus, path)
+
+def get_all_handleable(bus):
+    """Enumerate all handleable devices currently known to udisks."""
+    for device in get_all(bus):
+        if device.is_handleable:
+            yield device
+
+def get_device(bus, path):
+    """Get a device proxy by device name or any mount path of the device."""
+    logger = logging.getLogger('udiskie.udisks.get_device')
+    for device in get_all(bus):
+        if os.path.samefile(path, device.device_file):
+            return device
+        for p in device.mount_paths:
+            if os.path.samefile(path, p):
+                return device
+    logger.warn('Device not found: %s' % path)
+    return None
+
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.