Repository: manoss96/fluke (language: python)

Issue:
16 be able to gain access to the file api through dir api



Discussion / hints:

Fix patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 536a6d7..48c940b 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -6,7 +6,7 @@ on:
       - main
       - develop
   pull_request:
-    braches:
+    branches:
       - '**'
 
 jobs:
@@ -25,27 +25,21 @@ jobs:
         with:
           python-version: ${{ matrix.python-version }}
 
-      - name: Set PYTHONPATH
-        run: |
-          echo "PYTHONPATH=${GITHUB_WORKSPACE}" >> $GITHUB_ENV
-
       - name: Install dependencies
         run: |
           python -m pip install --upgrade pip
-          python -m pip install .
           python -m pip install .[tests]
 
       - name: Run tests
         run: |
-          cd tests
-          python -m coverage run -m unittest
+          python -m coverage run --rcfile=tests/.coveragerc -m unittest discover tests
           python -m coverage lcov
 
       - name: Python ${{ matrix.python-version }} Coveralls
         uses: coverallsapp/github-action@master
         with:
           github-token: ${{ secrets.GITHUB_TOKEN }}
-          path-to-lcov: tests/coverage.lcov
+          path-to-lcov: coverage.lcov
           flag-name: python-${{ matrix.python-version }}-run
           parallel: true
 
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fc0e141..ce08717 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,14 +2,63 @@
 # Change Log
 All notable changes to this project will be documented in this file.
 
-## [Unreleased] - DATE
+## [0.2.0] - 2023/03/25
 
 ### Added
+
+- *File* API now provides a *read* method
+  in order to read a file's bytes. (https://github.com/manoss96/fluke/issues/11)
+
+- *Dir* API now provides an *is_file* method
+  used for determining whether the specified
+  path points to a file or not. (https://github.com/manoss96/fluke/issues/16)
+
+- *Dir* API now provides a set of methods in order to gain
+  access to the directory's files via the *File* API. These
+  methods are the following:
+  + *get_file(file_path: str) -> File*
+  + *traverse_files(recursively: bool = False) -> Iterator[File]*
+  + *get_files(recursively: bool = False, show_abs_path: bool = False) -> dict[str, File]*
+  
+  All files spawned by a dictionary share the same metadata dictionaries
+  with the directory that spawned them. In the case of remote files, they
+  also share the same client and cache. (https://github.com/manoss96/fluke/issues/16)
+
+- *fluke.storage.AWSS3File* now has a *get_bucket_name* method.
+  (https://github.com/manoss96/fluke/issues/16)
    
 ### Changed
+
+- Parameter *path* in *fluke.storage.AWSS3Dir* and *fluke.storage.AzureBlobDir*
+  constructors now defaults to ``None`` in order to reference the whole
+  container and bucket respectively (https://github.com/manoss96/fluke/issues/15)
+
+- Providing a path that starts with a separator, e.g. ``/``, to constructors
+  *fluke.storage.AWSS3Dir* and *fluke.storage.AzureBlobDir* will now throw
+  an ``InvalidPathError`` exception. (https://github.com/manoss96/fluke/issues/15)
+
+- Both *File* and *Dir* API methods *get_metadata* will now return
+  an empty dictionary instead of ``None`` when no metadata has been
+  set (https://github.com/manoss96/fluke/issues/16).
+
+- *Dir* API method *iterate_contents* has been renamed to *traverse*.
+  (https://github.com/manoss96/fluke/issues/16)
  
 ### Fixed
+
+- Bug in *test_storage.MockSFTPClient.getfo*. (https://github.com/manoss96/fluke/issues/11)
+
+- Explicitly sort the results of ``os.listdir`` and ``os.walk``
+  due to inconsistent order. (https://github.com/manoss96/fluke/issues/12)
+
+- Bug where the recursive cache was considered empty if the directory
+  contained no sub-directories (https://github.com/manoss96/fluke/issues/16)
+
+- Bug where both *File* and *Dir* API methods *get_metadata* returned
+  the reference to the private metadata dictionary reference, instead
+  of a copy of said dictionary (https://github.com/manoss96/fluke/issues/16).
+
  
-## [0.1.0] - 2023-03-06
+## [0.1.0] - 2023/03/06
  
 First release
\ No newline at end of file
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..9ee81e3
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,4 @@
+Please be nice to each other and abide by the principles of the [Python Software Foundation][psf-coc].
+
+<!-- MARKDOWN LINKS & IMAGES -->
+[psf-coc]: https://www.python.org/psf/codeofconduct/
\ No newline at end of file
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..dabdb26
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,47 @@
+
+Contributing to Fluke
+============================
+
+No matter whether you wish to report a bug or propose a new feature,
+please raise a new issue by visiting the [Issues Page][issues-page].
+
+Setting up a development environment
+-------------------------------------
+Regardless of whether you want to work on fixing a bug or implementing a new feature,
+you should be able to set up a separate development environment just for Fluke. The
+fastest way to do this would be the following:
+
+1. Make sure that Python 3.9 or later is installed on your machine.
+2. Either clone or download the "fluke" repository.
+3. Create a virtual environment just for Fluke (by running ``python3 -m venv env``)
+4. Install all dependencies (you can find a ``requirements.txt`` file in ``docs/source/``)
+
+At this point you may want to create a simple script, e.g. ``main.py``
+and place it directly within the cloned repo. In this script, you are
+free to import from "fluke" so that you can experiment by executing
+various bits of code. Just make sure that, when running your script,
+your working directory is the "fluke" repo, and you are good to go!
+
+Running the tests
+-------------------------------------
+For a pull request to be merged, it is important that it passes all required
+tests. In order to ensure that, you can run the tests yourself by executing
+the following command:
+```
+python3 -m unittest discover tests
+```
+You are also able to specify the testing modules that
+you wish to be executed:
+```
+python3 -m unittest tests.test_storage
+```
+Finally, you can get the test coverage by executing the following commands:
+```
+python -m coverage run --rcfile=tests/.coveragerc -m unittest discover tests
+python -m coverage html
+```
+This will produce a ``htmlcov/index.html`` file which will contain
+various information regarding the test coverage.
+
+<!-- MARKDOWN LINKS & IMAGES -->
+[issues-page]: https://github.com/manoss96/fluke/issues
\ No newline at end of file
diff --git a/README.md b/README.md
index 2966b30..968da32 100644
--- a/README.md
+++ b/README.md
@@ -72,6 +72,6 @@ You can learn more about Fluke by visiting the [Fluke Documentation Page][docs-u
 [python-url]: https://www.python.org/downloads/release/python-390/
 [license-shield]: https://img.shields.io/badge/license-MIT-red
 [license-url]: https://github.com/manoss96/fluke/blob/main/LICENSE
-[coverage-shield]: https://coveralls.io/repos/github/manoss96/fluke/badge.svg?branch=develop&service=github
+[coverage-shield]: https://coveralls.io/repos/github/manoss96/fluke/badge.svg?branch=main&service=github
 [coverage-url]: https://coveralls.io/github/manoss96/fluke?branch=main
 [docs-url]: https://fluke.readthedocs.io/en/latest/
diff --git a/docs/source/conf.py b/docs/source/conf.py
index 0857fef..3fa60bb 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -22,7 +22,7 @@
 author = 'Manos Stoumpos'
 
 # The full version, including alpha/beta/rc tags
-release = '0.1.0'
+release = '0.2.0'
 
 
 # -- General configuration ---------------------------------------------------
diff --git a/docs/source/user_guide/handling_auth.rst b/docs/source/user_guide/handling_auth.rst
index 12067f8..0f0d023 100644
--- a/docs/source/user_guide/handling_auth.rst
+++ b/docs/source/user_guide/handling_auth.rst
@@ -9,7 +9,7 @@ resources require some form of authentication during which a user must identify
 themselves so that they are granted access to said resources. Fluke itself
 manages authentication through the `fluke.auth <../documentation/auth.html>`_ module.
 The general idea is that you must first initialize some *Auth* instance,
-which is then provided to the resource you wish to gain access to:
+which is then provided to the resource with which you wish to interact:
 
 .. code-block:: python
 
@@ -31,7 +31,8 @@ Authenticating with AWS
 ==========================================
 
 In order to use any AWS resource, you have to be able to authenticate with
-AWS first. This can be easily achieved by creating an ``AWSAuth`` instance
+AWS first. This can be easily achieved by creating an
+`AWSAuth <../documentation/auth.html#fluke.auth.AWSAuth>`_ instance
 and providing it with your access key:
 
 .. code-block:: python
@@ -95,8 +96,8 @@ Authenticating via an Azure service principal
 ----------------------------------------------
 
 In order to authenticate via an Azure service principal,
-you may use the ordinary ``AzureAuth`` class constructor,
-providing it with the storage account's URL, as well as
+you may use the ordinary `AzureAuth <../documentation/auth.html#fluke.auth.AzureAuth>`_
+class constructor, providing it with the storage account's URL, as well as
 all information relevant to the service principal, that is,
 the *tenant ID*, *client ID* and finally the *client secret*:
 
@@ -116,8 +117,9 @@ the *tenant ID*, *client ID* and finally the *client secret*:
 Authenticating via a connection string
 ----------------------------------------------
 
-Alternatively, you are able to use ``AzureAuth.from_conn_string`` so
-as to authenticate with a resource by using a connection string,
+Alternatively, you are able to use
+`AzureAuth.from_conn_string <../documentation/auth.html#fluke.auth.AzureAuth.from_conn_string>`_
+so as to authenticate with a resource by using a connection string,
 which must typically include the account's name and key, as well
 as all necessary endpoints regarding the resources to which we request
 access to:
@@ -166,9 +168,12 @@ This way of authentication has the following two prerequisites:
 * There exists a public/private SSH key pair on your local machine.
 * The remote machine to which you will be connecting has a copy of your public key.
 
-If both of the above are true, then you can simply create a ``RemoteAuth`` instance
-through the use of the function ``RemoteAuth.from_key``, to which you must provide
-via the ``pkey`` parameter the path of the file containing your private key:
+If both of the above are true, then you can simply create a
+`RemoteAuth <../documentation/auth.html#fluke.auth.RemoteAuth>`_ 
+instance through the use of the function
+`RemoteAuth.from_key <../documentation/auth.html#fluke.auth.RemoteAuth.from_key>`_,
+to which you must provide the path of the file containing your private key
+via the ``pkey`` parameter:
 
 .. code-block:: python
 
@@ -188,8 +193,8 @@ Authenticating via password is more straightforward
 as you only need to know the password of the user as
 which you will be logging into the remote machine.
 To do that, you simply need to invoke the function
-``RemoteAuth.from_password``, providing it with said
-password:
+`RemoteAuth.from_password <../documentation/auth.html#fluke.auth.RemoteAuth.from_password>`_,
+providing it with said password:
 
 .. code-block:: python
 
diff --git a/docs/source/user_guide/interacting_with_data.rst b/docs/source/user_guide/interacting_with_data.rst
index d248d8f..88ea09d 100644
--- a/docs/source/user_guide/interacting_with_data.rst
+++ b/docs/source/user_guide/interacting_with_data.rst
@@ -4,10 +4,9 @@
 Interacting with data
 ***********************
 
-Fluke makes it easy to inspect your data, but more imporantly,
-to transfer them from one location to another, no matter where
-they currently reside or to where they are being transfered.
-In this section, we will look at all aspects of working with
+Fluke makes it easy to interact with your data no matter
+where they currently reside or where they are going! In
+this section, we will look at all aspects of working with
 data through Fluke.
 
 ========================================
@@ -54,25 +53,28 @@ need a bit more information. This is typically an *Auth* instance used
 for authenticating with the remote host in order to gain access to its resources
 (see `Handling authentication <authentication.html>`_), as well as some
 more information regarding an entity's exact location. For example, in the
-case of an Amazon S3 object we also need the name of the bucket that contains
-it:
+case of a directory which resides within an Amazon S3 bucket, we also need
+the name of said bucket:
 
 .. code-block:: python
 
   from fluke.auth import AWSAuth
-  from fluke.storage import AWSS3File
+  from fluke.storage import AWSS3Dir
 
   # Create an "AWSAuth" instance.
   auth = AWSAuth(**credentials)
 
-  # Gain access to an S3 object.
-  s3_obj = AWSS3File(
+  # Gain access to an Amazon S3 directory.
+  aws_dir = AWSS3Dir(
     auth=auth,
     bucket='bucket_name',
-    path='path/within/amazon/s3/bucket/obj.txt')
+    path='path/within/amazon/s3/bucket/dir')
+
+  # Interact with "aws_dir".
+  ...
 
   # Close any open connections.
-  s3_obj.close()
+  aws_dir.close()
 
 Notice how at the end we invoked the instance's ``close`` method? This is
 done in order to prevent any connection leaks from occurring, and is in fact
@@ -83,22 +85,101 @@ class's context manager:
 .. code-block:: python
 
   from fluke.auth import AWSAuth
-  from fluke.storage import AWSS3File
+  from fluke.storage import AWSS3Dir
 
   # Create an "AWSAuth" instance.
   auth = AWSAuth(**credentials)
 
-  # Gain access to an S3 object through a context manager.
-  with AWSS3File(
+  # Gain access to an S3 directory through a context manager.
+  with AWSS3Dir(
     auth=auth,
     bucket='bucket_name',
-    path='path/within/amazon/s3/bucket/obj.txt'
-  ) as s3_obj:
+    path='path/within/amazon/s3/bucket/dir'
+  ) as aws_dir:
+
+    # Interact with aws_dir.
+    ...
+
+.. _accesing-files-through-a-directory:
+
+----------------------------------------
+Accessing files through a directory
+----------------------------------------
+
+After having gained access to a directory, you may also
+access its files as individual *File* instances through
+certain methods which are part of the *Dir* API. These
+methods are the following:
+
+* ``get_file(file_path: str) -> File``
+* ``get_files(recursively: bool, show_abs_path: bool) -> dict[str, File]``
+* ``traverse_files(recursively: bool) -> Iterator[File]``
+
+Consider the following example in which we use the directory's
+``get_file`` method in order to access a file which resides
+directly within it, namely ``file.txt``:
+
+.. code-block:: python
+
+  from fluke.storage import LocalDir, LocalFile
+
+  # Access directory.
+  local_dir: LocalDir = LocalDir(path='dir')
+
+  # Access file through directory.
+  local_file: LocalFile = local_dir.get_file('file.txt')
+
+This is almost equivalent to:
+
+.. code-block:: python
+
+  from fluke.storage import LocalFile
 
-    # Interact with s3_obj...
+  # Access file directly.
+  local_file: LocalFile = LocalFile(path='dir/file.txt')
+
+The only difference between these two methods of accessing a file
+is that when doing so through a directory, this results in the two
+sharing a bunch of resources with each other, them being:
+
+* The file metadata
+* The underlying connection (in case of remote entities)
+* The cache storage (in case of remote entities)
+
+Regarding remote entities, seeing that a file shares
+the same connection with the directory from which it came,
+you should restrain from invoking the file's ``close`` method,
+as this would also impact your ability to interact with the
+directory:
+
+.. code-block:: python
+
+  from fluke.auth import AWSAuth
+  from fluke.storage import AWSS3Dir, AWSS3File
+
+  # Create an "AWSAuth" instance.
+  auth = AWSAuth(**credentials)
+
+  # Access directory.
+  aws_dir = AWSS3Dir(auth=auth, bucket='bucket_name', path='dir')
+
+  # Access file through directory.
+  aws_file: AWSS3File = aws_dir.get_file('file.txt')
+
+  # Close connection through file.
+  aws_file.close()
+
+  # This would now cause an exception as
+  # the underlying connection has been closed.
+  dir_size = aws_dir.get_size()
+
+As for the file metadata and cache storage, you can read more in
+:ref:`Assigning metadata to files <assigning-metadata-to-files>` and
+:ref:`Speeding things up with caching <speeding-things-up-with-caching>`
+respectively.
 
 ========================================
-Inspecting data
+Exploring data
 ========================================
 
 After having gained access to an entity, you are then
@@ -112,6 +193,15 @@ fetch its size in bytes:
   file = LocalFile(path='/home/user/path/to/file.txt')
   size = file.get_size()
 
+or even read its entire contents:
+
+.. code-block:: python
+
+  from fluke.storage import LocalFile
+
+  file = LocalFile(path='/home/user/path/to/file.txt')
+  file_bytes = file.read()
+
 Since all *File* API methods are pretty self-explanatory, in this section
 we will focus on the *Dir* API, and more specifically, on parameter
 ``recursively``, as its value directly determines the result of most
@@ -136,12 +226,12 @@ printed onto the console after executing it:
 
 .. code-block:: python
 
-    from fluke.storage import LocalDir
+  from fluke.storage import LocalDir
 
-    dir = LocalDir(path='dir/')
+  local_dir = LocalDir(path='dir')
 
-    print(f"Ordinary count: {dir.count()}")
-    print(f"Recursive count: {dir.count(recursively=True)}")
+  print(f"Ordinary count: {local_dir.count()}")
+  print(f"Recursive count: {local_dir.count(recursively=True)}")
 
 This is the output we get after executing the above code block:
 
@@ -161,20 +251,20 @@ counting three separate entities within the context of our example, namely
 
 Note that whenever ``recursively`` is set to ``True``,
 subdirectories are not considered to be additional entities,
-and are only searched for any files that may or reside within them.
+and are only searched for any files that may reside within them.
 If, for example, ``subdir`` were empty, then ``dir.count(recursively=True)``
 would merely return the value ``1``.
 
 
 ========================================
-Moving data between locations
+Transfering data
 ========================================
 
-The ability to transfer data between various locations is arguably
-Fluke's predominant feature, and is rendered possible through
-the use of the ``transfer_to`` method, which is part of both
-*File* and *Dir* APIs. Below is a complete example in which we
-transfer the contents of a virtual directory residing within an
+The ability to move data between various locations is arguably
+Fluke's predominant feature, and it is rendered possible
+through the use of the ``transfer_to`` method, which is part of
+both *File* and *Dir* APIs. Below is a complete example in which
+we transfer the contents of a virtual directory residing within an
 Azure S3 bucket to a virtual directory of an Azure blob container,
 all in just a few lines of code:
 
@@ -190,8 +280,8 @@ all in just a few lines of code:
   azr_auth = AzureAuth(**azr_credentials)
 
   with (
-      AWSS3Dir(auth=aws_auth, bucket="bucket", path='dir/') as aws_dir,
-      AzureBlobDir(auth=azr_auth, container="container", path='dir/') as azr_dir
+      AWSS3Dir(auth=aws_auth, bucket='bucket', path='dir') as aws_dir,
+      AzureBlobDir(auth=azr_auth, container='container', path='dir') as azr_dir
   ):
       aws_dir.transfer_to(dst=azr_dir, recursively=True)
 
@@ -224,6 +314,8 @@ or a remote file system, as standard file systems have no way of associating fil
 metadata, they are quite important when uploading files to the cloud in case you wish to assign
 any metadata to them.
 
+..  _assigning-metadata-to-files:
+
 --------------------------------------------
 Assigning metadata to files
 --------------------------------------------
@@ -243,7 +335,7 @@ some metadata to it through the ``set_metadata`` method:
   file.set_metadata({'id': '12345', 'type': 'txt'})
 
   # Transfer file to Amazon S3 along with its metadata.
-  with AWSS3Dir(auth=AWSAuth(**aws_credentials), bucket="bucket", path='dir/') as aws_dir:
+  with AWSS3Dir(auth=AWSAuth(**aws_credentials), bucket='bucket', path='dir') as aws_dir:
       file.transfer_to(dst=aws_dir, include_metadata=True)
 
 Along with *file.txt* being uploaded to Amazon S3, any metadata that
@@ -252,13 +344,68 @@ In fact, we can easily confirm this by executing the following code:
 
 .. code-block:: python
 
-    print(aws_dir.get_metadata('file.txt'))
+  print(aws_dir.get_metadata('file.txt'))
 
 which results in the following output being printed onto the console:
 
 .. code-block::
 
-    {'id': '12345', 'type': 'txt'}
+  {'id': '12345', 'type': 'txt'}
+
+Finally, note that when accessing a file through a directory,
+any modification made to its metadata through either API,
+will be reflected in the other. Consider the following example:
+
+.. code-block:: python
+
+  from fluke.storage import LocalDir, LocalFile
+
+  # Access directory.
+  local_dir: LocalDir = LocalDir(path='dir')
+
+  # Access file through directory.
+  file_name = 'file.txt'
+  local_file: LocalFile = local_dir.get_file(file_name)
+
+  # Set file metadata through the "File" API..
+  local_file.set_metadata(metadata={'id': '12345', 'type': 'txt'})
+
+  # Get file metadata through the "Dir" API.
+  print(local_dir.get_metadata(file_path=file_name))
+
+Executing the above code produces the following output:
+
+.. code-block::
+
+  {'id': '12345', 'type': 'txt'}
+
+Even though ``local_dir.set_metadata`` was never invoked,
+``local_dir.get_metadata(file_path=file_name)`` returns the
+metadata dictionary that was set via ``local_file.set_metadata``.
+Naturally, the reverse is also possible:
+
+.. code-block:: python
+
+  from fluke.storage import LocalDir, LocalFile
+
+  # Access directory.
+  local_dir: LocalDir = LocalDir(path='dir')
+
+  # Access file through directory.
+  file_name = 'file.txt'
+  local_file: LocalFile = local_dir.get_file(file_name)
+
+  # Set file metadata through the "Dir" API..
+  local_dir.set_metadata(file_path=file_name, metadata={'id': '12345', 'type': 'txt'})
+
+  # Get file metadata through the "File" API.
+  print(local_file.get_metadata())
+
+After being executed, this produces the same output as before:
+
+.. code-block::
+
+  {'id': '12345', 'type': 'txt'}
 
 --------------------------------------------
 Loading metadata
@@ -275,24 +422,24 @@ assigned to it. Let's do just that and see what happens:
   from fluke.storage import AWSS3File
 
   # Gain access to 'file.txt' on Amazon S3 and print its metadata.
-  with AWSS3File(auth=AWSAuth(**aws_credentials), bucket="bucket", path='dir/file.txt') as aws_obj:
+  with AWSS3File(auth=AWSAuth(**aws_credentials), bucket='bucket', path='dir/file.txt') as aws_obj:
       print(aws_obj.get_metadata())
 
 By executing the above code, we get the following output:
 
 .. code-block::
 
-    None
+  {}
 
 That's strange. Shouldn't we see a dictionary containing the metadata we just assigned to
 the object while transfering it to Amazon S3? Actually, the answer is no, and the reason
 for this is quite simple: both methods ``get_metadata`` and ``set_metadata`` only interact
-with the instance through which they are being invoked, which is no more than an object stored
+with the instance through which they are being invoked, which is merely an object stored
 within our machine's local RAM. This consequently means that if we were to invoke an instance's
-``set_metadata`` method in order to assign some metadata to it, it would have absolutely no effect
+``set_metadata`` method in order to assign metadata to it, it would have absolutely no effect
 on the actual metadata of the instance's underlying object entity that is stored on Amazon S3.
 Similarly, invoking an instance's ``get_metadata`` method won't fetch the object's actual metadata,
-though it will look for any metadata we may have assigned to it locally.
+though it will search for any metadata we may have assigned to it locally.
 
 So how can we inspect the actual metadata of an object? This can be easily done
 by invoking an instance's ``load_metadata`` method, which goes on to fetch
@@ -306,7 +453,7 @@ add the aforementioned line of code:
   from fluke.storage import AWSS3File
 
   # Gain access to 'file.txt' on Amazon S3 and print its metadata.
-  with AWSS3File(auth=AWSAuth(**aws_credentials), bucket="bucket", path='dir/file.txt') as aws_obj:
+  with AWSS3File(auth=AWSAuth(**aws_credentials), bucket='bucket', path='dir/file.txt') as aws_obj:
       # Load metadata first.
       aws_obj.load_metadata()
       # Then print it.
@@ -316,7 +463,7 @@ By executing the above code, we now get the expected output:
 
 .. code-block::
 
-    {'id': '12345', 'type': 'txt'}
+  {'id': '12345', 'type': 'txt'}
 
 As a final note, whenever setting ``include_metadata`` to ``True``, ``transfer_to`` will
 always look first for any local metadata that can be assigned to the file(s) resulting from
@@ -333,10 +480,12 @@ has not been invoked:
   from fluke.storage import AWSS3File, AzureBlobFile
 
   with (
-      AWSS3File(auth=AWSAuth(**aws_credentials), bucket="bucket", path='dir/file.txt') as aws_obj,
-      AzureBlobDir(auth=AzureAuth(**azr_credentials), container="container", path='file.txt') as azr_blob
+      AWSS3File(auth=AWSAuth(**aws_credentials), bucket='bucket', path='dir/file.txt') as aws_obj,
+      AzureBlobDir(auth=AzureAuth(**azr_credentials), container='container', path='file.txt') as azr_dir
   ):
-      aws_obj.transfer_to(dst=azr_blob, include_metadata=True)
+      aws_obj.transfer_to(dst=azr_dir, include_metadata=True)
+
+..  _speeding-things-up-with-caching:
 
 ========================================
 Speeding things up with caching
@@ -351,8 +500,8 @@ Consider for instance requesting the size of a virtual directory on
 the cloud. In order to compute this value, Fluke must not only list
 all individual files within the directory, but also fetch their
 respective sizes so that it computes the total size of the directory.
-As directories grow larger and larger, tasks like this require more
-and more time to complete.
+As directories grow larger and larger, tasks similar to this require
+more and more time to complete.
 
 Due to the above stated reasons, Fluke offers the ability to cache
 certain bits of information about remote entities after you've requested
@@ -369,27 +518,27 @@ setting parameter ``cache`` to ``True`` during its instantiation:
 
   auth = AWSAuth(**aws_credentials)
 
-  with AWSS3Dir(auth=auth, bucket="bucket", path='dir/', cache=True) as aws_dir:
-      # Fetch metadata via HTTP.
-      t = time.time()
-      aws_dir.load_metadata()
-      print(f"Fetched metadata in {time.time() - t:.2f} seconds!")
+  with AWSS3Dir(auth=auth, bucket='bucket', path='dir', cache=True) as aws_dir:
+    # Fetch metadata via HTTP.
+    t = time.time()
+    aws_dir.load_metadata()
+    print(f"Fetched metadata in {time.time() - t:.2f} seconds!")
 
-      # Fetch metadata from cache.
-      t = time.time()
-      aws_dir.load_metadata()
-      print(f"Fetched metadata in {time.time() - t:.2f} seconds!")
+    # Fetch metadata from cache.
+    t = time.time()
+    aws_dir.load_metadata()
+    print(f"Fetched metadata in {time.time() - t:.2f} seconds!")
 
 Executing the above code block outputs the following:
 
 .. code-block::
 
-    Fetched metadata in 7.91 seconds!
-    Fetched metadata in 0.01 seconds!
+  Fetched metadata in 7.91 seconds!
+  Fetched metadata in 0.01 seconds!
 
-Note, however, that after caching an entity you are going to
-be missing on any potential updates it receives, as any information
-relating to it would be retrieved straight from the cache.
+Note, however, that after caching information about a remote entity
+you are going to be missing on any potential updates it receives,
+as said information would be retrieved straight from the cache.
 Be that as it may, you can always clear an instance's cache
 by invoking ``purge``:
 
@@ -398,28 +547,74 @@ by invoking ``purge``:
   from fluke.auth import AWSAuth
   from fluke.storage import AWSS3File
 
-  with AWSS3Dir(auth=AWSAuth(**aws_credentials), bucket="bucket", path='dir/', cache=True) as aws_dir:
-      # Count number of items in directory.
-      print(f"Directory count: {aws_dir.count()}")
+  with AWSS3Dir(auth=AWSAuth(**aws_credentials), bucket='bucket', path='dir', cache=True) as aws_dir:
+    # Count number of items in directory.
+    print(f"Directory count: {aws_dir.count()}")
 
-      # At this point, one more file
-      # is uploaded onto the directory.
+    # At this point, assume that one more
+    # file is uploaded to the directory.
 
-      # Re-count number of items in directory
-      # without purging the cache.
-      print(f"Directory count: {aws_dir.count()}")
+    # Re-count number of items in directory
+    # without purging the cache.
+    print(f"Directory count: {aws_dir.count()}")
 
-      # Re-count number of items in directory
-      # after purging the cache.
-      aws_dir.purge()
-      print(f"Directory count: {aws_dir.count()}")
+    # Re-count number of items in directory
+    # after purging the cache.
+    aws_dir.purge()
+    print(f"Directory count: {aws_dir.count()}")
 
 By executing the above code, we get the following output:
 
 .. code-block::
 
-    Directory count: 1
-    Directory count: 1
-    Directory count: 2
+  Directory count: 1
+  Directory count: 1
+  Directory count: 2
+
+Lastly, as mentioned in
+:ref:`Accessing files through a directory <accesing-files-through-a-directory>`,
+all files that are generated by a directory share the same cache storage with it.
+This means that fetching some information about a file might speed up fetching
+information about the directory which spawned it. Consider the following example:
+
+.. code-block:: python
+
+  from fluke.storage import AWSS3Dir
+
+  # This object will be used to authenticate with AWS.
+  aws_auth = AWSAuth(**aws_credentials)
+
+  # Access an AWS S3 directory and render it "cacheable".
+  with AWSS3Dir(auth=aws_auth, bucket="bucket", path='dir', cache=True) as aws_dir:
+    # Fetch the directory's total size and time it.
+    t = time.time()
+    _ = aws_dir.get_size(recursively=True)
+    print(f"Fetched size in {time.time() - t:.2f} seconds!")
 
+    # Now purge the directory's cache.
+    aws_dir.purge()
+
+    # Traverse the directory's files and fetch their respective sizes.
+    for file in aws_dir.traverse_files(recursively=True):
+      _ = file.get_size()
+
+    # Fetch the directory's total size and time it again.
+    t = time.time()
+    _ = aws_dir.get_size(recursively=True)
+    print(f"Fetched size in {time.time() - t:.2f} seconds!")
+
+The above code produces the following output when executed:
+
+.. code-block::
 
+  Fetched size in 21.17 seconds!
+  Fetched size in 0.03 seconds!
+
+The first time we requested the total size of the directory,
+Fluke had to list the directory recursively in order to fetch
+the respective sizes of all its files, and then finally
+compute their sum. However, the second time we requested the same
+value, we had already fetched all respective file sizes independently,
+resulting in these values being cached. Therefore, Fluke could just grab
+them from the cache and simply add them up so as to return the total
+size of the directory.
diff --git a/fluke/_cache.py b/fluke/_cache.py
index 8751346..a20b356 100644
--- a/fluke/_cache.py
+++ b/fluke/_cache.py
@@ -1,34 +1,66 @@
 from typing import Optional as _Optional
+from typing import Iterator as _Iterator
+from typing import Callable as _Callable
 
 
-class CacheManager():
+class Cache():
     '''
-    A class used in managing ``Cache`` instances.
+    A class whose instances represent cached \
+    information about a file.
     '''
 
     def __init__(self):
         '''
-        A class used in managing ``Cache`` instances.
+        A class whose instances represent cached \
+        information about a file.
         '''
-        self.__top_level_files: list[str] = list()
-        self.__top_level_dirs: list[str] = list()
-        self.__cache: dict[str, Cache] = dict()
+        self.__size: _Optional[int] = None
+        self.__metadata: _Optional[dict[str, str]] = None
 
 
-    def is_recursive_cache_empty(self):
+    def get_size(self) -> _Optional[int]:
         '''
-        Returns ``True`` if no items have been cached \
-        recursively, else returns ``False``.
+        Returns the size of the file. Returns ``None`` \
+        if no size has been cached.
         '''
-        return len([f for f in self.__cache if f not in set(self.__top_level_files)]) == 0
-    
+        return self.__size
 
-    def is_top_level_empty(self):
+
+    def set_size(self, size: int) -> None:
         '''
-        Returns ``True`` if the paths of any top-level \
-        objects have not been stored, else returns ``False``.
+        Sets the size of the file.
         '''
-        return len(self.__top_level_files + self.__top_level_dirs) == 0
+        self.__size = size
+
+
+    def get_metadata(self) -> _Optional[dict[str, str]]:
+        '''
+        Returns a dictionary containing the metadata of the file. \
+        Returns ``None`` if no metadata has been cached.
+        '''
+        if self.__metadata is not None:
+            return dict(self.__metadata)
+
+
+    def set_metadata(self, metadata: dict[str, str]) -> None:
+        '''
+        Sets the metadata of the file.
+        '''
+        self.__metadata = dict(metadata)
+
+
+class CacheManager():
+    '''
+    A class used in managing ``Cache`` instances.
+    '''
+
+    def __init__(self):
+        '''
+        A class used in managing ``Cache`` instances.
+        '''
+        self.__top_level_files: list[str] = list()
+        self.__top_level_dirs: list[str] = list()
+        self.__cache: dict[str, Cache] = dict()
 
 
     def get_size(self, file_path: str) -> _Optional[int]:
@@ -49,6 +81,8 @@ def cache_size(self, file_path: str, size: int) -> None:
         :param str file_path: The file's absolute path.
         :param int size: The file's size.
         '''
+        if not self.is_in_cache(path=file_path):
+            self.__cache.update({file_path: Cache()})
         self.__cache[file_path].set_size(size=size)
     
 
@@ -70,6 +104,8 @@ def cache_metadata(self, file_path: str, metadata: dict[str, str]) -> None:
         :param str file_path: The file's absolute path.
         :param dict[str, str]: The file's metadata.
         '''
+        if not self.is_in_cache(path=file_path):
+            self.__cache.update({file_path: Cache()})
         self.__cache[file_path].set_metadata(metadata=metadata)
 
 
@@ -93,41 +129,16 @@ def is_in_cache(self, path: str) -> bool:
         return path in self.__cache
 
 
-    def add_to_cache(self, path: str) -> None:
-        '''
-        Creates an entry within the cache based on the \
-        provided file path key.
-
-        :param str path: The file's absolute path.
-        '''
-        self.__cache.update({path: Cache()})
-        
-
-    def add_to_top_level(self, path: str, is_file: bool) -> None:
-        '''
-        Adds the provided path to the cache manager's \
-        top-level list.
-
-        :param str path: The object's absolute path.
-        :param bool is_file: Indicates whether the \
-            provided path points to a file or not.
-        '''
-        if is_file:
-            self.__top_level_files.append(path)
-            if not self.is_in_cache(path=path):
-                self.add_to_cache(path=path)
-        else:
-            self.__top_level_dirs.append(path)
-
-
-    def iterate_contents(
+    def get_content_iterator(
         self,
         recursively: bool,
         include_dirs: bool
-    ):
+    ) -> _Optional[_Iterator[str]]:
         '''
-        Iterate through all ``Cache`` instances stored. \
-        either within the ordinary cache or the top-level cache.
+        Returns an iterator capable of going through all ``Cache`` \
+        instances' keys, either within the ordinary cache or the top-level \
+        cache depending on the value of ``recursively``. Returns ``None`` \
+        if no ``Cache`` instances exist for the requested cache type.
 
         :param bool recursively: Indicates whether to iterate \
             instances recursively by looking in the ordinary \
@@ -137,52 +148,75 @@ def iterate_contents(
             to ``False``.
         '''
         if recursively:
-            iterable = (key for key in self.__cache)
+            if self._is_recursive_cache_empty():
+                return None
+            return (key for key in self.__cache)
         else:
+            if self._is_top_level_empty():
+                return None
             top_level = list(self.__top_level_files)
             if include_dirs:
                 top_level += self.__top_level_dirs
-            iterable = (key for key in top_level)
-
-        return iterable
-
-
-class Cache():
-    '''
-    A class whose instances represent cached \
-    information about a file.
-    '''
-
-    def __init__(self):
-        '''
-        A class whose instances represent cached \
-        information about a file.
-        '''
-        self.__size: _Optional[int] = None
-        self.__metadata: _Optional[dict[str, str]] = None
+            return (key for key in top_level)
+    
 
-    def get_size(self) -> _Optional[int]:
-        '''
-        Returns the size of the file. Returns ``None`` \
-        if no size has been cached.
+    def cache_contents(
+        self,
+        iterator: _Iterator[str],
+        recursively: bool,
+        is_file: _Callable[[str], bool]
+    ) -> None:
         '''
-        return self.__size
+        Goes through the provided iterator \
+        in order to cache its contents.
 
-    def set_size(self, size: int) -> None:
-        '''
-        Sets the size of the file.
+        :param Iterator[str] iterator: An iterator through \
+            which a directory's contents can be traversed.
+        :param bool recursively: Indicates whether the provided \
+            iterator traverses a directory recursively or not.
+        :param Callable[[str], bool] is_file: A function that \
+            receives a string path and returns a value indicating \
+            whether said path corresponds to a file or a directory.
         '''
-        self.__size = size
+        if recursively:
+            for path in iterator:
+                if not self.is_in_cache(path=path):
+                    self.__cache.update({path: Cache()})
+        else:
+            for path in iterator:
+                if is_file(path):
+                    self.__top_level_files.append(path)
+                    if not self.is_in_cache(path=path):
+                        self.__cache.update({path: Cache()})
+                else:
+                    self.__top_level_dirs.append(path)
+    
 
-    def get_metadata(self) -> _Optional[dict[str, str]]:
+    def _is_recursive_cache_empty(self):
         '''
-        Returns a dictionary containing the metadata of the file. \
-        Returns ``None`` if no metadata has been cached.
+        Returns ``True`` if no items have been cached \
+        recursively, else returns ``False``.
         '''
-        return self.__metadata
+        # If top-level cache is empty, then check if
+        # the recursive cache has items...
+        if self._is_top_level_empty():
+            return len(self.__cache) == 0
+        
+        # If top-level cache is not empty,
+        # then consider the recursive cache
+        # not empty if no sub-directories exist.
+        if len(self.__top_level_dirs) == 0:
+            return False
+        
+        # Else if sub-directories exist, check whether
+        # this directory has been traversed recursively.
+        return len([f for f in self.__cache if f not in set(self.__top_level_files)]) == 0
+    
 
-    def set_metadata(self, metadata: dict[str, str]) -> None:
+    def _is_top_level_empty(self):
         '''
-        Sets the metadata of the file.
+        Returns ``True`` if the paths of any top-level \
+        objects have not been stored, else returns ``False``.
         '''
-        self.__metadata = metadata
\ No newline at end of file
+        return len(self.__top_level_files + self.__top_level_dirs) == 0
+    
\ No newline at end of file
diff --git a/fluke/_handlers.py b/fluke/_handlers.py
index eb56866..ba0a60e 100644
--- a/fluke/_handlers.py
+++ b/fluke/_handlers.py
@@ -1,7 +1,10 @@
-from typing import Any as _Any
-from typing import Optional as _Optional
+import os as _os
+import io as _io
 from abc import ABC as _ABC
 from abc import abstractmethod as _absmethod
+from shutil import copyfileobj as _copyfileobj
+from typing import Optional as _Optional
+from typing import Iterator as _Iterator
 
 
 import boto3 as _boto3
@@ -13,23 +16,203 @@
 from .auth import AWSAuth as _AWSAuth
 from .auth import AzureAuth as _AzureAuth
 from .auth import RemoteAuth as _RemoteAuth
+from ._cache import CacheManager as _CacheManager
 from ._exceptions import UnknownKeyTypeError as _UKTE
+from ._helper import join_paths as _join_paths
+from ._helper import infer_separator as _infer_sep
+from ._helper import relativize_path as _relativize
 
 
 class ClientHandler(_ABC):
     '''
     An abstract class which serves as the \
     base class for all client-like classes.
+
+    :param bool cache: Indicates whether it is allowed for \
+        any fetched data to be cached for faster subsequent \
+        access. Defaults to ``False``.
     '''
+
+    def __init__(self, cache: bool):
+        '''
+        An abstract class which serves as the \
+        base class for all client-like classes.
+
+        :param bool cache: Indicates whether it is allowed for \
+            any fetched data to be cached for faster subsequent \
+            access. Defaults to ``False``.
+        '''
+        self.__cache_manager = _CacheManager() if cache else None
+
+
+    def is_cacheable(self) -> bool:
+        '''
+        Returns a value indicating whether this \
+        handler instance has been defined so that \
+        it is able to cache data.
+        '''
+        return self.__cache_manager is not None
     
+
+    def purge(self) -> None:
+        '''
+        If cacheable, then purges the handler's cache, \
+        else does nothing.
+        '''
+        if self.is_cacheable():
+            self.__cache_manager.purge()
+            self.__cache_manager = _CacheManager()
+
+
+    def get_file_size(self, file_path: str) -> int:
+        '''
+        Returns the size of a file in bytes.
+        
+        :param str file_path: The path of the file in question.
+
+        :note: This method will go on to fetch the requested value \
+            from a remote resource only when one of the following is \
+            true:
+            - Caching has not been enabled.
+            - Caching has not been enabled, though \
+              the requested value has not been cached.
+
+            In the second case, the value will be cached \
+            after it has been retrieved.
+        '''
+        if self.is_cacheable():
+            if (size := self.__cache_manager.get_size(file_path=file_path)) is not None:
+                return size
+            else:
+                size = self._get_file_size_impl(file_path)
+                self.__cache_manager.cache_size(file_path, size)
+                return size
+        else:
+            return self._get_file_size_impl(file_path)
+        
+
+    def get_file_metadata(self, file_path: str) -> dict[str, str]:
+        '''
+        Returns a dictionary containing the metadata of a file.
+        
+        :param str file_path: The path of the file in question.
+        
+        :note: This method will go on to fetch the requested value \
+            from a remote resource only when one of the following is \
+            true:
+            - Caching has not been enabled.
+            - Caching has not been enabled, though \
+              the requested value has not been cached.
+
+            In the second case, the value will be cached \
+            after it has been retrieved.
+        '''
+        if self.is_cacheable():
+            if (metadata := self.__cache_manager.get_metadata(file_path=file_path)) is not None:
+                return metadata
+            else:
+                metadata = self._get_file_metadata_impl(file_path)
+                self.__cache_manager.cache_metadata(file_path, metadata)
+                return metadata
+        else:
+            return self._get_file_metadata_impl(file_path)
+        
+
+    def traverse_dir(
+        self,
+        dir_path: str,
+        recursively: bool,
+        include_dirs: bool,
+        show_abs_path: bool
+    ) -> _Iterator[str]:
+        '''
+        Returns an iterator capable of going through the paths \
+        of the dictionary's contents as strings.
+
+        :param str dir_path: The absolute path of the directory \
+            whose contents are to be iterated.
+        :param bool recursively: Indicates whether the directory \
+            is to be traversed recursively or not. If set to  ``False``, \
+            then only those files that reside directly within the \
+            directory are to be considered. If set to ``True``, \
+            then all files are considered, no matter whether they \
+            reside directly within the directory or within any of \
+            its subdirectories.
+        :param bool include_dirs: Indicates whether to include any \
+            directories in the results in case ``recursively`` is \
+            set to ``False``.
+        :param bool show_abs_path: Indicates whether it \
+            should be displayed the absolute or the relative \
+            path of the contents.
+
+        :note: The resulting iterator may vary depending on the \
+            value of parameter ``recursively``.
+        '''
+
+        def relativize_iter(iterator: _Iterator[str]):
+            return map(lambda p: _relativize(
+                parent=dir_path,
+                child=p,
+                sep=_infer_sep(dir_path)
+            ), iterator)
+
+        if self.is_cacheable():
+            # Grab content iterator from cache if it exists.
+            if (iterator := self.__cache_manager.get_content_iterator(
+                recursively=recursively,
+                include_dirs=include_dirs)
+            ) is not None:
+                return iterator if show_abs_path \
+                    else relativize_iter(iterator)
+            # Else...
+            else:
+                # Fetch content iterator.
+                # NOTE: Set "show_abs_path" to "True" so that all
+                #       contents are cached via their absolute paths.
+                iterator = self._traverse_dir_impl(
+                    dir_path=dir_path,
+                    recursively=recursively,
+                    show_abs_path=True)
+                # Cache all contents.
+                self.__cache_manager.cache_contents(
+                    iterator=iterator,
+                    recursively=recursively,
+                    is_file=self.is_file)
+                # Reset iterator by grabbing it from cache.
+                iterator = self.__cache_manager.get_content_iterator(
+                    recursively=recursively,
+                    include_dirs=include_dirs)
+                if show_abs_path:
+                    return iterator
+                else:
+                    return relativize_iter(iterator)
+        else:
+            if recursively or include_dirs:
+                return self._traverse_dir_impl(
+                    dir_path=dir_path,
+                    recursively=recursively,
+                    show_abs_path=show_abs_path)
+            else:
+                iterator = filter(
+                    self.is_file,
+                    self._traverse_dir_impl(
+                        dir_path=dir_path,
+                        recursively=recursively,
+                        show_abs_path=True))
+                return iterator if show_abs_path \
+                    else relativize_iter(iterator)
+
+
     @_absmethod
-    def get_client(self) -> _Any:
+    def is_open(self) -> bool:
         '''
-        Returns the client through which the handler's \
-        underlying connection is established.
+        Returns a value indicating whether \
+        this handler's underlying client connection \
+        is open or not.
         '''
         pass
 
+
     @_absmethod
     def open_connections(self) -> None:
         '''
@@ -37,6 +220,7 @@ def open_connections(self) -> None:
         '''
         pass
 
+
     @_absmethod
     def close_connections(self) -> None:
         '''
@@ -45,33 +229,358 @@ def close_connections(self) -> None:
         pass
 
 
+    @_absmethod
+    def path_exists(self, path: str) -> bool:
+        '''
+        Returns ``True`` if the provided path exists \
+        within the directory, else returns ``False``.
+
+        :param str path: An absolute path.
+        '''
+        pass
+
+
+    @_absmethod
+    def is_file(self, file_path: str) -> bool:
+        '''
+        Returns ``True`` if the provided path points \
+        to a file, else returns ``False``.
+
+        :param str file_path: The absolute path of the \
+            file in question.
+        '''
+        pass
+
+
+    @_absmethod
+    def mkdir(self, path: str) -> None:
+        '''
+        Creates a directory into the provided path.
+
+        :param str path: The path of the directory \
+            that is to be created.
+        '''
+        pass
+
+
+    @_absmethod
+    def read(
+        self,
+        file_path: str,
+        buffer: _io.BytesIO,
+        include_metadata: bool
+    ) -> _Optional[dict[str, str]]:
+        '''
+        Reads the bytes of a file into the provided buffer. \
+        Returns either ``None`` or a dictionary containing \
+        file metadata, depending on the value of parameter \
+        ``include_metadata``.
+
+        :param str file_path: The absolute path of the \
+            file in question.
+        :param BytesIO buffer: A buffer to download the \
+            file into.
+        :param bool include_metadata: Indicates whether \
+            to download any existing file metadata as well.
+        '''
+        pass
+
+
+    @_absmethod
+    def write(
+        self,
+        file_path: str,
+        buffer: _io.BytesIO,
+        metadata: _Optional[dict[str, str]]
+    ) -> None:
+        '''
+        Writes the bytes contained within the provided \
+        buffer into the specified path.
+
+        :param str file_path: The absolute path of the \
+            file in question.
+        :param BytesIO buffer: A buffer containing the \
+            file's bytes.
+        :param dict[str, str] | None: If not ``None``, \
+            then assigns the provided metadata to the file \
+            during the upload.
+        '''
+        pass
+
+
+    @_absmethod
+    def _traverse_dir_impl(
+        self,
+        dir_path: str,
+        recursively: bool,
+        show_abs_path: bool
+    ) -> _Iterator[str]:
+        '''
+        Returns an iterator capable of going through the paths \
+        of the dictionary's contents as strings.
+
+        :param str dir_path: The absolute path of the directory \
+            whose contents are to be iterated.
+        :param bool recursively: Indicates whether the directory \
+            is to be traversed recursively or not. If set to  ``False``, \
+            then only those files that reside directly within the \
+            directory are to be considered. If set to ``True``, \
+            then all files are considered, no matter whether they \
+            reside directly within the directory or within any of \
+            its subdirectories.
+        :param bool show_abs_path: Indicates whether it \
+            should be displayed the absolute or the relative \
+            path of the contents.
+
+        :note: The resulting iterator may vary depending on the \
+            value of parameter ``recursively``.
+        '''
+        pass
+
+
+    @_absmethod
+    def _get_file_size_impl(self, file_path: str) -> int:
+        '''
+        Fetches and returns the size of a file in bytes.
+
+        :param str file_path: The absolute path of the \
+            file in question.
+        '''
+        pass
+
+
+    @_absmethod
+    def _get_file_metadata_impl(self, file_path: str) -> dict[str, str]:
+        '''
+        Fetches and returns a dictionary containing the \
+        metadata of a file.
+
+        :param str file_path: The absolute path of the \
+            file in question.
+        '''
+        pass
+
+
+class FileSystemHandler(ClientHandler):
+    '''
+    A class used in handling all local file \
+    system operations.
+    '''
+
+    def __init__(self):
+        '''
+        A class used in handling all local file \
+        system operations.
+        '''
+        super().__init__(cache=False)
+
+
+    def is_open(self) -> bool:
+        '''
+        Returns a value indicating whether \
+        this handler's underlying client connection \
+        is open or not.
+        '''
+        raise NotImplementedError()
+
+
+    def open_connections(self):
+        '''
+        Throws ``NotImplementedError``.
+        '''
+        raise NotImplementedError()
+
+
+    def close_connections(self):
+        '''
+        Throws ``NotImplementedError``.
+        '''
+        raise NotImplementedError()
+
+
+    def path_exists(self, path: str) -> bool:
+        '''
+        Returns ``True`` if the provided path exists \
+        within the directory, else returns ``False``.
+
+        :param str path: Either an absolute path or a \
+            path relative to the directory.
+        '''
+        return _os.path.exists(path=path)
+    
+
+    def is_file(self, file_path: str) -> bool:
+        '''
+        Returns ``True`` if the provided path points \
+        to a file, else returns ``False``.
+
+        :param str file_path: The absolute path of the \
+            file in question.
+        '''
+        return _os.path.isfile(file_path)
+    
+
+    def mkdir(self, path: str) -> None:
+        '''
+        Creates a directory into the provided path.
+
+        :param str path: The path of the directory \
+            that is to be created.
+        '''
+        _os.makedirs(path, exist_ok=True)
+    
+
+    def read(
+        self,
+        file_path: str,
+        buffer: _io.BytesIO,
+        include_metadata: bool
+    ) -> _Optional[dict[str, str]]:
+        '''
+        Reads the bytes of a file into the provided buffer. \
+        Returns either ``None`` or a dictionary containing \
+        file metadata, depending on the value of parameter \
+        ``include_metadata``.
+
+        :param str file_path: The absolute path of the \
+            file in question.
+        :param BytesIO buffer: A buffer to download the \
+            file into.
+        :param bool include_metadata: Indicates whether \
+            to download any existing file metadata as well.
+        '''
+        with open(file=file_path, mode='rb') as file:
+            _copyfileobj(fsrc=file, fdst=buffer)
+
+
+    def write(
+        self,
+        file_path: str,
+        buffer: _io.BytesIO,
+        metadata: _Optional[dict[str, str]]
+    ) -> None:
+        '''
+        Writes the bytes contained within the provided \
+        buffer into the specified path.
+
+        :param str file_path: The absolute path of the \
+            file in question.
+        :param BytesIO buffer: A buffer containing the \
+            file's bytes.
+        :param dict[str, str] | None: If not ``None``, \
+            then assigns the provided metadata to the file \
+            during the upload.
+        '''
+        # Create any necessary directories.
+        self.mkdir(_os.path.dirname(file_path))
+        # Write file.
+        with open(file=file_path, mode='wb') as file:
+            _copyfileobj(fsrc=buffer, fdst=file)
+    
+
+    def _get_file_size_impl(self, file_path) -> int:
+        '''
+        Fetches and returns the size of a file in bytes.
+
+        :param str file_path: The path of the file in question.
+        '''
+        return _os.path.getsize(file_path)
+    
+
+    def _get_file_metadata_impl(self, file_path: str) -> dict[str, str]:
+        '''
+        Throws ``NotImplementedError``.
+
+        :param str file_path: The absolute path of the \
+            file in question.
+        '''
+        raise NotImplementedError()
+
+
+    def _traverse_dir_impl(
+        self,
+        dir_path: str,
+        recursively: bool,
+        show_abs_path: bool
+    ) -> _Iterator[str]:
+        '''
+        Returns an iterator capable of going through the paths \
+        of the dictionary's contents as strings.
+
+        :param str dir_path: The absolute path of the directory \
+            whose contents are to be iterated.
+        :param bool recursively: Indicates whether the directory \
+            is to be traversed recursively or not. If set to  ``False``, \
+            then only those files that reside directly within the \
+            directory are to be considered. If set to ``True``, \
+            then all files are considered, no matter whether they \
+            reside directly within the directory or within any of \
+            its subdirectories.
+        :param bool show_abs_path: Indicates whether it \
+            should be displayed the absolute or the relative \
+            path of the contents.
+
+        :note: The resulting iterator may vary depending on the \
+            value of parameter ``recursively``.
+        '''
+        sep = _infer_sep(path=dir_path)
+
+        if recursively:
+            for dp, dn, fn in _os.walk(dir_path):
+                dn.sort()
+                for file in sorted(fn):
+                    dp = dp.replace(_os.sep, sep)
+                    if not show_abs_path:
+                        dp = _relativize(
+                            parent=dir_path,
+                            child=dp,
+                            sep=sep)
+                    yield _join_paths(sep, dp, file)
+        else:
+            for obj in sorted(_os.listdir(dir_path)):
+                abs_path = _join_paths(sep, dir_path, obj)
+                if not self.is_file(abs_path):
+                    abs_path += sep
+                    obj += sep
+                yield abs_path if show_abs_path else obj
+
+
 class SSHClientHandler(ClientHandler):
     '''
     A class used in handling the SSH and SFTP \
     connections to a remote server.
 
-    :param RemoteAuth auth: A ``RemoteAuth`` \
-        instance used in authenticating with a remote machine.
+    :param RemoteAuth auth: A ``RemoteAuth`` instance used \
+        for authenticating with a remote machine.
+    :param bool cache: Indicates whether it is allowed for \
+        any fetched data to be cached for faster subsequent \
+        access. Defaults to ``False``.
     '''
 
-    def __init__(self, auth: _RemoteAuth):
+    def __init__(self, auth: _RemoteAuth, cache: bool):
         '''
         A class used in handling the SSH and SFTP \
         connections to a remote server.
 
-        :param RemoteAuth auth: A ``RemoteAuth`` \
-            instance used in authenticating with a remote machine.
+        :param RemoteAuth auth: A ``RemoteAuth`` instance used \
+            for authenticating with a remote machine.
+        :param bool cache: Indicates whether it is allowed for \
+            any fetched data to be cached for faster subsequent \
+            access. Defaults to ``False``.
         '''
+        super().__init__(cache=cache)
         self.__auth: _RemoteAuth = auth
         self.__ssh: _prmk.SSHClient = None
         self.__sftp: _prmk.SFTPClient = None
-    
 
-    def get_client(self) -> _Optional[_prmk.SFTPClient]:
+
+    def is_open(self) -> bool:
         '''
-        Returns an SFTP client.
+        Returns a value indicating whether \
+        this handler's underlying client connection \
+        is open or not.
         '''
-        return self.__sftp
+        return self.__ssh is not None
 
 
     def open_connections(self):
@@ -151,6 +660,206 @@ def close_connections(self):
             self.__ssh = None
 
 
+    def path_exists(self, path: str) -> bool:
+        '''
+        Returns ``True`` if the provided path exists \
+        within the directory, else returns ``False``.
+
+        :param str path: Either an absolute path or a \
+            path relative to the directory.
+        '''
+        try:
+            self.__sftp.stat(path=path)
+        except FileNotFoundError:
+            return False
+        return True
+    
+
+    def is_file(self, file_path: str) -> bool:
+        '''
+        Returns ``True`` if the provided path points \
+        to a file, else returns ``False``.
+
+        :param str file_path: The absolute path of the \
+            file in question.
+        '''
+        from stat import S_ISDIR as _is_dir
+        return not _is_dir(self.__sftp.stat(path=file_path).st_mode)
+    
+
+    def mkdir(self, path: str) -> None:
+        '''
+        Creates a directory into the provided path.
+
+        :param str path: The path of the directory \
+            that is to be created.
+        '''
+        self.__sftp.mkdir(path=path)
+    
+
+    def read(
+        self,
+        file_path: str,
+        buffer: _io.BytesIO,
+        include_metadata: bool
+    ) -> _Optional[dict[str, str]]:
+        '''
+        Reads the bytes of a file into the provided buffer. \
+        Returns either ``None`` or a dictionary containing \
+        file metadata, depending on the value of parameter \
+        ``include_metadata``.
+
+        :param str file_path: The absolute path of the \
+            file in question.
+        :param BytesIO buffer: A buffer to download the \
+            file into.
+        :param bool include_metadata: Indicates whether \
+            to download any existing file metadata as well.
+        '''
+        self.__sftp.getfo(remotepath=file_path, fl=buffer)
+
+
+    def write(
+        self,
+        file_path: str,
+        buffer: _io.BytesIO,
+        metadata: _Optional[dict[str, str]]
+    ) -> None:
+        '''
+        Writes the bytes contained within the provided \
+        buffer into the specified path.
+
+        :param str file_path: The absolute path of the \
+            file in question.
+        :param BytesIO buffer: A buffer containing the \
+            file's bytes.
+        :param dict[str, str] | None: If not ``None``, \
+            then assigns the provided metadata to the file \
+            during the upload.
+        '''
+
+        sep = _infer_sep(file_path)
+
+        def get_parent_dir(file_path: str) -> _Optional[str]:
+            '''
+            Returns the path to the parent directory \
+            of the provided file path. Returns ``None`` \
+            if said directory is the root directory.
+
+            :param str file_path: The path of the file \
+                in question.
+            '''
+            file_path = file_path.rstrip(sep)
+            if sep in file_path:
+                return f"{sep.join(file_path.split(sep)[:-1])}{sep}"
+            return None
+
+        # Create any directories necessary.
+        parent_dir, non_existing_dirs = file_path, []
+        while (parent_dir := get_parent_dir(parent_dir)) is not None:
+            if not self.path_exists(path=parent_dir):
+                non_existing_dirs.append(parent_dir)
+        for dir in reversed(non_existing_dirs):
+            self.mkdir(path=dir)
+
+        # Write file from buffer.
+        self.__sftp.putfo(fl=buffer, remotepath=file_path)
+
+
+    def _get_file_size_impl(self, file_path) -> int:
+        '''
+        Fetches and returns the size of a file in bytes.
+
+        :param str file_path: The path of the file in question.
+        '''
+        return self.__sftp.stat(path=file_path).st_size
+    
+
+    def _get_file_metadata_impl(self, file_path: str) -> dict[str, str]:
+        '''
+        Fetches and returns a dictionary containing the \
+        metadata of a file.
+
+        :param str file_path: The absolute path of the \
+            file in question.
+        '''
+        raise NotImplementedError()
+
+
+    def _traverse_dir_impl(
+        self,
+        dir_path: str,
+        recursively: bool,
+        show_abs_path: bool
+    ) -> _Iterator[str]:
+        '''
+        Returns an iterator capable of going through the paths \
+        of the dictionary's contents as strings.
+
+        :param str dir_path: The absolute path of the directory \
+            whose contents are to be iterated.
+        :param bool recursively: Indicates whether the directory \
+            is to be traversed recursively or not. If set to  ``False``, \
+            then only those files that reside directly within the \
+            directory are to be considered. If set to ``True``, \
+            then all files are considered, no matter whether they \
+            reside directly within the directory or within any of \
+            its subdirectories.
+        :param bool show_abs_path: Indicates whether it \
+            should be displayed the absolute or the relative \
+            path of the contents.
+
+        :note: The resulting iterator may vary depending on the \
+            value of parameter ``recursively``.
+        '''
+        from stat import S_ISDIR as _is_dir
+
+        sep = _infer_sep(dir_path)
+
+        if recursively:
+
+            def filter_obj(
+                sftp: _prmk.SFTPClient,
+                attr: _prmk.SFTPAttributes,
+                parent_dir: str
+            ):
+                abs_path = _join_paths(
+                    sep,
+                    parent_dir,
+                    attr.filename)
+
+                if _is_dir(attr.st_mode):
+                    try:
+                        for sub_attr in sftp.listdir_attr(path=abs_path):
+                            yield from filter_obj(
+                                sftp=sftp,
+                                attr=sub_attr,
+                                parent_dir=abs_path)
+                    except:
+                        pass
+                else:
+                    yield abs_path
+
+            for attr in self.__sftp.listdir_attr(path=dir_path):
+                for file_path in filter_obj(
+                    sftp=self.__sftp,
+                    attr=attr,
+                    parent_dir=dir_path
+                ):
+                    yield (file_path if show_abs_path \
+                        else _relativize(
+                            parent=dir_path,
+                            child=file_path,
+                            sep=sep))
+        else:
+            for attr in self.__sftp.listdir_attr(path=dir_path):
+                path = attr.filename
+                if _is_dir(attr.st_mode):
+                    path += sep
+                yield _join_paths(sep, dir_path, path) \
+                    if show_abs_path else path
+
+
 class AWSClientHandler(ClientHandler):
     '''
     A class used in handling the HTTP \
@@ -160,9 +869,12 @@ class AWSClientHandler(ClientHandler):
         used in authenticating with AWS.
     :param str bucket: The name of the Amazon S3 bucket \
         to which a connection is to be established.
+    :param bool cache: Indicates whether it is allowed for \
+        any fetched data to be cached for faster subsequent \
+        access. Defaults to ``False``.
     '''
 
-    def __init__(self, auth: _AWSAuth, bucket: str):
+    def __init__(self, auth: _AWSAuth, bucket: str, cache: bool):
         '''
         A class used in handling the HTTP \
         connection to an Amazon S3 bucket.
@@ -171,17 +883,31 @@ def __init__(self, auth: _AWSAuth, bucket: str):
             used in authenticating with AWS.
         :param str bucket: The name of the Amazon S3 bucket \
             to which a connection is to be established.
+        :param bool cache: Indicates whether it is allowed for \
+            any fetched data to be cached for faster subsequent \
+            access. Defaults to ``False``.
         '''
+        super().__init__(cache=cache)
         self.__auth = auth
         self.__bucket_name = bucket
-        self.__s3_bucket = None
+        self.__bucket = None
+
+
+    def get_bucket_name(self) -> str:
+        '''
+        Returns the name of the bucket to which \
+        a connection has been established.
+        '''
+        return self.__bucket_name
     
 
-    def get_client(self) -> _Optional['_boto3.resources.factory.bucket.s3.Bucket']:
+    def is_open(self) -> bool:
         '''
-        Returns an Amazon S3 bucket resource.
+        Returns a value indicating whether \
+        this handler's underlying client connection \
+        is open or not.
         '''
-        return self.__s3_bucket
+        return self.__bucket is not None
 
 
     def open_connections(self) -> None:
@@ -189,10 +915,10 @@ def open_connections(self) -> None:
         Opens an HTTP connection to the Amazon S3 bucket.
         '''
 
-        if self.__s3_bucket is not None:
+        if self.__bucket is not None:
             return
 
-        self.__s3_bucket = _boto3.resource(
+        self.__bucket = _boto3.resource(
             service_name='s3',
             **self.__auth.get_credentials()
         ).Bucket(self.__bucket_name)
@@ -202,9 +928,211 @@ def close_connections(self):
         '''
         Closes the HTTP connection to the Amazon S3 bucket.
         '''
-        if self.__s3_bucket is not None:
-            self.__s3_bucket.meta.client.close()
-            self.__s3_bucket = None
+        if self.__bucket is not None:
+            self.__bucket.meta.client.close()
+            self.__bucket = None
+
+
+    def path_exists(self, path: str) -> bool:
+        '''
+        Returns ``True`` if the provided path exists \
+        within the directory, else returns ``False``.
+
+        :param str path: Either an absolute path or a \
+            path relative to the directory.
+        '''
+        from botocore.exceptions import ClientError as _CE
+        try:
+            self.__bucket.Object(path).load()
+        except _CE:
+            return False
+        return True
+
+
+    def is_file(self, file_path: str) -> bool:
+        '''
+        Returns ``True`` if the provided path points \
+        to a file, else returns ``False``.
+
+        :param str file_path: The absolute path of the \
+            file in question.
+        '''
+        return not file_path.endswith('/')
+    
+
+    def dir_exists(self, path: str) -> bool:
+        '''
+        Returns ``True`` if the provided path exists \
+        and is a directory, else returns ``False``.
+
+        :param str path: Either an absolute path or a \
+            path relative to the parent directory.
+        '''
+        for _ in self.__bucket.objects.filter(Prefix=path):
+            return True
+        return False
+        
+    
+
+    def mkdir(self, path: str) -> None:
+        '''
+        Creates a directory into the provided path.
+
+        :param str path: The path of the directory \
+            that is to be created.
+        '''
+        self.__bucket.put_object(
+            Key=path,
+            ContentType='application/x-directory; charset=UTF-8')
+    
+
+    def read(
+        self,
+        file_path: str,
+        buffer: _io.BytesIO,
+        include_metadata: bool
+    ) -> _Optional[dict[str, str]]:
+        '''
+        Reads the bytes of a file into the provided buffer. \
+        Returns either ``None`` or a dictionary containing \
+        file metadata, depending on the value of parameter \
+        ``include_metadata``.
+
+        :param str file_path: The absolute path of the \
+            file in question.
+        :param BytesIO buffer: A buffer to download the \
+            file into.
+        :param bool include_metadata: Indicates whether \
+            to download any existing file metadata as well.
+        '''
+        obj = self.__bucket.Object(key=file_path)
+        obj.download_fileobj(Fileobj=buffer)
+        if include_metadata:
+            return obj.metadata
+        
+
+    def write(
+        self,
+        file_path: str,
+        buffer: _io.BytesIO,
+        metadata: _Optional[dict[str, str]]
+    ) -> None:
+        '''
+        Writes the bytes contained within the provided \
+        buffer into the specified path.
+
+        :param str file_path: The absolute path of the \
+            file in question.
+        :param BytesIO buffer: A buffer containing the \
+            file's bytes.
+        :param dict[str, str] | None: If not ``None``, \
+            then assigns the provided metadata to the file \
+            during the upload.
+        '''
+        self.__bucket.upload_fileobj(
+            Key=file_path,
+            Fileobj=buffer,
+            ExtraArgs={ "Metadata": metadata }
+                if metadata is not None else None)
+        
+
+    def _get_file_size_impl(self, file_path) -> int:
+        '''
+        Fetches and returns the size of a file in bytes.
+
+        :param str file_path: The absolute path of the \
+            file in question.
+        '''
+        return self.__bucket.Object(key=file_path).content_length
+    
+
+    def _get_file_metadata_impl(self, file_path: str) -> dict[str, str]:
+        '''
+        Fetches and returns a dictionary containing the metadata of a file.
+
+        :param str file_path: The path of the file in question.
+        '''
+        return self.__bucket.Object(key=file_path).metadata
+
+
+    def _traverse_dir_impl(
+        self,
+        dir_path: str,
+        recursively: bool,
+        show_abs_path: bool
+    ) -> _Iterator[str]:
+        '''
+        Returns an iterator capable of going through the paths \
+        of the dictionary's contents as strings.
+
+        :param str dir_path: The absolute path of the directory \
+            whose contents are to be iterated.
+        :param bool recursively: Indicates whether the directory \
+            is to be traversed recursively or not. If set to  ``False``, \
+            then only those files that reside directly within the \
+            directory are to be considered. If set to ``True``, \
+            then all files are considered, no matter whether they \
+            reside directly within the directory or within any of \
+            its subdirectories.
+        :param bool show_abs_path: Indicates whether it \
+            should be displayed the absolute or the relative \
+            path of the contents.
+
+        :note: The resulting iterator may vary depending on the \
+            value of parameter ``recursively``.
+        '''
+        paginator = self.__bucket.meta.client.get_paginator('list_objects')
+
+        delimiter = '' if recursively else '/'
+
+        sep = _infer_sep(dir_path)
+
+        def page_iterator():
+            yield from paginator.paginate(
+                Bucket=self.__bucket_name,
+                Prefix=dir_path,
+                Delimiter=delimiter)
+
+        def object_iterator(response):
+            for obj in response.get('Contents', []):
+                file_path = obj['Key']
+                yield file_path if show_abs_path else \
+                    _relativize(dir_path, file_path, sep)
+                
+        if recursively:
+            for response in page_iterator():
+                yield from object_iterator(response)
+        else:
+
+            def dir_iterator(response):
+                for dir in response.get('CommonPrefixes', []):
+                    path = dir['Prefix']
+                    yield path if show_abs_path else \
+                        _relativize(dir_path, path, sep)
+                    
+            for response in page_iterator():
+                        
+                obj_iter = object_iterator(response)
+                dir_iter = dir_iterator(response)
+
+                obj = next(obj_iter, None)
+                dir = next(dir_iter, None)
+
+                while True:
+                    if obj is None and dir is None:
+                        break
+                    elif obj is None and dir is not None:
+                        yield dir
+                        dir = next(dir_iter, None)
+                    elif obj is not None and dir is None:
+                        yield obj
+                        obj = next(obj_iter, None)
+                    elif obj > dir:
+                        yield dir
+                        dir = next(dir_iter, None)
+                    else:
+                        yield obj
+                        obj = next(obj_iter, None)
 
 
 class AzureClientHandler(ClientHandler):
@@ -216,9 +1144,12 @@ class AzureClientHandler(ClientHandler):
         used in authenticating with Microsoft Azure.
     :param str container: The name of the Azure blob \
         container to which a connection is to be established.
+    :param bool cache: Indicates whether it is allowed for \
+        any fetched data to be cached for faster subsequent \
+        access. Defaults to ``False``.
     '''
 
-    def __init__(self, auth: _AzureAuth, container: str):
+    def __init__(self, auth: _AzureAuth, container: str, cache: bool):
         '''
         A class used in handling the HTTP \
         connection to an Azure blob container.
@@ -227,17 +1158,39 @@ def __init__(self, auth: _AzureAuth, container: str):
             used in authenticating with Microsoft Azure.
         :param str container: The name of the Azure blob \
             container to which a connection is to be established.
+        :param bool cache: Indicates whether it is allowed for \
+            any fetched data to be cached for faster subsequent \
+            access. Defaults to ``False``.
         '''
+        super().__init__(cache=cache)
         self.__auth = auth
         self.__container_name = container
-        self.__azr_container = None
+        self.__container = None
 
+
+    def container_exists(self) -> bool:
+        '''
+        Returns a value indicating whether the \
+        specified container exists or not.
+        '''
+        return self.__container.exists()
+
+
+    def get_container_name(self) -> str:
+        '''
+        Returns the name of the container to which \
+        a connection has been established.
+        '''
+        return self.__container_name
     
-    def get_client(self) -> _Optional[_ContainerClient]:
+
+    def is_open(self) -> bool:
         '''
-        Returns an Azure blob container client.
+        Returns a value indicating whether \
+        this handler's underlying client connection \
+        is open or not.
         '''
-        return self.__azr_container
+        return self.__container is not None
 
 
     def open_connections(self) -> None:
@@ -245,17 +1198,17 @@ def open_connections(self) -> None:
         Opens an HTTP connection to the Azure blob container.
         '''
 
-        if self.__azr_container is not None:
+        if self.__container is not None:
             return
 
         credentials = self.__auth.get_credentials()
 
         if 'conn_string' in credentials:
-            self.__azr_container = _ContainerClient.from_connection_string(
+            self.__container = _ContainerClient.from_connection_string(
                 conn_str=credentials['conn_string'],
                 container_name=self.__container_name)
         else:
-            self.__azr_container = _ContainerClient(
+            self.__container = _ContainerClient(
                 account_url=credentials.pop('account_url'),
                 container_name=self.__container_name,
                 credential=_CSC(**credentials))
@@ -265,6 +1218,167 @@ def close_connections(self):
         '''
         Closes the HTTP connection to the Azure blob container.
         '''
-        if self.__azr_container is not None:
-            self.__azr_container.close()
-            self.__azr_container = None
\ No newline at end of file
+        if self.__container is not None:
+            self.__container.close()
+            self.__container = None
+
+
+    def path_exists(self, path: str) -> bool:
+        '''
+        Returns ``True`` if the provided path exists \
+        within the directory, else returns ``False``.
+
+        :param str path: Either an absolute path or a \
+            path relative to the directory.
+        '''
+        with self.__container.get_blob_client(blob=path) as blob:
+            return blob.exists()
+        
+
+    def is_file(self, file_path: str) -> bool:
+        '''
+        Returns ``True`` if the provided path points \
+        to a file, else returns ``False``.
+
+        :param str file_path: The absolute path of the \
+            file in question.
+        '''
+        return not file_path.endswith('/')
+    
+
+    def mkdir(self, path: str) -> None:
+        '''
+        Creates a directory into the provided path.
+
+        :param str path: The path of the directory \
+            that is to be created.
+        '''
+        with self.__container.get_blob_client(blob=f"{path}DUMMY") as blob:
+            blob.create_append_blob()
+            blob.delete_blob()
+    
+
+    def read(
+        self,
+        file_path: str,
+        buffer: _io.BytesIO,
+        include_metadata: bool
+    ) -> _Optional[dict[str, str]]:
+        '''
+        Reads the bytes of a file into the provided buffer. \
+        Returns either ``None`` or a dictionary containing \
+        file metadata, depending on the value of parameter \
+        ``include_metadata``.
+
+        :param str file_path: The absolute path of the \
+            file in question.
+        :param BytesIO buffer: A buffer to download the \
+            file into.
+        :param bool include_metadata: Indicates whether \
+            to download any existing file metadata as well.
+        '''
+        blob = self.__container.download_blob(blob=file_path)
+        blob.readinto(stream=buffer)
+        if include_metadata:
+            return blob.properties.metadata
+        
+
+    def write(
+        self,
+        file_path: str,
+        buffer: _io.BytesIO,
+        metadata: _Optional[dict[str, str]]
+    ) -> None:
+        '''
+        Writes the bytes contained within the provided \
+        buffer into the specified path.
+
+        :param str file_path: The absolute path of the \
+            file in question.
+        :param BytesIO buffer: A buffer containing the \
+            file's bytes.
+        :param dict[str, str] | None: If not ``None``, \
+            then assigns the provided metadata to the file \
+            during the upload.
+        '''
+        self.__container.upload_blob(
+            name=file_path,
+            data=buffer,
+            metadata=metadata if metadata is not None else None,
+            overwrite=True)
+        
+
+    def _get_file_size_impl(self, file_path) -> int:
+        '''
+        Fetches and returns the size of a file in bytes.
+
+        :param str file_path: The absolute path of the \
+            file in question.
+        '''
+        return self.__container.download_blob(blob=file_path).size
+    
+
+    def _get_file_metadata_impl(self, file_path: str) -> dict[str, str]:
+        '''
+        Fetches and returns a dictionary containing the \
+        metadata of a file.
+
+        :param str file_path: The absolute path of the \
+            file in question.
+        '''
+        return self.__container.download_blob(
+            blob=file_path).properties.metadata
+
+
+    def _traverse_dir_impl(
+        self,
+        dir_path: str,
+        recursively: bool,
+        show_abs_path: bool
+    ) -> _Iterator[str]:
+        '''
+        Returns an iterator capable of going through the paths \
+        of the dictionary's contents as strings.
+
+        :param str dir_path: The absolute path of the directory \
+            whose contents are to be iterated.
+        :param bool recursively: Indicates whether the directory \
+            is to be traversed recursively or not. If set to  ``False``, \
+            then only those files that reside directly within the \
+            directory are to be considered. If set to ``True``, \
+            then all files are considered, no matter whether they \
+            reside directly within the directory or within any of \
+            its subdirectories.
+        :param bool show_abs_path: Indicates whether it \
+            should be displayed the absolute or the relative \
+            path of the contents.
+
+        :note: The resulting iterator may vary depending on the \
+            value of parameter ``recursively``.
+        '''
+        sep = _infer_sep(dir_path)
+
+        if recursively:
+            '''
+            NOTE:
+                Due to certain storage accounts having the
+                hierarchical namespace feature enabled,
+                virtual folders may appear as ordinary
+                blobs. For this reason, consider that any
+                blob whose size is equal to zero is a virtual folder.
+
+                Issue: https://github.com/Azure/azure-sdk-for-python/issues/29026
+            '''
+            iterable = filter(
+                lambda p: p['size'] > 0,
+                self.__container.list_blobs(
+                    name_starts_with=dir_path))
+        else:
+            iterable = self.__container.walk_blobs(
+                name_starts_with=dir_path, delimiter=sep)
+                    
+        for properties in iterable:
+            if show_abs_path:
+                yield properties['name']
+            else:
+                yield _relativize(dir_path, properties['name'], sep)
\ No newline at end of file
diff --git a/fluke/_helper.py b/fluke/_helper.py
index 0cac101..f8ca783 100644
--- a/fluke/_helper.py
+++ b/fluke/_helper.py
@@ -22,6 +22,18 @@ def join_paths(sep: str, *paths: str) -> str:
     return path
 
 
+def relativize_path(parent: str, child: str, sep: str) -> str:
+    '''
+    Modifies the child path so that it is \
+    relative to the parent path.
+
+    :param str parent: The parent path.
+    :param str child: The child path.
+    :param str sep: The path separator used.
+    '''
+    return child.removeprefix(parent).lstrip(sep)
+
+
 def infer_separator(path: str) -> str:
     '''
     Infers the separator from the provided path \
diff --git a/fluke/_ingesters.py b/fluke/_ingesters.py
deleted file mode 100644
index 8fa8a57..0000000
--- a/fluke/_ingesters.py
+++ /dev/null
@@ -1,460 +0,0 @@
-import io as _io
-import typing as _typ
-from abc import ABC as _ABC
-from abc import abstractmethod as _absmethod
-from shutil import copyfileobj as _copyfileobj
-
-
-from ._helper import infer_separator as _infer_sep
-from ._handlers import SSHClientHandler as _SSHClientHandler
-from ._handlers import AWSClientHandler as _AWSClientHandler
-from ._handlers import AzureClientHandler as _AzureClientHandler
-
-
-class Ingester(_ABC):
-    '''
-    An abstract class which serves as the \
-    base class for all ingester-like classes.
-    '''
-
-    def __init__(self):
-        '''
-        An abstract class which serves as the \
-        base class for all ingester-like classes.
-        '''
-        self.__src = None
-        self.__snk = None
-        self.__metadata = None
-
-    def get_source(self) -> _typ.Optional[_typ.Any]:
-        '''
-        Returns the ingester's current source.
-        '''
-        return self.__src
-
-    def set_source(self, src: _typ.Any):
-        '''
-        Sets the ingester's current source.
-
-        :param Any src: The source that is to be set.
-        '''
-        self.__src = src
-
-    def get_sink(self)-> _typ.Optional[_typ.Any]:
-        '''
-        Returns the ingester's current sink.
-        '''
-        return self.__snk
-
-    def set_sink(self, snk: _typ.Any):
-        '''
-        Sets the ingester's current sink.
-
-        :param Any snk: The sink that is to be set.
-        '''
-        self.__snk = snk
-
-    def get_metadata(self) -> _typ.Optional[dict[str, str]]:
-        '''
-        Returns any metadata currently held \
-        by the ingester.
-        '''
-        return self.__metadata
-
-    def set_metadata(self, metadata: dict[str, str]):
-        '''
-        Sets the metadata that is currently \
-        held by the ingester.
-
-        :param dict[str, str] metadata: A dictionary \
-            containing the metadata.
-        '''
-        self.__metadata = metadata
-
-
-    @_absmethod
-    def extract(
-        self,
-        snk: _typ.Union[_io.BufferedReader, _io.BytesIO],
-        include_metadata: bool
-    ) -> _typ.Optional[str]:
-        '''
-        Extracts data from the currently set source \
-        and into the provided sink. If an error occurs, \
-        then returns the corresponding error message, \
-        else returns ``None``.
-
-        :param Union[BufferedReader, BytesIO] snk: A \
-            buffer acting as a sink.
-        :param bool include_metadata: Indicates whether \
-            to ingest any existing metadata along with \
-            the primarily ingested data.
-        '''
-        pass
-
-
-    @_absmethod
-    def load(
-        self,
-        src: _typ.Union[_io.BufferedReader, _io.BytesIO],
-        metadata: dict[str, str]
-    ) -> _typ.Optional[str]:
-        '''
-        Extracts data from the provided source and \
-        into the currently set sink. If an error occurs, \
-        then returns the corresponding error message, \
-        else returns ``None``.
-
-        :param Union[BufferedReader, BytesIO] src: A \
-            buffer acting as a source.
-        :param dict[str, str]: A dictionary containing \
-            metadata that are to be assigned to the \
-            primarily ingested data.
-        '''
-        pass
-
-
-class LocalIngester(Ingester):
-    '''
-    A class used in extracting data from a local source, \
-    as well as loading data into a local sink.
-    '''
-
-    def __init__(self):
-        '''
-        A class used in extracting data from a local source, \
-        as well as loading data into a local sink.
-        '''
-        super().__init__()
-
-    def extract(
-        self,
-        snk: _typ.Union[_io.BufferedReader, _io.BytesIO],
-        include_metadata: bool
-    ) -> _typ.Optional[str]:
-        '''
-        Extracts data from the currently set source \
-        and into the provided sink. If an error occurs, \
-        then returns the corresponding error message, \
-        else returns ``None``.
-
-        :param Union[BufferedReader, BytesIO] snk: A \
-            buffer acting as a sink.
-        :param bool include_metadata: Indicates whether \
-            to ingest any existing metadata along with \
-            the primarily ingested data.
-
-        :note: Param ``include_metadata`` has no use within \
-            the context of method ``LocalIngester.extract``.
-        '''
-        
-        # NOTE: This method is not currently invoked,
-        #       as local files are read directly into
-        #       memory and are not written into a buffer.
-        try:
-            _copyfileobj(fsrc=self.get_source(), fdst=snk)
-        except Exception as e:
-            return str(e)
-        return None
-
-    def load(
-        self,
-        src: _typ.Union[_io.BufferedReader, _io.BytesIO],
-        metadata: dict[str, str]
-    ) -> _typ.Optional[str]:
-        '''
-        Extracts data from the provided source and \
-        into the currently set sink. If an error occurs, \
-        then returns the corresponding error message, \
-        else returns ``None``.
-
-        :param Union[BufferedReader, BytesIO] src: A \
-            buffer acting as a source.
-        :param dict[str, str]: A dictionary containing \
-            metadata that are to be assigned to the \
-            primarily ingested data.
-
-        :note: Param ``metadata`` has no use within the \
-            context of method ``LocalIngester.load``.
-        '''
-        try:
-            _copyfileobj(fsrc=src, fdst=self.get_sink())
-        except Exception as e:
-            return str(e)
-        return None
-
-
-class RemoteIngester(Ingester):
-    '''
-    A class used in extracting data from a remote source, \
-    as well as loading data into a remote sink.
-
-    :param SSHClientHandler handler: An ``SSHClientHandler`` \
-        instance through which access to a remote server is provided.
-    '''
-
-
-    def __init__(self, handler: _SSHClientHandler):
-        '''
-        A class used in extracting data from a remote source, \
-        as well as loading data into a remote sink.
-
-        :param SSHClientHandler handler: An ``SSHClientHandler`` \
-            instance through which access to a remote server is provided.
-        '''
-        super().__init__()
-        self.__handler = handler
-
-
-    def extract(
-        self,
-        snk: _typ.Union[_io.BufferedReader, _io.BytesIO],
-        include_metadata: bool
-    ) -> _typ.Optional[str]:
-        '''
-        Extracts data from the currently set source \
-        and into the provided sink. If an error occurs, \
-        then returns the corresponding error message, \
-        else returns ``None``.
-
-        :param Union[BufferedReader, BytesIO] snk: A \
-            buffer acting as a sink.
-        :param bool include_metadata: Indicates whether \
-            to ingest any existing metadata along with \
-            the primarily ingested data.
-
-        :note: Param ``include_metadata`` has no use within \
-            the context of method ``RemoteIngester.extract``.
-        '''
-
-        sftp = self.__handler.get_client()
-
-        try:
-            sftp.getfo(
-                remotepath=self.get_source(),
-                fl=snk
-            )
-        except Exception as e:
-            return str(e)
-        return None
-
-
-    def load(
-        self,
-        src: _typ.Union[_io.BufferedReader, _io.BytesIO],
-        metadata: dict[str, str]
-    ) -> _typ.Optional[str]:
-        '''
-        Extracts data from the provided source and \
-        into the currently set sink. If an error occurs, \
-        then returns the corresponding error message, \
-        else returns ``None``.
-
-        :param Union[BufferedReader, BytesIO] src: A \
-            buffer acting as a source.
-        :param dict[str, str]: A dictionary containing \
-            metadata that are to be assigned to the \
-            primarily ingested data.
-
-        :note: Param ``metadata`` has no use within the \
-            context of method ``RemoteIngester.load``.
-        '''
-
-        file_path: str = self.get_sink()
-        sftp = self.__handler.get_client()
-
-        sep = _infer_sep(file_path)
-
-        def get_parent_dir(file_path: str) -> _typ.Optional[str]:
-            file_path = file_path.rstrip(sep)
-            if sep in file_path:
-                return f"{sep.join(file_path.split(sep)[:-1])}{sep}"
-            return None
-
-        # Create any directories necessary.
-        parent_dir, non_existing_dirs = file_path, []
-        while (parent_dir := get_parent_dir(parent_dir)) is not None:
-            try:
-                sftp.stat(path=parent_dir)
-                break
-            except FileNotFoundError:
-                non_existing_dirs.append(parent_dir)
-
-        for dir in reversed(non_existing_dirs):
-            sftp.mkdir(path=dir)
-
-        # Load file to remote location.
-        try:
-            sftp.putfo(
-                fl=src,
-                remotepath=file_path)
-        except Exception as e:
-            return str(e)
-        return None
-
-
-class AWSS3Ingester(Ingester):
-    '''
-    A class used in extracting data from an Amazon S3 bucket, \
-    as well as loading data into an Amazon S3 bucket.
-
-    :param AWSClientHandler handler: An ``AWSClientHandler`` \
-        instance through which access to an Amazon S3 bucket is \
-        provided.
-    '''
-
-    def __init__(self, handler: _AWSClientHandler):
-        '''
-        A class used in extracting data from an Amazon S3 bucket, \
-        as well as loading data into an Amazon S3 bucket.
-
-        :param AWSClientHandler handler: An ``AWSClientHandler`` \
-            instance through which access to an Amazon S3 bucket is \
-            provided.
-        '''
-        self.__handler = handler
-        super().__init__()
-
-    
-    def extract(
-        self,
-        snk: _typ.Union[_io.BufferedReader, _io.BytesIO],
-        include_metadata: bool
-    ) -> _typ.Optional[str]:
-        '''
-        Extracts data from the currently set source \
-        and into the provided sink. If an error occurs, \
-        then returns the corresponding error message, \
-        else returns ``None``.
-
-        :param Union[BufferedReader, BytesIO] snk: A \
-            buffer acting as a sink.
-        :param bool include_metadata: Indicates whether \
-            to ingest any existing metadata along with \
-            the primarily ingested data.
-        '''
-        try:
-            # Ingest object from its source.
-            if include_metadata:
-                obj = self.__handler.get_client().Object(key=self.get_source())
-                obj.download_fileobj(Fileobj=snk)
-                self.set_metadata(obj.metadata)
-            else:
-                self.__handler.get_client().download_fileobj(
-                    Key=self.get_source(),
-                    Fileobj=snk)
-        except Exception as e:
-            return str(e)
-        return None
-
-
-    def load(
-        self,
-        src: _typ.Union[_io.BufferedReader, _io.BytesIO],
-        metadata: dict[str, str]
-    ) -> _typ.Optional[str]:
-        '''
-        Extracts data from the provided source and \
-        into the currently set sink. If an error occurs, \
-        then returns the corresponding error message, \
-        else returns ``None``.
-
-        :param Union[BufferedReader, BytesIO] src: A \
-            buffer acting as a source.
-        :param dict[str, str]: A dictionary containing \
-            metadata that are to be assigned to the \
-            primarily ingested data.
-        '''
-        try:
-            self.__handler.get_client().upload_fileobj(
-                Key=self.get_sink(),
-                Fileobj=src,
-                ExtraArgs={ "Metadata": metadata }
-                    if metadata is not None else None)
-        except Exception as e:
-            return str(e)
-        return None
-
-
-class AzureIngester(Ingester):
-    '''
-    A class used in extracting data from an Azure blob container, \
-    as well as loading data into an Azure blob container.
-
-    :param AzureClientHandler handler: An ``AzureClientHandler`` \
-        instance through which access to an Azure blob container \
-        is provided.
-    '''
-
-    def __init__(
-        self,
-        handler: _AzureClientHandler
-    ):
-        '''
-        A class used in extracting data from an Azure blob container, \
-        as well as loading data into an Azure blob container.
-
-        :param AzureClientHandler handler: An ``AzureClientHandler`` \
-            instance through which access to an Azure blob container \
-            is provided.
-        '''
-        self.__handler = handler
-        super().__init__()
-
-    
-    def extract(
-        self,
-        snk: _typ.Union[_io.BufferedReader, _io.BytesIO],
-        include_metadata: bool
-    ) -> _typ.Optional[str]:
-        '''
-        Extracts data from the currently set source \
-        and into the provided sink. If an error occurs, \
-        then returns the corresponding error message, \
-        else returns ``None``.
-
-        :param Union[BufferedReader, BytesIO] snk: A \
-            buffer acting as a sink.
-        :param bool include_metadata: Indicates whether \
-            to ingest any existing metadata along with \
-            the primarily ingested data.
-        '''
-        try:
-            blob = self.__handler.get_client().download_blob(
-                blob=self.get_source())
-            # Ingest object from its source.
-            blob.readinto(stream=snk)
-            if include_metadata:
-                self.set_metadata(blob.properties.metadata)
-        except Exception as e:
-            return str(e)
-        return None
-
-
-    def load(
-        self,
-        src: _typ.Union[_io.BufferedReader, _io.BytesIO],
-        metadata: dict[str, str]
-    ) -> _typ.Optional[str]:
-        '''
-        Extracts data from the provided source and \
-        into the currently set sink. If an error occurs, \
-        then returns the corresponding error message, \
-        else returns ``None``.
-
-        :param Union[BufferedReader, BytesIO] src: A \
-            buffer acting as a source.
-        :param dict[str, str]: A dictionary containing \
-            metadata that are to be assigned to the \
-            primarily ingested data.
-        '''
-        try:
-            self.__handler.get_client().upload_blob(
-                name=self.get_sink(),
-                data=src,
-                metadata=metadata,
-                overwrite=True)
-        except Exception as e:
-            return str(e)
-        return None
-    
\ No newline at end of file
diff --git a/fluke/storage.py b/fluke/storage.py
index e8d5fa4..c74697a 100644
--- a/fluke/storage.py
+++ b/fluke/storage.py
@@ -20,9 +20,7 @@
 from abc import abstractmethod as _absmethod
 
 
-import paramiko as _prmk
 from tqdm import tqdm as _tqdm
-from azure.storage.blob import ContainerClient as _ContainerClient
 
 
 from ._helper import join_paths as _join_paths
@@ -30,10 +28,9 @@
 from ._errors import Error as _Error
 from .auth import AWSAuth as _AWSAuth
 from .auth import AzureAuth as _AzureAuth
-from ._cache import Cache as _Cache
-from ._cache import CacheManager as _CacheManager
 from .auth import RemoteAuth as _RemoteAuth
 from ._handlers import ClientHandler as _ClientHandler
+from ._handlers import FileSystemHandler as _FileSystemHandler
 from ._handlers import SSHClientHandler as _SSHClientHandler
 from ._handlers import AWSClientHandler as _AWSClientHandler
 from ._handlers import AzureClientHandler as _AzureClientHandler
@@ -44,11 +41,6 @@
 from ._exceptions import NonStringMetadataKeyError as _NSMKE
 from ._exceptions import NonStringMetadataValueError as _NSMVE
 from ._exceptions import AzureBlobContainerNotFoundError as _ABCNFE 
-from ._ingesters import Ingester as _Ingester
-from ._ingesters import LocalIngester as _LocalIngester
-from ._ingesters import RemoteIngester as _RemoteIngester
-from ._ingesters import AWSS3Ingester as _AWSS3Ingester
-from ._ingesters import AzureIngester as _AzureIngester
 
 
 class _File(_ABC):
@@ -57,22 +49,41 @@ class _File(_ABC):
     base class for all file-like classes.
 
     :param str path: A path pointing to the file.
-    :param Ingester ingester: An ``Ingester`` class instance.
+    :param dict[str, str] metadata: A dictionary \
+        containing any metadata associated with the file.
+    :param ClientHandler handler: A ``ClientHandler`` class \
+        instance used for interacting with the underlying handler.
+    :param bool close_after_use: This value indicates whether \
+        all open connections should close before the instance \
+        destructor is called.
     '''
 
-    def __init__(self, path: str, ingester: _Ingester):
+    def __init__(
+        self,
+        path: str,
+        metadata: dict[str, str],
+        handler: _ClientHandler,
+        close_after_use: bool = True
+    ):
         '''
         An abstract class which serves as the \
         base class for all file-like classes.
 
         :param str path: A path pointing to the file.
-        :param Ingester ingester: An ``Ingester`` class instance.
+        :param dict[str, str] metadata: A dictionary \
+            containing any metadata associated with the file.
+        :param ClientHandler handler: A ``ClientHandler`` class \
+            instance used for interacting with the underlying handler.
+        :param bool close_after_use: This value indicates whether \
+            all open connections should close before the instance \
+            destructor is called.
         '''
         self.__path = path
+        self.__metadata = metadata
         self.__separator = _infer_sep(path=path)
-        self.__ingester = ingester
         self.__name = path.split(self.__separator)[-1]
-        self.__metadata = None
+        self.__handler = handler
+        self.__close_after_use = close_after_use
 
 
     def get_path(self) -> str:
@@ -82,20 +93,19 @@ def get_path(self) -> str:
         return self.__path
     
 
-    def _get_separator(self) -> str:
+    def get_name(self) -> str:
         '''
-        Returns the file's path separator.
+        Returns the file's name.
         '''
-        return self.__separator
+        return self.__name
 
 
-    def get_metadata(self) -> _typ.Optional[dict[str, str]]:
+    def get_metadata(self) -> dict[str, str]:
         '''
         Returns a dictionary containing any \
-        metadata associated with the file. \
-        Returns ``None`` if no metadata are found.
+        metadata associated with the file.
         '''
-        return self.__metadata
+        return dict(self.__metadata)
 
 
     def set_metadata(self, metadata: dict[str, str]) -> None:
@@ -119,41 +129,33 @@ def set_metadata(self, metadata: dict[str, str]) -> None:
                 raise _NSMKE(key=key)
             if not isinstance(val, str):
                 raise _NSMVE(val=val)
-
-        self.__metadata = metadata
-
-
-    def get_name(self) -> str:
-        '''
-        Returns the file's name.
-        '''
-        return self.__name
+            
+        # NOTE: Update the metadata dictionary without
+        #       creating a new reference.
+        self.__metadata.clear()
+        for (key, val) in metadata.items():
+            self.__metadata.update({ key: val })
 
 
-    def _get_ingester(self) -> _Ingester:
+    def get_size(self) -> int:
         '''
-        Returns the instance's ingester.
+        Returns the file's size in bytes.
         '''
-        return self.__ingester
+        return self._get_handler().get_file_size(self.get_path())
     
 
-    @_absmethod
-    def get_uri(self) -> str:
-        '''
-        Returns the file's URI.
-        '''
-        pass
-
-
-    @_absmethod
-    def get_size(self) -> int:
+    def read(self) -> bytes:
         '''
-        Returns the file's size in bytes.
+        Reads and returns the file's bytes.
         '''
-        pass
+        with _io.BytesIO() as buffer:
+            self._get_handler().read(
+                file_path=self.get_path(),
+                buffer=buffer,
+                include_metadata=False)
+            return buffer.getvalue()
 
 
-    @_absmethod
     def transfer_to(
         self,
         dst: '_Directory',
@@ -174,6 +176,93 @@ def transfer_to(
         :raises OverwriteError: File already exists while parameter \
             ``overwrite`` has been set to ``False``.
         '''
+        destination = dst.get_uri() \
+            if isinstance(dst, _NonLocalDir) \
+            else dst.get_path()
+        print(f'\nCopying file "{self.get_path()}" into "{destination}".')
+
+        file_name = self.get_name()
+        error = None
+
+        if not overwrite and dst.path_exists(file_name):
+            raise _OverwriteError(
+                file_path=_join_paths(self._get_separator(), destination, file_name))
+        else:
+            with _io.BytesIO() as buffer:
+                # Read file contents (and metadata optionally)
+                try:
+                    metadata = self.__handler.read(self.get_path(), buffer, include_metadata)
+                    # Update metadata dictionary if necessary.
+                    if include_metadata and (custom_metadata := self.get_metadata()):
+                        metadata = custom_metadata
+                    # Rewind buffer.
+                    buffer.seek(0)
+                    # Write file contents.
+                    dst_file_path = _join_paths(dst._get_separator(), dst.get_path(), file_name)
+                    try:
+                        dst._get_handler().write(
+                            file_path=dst_file_path,
+                            buffer=buffer,
+                            metadata=metadata)
+                        # Upsert the destination's metadata dictionary.
+                        if metadata is not None:
+                            dst._upsert_metadata(file_path=dst_file_path, metadata=metadata)
+                    except Exception as e:
+                        error = _Error(uri=dst_file_path, is_src=False, msg=str(e))
+                except Exception as e:
+                    error = _Error(uri=self.get_uri(), is_src=True, msg=str(e))
+                
+        if error is None:
+            print("Operation successful!")
+        else:
+            print(f"Operation unsuccessful: {error.get_message()}")
+    
+
+    def _get_close_after_use(self) -> bool:
+        '''
+        Returns a value indicating whether all open connections \
+        should close before the instance destructor is called.
+        '''
+        return self.__close_after_use
+    
+
+    def _get_handler(self) -> _ClientHandler:
+        '''
+        Returns this instance's ``ClientHandler``.
+        '''
+        return self.__handler
+    
+
+    def _get_separator(self) -> str:
+        '''
+        Returns the file's path separator.
+        '''
+        return self.__separator
+    
+
+    def __new__(
+        cls,
+        *args,
+        **kwargs
+    ) -> 'LocalFile':
+        '''
+        Creates an instance of this class.
+
+        :note: This method defines field ``__handler`` \
+            so that throwing an exception before invoking \
+            the parent constructor does not result in a \
+            second exception being thrown due to ``__del__``.
+        '''
+        instance = object.__new__(cls)
+        instance.__handler = None
+        return instance
+    
+
+    @_absmethod
+    def get_uri(self) -> str:
+        '''
+        Returns the file's URI.
+        '''
         pass
 
 
@@ -210,7 +299,8 @@ def __init__(self, path: str):
         sep = _infer_sep(path=path)
         super().__init__(
             path=_os.path.abspath(path).replace(_os.sep, sep),
-            ingester=_LocalIngester())
+            metadata=dict(),
+            handler=_FileSystemHandler())
         
 
     def get_uri(self) -> str:
@@ -218,57 +308,32 @@ def get_uri(self) -> str:
         Returns the file's URI.
         '''
         return f"file:///{self.get_path().lstrip(self._get_separator())}"
-        
-
-    def get_size(self) -> int:
-        '''
-        Returns the file's size in bytes.
-        '''
-        return _os.path.getsize(self.get_path())
-
-
-    def transfer_to(
-        self,
-        dst: '_Directory',
-        overwrite: bool = False,
-        include_metadata: bool = False
-    ) -> None:
-        '''
-        Copies the file into the provided directory.
 
-        :param _Directory dst: A ``_Directory`` class instance, \
-            which represents the transfer operation's destination.
-        :param bool overwrite: Indicates whether to overwrite \
-            the file if it already exists. Defaults to ``False``.
-        :param bool include_metadata: Indicates whether any \
-            existing metadata are to be assigned to the resulting \
-            file. Defaults to ``False``.
 
-        :raises OverwriteError: File already exists while parameter \
-            ``overwrite`` has been set to ``False``.
+    @classmethod
+    def _create_file(
+        cls,
+        path: str,
+        handler: _FileSystemHandler,
+        metadata: dict[str, str]
+    ) -> 'LocalFile':
         '''
-        destination = dst.get_uri() \
-            if isinstance(dst, _NonLocalDir) \
-            else dst.get_path()
-        print(f'\nCopying file "{self.get_path()}" into "{destination}".')
+        Creates and returns a ``LocalFile`` instance.
 
-        file_name = self.get_name()
-
-        if not overwrite and dst.path_exists(file_name):
-            raise _OverwriteError(
-                file_path=_join_paths(self._get_separator(), destination, file_name))
-        else:
-            with open(self.get_path(), "rb") as file:
-                error = dst._load_from_source(
-                    file_name=file_name,
-                    src=file,
-                    metadata=self.get_metadata()
-                        if include_metadata else None)
-
-            if error is None:
-                print("Operation successful!")
-            else:
-                print(f"Operation unsuccessful: {error.get_message()}")
+        :param str path: The path pointing to the file.
+        :param FileSystemHandler handler: A ``FileSystemHandler`` \
+            class instance.
+        :param dict[str, str] metadata: A dictionary containing \
+            any metadata associated with the file.
+        '''
+        instance = cls.__new__(cls)
+        _File.__init__(
+            instance,
+            path=path,
+            metadata=metadata,
+            handler=handler,
+            close_after_use=False)
+        return instance
 
 
 class _NonLocalFile(_File, _ABC):
@@ -278,21 +343,14 @@ class _NonLocalFile(_File, _ABC):
     or files in the cloud.
 
     :param str path: A path pointing to the file.
-    :param bool cache: Indicates whether it is allowed for \
-        any fetched data to be cached for faster subsequent \
-        access.
-    :param ClientHandler handler: A ``ClientHandler`` class instance used \
-        in handling connections.
-    :param Ingester ingester: An ``Ingester`` class instance used \
-        for ingesting data.
+    :param ClientHandler handler: A ``ClientHandler`` class \
+        instance used for interacting with the underlying handler.
     '''
 
     def __init__(
         self,
         path: str,
-        cache: bool,
-        handler: _ClientHandler,
-        ingester: _Ingester
+        handler: _ClientHandler
     ):
         '''
         An abstract class which serves as the base class for \
@@ -300,17 +358,13 @@ def __init__(
         or files in the cloud.
 
         :param str path: A path pointing to the file.
-        :param bool cache: Indicates whether it is allowed for \
-            any fetched data to be cached for faster subsequent \
-            access.
-        :param ClientHandler handler: A ``ClientHandler`` class instance used \
-            in handling connections.
-        :param Ingester ingester: An ``Ingester`` class instance used \
-            for ingesting data.
-        '''
-        super().__init__(path=path, ingester=ingester)
-        self.__cache = _Cache() if cache else None
-        self.__handler = handler
+        :param ClientHandler handler: A ``ClientHandler`` class \
+            instance used for interacting with the underlying handler.
+        '''
+        super().__init__(
+            path=path,
+            metadata=dict(),
+            handler=handler)
         self.open()
 
 
@@ -319,7 +373,7 @@ def is_cacheable(self) -> bool:
         Returns ``True`` if file has been defined \
         as cacheable, else returns ``False``.
         '''
-        return self.__cache is not None
+        return self._get_handler().is_cacheable()
 
 
     def purge(self) -> None:
@@ -327,138 +381,22 @@ def purge(self) -> None:
         If cacheable, then purges the file's cache, \
         else does nothing.
         '''
-        if self.is_cacheable():
-            self.__cache = _Cache()
+        self._get_handler().purge()
 
 
     def open(self) -> None:
         '''
         Opens all necessary connections.
         '''
-        self.__handler.open_connections()
+        self._get_handler().open_connections()
 
 
     def close(self) -> None:
         '''
         Closes all open connections.
         '''
-        self.__handler.close_connections()
-
-
-    def get_size(self) -> int:
-        '''
-        Returns the file's size in bytes.
-        '''
-        if self.is_cacheable():
-            if (size := self.__get_size_from_cache()) is not None:
-                return size
-            else:
-                size = self._get_size_impl()
-                self.__cache_size(size)
-                return size
-        return self._get_size_impl()
-        
-
-    def transfer_to(
-        self,
-        dst: '_Directory',
-        overwrite: bool = False,
-        include_metadata: bool = False
-    ) -> None:
-        '''
-        Copies the file into the provided directory.
-
-        :param _Directory dst: A ``_Directory`` class instance, \
-            which represents the transfer operation's destination.
-        :param bool overwrite: Indicates whether to overwrite \
-            the file if it already exists. Defaults to ``False``.
-        :param bool include_metadata: Indicates whether any \
-            existing metadata are to be assigned to the resulting \
-            file. Defaults to ``False``.
-
-        :raises OverwriteError: File already exists while parameter \
-            ``overwrite`` has been set to ``False``.
-        '''
-        destination = dst.get_uri() \
-            if isinstance(dst, _NonLocalDir) \
-            else dst.get_path()
-        print(f'\nCopying file "{self.get_uri()}" into "{destination}".')
-
-        file_name = self.get_name()
-
-        if not overwrite and dst.path_exists(file_name):
-            raise _OverwriteError(
-                file_path=_join_paths(self._get_separator(), destination, file_name))
-        else:
-            ingester = self._get_ingester()
-            ingester.set_source(src=self.get_path())
-
-            fetch_metadata = include_metadata
-            if include_metadata and (
-                (custom_metadata := self.get_metadata())
-                is not None
-            ):
-                ingester.set_metadata(metadata=custom_metadata)
-                fetch_metadata = False
-
-            error = dst._load_from_ingester(
-                file_name=file_name,
-                ingester=ingester,
-                fetch_metadata=fetch_metadata)
-
-            if error is None:
-                print("Operation successful!")
-            else:
-                print(f"Operation unsuccessful: {error.get_message()}")
-
-
-    def _get_client(self) -> _typ.Any:
-        '''
-        Returns the underlying client of this \
-        instance's ``ClientHandler`` instance.
-        '''
-        return self.__handler.get_client() \
-            if self.__handler is not None \
-            else None
-
-
-    def _get_metadata_from_cache(self) -> _typ.Optional[dict[str, str]]:
-        '''
-        Returns the file's cached metadata if they exist, \
-        else returns ``None``.
-        '''
-        if self.__cache is not None:
-            return self.__cache.get_metadata()
-
-
-    def _cache_metadata(self, metadata: dict[str, str]) -> None:
-        '''
-        Caches the provided metadata.
-
-        :param dict[str, str] metadata: The metadata that is to be cached.
-        '''
-        if self.__cache is not None:
-            self.__cache.set_metadata(metadata=metadata)
-
-
-    def __get_size_from_cache(self) -> _typ.Optional[int]:
-        '''
-        Returns the file's cached size if it exists, \
-        else returns ``None``.
-        '''
-        if self.__cache is not None:
-            return self.__cache.get_size()
-
-
-    def __cache_size(self, size: int) -> None:
-        '''
-        Caches the provided size.
-
-        :param int size: The size that is to be cached.
-        '''
-        if self.__cache is not None:
-            self.__cache.set_size(size=size)
-
+        self._get_handler().close_connections()
+    
 
     def __enter__(self) -> '_NonLocalFile':
         '''
@@ -474,12 +412,23 @@ def __exit__(self, exc_type, exc_value, traceback) -> None:
         self.close()
 
 
-    @_absmethod
-    def _get_size_impl(self) -> int:
-        '''
-        Returns the size of the file in bytes.
+    def __del__(self) -> None:
         '''
-        pass
+        The class destructor method.
+        '''
+        if self._get_close_after_use():
+            if (handler := self._get_handler()) is not None:
+                # Purge cache.
+                self.purge()
+                # Warn if connections are open.
+                if  handler.is_open():
+                    msg = f'You might want to consider instantiating class "{self.__class__.__name__}"'
+                    msg += " through the use of a context manager by utilizing Python's"
+                    msg += ' "with" statement, or by simply invoking an instance\'s'
+                    msg += ' "close" method after being done using it.'
+                    _warn.warn(msg, ResourceWarning)
+                    # Close connections.
+                    self.close()
 
 
 class RemoteFile(_NonLocalFile):
@@ -524,28 +473,21 @@ def __init__(
         :raises InvalidFileError: The provided path \
             points to a directory.
         '''
-        ssh_handler = _SSHClientHandler(auth=auth)
+        # Instantiate a connection handler,
+        # if none has been set.
+        if (ssh_handler := self._get_handler()) is None:
+            ssh_handler = _SSHClientHandler(auth=auth, cache=cache)
+            self.__host = auth.get_credentials()['hostname']
+
         super().__init__(
             path=path,
-            cache=cache,
-            handler=ssh_handler,
-            ingester=_RemoteIngester(ssh_handler))
-        sftp = self._get_client()
-
-        self.__host = auth.get_credentials()['hostname']
+            handler=ssh_handler)
 
-        from stat import S_ISDIR as _is_dir
-
-        try:
-            stats = sftp.stat(path=path)
-        except FileNotFoundError:
+        if not ssh_handler.path_exists(path=path):
             self.close()
             raise _IPE(path)
-        except:
-            self.close()
-            raise ConnectionError()
-
-        if _is_dir(stats.st_mode):
+        
+        if not ssh_handler.is_file(file_path=path):
             self.close()
             raise _IFE(path)
 
@@ -563,22 +505,43 @@ def get_uri(self) -> str:
         Returns the file's URI.
         '''
         return f"sftp://{self.__host}/{self.get_path().lstrip(self._get_separator())}"
+    
 
-
-    def _get_size_impl(self) -> int:
-        '''
-        Returns the size of the file in bytes.
+    @classmethod
+    def _create_file(
+        cls,
+        path: str,
+        host: str,
+        handler: _SSHClientHandler,
+        metadata: dict[str, str]
+    ) -> 'RemoteFile':
         '''
-        return self._get_client().stat(path=self.get_path()).st_size
+        Creates and returns a ``RemoteFile`` instance.
 
+        :param str path: The path pointing to the file.
+        :param str host: The name of the host in which \
+            the file resides.
+        :param SSHClientHandler handler: An ``SSHClientHandler`` \
+            class instance.
+        :param dict[str, str] metadata: A dictionary containing \
+            any metadata associated with the file.
+        '''
+        instance = cls.__new__(cls)
+        instance.__host = host
+        _File.__init__(
+            instance,
+            path=path,
+            metadata=metadata,
+            handler=handler,
+            close_after_use=False)
+        return instance
+    
 
-    def __del__(self) -> None:
-        if self._get_client() is not None:
-            msg = "You might want to consider instantiating class ``RemoteFile``"
-            msg += " through the use of a context manager by utilizing Python's"
-            msg += " ``with`` statement, or by simply invoking an instance's"
-            msg += " ``close`` method after being done using it."
-            _warn.warn(msg, ResourceWarning)
+    def __enter__(self) -> 'RemoteFile':
+        '''
+        Enter the runtime context related to this instance.
+        '''
+        return self
 
 
 class _CloudFile(_NonLocalFile, _ABC):
@@ -597,26 +560,10 @@ def load_metadata(self) -> None:
             method will be overriden after invoking this \
             method.
         '''
-        if self.is_cacheable():
-            metadata = self._get_metadata_from_cache()
-            if metadata is None:
-                metadata = self._load_metadata_impl()
-                self._cache_metadata(metadata=metadata) 
-        else:
-            metadata = self._load_metadata_impl()
+        metadata = self._get_handler().get_file_metadata(self.get_path())
         self.set_metadata(metadata=metadata)
 
 
-    @_absmethod
-    def _load_metadata_impl(self) -> dict[str, str]:
-        '''
-        Loads any metadata associated with the file, \
-        which can then be accessed via the instance's \
-        ``get_metadata`` method.
-        '''
-        pass
-
-
 class AWSS3File(_CloudFile):
     '''
     This class represents an object which resides \
@@ -635,6 +582,10 @@ class AWSS3File(_CloudFile):
         does not exist.
     :raises InvalidFileError: The provided path \
         points to a directory.
+
+    :note: The provided path must not begin with a separator.
+        - Wrong: ``/path/to/file.txt``
+        - Right: ``path/to/file.txt``
     '''
 
     def __init__(
@@ -661,65 +612,88 @@ def __init__(
             does not exist.
         :raises InvalidFileError: The provided path \
             points to a directory.
+
+        :note: The provided path must not begin with a separator.
+            - Wrong: ``/path/to/file.txt``
+            - Right: ``path/to/file.txt``
         '''
-        aws_handler = _AWSClientHandler(
-            auth=auth,
-            bucket=bucket
-        )
+        # Validate path.
+        sep = _infer_sep(path=path)
+
+        if path.startswith(sep):
+            raise _IPE(path=path)
+        
+        # Instantiate a connection handler,
+        # if none has been set.
+        if (aws_handler := self._get_handler()) is None:
+            aws_handler = _AWSClientHandler(
+                auth=auth,
+                bucket=bucket,
+                cache=cache)
+        
         super().__init__(
             path=path,
-            cache=cache,
-            handler=aws_handler,
-            ingester=_AWSS3Ingester(aws_handler))
-        
-        # Check whether this is a path to
-        # a directory instead of a file.
-        if path.endswith(self._get_separator()):
+            handler=aws_handler)
+
+        if not aws_handler.path_exists(path=path):
+            if aws_handler.dir_exists(path=path):
+                self.close()
+                raise _IFE(path)
+            self.close()
+            raise _IPE(path)     
+        if not aws_handler.is_file(file_path=path):
             self.close()
             raise _IFE(path)
+        
 
-        # Check whether the file exists.
-        from botocore.exceptions import ClientError as _CE
-
-        try:
-            self._get_client().Object(key=path).load()
-        except _CE:
-            self.close()
-            raise _IPE(path)
+    def get_bucket_name(self) -> str:
+        '''
+        Returns the name of the bucket in which \
+        the directory resides.
+        '''
+        return self._get_handler().get_bucket_name()
 
 
     def get_uri(self) -> str:
         '''
         Returns the object's URI.
         '''
-        return f"s3://{self._get_client().name}{self._get_separator()}{self.get_path()}"
-
+        return f"s3://{self.get_bucket_name()}{self._get_separator()}{self.get_path()}"
+    
 
-    def _get_size_impl(self) -> int:
-        '''
-        Returns the size of the file in bytes.
+    @classmethod
+    def _create_file(
+        cls,
+        path: str,
+        handler: _AWSClientHandler,
+        metadata: dict[str, str]
+    ) -> 'AWSS3File':
         '''
-        return self._get_client().Object(
-            key=self.get_path()).content_length
+        Creates and returns an ``AWSS3File`` instance.
 
+        :param str path: The path pointing to the file.
+        :param str host: The name of the host in which \
+            the file resides.
+        :param AWSClientHandler handler: An ``AWSClientHandler`` \
+            class instance.
+        :param dict[str, str] metadata: A dictionary containing \
+            any metadata associated with the file.
+        '''
+        instance = cls.__new__(cls)
+        _File.__init__(
+            instance,
+            path=path,
+            metadata=metadata,
+            handler=handler,
+            close_after_use=False)
+        return instance
+    
 
-    def _load_metadata_impl(self) -> dict[str, str]:
+    def __enter__(self) -> 'AWSS3File':
         '''
-        Loads any metadata associated with the file, \
-        which can then be accessed via the instance's \
-        ``get_metadata`` method.
+        Enter the runtime context related to this instance.
         '''
-        return self._get_client().Object(
-            key=self.get_path()).metadata
-
-
-    def __del__(self) -> None:
-        if self._get_client() is not None:
-            msg = "You might want to consider instantiating class `AWSS3File``"
-            msg += " through the use of a context manager by utilizing Python's"
-            msg += " ``with`` statement, or by simply invoking an instance's"
-            msg += " ``close`` method after being done using it."
-            _warn.warn(msg, ResourceWarning)
+        return self
 
 
 class AzureBlobFile(_CloudFile):
@@ -736,10 +710,16 @@ class AzureBlobFile(_CloudFile):
         any fetched data to be cached for faster subsequent \
         access.
 
+    :raises AzureBlobContainerNotFoundError: The \
+        specified container does not exist.
     :raises InvalidPathError: The provided path \
         does not exist.
     :raises InvalidFileError: The provided path \
         points to a directory.
+
+    :note: The provided path must not begin with a separator.
+        - Wrong: ``/path/to/file.txt``
+        - Right: ``path/to/file.txt``
     '''
 
     def __init__(
@@ -762,42 +742,48 @@ def __init__(
             any fetched data to be cached for faster subsequent \
             access. Defaults to ``False``.
 
+        :raises AzureBlobContainerNotFoundError: The \
+            specified container does not exist.
         :raises InvalidPathError: The provided path \
             does not exist.
         :raises InvalidFileError: The provided path \
             points to a directory.
+
+        :note: The provided path must not begin with a separator.
+            - Wrong: ``/path/to/file.txt``
+            - Right: ``path/to/file.txt``
         '''
-        azr_handler = _AzureClientHandler(
-            auth=auth,
-            container=container)
+        # Validate path.
+        sep = _infer_sep(path=path)
+        if path.startswith(sep):
+            raise _IPE(path=path)
+
+        # Instantiate a connection handler,
+        # if none has been set.
+        if (azr_handler := self._get_handler()) is None:
+            azr_handler = _AzureClientHandler(
+                auth=auth,
+                container=container,
+                cache=cache)
         
+        # Infer storage account.
         self.__storage_account = auth._get_storage_account()
         
         super().__init__(
             path=path,
-            cache=cache,
-            handler=azr_handler,
-            ingester=_AzureIngester(handler=azr_handler))
+            handler=azr_handler)
         
-        # Check whether this is a path to
-        # a directory instead of a file.
-        if path.endswith(self._get_separator()):
-            self.close()
-            raise _IFE(path)
-
-        # Fetch container client.
-        azr_container: _ContainerClient = azr_handler.get_client()
-
         # Throw an exception if container does not exist.
-        if not azr_container.exists():
+        if not azr_handler.container_exists():
             self.close()
             raise _ABCNFE(container)
-
-        # Throw an exception if blob does not exist.  
-        with azr_container.get_blob_client(blob=path) as blob:
-            if not blob.exists():
-                self.close()
-                raise _IPE(path)
+        
+        if not azr_handler.path_exists(path=path):
+            self.close()
+            raise _IPE(path)
+        if not azr_handler.is_file(file_path=path):
+            self.close()
+            raise _IFE(path)
             
 
     def get_container_name(self) -> str:
@@ -805,7 +791,7 @@ def get_container_name(self) -> str:
         Returns the name of the bucket in which \
         the directory resides.
         '''
-        return self._get_client().container_name
+        return self._get_handler().get_container_name()
 
 
     def get_uri(self) -> str:
@@ -815,33 +801,41 @@ def get_uri(self) -> str:
         uri = f"abfss://{self.get_container_name()}@{self.__storage_account}"
         uri += f".dfs.core.windows.net/{self.get_path()}"
         return uri
+    
 
-
-    def _get_size_impl(self) -> int:
-        '''
-        Returns the size of the file in bytes.
+    @classmethod
+    def _create_file(
+        cls,
+        path: str,
+        handler: _AzureClientHandler,
+        metadata: dict[str, str]
+    ) -> 'AzureBlobFile':
         '''
-        return self._get_client().download_blob(
-            blob=self.get_path()).size
+        Creates and returns an ``AzureBlobFile`` instance.
 
+        :param str path: The path pointing to the file.
+        :param str host: The name of the host in which \
+            the file resides.
+        :param AWSClientHandler handler: An ``AzureClientHandler`` \
+            class instance.
+        :param dict[str, str] metadata: A dictionary containing \
+            any metadata associated with the file.
+        '''
+        instance = cls.__new__(cls)
+        _File.__init__(
+            instance,
+            path=path,
+            metadata=metadata,
+            handler=handler,
+            close_after_use=False)
+        return instance
+    
 
-    def _load_metadata_impl(self) -> dict[str, str]:
+    def __enter__(self) -> 'AzureBlobFile':
         '''
-        Loads any metadata associated with the file, \
-        which can then be accessed via the instance's \
-        ``get_metadata`` method.
+        Enter the runtime context related to this instance.
         '''
-        return self._get_client().download_blob(
-            blob=self.get_path()).properties.metadata
-
-
-    def __del__(self) -> None:
-        if self._get_client() is not None:
-            msg = "You might want to consider instantiating class `AzureBlobFile``"
-            msg += " through the use of a context manager by utilizing Python's"
-            msg += " ``with`` statement, or by simply invoking an instance's"
-            msg += " ``close`` method after being done using it."
-            _warn.warn(msg, ResourceWarning)
+        return self
 
 
 class _Directory(_ABC):
@@ -850,24 +844,31 @@ class _Directory(_ABC):
     for all directory-like classes.
 
     :param str path: The path pointing to the directory.
-    :param Ingester ingester: An "Ingester" class instance used \
-        for ingesting data.
+    :param ClientHandler handler: A ``ClientHandler`` class \
+        instance used for interacting with the underlying handler.
     '''
 
-    def __init__(self, path: str, ingester: _Ingester):
+    def __init__(
+        self,
+        path: str,
+        handler: _ClientHandler,
+    ):
         '''
         An abstract class which serves as the base class \
         for all directory-like classes.
 
         :param str path: The path pointing to the directory.
-        :param Ingester ingester: An "Ingester" class instance used \
-            for ingesting data.
+        :param ClientHandler handler: A ``ClientHandler`` class \
+            instance used for interacting with the underlying handler.
         '''
         sep = _infer_sep(path)
         self.__path = f"{path.rstrip(sep)}{sep}" if path != '' else path
+        self.__name = name if (
+                name := self.__path.rstrip(sep).split(sep)[-1]
+            ) != '' else None
         self.__separator = sep
-        self.__ingester = ingester
-        self.__metadata: dict[str, _typ.Optional[dict[str, str]]] = {}
+        self.__handler = handler
+        self.__metadata: dict[str, dict[str, str]] = dict()
 
 
     def get_path(self) -> str:
@@ -882,12 +883,10 @@ def get_name(self) -> _typ.Optional[str]:
         Returns the name of the directory, \
         or ``None`` if it's the root directory.
         '''
-        sep = self.__separator
-        name = self.__path.rstrip(sep).split(sep)[-1]
-        return name if name != "" else None
+        return self.__name
     
 
-    def get_metadata(self, file_path: str) -> _typ.Optional[dict[str, str]]:
+    def get_metadata(self, file_path: str) -> dict[str, str]:
         '''
         Returns a dictionary containing any metadata \
             associated with the file in question.
@@ -899,9 +898,7 @@ def get_metadata(self, file_path: str) -> _typ.Optional[dict[str, str]]:
         :raises InvalidFileError: The provided path does \
             not point to a file within the directory.
         '''
-        if not self._is_file(path=file_path):
-            raise _IFE(path=file_path)
-        return self.__metadata.get(self._relativize(path=file_path), None)
+        return dict(self._get_metadata_ref(file_path=file_path))
 
 
     def set_metadata(self, file_path: str, metadata: dict[str, str]) -> None:
@@ -931,77 +928,48 @@ def set_metadata(self, file_path: str, metadata: dict[str, str]) -> None:
             if not isinstance(val, str):
                 raise _NSMVE(val=val)
 
-        if not self._is_file(path=file_path):
+        if not (self.path_exists(file_path) and self.is_file(file_path)):
             raise _IFE(path=file_path)
 
-        self.__metadata[self._relativize(path=file_path)] = metadata
+        # Upsert metadata into the dictionary.
+        self._upsert_metadata(file_path, metadata)
 
 
-    def count(self, recursively: bool = False) -> int:
+    def path_exists(self, path: str) -> bool:
         '''
-        Returns the total number of files within \
-        within the directory.
-
-        :param bool recursively: Indicates whether the directory \
-            is to be scanned recursively or not. If set to  ``False``, \
-            then only those files that reside directly within the \
-            directory are to be considered. If set to ``True``, \
-            then all files are considered, no matter whether they \
-            reside directly within the directory or within any of \
-            its subdirectories. Defaults to ``False``.
+        Returns ``True`` if the provided path exists \
+        within the directory, else returns ``False``.
 
-        :note: The resulting number may vary depending on the value \
-            of parameter ``recursively``.
+        :param str path: Either an absolute path or a \
+            path relative to the directory.
         '''
-        count = 0
-        
-        for _ in self._get_contents_iterable(
-            recursively=recursively,
-            include_dirs=True,
-            show_abs_path=True
-        ):
-            count += 1
-
-        return count
-
+        path = self._to_absolute(path, replace_sep=False)
+        return self._get_handler().path_exists(path)
+    
 
-    def ls(self, recursively: bool = False, show_abs_path: bool = False) -> None:
+    def is_file(self, path: str) -> bool:
         '''
-        Lists the contents of the directory.
-
-        :param bool recursively: Indicates whether the directory \
-            is to be scanned recursively or not. If set to  ``False``, \
-            then only those files that reside directly within the \
-            directory are to be considered. If set to ``True``, \
-            then all files are considered, no matter whether they \
-            reside directly within the directory or within any of \
-            its subdirectories. Defaults to ``False``.
-        :param bool show_abs_path: Determines whether to include the \
-            contents' absolute path or their path relative to this directory. \
-            Defaults to ``False``.
+        Returns ``True`` if the provided path points \
+        to a file, else returns ``False``.
 
-        :note: The resulting output may vary depending on the value \
-            of parameter ``recursively``.
+        :param str path: Either an absolute path or a \
+            path relative to the directory.
         '''
-        for file in self._get_contents_iterable(
-            recursively=recursively,
-            include_dirs=True,
-            show_abs_path=show_abs_path
-        ):
-            print(file)
+        path = self._to_absolute(path, replace_sep=False)
+        return self._get_handler().is_file(path)
 
 
-    def iterate_contents(
+    def traverse(
         self,
         recursively: bool = False,
         show_abs_path: bool = False
     ) -> _typ.Iterator[str]:
         '''
-        Returns an iterator capable of going through the paths \
-        ofthe dictionary's contents as strings.
+        Returns an iterator capable of traversing the dictionary's \
+        contents as strings representing their paths.
 
         :param bool recursively: Indicates whether the directory \
-            is to be scanned recursively or not. If set to  ``False``, \
+            is to be traversed recursively or not. If set to  ``False``, \
             then only those files that reside directly within the \
             directory are to be considered. If set to ``True``, \
             then all files are considered, no matter whether they \
@@ -1014,7 +982,8 @@ def iterate_contents(
         :note: The resulting iterator may vary depending on the \
             value of parameter ``recursively``.
         '''
-        return self._get_contents_iterable(
+        return self._get_handler().traverse_dir(
+            dir_path=self.get_path(),
             recursively=recursively,
             include_dirs=True,
             show_abs_path=show_abs_path)
@@ -1026,10 +995,11 @@ def get_contents(
         show_abs_path: bool = False
     ) -> list[str]:
         '''
-        Returns a list containing the paths of the directory's contents.
+        Returns an iterator capable of traversing the dictionary's \
+        contents as strings representing their paths.
 
         :param bool recursively: Indicates whether the directory \
-            is to be scanned recursively or not. If set to  ``False``, \
+            is to be traversed recursively or not. If set to  ``False``, \
             then only those files that reside directly within the \
             directory are to be considered. If set to ``True``, \
             then all files are considered, no matter whether they \
@@ -1042,126 +1012,70 @@ def get_contents(
         :note: The resulting list may vary depending on the value \
             of parameter ``recursively``.
         '''
-        return list(self._get_contents_iterable(
+        return list(self.traverse(
             recursively=recursively,
-            include_dirs=True,
             show_abs_path=show_abs_path))
     
 
-    def _get_separator(self) -> str:
-        '''
-        Returns the path's separator.
-        '''
-        return self.__separator
-    
-
-    def _get_contents_iterable(
-        self,
-        recursively: bool,
-        include_dirs: bool,
-        show_abs_path: bool
-    ) -> _typ.Iterator[str]:
+    def ls(self, recursively: bool = False, show_abs_path: bool = False) -> None:
         '''
-        Returns an iterator over the dictionary's contents.
+        Lists the contents of the directory.
 
         :param bool recursively: Indicates whether the directory \
-            is to be scanned recursively or not. If set to  ``False``, \
+            is to be traversed recursively or not. If set to  ``False``, \
             then only those files that reside directly within the \
             directory are to be considered. If set to ``True``, \
             then all files are considered, no matter whether they \
             reside directly within the directory or within any of \
-            its subdirectories.
-        :param bool include_dirs: Indicates whether to include any \
-            directories in the results in case ``recursively`` is \
-            set to ``False``.
+            its subdirectories. Defaults to ``False``.
         :param bool show_abs_path: Determines whether to include the \
-            contents' absolute path or their path relative to this directory.
-        '''
-        if recursively:
-            return self._iterate_contents_impl(
-                recursively=True,
-                show_abs_path=show_abs_path)
-
-        iterable = self._iterate_contents_impl(
-            recursively=False,
-            show_abs_path=show_abs_path)
-                 
-        if not include_dirs:
-            iterable = filter(
-                lambda path: self._is_file(path=path),
-                self._iterate_contents_impl(
-                    recursively=False,
-                    show_abs_path=show_abs_path))
-
-        return iterable
-    
-
-    def _relativize(self, path: str) -> str:
-        '''
-        Transforms the provided path so that it is \
-        relative to the directory.
-
-        :param str path: The provided path.
-        '''
-        return path.removeprefix(self.__path).lstrip(self._get_separator())
-
-
-    def _get_ingester(self) -> _Ingester:
-        '''
-        Returns the class's ``Ingester`` instance.
-        '''
-        return self.__ingester
-    
-
-    def _add_file_to_metadata_dict(
-        self,
-        file_path: str,
-        metadata: _typ.Optional[dict[str, str]]
-    ) -> None:
-        '''
-        Adds the provided path entry to the directory's \
-        metadata store.
+            contents' absolute path or their path relative to this directory. \
+            Defaults to ``False``.
 
-        :param str file_path: The relative path of the  \
-            file in question.
-        :param dict[str, str] | None metadata: A dictionary \
-            containing the metadata that are to be \
-            associated with the file.
+        :note: The resulting output may vary depending on the value \
+            of parameter ``recursively``.
         '''
-        self.__metadata.update({ file_path: None })
+        for entity in self.traverse(
+            recursively=recursively,
+            show_abs_path=show_abs_path
+        ):
+            print(entity)
 
-        if metadata is not None:
-            self.set_metadata(file_path=file_path, metadata=metadata)
-    
 
-    @_absmethod
-    def get_uri(self) -> str:
-        '''
-        Returns the directory's URI.
+    def count(self, recursively: bool = False) -> int:
         '''
-        pass
+        Returns the total number of files within \
+        within the directory.
 
+        :param bool recursively: Indicates whether the directory \
+            is to be traversed recursively or not. If set to  ``False``, \
+            then only those files that reside directly within the \
+            directory are to be considered. If set to ``True``, \
+            then all files are considered, no matter whether they \
+            reside directly within the directory or within any of \
+            its subdirectories. Defaults to ``False``.
 
-    @_absmethod
-    def path_exists(self, path: str) -> bool:
+        :note: The resulting number may vary depending on the value \
+            of parameter ``recursively``.
         '''
-        Returns ``True`` if the provided path exists \
-        within the directory, else returns ``False``.
+        count = 0
 
-        :param str path: Either an absolute path or a \
-            path relative to the directory.
-        '''
-        pass
+        for _ in self.traverse(
+            recursively=recursively,
+            show_abs_path=True
+        ):
+            count += 1
 
+        return count
+    
 
-    @_absmethod
     def get_size(self, recursively: bool = False) -> int:
         '''
         Returns the total sum of the sizes of all files \
         within the directory, in bytes.
 
         :param bool recursively: Indicates whether the directory \
-            is to be scanned recursively or not. If set to  ``False``, \
+            is to be traversed recursively or not. If set to  ``False``, \
             then only those files that reside directly within the \
             directory are to be considered. If set to ``True``, \
             then all files are considered, no matter whether they \
@@ -1171,10 +1085,20 @@ def get_size(self, recursively: bool = False) -> int:
         :note: The resulting size may vary depending on the value \
             of parameter ``recursively``.
         '''
-        pass
+        handler = self._get_handler()
+        size = 0
 
+        for file_path in handler.traverse_dir(
+            dir_path=self.get_path(),
+            recursively=recursively,
+            include_dirs=False,
+            show_abs_path=True
+        ):
+            size += handler.get_file_size(file_path)
+
+        return size
+    
 
-    @_absmethod
     def transfer_to(
         self,
         dst: '_Directory',
@@ -1190,7 +1114,7 @@ def transfer_to(
         :param _Directory dst: A ``_Directory`` class instance, \
             which represents the transfer operation's destination.
         :param bool recursively: Indicates whether the directory \
-            is to be scanned recursively or not. If set to  ``False``, \
+            is to be traversed recursively or not. If set to  ``False``, \
             then only those files that reside directly within the \
             directory are to be considered. If set to ``True``, \
             then all files are considered, no matter whether they \
@@ -1205,105 +1129,259 @@ def transfer_to(
             a loading bar on the progress of the operations. \
             Defaults to ``True``.
         '''
-        pass
+        destination = dst.get_uri() \
+            if isinstance(dst, _NonLocalDir) \
+            else dst.get_path()
+
+        print_msg = f'\nCopying files from "{self.get_path()}" '
+        print_msg += f'into "{destination}".'
+        print(print_msg)
+
+        # Store the directory's files in a list.
+        file_paths = [fp for fp in self.__handler.traverse_dir(
+            dir_path = self.get_path(),
+            recursively=recursively,
+            include_dirs=False,
+            show_abs_path=True)]
 
+        errors: list[_Error] = list()
+        total_num_files = len(file_paths)
 
-    @_absmethod
-    def _iterate_contents_impl(
+        # Iterate through all files that are to be transfered.
+        for fp in _tqdm(
+            iterable=file_paths,
+            disable=not show_progress,
+            desc="Progress",
+            unit='files',
+            total=total_num_files
+        ):
+            rel_fp = self._to_relative(fp, replace_sep=False)
+            if not overwrite and dst.path_exists(rel_fp):
+                errors.append(_Error(
+                    uri=_join_paths(dst._get_separator(), destination, rel_fp),
+                    is_src=False,
+                    msg='File already exists. Try setting "overwrite" to "True".'))
+                continue
+
+            with _io.BytesIO() as buffer:
+                try:
+                    # Read file contents (and metadata optionally).
+                    metadata = self.__handler.read(fp, buffer, include_metadata)
+                    # Update metadata dictionary if necessary.
+                    if include_metadata and (custom_metadata := self.get_metadata(fp)):
+                        metadata = custom_metadata
+                    # Rewind buffer.
+                    buffer.seek(0)
+                    # Write file contents.
+                    dst_fp = dst._to_absolute(rel_fp, replace_sep=True)
+                    dst._get_handler().write(
+                        file_path=dst_fp,
+                        buffer=buffer,
+                        metadata=metadata)
+                    # Upsert the destination's metadata dictionary.
+                    if metadata is not None:
+                        dst._upsert_metadata(file_path=dst_fp, metadata=metadata)
+                except Exception as e:
+                    errors.append(_Error(uri=self.get_uri(), is_src=True, msg=str(e)))
+
+        if len(errors) == 0:
+            print(f'Operation successful: Copied all {total_num_files} files!')
+        else:
+            msg = "Operation unsuccessful: Failed to copy "
+            msg += f"{len(errors)} out of {total_num_files} files."
+            msg += f"\n\nDisplaying {len(errors)} errors:\n"
+            for err in errors:
+                msg += str(err)
+            print(msg)
+    
+
+    def traverse_files(
         self,
-        recursively: bool = False,
-        show_abs_path: bool = False
-    ) -> _typ.Iterator[str]:
+        recursively: bool = False
+    ) -> _typ.Iterator[_File]:
         '''
-        Returns an iterator capable of going through the paths \
-        ofthe dictionary's contents as strings.
+        Returns an iterator capable of going through the \
+        dictionaries files as ``File`` instances.
 
         :param bool recursively: Indicates whether the directory \
-            is to be scanned recursively or not. If set to  ``False``, \
+            is to be traversed recursively or not. If set to  ``False``, \
             then only those files that reside directly within the \
             directory are to be considered. If set to ``True``, \
             then all files are considered, no matter whether they \
             reside directly within the directory or within any of \
             its subdirectories. Defaults to ``False``.
-        :param bool show_abs_path: Indicates whether it \
-            should be displayed the absolute or the relative \
-            path of the contents. Defaults to ``False``.
 
         :note: The resulting iterator may vary depending on the \
             value of parameter ``recursively``.
         '''
-        pass
+        for file_path in self.__handler.traverse_dir(
+            dir_path=self.get_path(),
+            recursively=recursively,
+            include_dirs=False,
+            show_abs_path=True
+        ):
+            yield self.get_file(file_path)
 
 
-    @_absmethod
-    def _load_from_source(
+    def get_files(
         self,
-        file_name: str,
-        src: _typ.Union[_io.BufferedReader, _io.BytesIO],
-        metadata: _typ.Optional[dict[str, str]]
-    ) -> _typ.Optional[_Error]:
-        '''
-        Loads a file directly from the given source \
-        and into this directory. Returns ``None`` if \
-        the operation was successful, else returns an \
-        ``Error`` instance.
-
-        :param str file_name: The name of the file that is to be \
-            loaded.
-        :param Union[io.BufferedReader, io.BytesIO] src: A buffer \
-            containing the file in bytes.
-        :param dict[str, str] | None metadata: A dictionary \
-            containing any metadata associated with the file.
+        recursively: bool = False,
+        show_abs_path: bool = False
+    ) -> dict[str, _File]:
         '''
-        pass
+        Returns a dictionary mapping file paths to ``File`` instances \
+        regarding the files contained within the directory.
 
+        :param bool recursively: Indicates whether the directory \
+            is to be traversed recursively or not. If set to  ``False``, \
+            then only those files that reside directly within the \
+            directory are to be considered. If set to ``True``, \
+            then all files are considered, no matter whether they \
+            reside directly within the directory or within any of \
+            its subdirectories. Defaults to ``False``.
+        :param bool show_abs_path: Determines whether to include the \
+            files' absolute path or their path relative to this directory. \
+            Defaults to ``False``.
 
-    @_absmethod
-    def _load_from_ingester(
-        self,
-        file_name: str,
-        ingester: _Ingester,
-        fetch_metadata: bool
-    ) -> _typ.Optional[_Error]:
-        '''
-        Loads a file into this directory by using \
-        the provided ingester. Returns ``None`` if \
-        the operation was successful, else returns \
-        an ``Error`` instance.
-
-        :param str file_name: The name of the file that is to be \
-            loaded.
-        :param Ingester ingester: An ingester method used in \
-            ingesting the file.
-        :param bool overwrite: Indicates whether to overwrite \
-            the file if it already exists. Defaults to ``False``.
-        :param bool fetch_metadata: Indicates whether to \
-            fetch any metadata associated with the file or not.
+        :note: The resulting iterator may vary depending on the \
+            value of parameter ``recursively``.
         '''
-        pass
+        file_dict = dict()
+        for file_path in self.__handler.traverse_dir(
+            dir_path=self.get_path(),
+            recursively=recursively,
+            include_dirs=False,
+            show_abs_path=show_abs_path
+        ):
+            file_dict.update({ file_path: self.get_file(file_path)})
+        return file_dict
 
 
-    @_absmethod
-    def _is_file(self, path: str) -> bool:
+    def _get_separator(self) -> str:
+        '''
+        Returns the path's separator.
+        '''
+        return self.__separator
+    
+
+    def _get_handler(self) -> _ClientHandler:
+        '''
+        Returns this instance's ``ClientHandler``.
+        '''
+        return self.__handler
+    
+
+    def _to_relative(self, path: str, replace_sep: bool) -> str:
+        '''
+        Transforms the provided path so that it is \
+        relative to the directory.
+
+        :param str path: The provided path.
+        :param bool replace_sep: Indicates whether \
+            to replace the provided path's separator \
+            with the separator used by this directory.
+        '''
+        if replace_sep:
+            sep = _infer_sep(path)
+            path = path.replace(sep, self._get_separator())
+        return path.removeprefix(self.__path).lstrip(self._get_separator())
+    
+
+    def _to_absolute(self, path: str, replace_sep: bool) -> str:
+        '''
+        Transforms the provided path so that it is \
+        absolute.
+
+        :param str path: The provided path.
+        :param bool replace_sep: Indicates whether \
+            to replace the provided path's separator \
+            with the separator used by this directory.
+        '''
+        path = self._to_relative(path, replace_sep)
+        return _join_paths(self._get_separator(), self.__path, path)
+    
+
+    def _get_metadata_ref(self, file_path: str) -> dict[str, str]:
+        '''
+        Returns the reference to the metadata dictionary \
+        that corresponds to the specified path. If said \
+        dictionary doesn't exist, then this method creates it \
+        and returns it.
+
+        :param str file_path: Either the absolute path \
+            or the path relative to the directory of the \
+            file in question.
+
+        :raises InvalidFileError: The provided path does \
+            not point to a file within the directory.
+        '''
+        if not (self.path_exists(file_path) and self.is_file(file_path)):
+            raise _IFE(path=file_path)
+        
+        rel_path = self._to_relative(path=file_path, replace_sep=False)
+        
+        if rel_path not in self.__metadata:
+            self.__metadata.update({rel_path: dict()})
+
+        return self.__metadata.get(rel_path)
+    
+
+    def _upsert_metadata(self, file_path: str, metadata: dict[str, str]) -> None:
+        '''
+        Updates the metadata dictionary by \
+        upserting the provided metadata.
+
+        :param str file_path: Either the absolute path \
+            or the path relative to the directory of the \
+            file in question.
+        :param dict[str, str] metadata: A dictionary \
+            containing the metadata that are to be \
+            associated with the file.
+        '''
+        # NOTE: Update the metadata dictionary without
+        #       creating a new reference.
+        rel_path = self._to_relative(path=file_path, replace_sep=False)
+
+        if rel_path not in self.__metadata:
+            self.__metadata.update({rel_path: dict()})
+
+        self.__metadata[rel_path].clear()
+
+        for (key, val) in metadata.items():
+            self.__metadata[rel_path].update({ key: val })
+    
+
+    def __new__(cls, *args, **kwargs) -> '_Directory':
         '''
-        Returns ``True`` if the provided path \
-        points to a file, else returns ``False``.
+        Creates an instance of this class.
 
-        :param str path: Either the absolute path or the \
-            path relative to the directory of the file in \
-            question.
+        :note: This method defines field ``__handler`` \
+            so that throwing an exception before invoking \
+            the parent constructor does not result in a \
+            second exception being thrown due to ``__del__``.
+        '''
+        instance = super().__new__(cls)
+        instance.__handler = None
+        return instance
+    
+
+    @_absmethod
+    def get_uri(self) -> str:
+        '''
+        Returns the directory's URI.
         '''
         pass
 
 
     @_absmethod
-    def _get_file_size(path: str) -> int:
+    def get_file(self, file_path: str) -> '_File':
         '''
-        Returns the size of the file that corresponds \
-        to the provided path in bytes.
+        Returns the file residing in the specified \
+        path as a ``_File`` instance.
 
-        :param str path: The absolute path pointing to \
-            the file in question.
+        :param str file_path: Either the absolute path \
+            or the path relative to the directory of the \
+            file in question.
         '''
         pass
 
@@ -1357,7 +1435,7 @@ def __init__(
 
         super().__init__(
             path=f"{_os.path.abspath(path).replace(_os.sep, sep).rstrip(sep)}{sep}",
-            ingester=_LocalIngester())
+            handler=_FileSystemHandler())
 
 
     def get_uri(self) -> str:
@@ -1368,284 +1446,69 @@ def get_uri(self) -> str:
         return f"file:///{self.get_path().lstrip(sep)}"
 
 
-    def path_exists(self, path: str) -> bool:
+    def get_file(self, file_path: str) -> LocalFile:
         '''
-        Returns ``True`` if the provided path exists \
-        within the directory, else returns ``False``.
+        Returns the file residing in the specified \
+        path as a ``LocalFile`` instance.
 
-        :param str path: Either an absolute path or a \
-            path relative to the directory.
-        '''
-        return _os.path.exists(_join_paths(
-            self._get_separator(),
-            self.get_path(),
-            self._relativize(path=path)))
-
-
-    def get_size(self, recursively: bool = False) -> int:
-        '''
-        Returns the total sum of the sizes of all files \
-        within the directory, in bytes.
-
-        :param bool recursively: Indicates whether the directory \
-            is to be scanned recursively or not. If set to  ``False``, \
-            then only those files that reside directly within the \
-            directory are to be considered. If set to ``True``, \
-            then all files are considered, no matter whether they \
-            reside directly within the directory or within any of \
-            its subdirectories. Defaults to ``False``.
-
-        :note: The resulting size may vary depending on the value \
-            of parameter ``recursively``.
+        :param str file_path: Either the absolute path \
+            or the path relative to the directory of the \
+            file in question.
         '''
-        size = 0
-
-        for file_path in self._get_contents_iterable(
-            recursively=recursively,
-            include_dirs=False,
-            show_abs_path=True
-        ):
-            size += self._get_file_size(file_path)
-        
-        return size
-
+        file_path = self._to_absolute(path=file_path, replace_sep=False)
+        return LocalFile._create_file(
+            path=file_path,
+            handler=self._get_handler(),
+            metadata=self._get_metadata_ref(file_path))
     
-    def transfer_to(
+
+    def traverse_files(
         self,
-        dst: '_Directory',
-        recursively: bool = False,
-        overwrite: bool = False,
-        include_metadata: bool = False,
-        show_progress: bool = True
-    ) -> None:
+        recursively: bool = False
+    ) -> _typ.Iterator[LocalFile]:
         '''
-        Copies all files within this directory into \
-        the destination directory.
+        Returns an iterator capable of going through the \
+        dictionaries files as ``File`` instances.
 
-        :param _Directory dst: A ``_Directory`` class instance, \
-            which represents the transfer operation's destination.
         :param bool recursively: Indicates whether the directory \
-            is to be scanned recursively or not. If set to  ``False``, \
+            is to be traversed recursively or not. If set to  ``False``, \
             then only those files that reside directly within the \
             directory are to be considered. If set to ``True``, \
             then all files are considered, no matter whether they \
             reside directly within the directory or within any of \
             its subdirectories. Defaults to ``False``.
-        :param bool overwrite: Indicates whether to overwrite \
-            any files if they already exist. Defaults to ``False``.
-        :param bool include_metadata: Indicates whether any \
-            existing metadata are to be assigned to the resulting \
-            files. Defaults to ``False``.
-        :param bool show_progress: Indicates whether to display \
-            a loading bar on the progress of the operations. \
-            Defaults to ``True``.
-        '''
-        destination = dst.get_uri() \
-            if isinstance(dst, _NonLocalDir) \
-            else dst.get_path()
-
-        print_msg = f'\nCopying files from "{self.get_path()}" '
-        print_msg += f'into "{destination}".'
-        print(print_msg)
-
-        if recursively:
-            total_num_files = self.count(recursively=True)
-        else:
-            total_num_files = 0
-            for _ in self._get_contents_iterable(
-                recursively=False,
-                include_dirs=False,
-                show_abs_path=True
-            ):
-                total_num_files += 1
-
-        errors: list[_Error] = list()
-
-        for file_path in _tqdm(
-            iterable=self._get_contents_iterable(
-                recursively=recursively,
-                include_dirs=False,
-                show_abs_path=True
-            ),
-            disable=not show_progress,
-            desc="Progress",
-            unit='files',
-            total=total_num_files
-        ):
-            relative_path = self._relativize(file_path)
-            if not overwrite and dst.path_exists(relative_path):
-                error = _Error(
-                    uri=_join_paths(dst._get_separator(), destination, relative_path),
-                    is_src=False,
-                    msg='File already exists. Try setting "overwrite" to "True".')
-            else:
-                with open(file_path, "rb") as file:
-                    error = dst._load_from_source(
-                        file_name=relative_path,
-                        src=file,
-                        metadata=self.get_metadata(
-                            file_path=relative_path)
-                            if include_metadata else None)
-
-            if error is not None:
-                errors.append(error)
 
-        if len(errors) == 0:
-            print(f'Operation successful: Copied all {total_num_files} files!')
-        else:
-            msg = "Operation unsuccessful: Failed to copy "
-            msg += f"{len(errors)} out of {total_num_files} files."
-            msg += f"\n\nDisplaying {len(errors)} errors:\n"
-            for err in errors:
-               msg += str(err)
-            print(msg)
+        :note: The resulting iterator may vary depending on the \
+            value of parameter ``recursively``.
+        '''
+        yield from super().traverse_files(recursively=recursively)
 
 
-    def _iterate_contents_impl(
+    def get_files(
         self,
         recursively: bool = False,
         show_abs_path: bool = False
-    ) -> _typ.Iterator[str]:
+    ) -> dict[str, LocalFile]:
         '''
-        Returns an iterator capable of going through the paths \
-        ofthe dictionary's contents as strings.
+        Returns a dictionary mapping file paths to ``File`` instances \
+        regarding the files contained within the directory.
 
         :param bool recursively: Indicates whether the directory \
-            is to be scanned recursively or not. If set to  ``False``, \
+            is to be traversed recursively or not. If set to  ``False``, \
             then only those files that reside directly within the \
             directory are to be considered. If set to ``True``, \
             then all files are considered, no matter whether they \
             reside directly within the directory or within any of \
             its subdirectories. Defaults to ``False``.
-        :param bool show_abs_path: Indicates whether it \
-            should be displayed the absolute or the relative \
-            path of the contents. Defaults to ``False``.
+        :param bool show_abs_path: Determines whether to include the \
+            files' absolute path or their path relative to this directory. \
+            Defaults to ``False``.
 
         :note: The resulting iterator may vary depending on the \
             value of parameter ``recursively``.
         '''
-
-        sep = self._get_separator()
-
-        if recursively:
-            for dp, _, fn in _os.walk(self.get_path()):
-                for file in fn:
-                    if not show_abs_path:
-                        dp = self._relativize(dp.replace(_os.sep, sep))
-                    yield _join_paths(sep, dp, file)
-        else:
-            for obj in _os.listdir(self.get_path()):
-                if not self._is_file(obj):
-                    obj += sep
-                yield _join_paths(sep, self.get_path(), obj) \
-                    if show_abs_path else obj
-
-
-    def _load_from_source(
-        self,
-        file_name: str,
-        src: _typ.Union[_io.BufferedReader, _io.BytesIO],
-        metadata: _typ.Optional[dict[str, str]]
-    ) -> _typ.Optional[_Error]:
-        '''
-        Loads a file directly from the given source \
-        and into this directory. Returns ``None`` if \
-        the operation was successful, else returns an \
-        ``Error`` instance.
-
-        :param str file_name: The name of the file that is to be \
-            loaded.
-        :param Union[io.BufferedReader, io.BytesIO] src: A buffer \
-            containing the file in bytes.
-        :param dict[str, str] | None metadata: A dictionary \
-            containing any metadata associated with the file.
-        '''
-
-        file_path = _join_paths(self._get_separator(), self.get_path(), file_name)
-
-        _os.makedirs(_os.path.dirname(file_path), exist_ok=True)
-
-        ingester = self._get_ingester()
-
-        with open(file_path, 'wb') as file:
-
-            ingester.set_sink(snk=file)
-
-            error_msg = ingester.load(src=src, metadata=metadata)
-
-            if error_msg is None:
-                self._add_file_to_metadata_dict(
-                    file_path=file_name,
-                    metadata=metadata)
-            else:
-                return _Error(
-                    uri=file_path,
-                    is_src=False,
-                    msg=error_msg)
-
-
-    def _load_from_ingester(
-        self,
-        file_name: str,
-        ingester: _Ingester,
-        fetch_metadata: bool
-    ) -> _typ.Optional[_Error]:
-        '''
-        Loads a file into this directory by using \
-        the provided ingester. Returns ``None`` if \
-        the operation was successful, else returns \
-        an ``Error`` instance.
-
-        :param str file_name: The name of the file that is to be \
-            loaded.
-        :param Ingester ingester: An ingester method used in \
-            ingesting the file.
-        :param bool fetch_metadata: Indicates whether to \
-            fetch any metadata associated with the file or not.
-        '''
-
-        file_path = _join_paths(self._get_separator(), self.get_path(), file_name)
-
-        _os.makedirs(_os.path.dirname(file_path), exist_ok=True)
-
-        with open(file_path, 'wb') as file:
-
-            error_msg = ingester.extract(snk=file, include_metadata=fetch_metadata)
-
-            if error_msg is None:
-                self._add_file_to_metadata_dict(
-                    file_path=file_name,
-                    metadata=ingester.get_metadata())
-            else:
-                return _Error(
-                    uri=ingester.get_source(),
-                    is_src=True,
-                    msg=error_msg)
-
-
-    def _get_file_size(self, path: str) -> int:
-        '''
-        Returns the size of the file that corresponds \
-        to the provided path in bytes.
-
-        :param str path: The absolute path pointing to \
-            the file in question.
-        '''
-        return _os.path.getsize(path) 
-
-
-    def _is_file(self, path: str) -> bool:
-        '''
-        Returns ``True`` if the provided path \
-        points to a file, else returns ``False``.
-
-        :param str path: Either the absolute path or the \
-            path relative to the directory of the file in \
-            question.
-        '''
-        if not path.startswith(self.get_path()):
-            path = _join_paths(self._get_separator(), self.get_path(), path)
-        return _os.path.isfile(path)
+        return super().get_files(
+            recursively=recursively, show_abs_path=show_abs_path)
 
 
 class _NonLocalDir(_Directory, _ABC):
@@ -1655,20 +1518,13 @@ class _NonLocalDir(_Directory, _ABC):
     directories or directories in the cloud.
 
     :param str path: The path pointing to the directory.
-    :param bool cache: Indicates whether it is allowed for \
-        any fetched data to be cached for faster subsequent \
-        access.
-    :param ClientHandler handler: A ``ClientHandler`` class instance used \
-        in handling connections.
-    :param Ingester ingester: An ``Ingester`` class instance used \
-        for ingesting data.
+    :param ClientHandler handler: A ``ClientHandler`` class \
+        instance used for interacting with the underlying handler.
     '''
     def __init__(
         self,
         path: str,
-        cache: bool,
-        handler: _ClientHandler,
-        ingester: _Ingester
+        handler: _ClientHandler
     ):
         '''
         An abstract class which serves as the base class for \
@@ -1676,17 +1532,10 @@ def __init__(
         directories or directories in the cloud.
 
         :param str path: The path pointing to the directory.
-        :param bool cache: Indicates whether it is allowed for \
-            any fetched data to be cached for faster subsequent \
-            access.
-        :param ClientHandler handler: A ``ClientHandler`` class instance used \
-            in handling connections.
-        :param Ingester ingester: An ``Ingester`` class instance used \
-            for ingesting data.
-        '''
-        super().__init__(path=path, ingester=ingester)
-        self.__cache_manager = _CacheManager() if cache else None
-        self.__handler = handler
+        :param ClientHandler handler: A ``ClientHandler`` class \
+            instance used for interacting with the underlying handler.
+        '''
+        super().__init__(path=path, handler=handler)
         self.open()
 
 
@@ -1695,326 +1544,50 @@ def is_cacheable(self) -> bool:
         Returns ``True`` if directory has been \
         defined as cacheable, else returns ``False``.
         '''
-        return self.__cache_manager is not None
+        return self._get_handler().is_cacheable()
 
 
-    def purge(self) -> None:
-        '''
-        If cacheable, then purges the directory's cache, \
-        else does nothing.
-        '''
-        if self.is_cacheable():
-            self.__cache_manager.purge()
-
-
-    def open(self) -> None:
-        '''
-        Opens all necessary connections.
-        '''
-        self.__handler.open_connections()
-
-
-    def close(self) -> None:
-        '''
-        Closes all open connections.
-        '''
-        self.__handler.close_connections()
-
-
-    def get_size(self, recursively: bool = False) -> int:
-        '''
-        Returns the total sum of the sizes of all files \
-        within the directory, in bytes.
-
-        :param bool recursively: Indicates whether the directory \
-            is to be scanned recursively or not. If set to  ``False``, \
-            then only those files that reside directly within the \
-            directory are to be considered. If set to ``True``, \
-            then all files are considered, no matter whether they \
-            reside directly within the directory or within any of \
-            its subdirectories. Defaults to ``False``.
-
-        :note: The resulting size may vary depending on the value \
-            of parameter ``recursively``.
-        '''
-        total_size = 0
-
-        for file_path in self._get_contents_iterable(
-            recursively=recursively,
-            include_dirs=False,
-            show_abs_path=True
-        ):
-            if self.is_cacheable():
-                if (size := self.__cache_manager.get_size(file_path=file_path)) is not None:
-                    total_size += size
-                else:
-                    size = self._get_file_size(file_path)
-                    self.__cache_manager.cache_size(file_path, size)
-                    total_size += size
-            else:
-                total_size += self._get_file_size(file_path)
-
-        return total_size
-
-
-    def transfer_to(
-        self,
-        dst: '_Directory',
-        recursively: bool = False,
-        overwrite: bool = False,
-        include_metadata: bool = False,
-        show_progress: bool = True
-    ) -> None:
-        '''
-        Copies all files within this directory into \
-        the destination directory.
-
-        :param _Directory dst: A ``_Directory`` class instance, \
-            which represents the transfer operation's destination.
-        :param bool recursively: Indicates whether the directory \
-            is to be scanned recursively or not. If set to  ``False``, \
-            then only those files that reside directly within the \
-            directory are to be considered. If set to ``True``, \
-            then all files are considered, no matter whether they \
-            reside directly within the directory or within any of \
-            its subdirectories. Defaults to ``False``.
-        :param bool overwrite: Indicates whether to overwrite \
-            any files if they already exist. Defaults to ``False``.
-        :param bool include_metadata: Indicates whether any \
-            existing metadata are to be assigned to the resulting \
-            files. Defaults to ``False``.
-        :param bool show_progress: Indicates whether to display \
-            a loading bar on the progress of the operations. \
-            Defaults to ``True``.
-        '''
-        destination = dst.get_uri() \
-            if isinstance(dst, _NonLocalDir) \
-            else dst.get_path()
-
-        print_msg = f'\nCopying files from "{self.get_path()}" '
-        print_msg += f'into "{destination}".'
-        print(print_msg)
-
-        if recursively:
-            total_num_files = self.count(recursively=True)
-        else:
-            total_num_files = 0
-            for _ in self._get_contents_iterable(
-                recursively=recursively,
-                include_dirs=False,
-                show_abs_path=True
-            ):
-                total_num_files += 1
-
-        errors: list[_Error] = list()
-
-        ingester = self._get_ingester()
-
-        for file_path in _tqdm(
-            iterable=self._get_contents_iterable(
-                recursively=recursively,
-                include_dirs=False,
-                show_abs_path=True
-            ),
-            disable=not show_progress,
-            desc="Progress",
-            unit='files',
-            total=total_num_files
-        ):
-            relative_path = self._relativize(file_path)
-            if not overwrite and dst.path_exists(relative_path):
-                error = _Error(
-                    uri=_join_paths(dst._get_separator(), destination, relative_path),
-                    is_src=False,
-                    msg='File already exists. Try setting "overwrite" to "True".')
-            else:
-                ingester.set_source(src=file_path)
-
-                fetch_metadata = include_metadata
-
-                if include_metadata and (
-                    (custom_metadata := self.get_metadata(file_path))
-                    is not None
-                ):
-                    ingester.set_metadata(metadata=custom_metadata)
-                    fetch_metadata = False
-
-                error = dst._load_from_ingester(
-                    file_name = relative_path,
-                    ingester=ingester,
-                    fetch_metadata=fetch_metadata)
-
-            if error is not None:
-                errors.append(error)
-        
-        if len(errors) == 0:
-            print(f'Operation successful: Copied all {total_num_files} files!')
-        else:
-            msg = "Operation unsuccessful: Failed to copy "
-            msg += f"{len(errors)} out of {total_num_files} files."
-            msg += f"\n\nDisplaying {len(errors)} errors:\n"
-            for err in errors:
-               msg += str(err)
-            print(msg)
-
-
-    def _load_from_source(
-        self,
-        file_name: str,
-        src: _typ.Union[_io.BufferedReader, _io.BytesIO],
-        metadata: _typ.Optional[dict[str, str]]
-    ) -> _typ.Optional[_Error]:
-        '''
-        Loads a file directly from the given source \
-        and into this directory. Returns ``None`` if \
-        the operation was successful, else returns an \
-        ``Error`` instance.
-
-        :param str file_name: The name of the file that is to be \
-            loaded.
-        :param Union[io.BufferedReader, io.BytesIO] src: A buffer \
-            containing the file in bytes.
-        :param dict[str, str] | None metadata: A dictionary \
-            containing any metadata associated with the file.
-        '''
-
-        ingester = self._get_ingester()
-        ingester.set_sink(_join_paths(self._get_separator(), self.get_path(), file_name))
-
-        error_msg = ingester.load(src=src, metadata=metadata)
-
-        if error_msg is None:
-            self._add_file_to_metadata_dict(
-                file_path=file_name,
-                metadata=metadata)
-        else:
-            return _Error(
-                uri=_join_paths(self._get_separator(), self.get_uri(), file_name),
-                is_src=False,
-                msg=error_msg)
-
-
-    def _load_from_ingester(
-        self,
-        file_name: str,
-        ingester: _Ingester,
-        fetch_metadata: bool
-    ) -> _typ.Optional[_Error]:
-        '''
-        Loads a file into this directory by using \
-        the provided ingester. Returns ``None`` if \
-        the operation was successful, else returns \
-        an ``Error`` instance.
-
-        :param str file_name: The name of the file that is to be \
-            loaded.
-        :param Ingester ingester: An ingester method used in \
-            ingesting the file.
-        :param bool fetch_metadata: Indicates whether to \
-            fetch any metadata associated with the file or not.
-        '''
-        # Load data into buffer via the ingester.
-        buffer = _io.BytesIO()
-
-        error_msg = ingester.extract(
-            snk=buffer,
-            include_metadata=fetch_metadata)
-
-        # If successful, then load data directly from buffer.
-        if error_msg is None:
-
-            buffer.seek(0)
-            error: _Error = self._load_from_source(
-                file_name=file_name,
-                src=buffer,
-                metadata=ingester.get_metadata())
-
-            if error is None:
-                self._add_file_to_metadata_dict(
-                    file_path=file_name,
-                    metadata=ingester.get_metadata())
-            else:
-                return error
-        else:
-            return _Error(
-                uri=ingester.get_source(),
-                is_src=True,
-                msg=error_msg)
-        
-
-    def _get_contents_iterable(
-        self,
-        recursively: bool,
-        include_dirs: bool,
-        show_abs_path: bool
-    ) -> _typ.Iterator[str]:
-        '''
-        Returns an iterator over the dictionary's contents.
-
-        :param bool recursively: Indicates whether the directory \
-            is to be scanned recursively or not. If set to  ``False``, \
-            then only those files that reside directly within the \
-            directory are to be considered. If set to ``True``, \
-            then all files are considered, no matter whether they \
-            reside directly within the directory or within any of \
-            its subdirectories.
-        :param bool include_dirs: Indicates whether to include any \
-            directories in the results in case ``recursively`` is \
-            set to ``False``.
-        :param bool show_abs_path: Determines whether to include the \
-            contents' absolute path or their path relative to this directory.
+    def purge(self) -> None:
         '''
-        iterable = super()._get_contents_iterable(
-            recursively=recursively,
-            include_dirs=include_dirs,
-            show_abs_path=show_abs_path)
+        If cacheable, then purges the directory's cache, \
+        else does nothing.
+        '''
+        self._get_handler().purge()
 
-        if not self.is_cacheable():
-            return iterable
-        
-        if recursively and self.__cache_manager.is_recursive_cache_empty():
-            for path in self._iterate_contents_impl(
-                recursively=True,
-                show_abs_path=True
-            ):
-                self.__cache_manager.add_to_cache(path=path)
-        elif not recursively and self.__cache_manager.is_top_level_empty():
-            for path in self._iterate_contents_impl(
-                recursively=False,
-                show_abs_path=True
-            ):
-                self.__cache_manager.add_to_top_level(
-                    path=path,
-                    is_file=self._is_file(path=path))
-        
-        iterable = self.__cache_manager.iterate_contents(
-            recursively=recursively,
-            include_dirs=include_dirs)
-        
-        if not show_abs_path:
-            iterable = map(lambda p: self._relativize(p), iterable)
 
-        return iterable
+    def open(self) -> None:
+        '''
+        Opens all necessary connections.
+        '''
+        self._get_handler().open_connections()
+
 
-    
-    def _get_client(self) -> _typ.Any:
+    def close(self) -> None:
         '''
-        Returns the underlying client of this \
-        instance's ``ClientHandler`` instance.
+        Closes all open connections.
         '''
-        return self.__handler.get_client() \
-            if self.__handler is not None \
-            else None
-    
+        self._get_handler().close_connections()
+
 
-    def _get_cache_manager(self) -> _typ.Optional[_CacheManager]:
+    def __del__(self) -> None:
         '''
-        Returns the directory's cache manager.
+        The class destructor method.
         '''
-        return self.__cache_manager
+        handler = self._get_handler()
+        if handler is not None and handler.is_open():
+            # Purge cache (if it exists).
+            self.purge()
+            # Close any open connections.
+            self.close()
+            # Display warning.
+            msg = f'You might want to consider instantiating class "{self.__class__.__name__}"'
+            msg += " through the use of a context manager by utilizing Python's"
+            msg += ' "with" statement, or by simply invoking an instance\'s'
+            msg += ' "close" method after being done using it.'
+            _warn.warn(msg, ResourceWarning)
     
 
-    def __enter__(self) -> '_NonLocalFile':
+    def __enter__(self) -> '_NonLocalDir':
         '''
         Enter the runtime context related to this instance.
         '''
@@ -2078,42 +1651,25 @@ def __init__(
         :raises InvalidDirectoryError: The provided path \
             does not point to a directory.
         '''
-        ssh_handler = _SSHClientHandler(auth=auth)
-
-        super().__init__(
-            path=path,
-            cache=cache,
-            handler=ssh_handler,
-            ingester=_RemoteIngester(handler=ssh_handler))
-        
         # Validate path.
         if path == '':
-            self.close()
             raise _IPE(path=path)
+        
+        ssh_handler = _SSHClientHandler(auth=auth, cache=cache)
 
-        self.__host = auth.get_credentials()['hostname']
-
-        from stat import S_ISDIR as _is_dir
-
-        sftp = ssh_handler.get_client()
+        super().__init__(
+            path=path,
+            handler=ssh_handler)
 
-        stats = None
+        self.__host = auth.get_credentials()['hostname']
 
-        try:
-            stats = sftp.stat(path=path)
-        except FileNotFoundError:
+        if not ssh_handler.path_exists(path=path):
             if create_if_missing:
-                sftp.mkdir(path=path)
-                stats = sftp.stat(path=path)
+                ssh_handler.mkdir(path=path)
             else:
                 self.close()
                 raise _IPE(path)
-        except:
-            self.close()
-            raise ConnectionError()
-
-        if not _is_dir(stats.st_mode):
-            self.close()
+        if ssh_handler.is_file(file_path=path):
             raise _IDE(path)
 
 
@@ -2130,135 +1686,79 @@ def get_uri(self) -> str:
         Returns the directory's URI.
         '''
         return f"sftp://{self.__host}/{self.get_path().lstrip(self._get_separator())}"
+    
 
-
-    def path_exists(self, path: str) -> bool:
+    def get_file(self, file_path: str) -> RemoteFile:
         '''
-        Returns ``True`` if the provided path exists \
-        within the directory, else returns ``False``.
+        Returns the file residing in the specified \
+        path as a ``RemoteFile`` instance.
 
-        :param str path: Either an absolute path or a \
-            path relative to the directory.
+        :param str file_path: Either the absolute path \
+            or the path relative to the directory of the \
+            file in question.
         '''
-        sftp: _prmk.SFTPClient = self._get_client()
-        try:
-            path = _join_paths(
-                self._get_separator(),
-                self.get_path(),
-                self._relativize(path=path))
-            sftp.stat(path=path)
-        except FileNotFoundError:
-            return False
-        return True
-
+        file_path = self._to_absolute(file_path, replace_sep=False)
+        return RemoteFile._create_file(
+            path=file_path,
+            host=self.get_hostname(),
+            handler=self._get_handler(),
+            metadata=self._get_metadata_ref(file_path))
+    
 
-    def _iterate_contents_impl(
+    def traverse_files(
         self,
-        recursively: bool = False,
-        show_abs_path: bool = False
-    ) -> _typ.Iterator[str]:
+        recursively: bool = False
+    ) -> _typ.Iterator[RemoteFile]:
         '''
-        Returns an iterator capable of going through the paths \
-        ofthe dictionary's contents as strings.
+        Returns an iterator capable of going through the \
+        dictionaries files as ``File`` instances.
 
         :param bool recursively: Indicates whether the directory \
-            is to be scanned recursively or not. If set to  ``False``, \
+            is to be traversed recursively or not. If set to  ``False``, \
             then only those files that reside directly within the \
             directory are to be considered. If set to ``True``, \
             then all files are considered, no matter whether they \
             reside directly within the directory or within any of \
             its subdirectories. Defaults to ``False``.
-        :param bool show_abs_path: Indicates whether it \
-            should be displayed the absolute or the relative \
-            path of the contents. Defaults to ``False``.
 
         :note: The resulting iterator may vary depending on the \
             value of parameter ``recursively``.
         '''
+        yield from super().traverse_files(recursively=recursively)
 
-        from stat import S_ISDIR as _is_dir
-
-        sftp: _prmk.SFTPClient = self._get_client()
-
-        sep = self._get_separator()
-
-        if recursively:
-
-            def filter_obj(
-                sftp: _prmk.SFTPClient,
-                attr: _prmk.SFTPAttributes,
-                parent_dir: str
-            ):
-                abs_path = _join_paths(
-                    sep,
-                    parent_dir,
-                    attr.filename)
 
-                if _is_dir(attr.st_mode):
-                    try:
-                        for sub_attr in sftp.listdir_attr(path=abs_path):
-                            yield from filter_obj(
-                                sftp=sftp,
-                                attr=sub_attr,
-                                parent_dir=abs_path)
-                    except:
-                        pass
-                else:
-                    yield abs_path
-
-            for attr in sftp.listdir_attr(path=self.get_path()):
-                for file_path in filter_obj(
-                    sftp=sftp,
-                    attr=attr,
-                    parent_dir=self.get_path()
-                ):
-                    yield (file_path if show_abs_path \
-                        else self._relativize(path=file_path))
-        else:
-            for attr in sftp.listdir_attr(path=self.get_path()):
-                path = attr.filename
-                if _is_dir(attr.st_mode):
-                    path += sep
-                yield _join_paths(sep, self.get_path(), path) \
-                    if show_abs_path else path
-
-
-    def _get_file_size(self, file_path: str) -> int:
-        '''
-        Returns the size of the file that corresponds \
-        to the provided path in bytes.
-
-        :param str file_path: The absolute path pointing \
-            to the file in question.
+    def get_files(
+        self,
+        recursively: bool = False,
+        show_abs_path: bool = False
+    ) -> dict[str, RemoteFile]:
         '''
-        return self._get_client().stat(path=file_path).st_size
+        Returns a dictionary mapping file paths to ``File`` instances \
+        regarding the files contained within the directory.
 
+        :param bool recursively: Indicates whether the directory \
+            is to be traversed recursively or not. If set to  ``False``, \
+            then only those files that reside directly within the \
+            directory are to be considered. If set to ``True``, \
+            then all files are considered, no matter whether they \
+            reside directly within the directory or within any of \
+            its subdirectories. Defaults to ``False``.
+        :param bool show_abs_path: Determines whether to include the \
+            files' absolute path or their path relative to this directory. \
+            Defaults to ``False``.
 
-    def _is_file(self, path: str) -> bool:
+        :note: The resulting iterator may vary depending on the \
+            value of parameter ``recursively``.
         '''
-        Returns ``True`` if the provided path \
-        points to a file, else returns ``False``.
+        return super().get_files(
+            recursively=recursively, show_abs_path=show_abs_path)
+    
 
-        :param str path: Either the absolute path or the \
-            path relative to the directory of the file in \
-            question.
+    def __enter__(self) -> 'RemoteDir':
         '''
-        from stat import S_ISDIR as _is_dir
-        if not path.startswith(self.get_path()):
-            path = _join_paths(self._get_separator(), self.get_path(), path)
-        try:
-            return not _is_dir(self._get_client().stat(path=path).st_mode)
-        except FileNotFoundError:
-            return False
-
-
-    def __del__(self) -> None:
-        if self._get_client() is not None:
-            msg = "You might want to consider instantiating class ``RemoteDir``"
-            msg += " through the use of a context manager by utilizing Python's"
-            msg += " ``with`` statement, or by simply invoking an instance's"
-            msg += " ``close`` method after being done using it."
-            _warn.warn(msg, ResourceWarning)
+        Enter the runtime context related to this instance.
+        '''
+        return super().__enter__()
 
 
 class _CloudDir(_NonLocalDir, _ABC):
@@ -2275,7 +1775,7 @@ def load_metadata(self, recursively: bool = False) -> None:
         via the instance's ``get_metadata`` method.
 
         :param bool recursively: Indicates whether the directory \
-            is to be scanned recursively or not. If set to  ``False``, \
+            is to be traversed recursively or not. If set to  ``False``, \
             then only those files that reside directly within the \
             directory are to be considered. If set to ``True``, \
             then all files are considered, no matter whether they \
@@ -2289,64 +1789,16 @@ def load_metadata(self, recursively: bool = False) -> None:
               method will be overriden after invoking this \
               method.
         '''
+        handler = self._get_handler()
 
-        cache_manager = self._get_cache_manager()
-
-        for file_path in self._get_contents_iterable(
+        for file_path in handler.traverse_dir(
+            dir_path=self.get_path(),
             recursively=recursively,
             include_dirs=False,
             show_abs_path=True
         ):
-            if self.is_cacheable():
-                if (metadata := cache_manager.get_metadata(file_path=file_path)) is not None:
-                    self.set_metadata(file_path, metadata)
-                else:
-                    metadata = self._load_metadata_impl(file_path)
-                    cache_manager.cache_metadata(file_path, metadata)
-                    self.set_metadata(file_path, metadata)
-            else:
-                self.set_metadata(file_path,
-                    self._load_metadata_impl(file_path))
-                
-
-    def get_metadata(self, file_path: str) -> _typ.Optional[dict[str, str]]:
-        '''
-        Returns a dictionary containing any metadata \
-            associated with the file in question.
-
-        :param str file_path: Either the absolute path \
-            or the path relative to the directory of the \
-            file in question.
-
-        :raises InvalidFileError: The provided path does \
-            not point to a file within the directory.
-        '''
-        metadata = super().get_metadata(file_path=file_path)
-   
-        if metadata is not None:
-            return metadata
-        
-        if self.is_cacheable():
-            abs_path = _join_paths(
-                self._get_separator(),
-                self.get_path(),
-                self._relativize(file_path))
-            return self._get_cache_manager().get_metadata(file_path=abs_path)
-        
-        return None
-
-
-    @_absmethod
-    def _load_metadata_impl(self, file_path: str) -> dict[str, str]:
-        '''
-        Loads any metadata associated with the file, \
-        which can then be accessed via the instance's \
-        ``get_metadata`` method.
-
-        :param str file_path: The absolute path pointing \
-            to the file in question.
-        '''
-        pass
+            metadata = handler.get_file_metadata(file_path)
+            self.set_metadata(file_path, metadata)
 
 
 class AWSS3Dir(_CloudDir):
@@ -2358,7 +1810,9 @@ class AWSS3Dir(_CloudDir):
         for authenticating with AWS.
     :param str bucket: The name of the bucket in which \
         the directory resides.
-    :param str path: The path pointing to the directory.
+    :param str | None path: The path pointing to the directory. \
+        If ``None``, then the whole bucket is considered. \
+        Defaults to ``None``.
     :param bool cache: Indicates whether it is allowed for \
         any fetched data to be cached for faster subsequent \
         access. Defaults to ``False``.
@@ -2369,12 +1823,16 @@ class AWSS3Dir(_CloudDir):
 
     :raises InvalidPathError: The provided path \
         does not exist.
+
+    :note: The provided path must not begin with a separator.
+        - Wrong: ``/path/to/dir/``
+        - Right: ``path/to/dir/``
     '''
     def __init__(
         self,
         auth: _AWSAuth,
         bucket: str,
-        path: str,
+        path: _typ.Optional[str] = None,
         cache: bool = False,
         create_if_missing: bool = False
     ):
@@ -2386,7 +1844,9 @@ def __init__(
             for authenticating with AWS.
         :param str bucket: The name of the bucket in which \
             the directory resides.
-        :param str path: The path pointing to the directory.
+        :param str | None path: The path pointing to the directory. \
+            If ``None``, then the whole bucket is considered. \
+            Defaults to ``None``.
         :param bool cache: Indicates whether it is allowed for \
             any fetched data to be cached for faster subsequent \
             access. Defaults to ``True``.
@@ -2397,34 +1857,32 @@ def __init__(
 
         :raises InvalidPathError: The provided path \
             does not exist.
+
+        :note: The provided path must not begin with a separator.
+            - Wrong: ``/path/to/dir/``
+            - Right: ``path/to/dir/``
         '''
-        # Connect to S3 resource.
+        # Validate path.
+        if path is None:
+            path = ''
+        else:
+            sep = _infer_sep(path=path)
+            if path.startswith(sep):
+                raise _IPE(path=path)
+            
+        # Instantiate a connection handler.
         aws_handler = _AWSClientHandler(
             auth=auth,
-            bucket=bucket)
+            bucket=bucket,
+            cache=cache)
 
         super().__init__(
             path=path,
-            cache=cache,
-            handler=aws_handler,
-            ingester=_AWSS3Ingester(handler=aws_handler))
-
-        s3_bucket = aws_handler.get_client()
+            handler=aws_handler)
 
-        # Check if directory exists.
-        dir_exists: bool = False
-
-        for _ in s3_bucket.objects.filter(Prefix=path):
-            dir_exists = True
-            break
-
-        # If it doesn't, either create it
-        # or throw an exception.
-        if not dir_exists:
+        if not aws_handler.dir_exists(path=path):
             if create_if_missing:
-                s3_bucket.put_object(
-                    Key=path,
-                    ContentType='application/x-directory; charset=UTF-8')
+                aws_handler.mkdir(path=path)
             else:
                 self.close()
                 raise _IPE(path)
@@ -2435,7 +1893,7 @@ def get_bucket_name(self) -> str:
         Returns the name of the bucket in which \
         the directory resides.
         '''
-        return self._get_client().name
+        return self._get_handler().get_bucket_name()
 
 
     def get_uri(self) -> str:
@@ -2443,146 +1901,78 @@ def get_uri(self) -> str:
         Returns the directory's URI.
         '''
         return f"s3://{self.get_bucket_name()}/{self.get_path()}"
+    
 
-
-    def path_exists(self, path: str) -> bool:
+    def get_file(self, file_path: str) -> AWSS3File:
         '''
-        Returns ``True`` if the provided path exists \
-        within the directory, else returns ``False``.
+        Returns the file residing in the specified \
+        path as a ``AWSS3File`` instance.
 
-        :param str path: Either an absolute path or a \
-            path relative to the directory.
+        :param str file_path: Either the absolute path \
+            or the path relative to the directory of the \
+            file in question.
         '''
-        path = _join_paths(
-            self._get_separator(),
-            self.get_path(),
-            self._relativize(path=path))
-        try:
-            self._get_client().Object(path).load()
-        except:
-            return False
-        return True
+        file_path = self._to_absolute(file_path, replace_sep=False)
+        return AWSS3File._create_file(
+            path=file_path,
+            handler=self._get_handler(),
+            metadata=self._get_metadata_ref(file_path))
 
 
-    def _iterate_contents_impl(
+    def traverse_files(
         self,
-        recursively: bool = False,
-        show_abs_path: bool = False
-    ) -> _typ.Iterator[str]:
+        recursively: bool = False
+    ) -> _typ.Iterator[AWSS3File]:
         '''
-        Returns an iterator capable of going through the paths \
-        ofthe dictionary's contents as strings.
+        Returns an iterator capable of going through the \
+        dictionaries files as ``File`` instances.
 
         :param bool recursively: Indicates whether the directory \
-            is to be scanned recursively or not. If set to  ``False``, \
+            is to be traversed recursively or not. If set to  ``False``, \
             then only those files that reside directly within the \
             directory are to be considered. If set to ``True``, \
             then all files are considered, no matter whether they \
             reside directly within the directory or within any of \
             its subdirectories. Defaults to ``False``.
-        :param bool show_abs_path: Indicates whether it \
-            should be displayed the absolute or the relative \
-            path of the contents. Defaults to ``False``.
 
         :note: The resulting iterator may vary depending on the \
             value of parameter ``recursively``.
         '''
+        yield from super().traverse_files(recursively=recursively)
 
-        paginator = self._get_client().meta.client.get_paginator('list_objects')
-
-        delimiter = '' if recursively else '/'
-
-        def page_iterator():
-            yield from paginator.paginate(
-                Bucket=self.get_bucket_name(),
-                Prefix=self.get_path(),
-                Delimiter=delimiter
-            )
-
-        def object_iterator(response):
-            for obj in response.get('Contents', []):
-                file_path = obj['Key']
-                yield file_path if show_abs_path else \
-                    self._relativize(file_path)
-                
-        if recursively:
-            for response in page_iterator():
-                yield from object_iterator(response)
-        else:
-
-            def dir_iterator(response):
-                for dir in response.get('CommonPrefixes', []):
-                    dir_path = dir['Prefix']
-                    yield dir_path if show_abs_path else \
-                        self._relativize(dir_path)
-                    
-            for response in page_iterator():
-                        
-                obj_iter = object_iterator(response)
-                dir_iter = dir_iterator(response)
 
-                obj = next(obj_iter, None)
-                dir = next(dir_iter, None)
-
-                while True:
-                    if obj is None and dir is None:
-                        break
-                    elif obj is None and dir is not None:
-                        yield dir
-                        dir = next(dir_iter, None)
-                    elif obj is not None and dir is None:
-                        yield obj
-                        obj = next(obj_iter, None)
-                    elif obj > dir:
-                        yield dir
-                        dir = next(dir_iter, None)
-                    else:
-                        yield obj
-                        obj = next(obj_iter, None)
-
-
-    def _get_file_size(self, file_path: str) -> int:
-        '''
-        Returns the size of the file that corresponds \
-        to the provided path in bytes.
-
-        :param str file_path: The absolute path pointing \
-            to the file in question.
+    def get_files(
+        self,
+        recursively: bool = False,
+        show_abs_path: bool = False
+    ) -> dict[str, AWSS3File]:
         '''
-        return self._get_client().Object(file_path).content_length
+        Returns a dictionary mapping file paths to ``File`` instances \
+        regarding the files contained within the directory.
 
+        :param bool recursively: Indicates whether the directory \
+            is to be traversed recursively or not. If set to  ``False``, \
+            then only those files that reside directly within the \
+            directory are to be considered. If set to ``True``, \
+            then all files are considered, no matter whether they \
+            reside directly within the directory or within any of \
+            its subdirectories. Defaults to ``False``.
+        :param bool show_abs_path: Determines whether to include the \
+            files' absolute path or their path relative to this directory. \
+            Defaults to ``False``.
 
-    def _load_metadata_impl(self, file_path: str) -> dict[str, str]:
-        '''
-        Loads any metadata associated with the file, \
-        which can then be accessed via the instance's \
-        ``get_metadata`` method.
-
-        :param str file_path: The absolute path pointing \
-            to the file in question.
+        :note: The resulting iterator may vary depending on the \
+            value of parameter ``recursively``.
         '''
-        return self._get_client().Object(key=file_path).metadata
-
+        return super().get_files(
+            recursively=recursively, show_abs_path=show_abs_path)
+    
 
-    def _is_file(self, path: str) -> bool:
+    def __enter__(self) -> 'AWSS3Dir':
         '''
-        Returns ``True`` if the provided path \
-        points to a file, else returns ``False``.
-
-        :param str path: Either the absolute path or the \
-            path relative to the directory of the file in \
-            question.
+        Enter the runtime context related to this instance.
         '''
-        return not path.endswith('/') and self.path_exists(path)
-
-
-    def __del__(self) -> None:
-        if self._get_client() is not None:
-            msg = "You might want to consider instantiating class `AWSS3Dir``"
-            msg += " through the use of a context manager by utilizing Python's"
-            msg += " ``with`` statement, or by simply invoking an instance's"
-            msg += " ``close`` method after being done using it."
-            _warn.warn(msg, ResourceWarning)
+        return super().__enter__()
 
 
 class AzureBlobDir(_CloudDir):
@@ -2594,7 +1984,9 @@ class AzureBlobDir(_CloudDir):
         for authenticating with Microsoft Azure.
     :param str container: The name of the container in which \
         the directory resides.
-    :param str path: The path pointing to the directory.
+    :param str | None path: The path pointing to the directory. \
+        If ``None``, then the whole container is considered. \
+        Defaults to ``None``.
     :param bool cache: Indicates whether it is allowed for \
         any fetched data to be cached for faster subsequent \
         access. Defaults to ``False``.
@@ -2607,12 +1999,16 @@ class AzureBlobDir(_CloudDir):
         does not exist.
     :raises InvalidDirectoryError: The provided path \
         does not point to a directory.
+
+    :note: The provided path must not begin with a separator.
+        - Wrong: ``/path/to/dir/``
+        - Right: ``path/to/dir/``
     '''
     def __init__(
         self,
         auth: _AzureAuth,
         container: str,
-        path: str,
+        path: _typ.Optional[str] = None,
         cache: bool = False,
         create_if_missing: bool = False
     ):
@@ -2624,7 +2020,9 @@ def __init__(
             for authenticating with Microsoft Azure.
         :param str container: The name of the container in which \
             the directory resides.
-        :param str path: The path pointing to the directory.
+        :param str | None path: The path pointing to the directory. \
+            If ``None``, then the whole container is considered. \
+            Defaults to ``None``.
         :param bool cache: Indicates whether it is allowed for \
             any fetched data to be cached for faster subsequent \
             access. Defaults to ``True``.
@@ -2637,44 +2035,45 @@ def __init__(
             does not exist.
         :raises InvalidDirectoryError: The provided path \
             does not point to a directory.
-        '''
-        # Connect to Azure blob container.
+
+        :note: The provided path must not begin with a separator.
+            - Wrong: ``/path/to/dir/``
+            - Right: ``path/to/dir/``
+        '''        
+        # Validate path.
+        if path is None:
+            path = '/'
+        else:
+            sep = _infer_sep(path=path)
+            if path.startswith(sep):
+                raise _IPE(path=path)
+            
+        # Instantiate a connection handler.
         azr_handler = _AzureClientHandler(
             auth=auth,
+            cache=cache,
             container=container)
         
+        # Infer storage account.
         self.__storage_account = auth._get_storage_account()
 
-        # Re-format path.
-        if path == '':
-            path = '/'
-
         super().__init__(
             path=path,
-            cache=cache,
-            handler=azr_handler,
-            ingester=_AzureIngester(handler=azr_handler))
-
-        # Fetch container client.
-        azr_container: _ContainerClient = azr_handler.get_client()
+            handler=azr_handler)
 
         # Throw an exception if container does not exist.
-        if not azr_container.exists():
+        if not azr_handler.container_exists():
             self.close()
             raise _ABCNFE(container)
 
         # Create directory or throw an exception
         # depending on the value of "create_if_missing".
-        with azr_container.get_blob_client(blob=path) as blob:
-            if not blob.exists():
-                if create_if_missing:
-                    dummy_blob = azr_container.get_blob_client(blob=f"{path}DUMMY")
-                    dummy_blob.create_append_blob()
-                    dummy_blob.delete_blob()
-                    dummy_blob.close()
-                else:
-                    self.close()
-                    raise _IPE(path)
+        if not azr_handler.path_exists(path=path):
+            if create_if_missing:
+                azr_handler.mkdir(path=path)
+            else:
+                self.close()
+                raise _IPE(path)
 
 
     def get_container_name(self) -> str:
@@ -2682,7 +2081,7 @@ def get_container_name(self) -> str:
         Returns the name of the bucket in which \
         the directory resides.
         '''
-        return self._get_client().container_name
+        return self._get_handler().get_container_name()
 
 
     def get_uri(self) -> str:
@@ -2692,117 +2091,75 @@ def get_uri(self) -> str:
         uri = f"abfss://{self.get_container_name()}@{self.__storage_account}"
         uri += f".dfs.core.windows.net/{self.get_path()}"
         return uri
+    
 
-
-    def path_exists(self, path: str) -> bool:
+    def get_file(self, file_path: str) -> AzureBlobFile:
         '''
-        Returns ``True`` if the provided path exists \
-        within the directory, else returns ``False``.
+        Returns the file residing in the specified \
+        path as a ``AzureBlobFile`` instance.
 
-        :param str path: Either an absolute path or a \
-            path relative to the directory.
+        :param str file_path: Either the absolute path \
+            or the path relative to the directory of the \
+            file in question.
         '''
-        path = _join_paths(
-            self._get_separator(),
-            self.get_path(),
-            self._relativize(path=path))
-        container: _ContainerClient = self._get_client()
-        with container.get_blob_client(blob=path) as blob:
-            return blob.exists()
-        
+        file_path = self._to_absolute(file_path, replace_sep=False)
+        return AzureBlobFile._create_file(
+            path=file_path,
+            handler=self._get_handler(),
+            metadata=self._get_metadata_ref(file_path))
+    
 
-    def _iterate_contents_impl(
+    def traverse_files(
         self,
-        recursively: bool = False,
-        show_abs_path: bool = False
-    ) -> _typ.Iterator[str]:
+        recursively: bool = False
+    ) -> _typ.Iterator[AzureBlobFile]:
         '''
-        Returns an iterator capable of going through the paths \
-        ofthe dictionary's contents as strings.
+        Returns an iterator capable of going through the \
+        dictionaries files as ``File`` instances.
 
         :param bool recursively: Indicates whether the directory \
-            is to be scanned recursively or not. If set to  ``False``, \
+            is to be traversed recursively or not. If set to  ``False``, \
             then only those files that reside directly within the \
             directory are to be considered. If set to ``True``, \
             then all files are considered, no matter whether they \
             reside directly within the directory or within any of \
             its subdirectories. Defaults to ``False``.
-        :param bool show_abs_path: Indicates whether it \
-            should be displayed the absolute or the relative \
-            path of the contents. Defaults to ``False``.
 
         :note: The resulting iterator may vary depending on the \
             value of parameter ``recursively``.
         '''
-        client: _ContainerClient = self._get_client()
-
-        if recursively:
-            '''
-            NOTE:
-                Due to certain storage accounts having the
-                hierarchical namespace feature enabled,
-                virtual folders may appear as ordinary
-                blobs. For this reason, consider that any
-                blob whose size is equal to zero is a virtual folder.
-
-                Issue: https://github.com/Azure/azure-sdk-for-python/issues/29026
-            '''
-            iterable = filter(
-                lambda p: p['size'] > 0,
-                client.list_blobs(
-                    name_starts_with=self.get_path()))
-        else:
-            iterable = client.walk_blobs(
-                name_starts_with=self.get_path(),
-                delimiter=self._get_separator())
-                    
-        for properties in iterable:
-            if show_abs_path:
-                yield properties['name']
-            else:
-                yield self._relativize(path=properties['name'])
-
+        yield from super().traverse_files(recursively=recursively)
 
-    def _get_file_size(self, file_path: str) -> int:
-        '''
-        Returns the size of the file that corresponds \
-        to the provided path in bytes.
 
-        :param str file_path: The absolute path pointing \
-            to the file in question.
+    def get_files(
+        self,
+        recursively: bool = False,
+        show_abs_path: bool = False
+    ) -> dict[str, AzureBlobFile]:
         '''
-        return self._get_client().download_blob(blob=file_path).size
+        Returns a dictionary mapping file paths to ``File`` instances \
+        regarding the files contained within the directory.
 
+        :param bool recursively: Indicates whether the directory \
+            is to be traversed recursively or not. If set to  ``False``, \
+            then only those files that reside directly within the \
+            directory are to be considered. If set to ``True``, \
+            then all files are considered, no matter whether they \
+            reside directly within the directory or within any of \
+            its subdirectories. Defaults to ``False``.
+        :param bool show_abs_path: Determines whether to include the \
+            files' absolute path or their path relative to this directory. \
+            Defaults to ``False``.
 
-    def _load_metadata_impl(self, file_path: str) -> dict[str, str]:
-        '''
-        Loads any metadata associated with the file, \
-        which can then be accessed via the instance's \
-        ``get_metadata`` method.
-
-        :param str file_path: The absolute path pointing \
-            to the file in question.
+        :note: The resulting iterator may vary depending on the \
+            value of parameter ``recursively``.
         '''
-        return self._get_client().download_blob(
-            blob=file_path).properties.metadata
-
-
-    def _is_file(self, path: str) -> bool:
+        return super().get_files(
+            recursively=recursively, show_abs_path=show_abs_path)
+    
+    
+    def __enter__(self) -> 'AzureBlobDir':
         '''
-        Returns ``True`` if the provided path \
-        points to a file, else returns ``False``.
-
-        :param str path: Either the absolute path or the \
-            path relative to the directory of the file in \
-            question.
+        Enter the runtime context related to this instance.
         '''
-        return not path.endswith('/') and self.path_exists(path)
-
-
-    def __del__(self) -> None:
-        if self._get_client() is not None:
-            msg = "You might want to consider instantiating class ``AzureBlobDir``"
-            msg += " through the use of a context manager by utilizing Python's"
-            msg += " ``with`` statement, or by simply invoking an instance's"
-            msg += " ``close`` method after being done using it."
-            _warn.warn(msg, ResourceWarning)
+        return super().__enter__()
diff --git a/pyproject.toml b/pyproject.toml
index 96d9de1..d9a1b5e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [project]
 name = "fluke-api"
-version = "0.1.0"
+version = "0.2.0"
 authors = [
   {email = "manosstoumpos@gmail.com"},
   {name = "Manos Stoumpos"}
@@ -33,4 +33,4 @@ tests = [
 "Homepage" = "https://github.com/manoss96/fluke"
 "Bug Tracker" = "https://github.com/manoss96/fluke/issues"
 "Documentation" = "https://fluke.rtfd.io"
-"Changelog" = "https://github.com/manoss96/fluke/CHANGELOG.md"
\ No newline at end of file
+"Changelog" = "https://github.com/manoss96/fluke/blob/main/CHANGELOG.md"
\ No newline at end of file


Test patch:
diff --git a/tests/test_storage.py b/tests/test_storage.py
index 7b08d31..725bdcf 100644
--- a/tests/test_storage.py
+++ b/tests/test_storage.py
@@ -102,8 +102,9 @@ def create_aws_s3_bucket(mock_s3, bucket_name: str, metadata: dict[str, str]):
     s3.create_bucket(Bucket=bucket_name)
     bucket = s3.Bucket(bucket_name)
     # Structure it like "test_files".
-    for dp, _, fn in os.walk(TEST_FILES_DIR):
-        for file in fn:
+    for dp, dn, fn in os.walk(TEST_FILES_DIR):
+        dn.sort()
+        for file in sorted(fn):
             file_path = join_paths(dp.replace(os.sep, SEPARATOR), file)
             with open(file_path, "rb") as file:
                 bucket.upload_fileobj(
@@ -139,7 +140,7 @@ def wrapper(*args, **kwargs):
 CONSTANTS
 '''
 SEPARATOR = '/'
-TEST_FILES_DIR = "test_files"
+TEST_FILES_DIR = "tests/test_files"
 METADATA = {'1': '1'}
 
 '''
@@ -224,12 +225,13 @@ def mkdir(self, path: str):
 
     @simulate_latency
     def listdir_attr(self, path: str) -> list[paramiko.SFTPAttributes]:
-        return [self.stat(join_paths(path, f)) for f in os.listdir(path=path)]
+        return [self.stat(join_paths(path, f)) for f in sorted(os.listdir(path=path))]
     
     @simulate_latency
     def getfo(self, remotepath, fl, callback=None, prefetch=True):
         with open(remotepath, "rb") as fr:
-            return fr
+            fl.write(fr.read())
+            return fr.tell()
         
     @simulate_latency
     def putfo(self, fl, remotepath, file_size=0, callback=None, confirm=True):
@@ -309,8 +311,9 @@ def __init__(self, container_name: str, path: str):
         self.container_name = container_name
         self.metadata: dict[str, dict[str, str]] = dict()
         # Add metadata for each file.
-        for dp, _, fn in os.walk(path):
-            for file in fn:
+        for dp, dn, fn in os.walk(path):
+            dn.sort()
+            for file in sorted(fn):
                 file_path = join_paths(dp, file)
                 self.metadata.update({file_path : METADATA})
 
@@ -346,8 +349,9 @@ def list_blobs(
         self,
         name_starts_with: str
     ) -> Iterator[MockBlobProperties]:
-        for dp, _, fn in os.walk(name_starts_with):
-            for file in fn:
+        for dp, dn, fn in os.walk(name_starts_with):
+            dn.sort()
+            for file in sorted(fn):
                 obj_name = join_paths(dp, file)
                 file_path = to_abs(obj_name)
                 yield MockContainerClient.MockBlobProperties(
@@ -369,7 +373,7 @@ def walk_blobs(
         if delimiter == '':
             yield from self.list_blobs(name_starts_with)       
         elif delimiter == SEPARATOR:
-            for name in os.listdir(name_starts_with):
+            for name in sorted(os.listdir(name_starts_with)):
                 obj_name = join_paths(name_starts_with, name)
 
                 if os.path.isdir(obj_name):
@@ -447,9 +451,9 @@ def test_get_uri(self):
         file = self.build_file()
         self.assertEqual(file.get_uri(), f"file:///{ABS_FILE_PATH.lstrip(SEPARATOR)}")
 
-    def test_get_metadata_on_none(self):
+    def test_get_metadata(self):
         file = self.build_file()
-        self.assertEqual(file.get_metadata(), None)
+        self.assertEqual(file.get_metadata(), {})
 
     def test_set_and_get_metadata(self):
         file = self.build_file()
@@ -471,6 +475,10 @@ def test_get_size(self):
         file = self.build_file()
         self.assertEqual(file.get_size(), 4)
 
+    def test_read(self):
+        file = self.build_file()
+        self.assertEqual(file.read(), b"TEXT")
+
     def test_transfer_to(self):
         file = self.build_file()
         dir = TestLocalDir.build_dir(path=ABS_DIR_PATH)
@@ -531,13 +539,17 @@ def test_get_name(self):
         with self.build_file() as file:
             self.assertEqual(file.get_name(), FILE_NAME)
 
+    def test_get_hostname(self):
+        with self.build_file() as file:
+            self.assertEqual(file.get_hostname(), HOST)
+
     def test_get_uri(self):
         with self.build_file() as file:
             self.assertEqual(file.get_uri(), f"sftp://{HOST}/{ABS_FILE_PATH.lstrip(os.sep)}")
 
-    def test_get_metadata_on_None(self):
+    def test_get_metadata(self):
         with self.build_file() as file:
-            self.assertEqual(file.get_metadata(), None)
+            self.assertEqual(file.get_metadata(), {})
 
     def test_set_and_get_metadata(self):
         with self.build_file() as file:
@@ -559,6 +571,10 @@ def test_get_size(self):
         with self.build_file() as file:
             self.assertEqual(file.get_size(), 4)
 
+    def test_read(self):
+        with self.build_file() as file:
+            self.assertEqual(file.read(), b"TEXT")
+
     def test_transfer_to(self):
         with self.build_file() as file:
             # Copy file into dir.
@@ -643,13 +659,13 @@ def test_open(self):
         file = self.build_file()
         file.close()
         file.open()
-        self.assertIsNotNone(file._get_client())
+        self.assertTrue(file._get_handler().is_open())
         file.close()
 
     def test_close(self):
         file = self.build_file()
         file.close()
-        self.assertIsNone(file._get_client())
+        self.assertFalse(file._get_handler().is_open())
 
     '''
     Test cache methods.
@@ -705,8 +721,32 @@ class TestAWSS3File(unittest.TestCase):
     def setUp(self):
         create_aws_s3_bucket(self.MOCK_S3, BUCKET, METADATA)
 
+        from fluke._handlers import AWSClientHandler
+
+        m1 = patch.object(AWSClientHandler, '_get_file_size_impl', autospec=True)
+        m2 = patch.object(AWSClientHandler, '_get_file_metadata_impl', autospec=True)
+        m3 = patch.object(AWSClientHandler, '_traverse_dir_impl', autospec=True)
+
+        def simulate_latency_1(*args, **kwargs):
+            time.sleep(0.2)
+            return m1.temp_original(*args, **kwargs)
+        
+        def simulate_latency_2(*args, **kwargs):
+            time.sleep(0.2)
+            return m2.temp_original(*args, **kwargs)
+        
+        def simulate_latency_3(*args, **kwargs):
+            time.sleep(0.2)
+            return m3.temp_original(*args, **kwargs)
+
+        m1.start().side_effect = simulate_latency_1
+        m2.start().side_effect = simulate_latency_2
+        m3.start().side_effect = simulate_latency_3
+
+
     def tearDown(self):
         self.MOCK_S3.stop()
+        patch.stopall()
     
     @staticmethod
     def build_file(path: str = REL_FILE_PATH, cache: bool = False) -> AWSS3File:
@@ -735,9 +775,13 @@ def test_get_uri(self):
         with self.build_file() as file:
             self.assertEqual(file.get_uri(), f"s3://{BUCKET}/{REL_FILE_PATH}")
 
-    def test_get_metadata_on_None(self):
+    def test_get_bucket_name(self):
+        with self.build_file() as file:
+            self.assertEqual(file.get_bucket_name(), BUCKET)
+
+    def test_get_metadata(self):
         with self.build_file() as file:
-            self.assertEqual(file.get_metadata(), None)
+            self.assertEqual(file.get_metadata(), {})
 
     def test_set_and_get_metadata(self):
         with self.build_file() as file:
@@ -777,6 +821,10 @@ def test_get_size(self):
         with self.build_file() as file:
             self.assertEqual(file.get_size(), 4)
 
+    def test_read(self):
+        with self.build_file() as file:
+            self.assertEqual(file.read(), b"TEXT")
+
     def test_transfer_to(self):
         with self.build_file() as file:
             # Copy file into dir.
@@ -844,13 +892,13 @@ def test_open(self):
         file = self.build_file()
         file.close()
         file.open()
-        self.assertIsNotNone(file._get_client())
+        self.assertTrue(file._get_handler().is_open())
         file.close()
 
     def test_close(self):
         file = self.build_file()
         file.close()
-        self.assertIsNone(file._get_client())
+        self.assertFalse(file._get_handler().is_open())
 
     '''
     Test cache methods.
@@ -969,9 +1017,9 @@ def test_get_uri(self):
             uri += f".dfs.core.windows.net/{REL_FILE_PATH}"
             self.assertEqual(file.get_uri(), uri)
 
-    def test_get_metadata_on_None(self):
+    def test_get_metadata(self):
         with self.build_file() as file:
-            self.assertEqual(file.get_metadata(), None)
+            self.assertEqual(file.get_metadata(), {})
 
     def test_set_and_get_metadata(self):
         with self.build_file() as file:
@@ -1011,6 +1059,11 @@ def test_get_size(self):
         with self.build_file() as file:
             self.assertEqual(file.get_size(), 4)
 
+
+    def test_read(self):
+        with self.build_file() as file:
+            self.assertEqual(file.read(), b"TEXT")
+
     def test_transfer_to(self):
         with self.build_file() as file:
             # Copy file into dir.
@@ -1095,13 +1148,13 @@ def test_open(self):
         file = self.build_file()
         file.close()
         file.open()
-        self.assertIsNotNone(file._get_client())
+        self.assertTrue(file._get_handler().is_open())
         file.close()
 
     def test_close(self):
         file = self.build_file()
         file.close()
-        self.assertIsNone(file._get_client())
+        self.assertFalse(file._get_handler().is_open())
 
     '''
     Test cache methods.
@@ -1217,9 +1270,9 @@ def test_get_metadata_on_invalid_file_error(self):
         dir = self.build_dir()
         self.assertRaises(InvalidFileError, dir.get_metadata, file_path="NON_EXISTING_PATH")
 
-    def test_get_metadata_on_none(self):
+    def test_get_metadata(self):
         dir = self.build_dir(path=ABS_DIR_PATH)
-        self.assertEqual(dir.get_metadata(file_path=DIR_FILE_NAME), None)
+        self.assertEqual(dir.get_metadata(file_path=DIR_FILE_NAME), {})
 
     def test_set_and_get_metadata_on_relative_path(self):
         dir = self.build_dir(path=ABS_DIR_PATH)
@@ -1296,26 +1349,26 @@ def test_get_contents_on_show_abs_path_and_recursively(self):
                 show_abs_path=True, recursively=True),
             get_abs_contents(recursively=True))
         
-    def test_iterate_contents(self):
+    def test_traverse(self):
         self.assertEqual(list(self.build_dir(
-            path=ABS_DIR_PATH).iterate_contents()), CONTENTS)
+            path=ABS_DIR_PATH).traverse()), CONTENTS)
 
-    def test_iterate_contents_on_show_abs_path(self):
+    def test_traverse_on_show_abs_path(self):
         self.assertEqual(
             list(self.build_dir(
-                path=ABS_DIR_PATH).iterate_contents(show_abs_path=True)),
+                path=ABS_DIR_PATH).traverse(show_abs_path=True)),
             get_abs_contents(recursively=False))
         
-    def test_iterate_contents_on_recursively(self):
+    def test_traverse_on_recursively(self):
         self.assertEqual(
             list(self.build_dir(
-                path=ABS_DIR_PATH).iterate_contents(recursively=True)),
+                path=ABS_DIR_PATH).traverse(recursively=True)),
             RECURSIVE_CONTENTS)
         
-    def test_iterate_contents_on_show_abs_path_and_recursively(self):
+    def test_traverse_on_show_abs_path_and_recursively(self):
         self.assertEqual(
             list(self.build_dir(
-                path=ABS_DIR_PATH).iterate_contents(show_abs_path=True, recursively=True)),
+                path=ABS_DIR_PATH).traverse(show_abs_path=True, recursively=True)),
             get_abs_contents(recursively=True))
 
     def test_ls(self):
@@ -1329,7 +1382,6 @@ def test_ls(self):
             ls_expected_output = '\n'.join(CONTENTS) + '\n'
             self.assertEqual(stdo.getvalue(), ls_expected_output)
 
-
     def test_ls_on_show_abs_path(self):
         with io.StringIO() as stdo:
             sys.stdout = stdo
@@ -1388,8 +1440,8 @@ def test_transfer_to(self):
             dst=self.build_dir(path=tmp_dir_path))
         # Assert that the two directories contains the same contents
         self.assertEqual(
-            ''.join(s for s in os.listdir(ABS_DIR_PATH) if s.endswith('.txt')),
-            ''.join(s for s in os.listdir(tmp_dir_path)))
+            ''.join(s for s in sorted(os.listdir(ABS_DIR_PATH)) if s.endswith('.txt')),
+            ''.join(s for s in sorted(os.listdir(tmp_dir_path))))
         # Remove temporary directory.
         shutil.rmtree(tmp_dir_path)
 
@@ -1408,7 +1460,7 @@ def test_transfer_to_on_recursively(self):
             ''.join(join_paths(dp, f).replace(
                 f'{SEPARATOR}dir{SEPARATOR}',
                 f'{SEPARATOR}{tmp_dir_name}{SEPARATOR}')
-            for dp, _, fn in os.walk(ABS_DIR_PATH) for f in fn),
+                for dp, _, fn in os.walk(ABS_DIR_PATH) for f in fn),
             ''.join(join_paths(dp, f)
                 for dp, _, fn in os.walk(tmp_dir_path) for f in fn))
         # Remove temporary directory.
@@ -1465,7 +1517,7 @@ def test_transfer_to_on_include_metadata_set_to_false(self):
         # tmp directory without including metadata.
         dir.transfer_to(dst=tmp_dir, include_metadata=False)
         # Assert that no metadata were transfered.
-        self.assertIsNone(tmp_dir.get_metadata(file_path=filename))
+        self.assertEqual(tmp_dir.get_metadata(file_path=filename), {})
         # Remove temporary directory.
         shutil.rmtree(tmp_dir_path)
 
@@ -1505,6 +1557,73 @@ def test_transfer_to_on_cloud_dir_include_metadata_set_to_true(self):
                         aws_dir.get_bucket_name(),
                         REL_DIR_FILE_PATH).metadata,
                     metadata)
+                
+    def test_traverse_files(self):
+        dir = self.build_dir(path=ABS_DIR_PATH)
+        self.assertEqual(
+            ''.join(map(lambda file: file.get_path(), dir.traverse_files())),
+            ''.join(filter(lambda path: not path.endswith('/'), get_abs_contents(recursively=False))))
+
+    def test_traverse_files_on_recursively(self):
+        dir = self.build_dir(path=ABS_DIR_PATH)
+        self.assertEqual(
+            ''.join(map(lambda file: file.get_path(), dir.traverse_files(recursively=True))),
+            ''.join(get_abs_contents(recursively=True)))
+        
+    def test_get_files(self):
+        dir = self.build_dir(path=ABS_DIR_PATH)
+        files = dir.get_files()
+        self.assertEqual(
+            ''.join(map(lambda path: files[path].get_path(), files)),
+            ''.join(filter(lambda path: not path.endswith('/'), get_abs_contents(recursively=False))))
+
+    def test_get_files_on_recursively(self):
+        dir = self.build_dir(path=ABS_DIR_PATH)
+        files = dir.get_files(recursively=True)
+        self.assertEqual(
+            ''.join(map(lambda path: files[path].get_path(), files)),
+            ''.join(get_abs_contents(recursively=True)))
+        
+    def test_get_files_on_show_abs_path(self):
+        dir = self.build_dir(path=ABS_DIR_PATH)
+        files = dir.get_files(show_abs_path=True)
+        self.assertEqual(
+            ''.join(files),
+            ''.join(filter(lambda path: not path.endswith('/'), get_abs_contents(recursively=False))))
+        
+    def test_get_files_on_recursively_and_show_abs_path(self):
+        dir = self.build_dir(path=ABS_DIR_PATH)
+        files = dir.get_files(recursively=True, show_abs_path=True)
+        self.assertEqual(
+            ''.join(files),
+            ''.join(get_abs_contents(recursively=True)))
+        
+    def test_get_file(self):
+        dir = self.build_dir(path=ABS_DIR_PATH)
+        file_path = REL_DIR_FILE_PATH.removeprefix(REL_DIR_PATH)
+        file = dir.get_file(file_path)
+        self.assertEqual(file.get_path(), ABS_DIR_FILE_PATH)
+
+    def test_file_shared_metadata_on_modify_from_dir(self):
+        # Create dir and get file.
+        dir = self.build_dir(path=ABS_DIR_PATH)
+        file_path = REL_DIR_FILE_PATH.removeprefix(REL_DIR_PATH)
+        file = dir.get_file(file_path)
+        # Change metadata via "Dir" API.
+        dir.set_metadata(file_path, METADATA)
+        # Assert file metadata have been changed.
+        self.assertEqual(file.get_metadata(), METADATA)
+
+    def test_file_shared_metadata_on_modify_from_file(self):
+        # Create dir and get file.
+        dir = self.build_dir(path=ABS_DIR_PATH)
+        file_path = REL_DIR_FILE_PATH.removeprefix(REL_DIR_PATH)
+        file = dir.get_file(file_path)
+        # Change metadata via "File" API.
+        file.set_metadata(METADATA)
+        # Assert file metadata have been changed.
+        self.assertEqual(dir.get_metadata(file_path), METADATA)
+        
 
 
 class TestRemoteDir(unittest.TestCase):
@@ -1545,6 +1664,10 @@ def test_constructor_on_empty_path_error(self):
     def test_constructor_on_invalid_path_error(self):
         self.assertRaises(InvalidPathError, self.build_dir, path="NON_EXISTING_PATH")
 
+    def test_get_hostname(self):
+        with self.build_dir() as dir:
+            self.assertEqual(dir.get_hostname(), HOST)
+
     def test_get_name(self):
         with self.build_dir() as dir:
             self.assertEqual(dir.get_name(), DIR_NAME)    
@@ -1560,9 +1683,9 @@ def test_set_and_get_metadata(self):
             dir.set_metadata(file_path=file_path, metadata=metadata)
             self.assertEqual(dir.get_metadata(file_path=file_path), metadata)
 
-    def test_get_metadata_on_none(self):
+    def test_get_metadata(self):
         with self.build_dir() as dir:
-            self.assertEqual(dir.get_metadata(file_path=ABS_DIR_FILE_PATH), None)
+            self.assertEqual(dir.get_metadata(file_path=ABS_DIR_FILE_PATH), {})
 
     def test_get_metadata_on_invalid_file_error(self):
         with self.build_dir() as dir:
@@ -1622,26 +1745,26 @@ def test_get_contents_on_show_abs_path_and_recursively(self):
                 dir.get_contents(show_abs_path=True, recursively=True),
                 get_abs_contents(recursively=True))
             
-    def test_iterate_contents(self):
+    def test_traverse(self):
         with self.build_dir() as dir:
-            self.assertEqual(list(dir.iterate_contents()), CONTENTS)
+            self.assertEqual(list(dir.traverse()), CONTENTS)
 
-    def test_iterate_contents_on_show_abs_path(self):
+    def test_traverse_on_show_abs_path(self):
         with self.build_dir() as dir:
             self.assertEqual(
-                list(dir.iterate_contents(show_abs_path=True)),
+                list(dir.traverse(show_abs_path=True)),
                 get_abs_contents(recursively=False))
         
-    def test_iterate_contents_on_recursively(self):
+    def test_traverse_on_recursively(self):
         with self.build_dir() as dir:
             self.assertEqual(
-                list(dir.iterate_contents(recursively=True)),
+                list(dir.traverse(recursively=True)),
                 RECURSIVE_CONTENTS)
         
-    def test_iterate_contents_on_show_abs_path_and_recursively(self):
+    def test_traverse_on_show_abs_path_and_recursively(self):
         with self.build_dir() as dir:
             self.assertEqual(
-                list(dir.iterate_contents(show_abs_path=True, recursively=True)),
+                list(dir.traverse(show_abs_path=True, recursively=True)),
                 get_abs_contents(recursively=True))
             
     def test_ls(self):
@@ -1726,8 +1849,8 @@ def test_transfer_to(self):
             dir.transfer_to(dst=LocalDir(path=tmp_dir_path))
         # Assert that the two directories contain the same contents.
         self.assertEqual(
-            ''.join(s for s in os.listdir(REL_DIR_PATH) if s.endswith('.txt')),
-            ''.join(s for s in os.listdir(tmp_dir_path)))
+            ''.join(s for s in sorted(os.listdir(REL_DIR_PATH)) if s.endswith('.txt')),
+            ''.join(s for s in sorted(os.listdir(tmp_dir_path))))
         # Remove temporary directory.
         shutil.rmtree(tmp_dir_path)
 
@@ -1806,8 +1929,8 @@ def test_transfer_to_on_include_metadata_set_to_false(self):
             # tmp directory without including metadata.
             dir.transfer_to(dst=tmp_dir, include_metadata=False)
         # Assert that no metadata have been transfered.
-        self.assertIsNone(tmp_dir.get_metadata(
-            file_path=filename.replace(ABS_DIR_PATH, '')))
+        self.assertEqual(tmp_dir.get_metadata(
+            file_path=filename.replace(ABS_DIR_PATH, '')), {})
         # Remove temporary directory.
         shutil.rmtree(tmp_dir_path)
 
@@ -1853,7 +1976,7 @@ def test_transfer_to_on_cloud_dir_include_metadata_set_to_true(self):
                 
     def test_transfer_to_as_destination(self):
         # Create a "remote" temporary dictionary.
-        tmp_dir_path = ABS_DIR_PATH.replace(DIR_NAME, 'TMP_DIR')
+        tmp_dir_path = REL_DIR_PATH.replace(DIR_NAME, 'TMP_DIR')
         os.mkdir(tmp_dir_path)
         # Copy the local dir's contents into this
         # "remote" tmp directory.
@@ -1861,10 +1984,76 @@ def test_transfer_to_as_destination(self):
             TestLocalDir.build_dir(ABS_DIR_PATH).transfer_to(dir)
         # Assert that the two directories contain the same contents.
         self.assertEqual(
-            ''.join(s for s in os.listdir(ABS_DIR_PATH) if s.endswith('.txt')),
-            ''.join(s for s in os.listdir(tmp_dir_path)))
+            ''.join(s for s in sorted(os.listdir(ABS_DIR_PATH)) if s.endswith('.txt')),
+            ''.join(s for s in sorted(os.listdir(tmp_dir_path))))
         # Remove temporary directory.
         shutil.rmtree(tmp_dir_path)
+
+    def test_traverse_files(self):
+        with self.build_dir() as dir:
+            self.assertEqual(
+                ''.join(map(lambda file: file.get_path(), dir.traverse_files())),
+                ''.join(filter(lambda path: not path.endswith('/'), get_abs_contents(recursively=False))))
+
+    def test_traverse_files_on_recursively(self):
+        with self.build_dir() as dir:
+            self.assertEqual(
+                ''.join(map(lambda file: file.get_path(), dir.traverse_files(recursively=True))),
+                ''.join(get_abs_contents(recursively=True)))
+        
+    def test_get_files(self):
+        with self.build_dir() as dir:
+            files = dir.get_files()
+            self.assertEqual(
+                ''.join(map(lambda path: files[path].get_path(), files)),
+                ''.join(filter(lambda path: not path.endswith('/'), get_abs_contents(recursively=False))))
+
+    def test_get_files_on_recursively(self):
+        with self.build_dir() as dir:
+            files = dir.get_files(recursively=True)
+            self.assertEqual(
+                ''.join(map(lambda path: files[path].get_path(), files)),
+                ''.join(get_abs_contents(recursively=True)))
+        
+    def test_get_files_on_show_abs_path(self):
+        with self.build_dir() as dir:
+            files = dir.get_files(show_abs_path=True)
+            self.assertEqual(
+                ''.join(files),
+                ''.join(filter(lambda path: not path.endswith('/'), get_abs_contents(recursively=False))))
+        
+    def test_get_files_on_recursively_and_show_abs_path(self):
+        with self.build_dir() as dir:
+            files = dir.get_files(recursively=True, show_abs_path=True)
+            self.assertEqual(
+                ''.join(files),
+                ''.join(get_abs_contents(recursively=True)))
+        
+    def test_get_file(self):
+        with self.build_dir() as dir:
+            file_path = REL_DIR_FILE_PATH.removeprefix(REL_DIR_PATH)
+            file = dir.get_file(file_path)
+            self.assertEqual(file.get_path(), ABS_DIR_FILE_PATH)
+
+    def test_file_shared_metadata_on_modify_from_dir(self):
+        with self.build_dir() as dir:
+            # Access file via dir.
+            file_path = REL_DIR_FILE_PATH.removeprefix(REL_DIR_PATH)
+            file = dir.get_file(file_path)
+            # Change metadata via "Dir" API.
+            dir.set_metadata(file_path, METADATA)
+            # Assert file metadata have been changed.
+            self.assertEqual(file.get_metadata(), METADATA)
+
+    def test_file_shared_metadata_on_modify_from_file(self):
+        with self.build_dir() as dir:
+            # Access file via dir.
+            file_path = REL_DIR_FILE_PATH.removeprefix(REL_DIR_PATH)
+            file = dir.get_file(file_path)
+            # Change metadata via "File" API.
+            file.set_metadata(METADATA)
+            # Assert file metadata have been changed.
+            self.assertEqual(dir.get_metadata(file_path), METADATA)
         
     '''
     Test connection methods.
@@ -1873,13 +2062,13 @@ def test_open(self):
         dir = self.build_dir()
         dir.close()
         dir.open()
-        self.assertIsNotNone(dir._get_client())
+        self.assertTrue(dir._get_handler().is_open())
         dir.close()
 
     def test_close(self):
         dir = self.build_dir()
         dir.close()
-        self.assertIsNone(dir._get_client())
+        self.assertFalse(dir._get_handler().is_open())
 
     '''
     Test cache methods.
@@ -1908,75 +2097,62 @@ def test_purge(self):
             normal_time = time.time() - t
             self.assertGreater(normal_time, cache_time)
 
-    def test_iterate_contents_from_cache_on_value(self):
+    def test_traverse_from_cache_on_value(self):
         with self.build_dir(cache=True) as dir:
-            _ = (_ for _ in dir.iterate_contents())
+            _ = (_ for _ in dir.traverse())
             self.assertEqual(
-                ''.join([p for p in dir.iterate_contents()]),
+                ''.join([p for p in dir.traverse()]),
                 ''.join(CONTENTS))
             
-    def test_iterate_contents_recursively_from_cache_on_value(self):
+    def test_traverse_recursively_from_cache_on_value(self):
         with self.build_dir(cache=True) as dir:
-            _ = (_ for _ in dir.iterate_contents(recursively=True))
+            _ = (_ for _ in dir.traverse(recursively=True))
+            expected_results = []
+            for dp, dn, fn in os.walk(REL_DIR_PATH):
+                dn.sort()
+                for f in sorted(fn):
+                    expected_results.append(
+                        join_paths(dp, f).removeprefix(REL_DIR_PATH))
             self.assertEqual(
-                ''.join([p for p in dir.iterate_contents(recursively=True)]),
-                ''.join(join_paths(dp, f).removeprefix(REL_DIR_PATH)
-                    for dp, _, fn in os.walk(REL_DIR_PATH) for f in fn))
-            
-    def test_iterate_contents_non_recursively_from_cache_on_none(self):
-        with self.build_dir(cache=True) as dir:
-            _ = (_ for _ in dir.iterate_contents(recursively=True))
-            self.assertEqual(
-                ''.join(dir._get_cache_manager().iterate_contents(
-                    recursively=False,
-                    include_dirs=True)),
-                '')
-            
-    def test_iterate_contents_from_cache_on_time(self):
+                ''.join([p for p in dir.traverse(recursively=True)]),
+                ''.join(expected_results))
+
+    def test_traverse_from_cache_on_time(self):
         with self.build_dir(cache=True) as dir:
             # Fetch contents via HTTP.
             t = time.time()
-            _ = (_ for _ in dir.iterate_contents())
+            _ = (_ for _ in dir.traverse())
             normal_time = time.time() - t
             # Fetch contents from cache.
             t = time.time()
-            _ = (_ for _ in dir.iterate_contents())
+            _ = (_ for _ in dir.traverse())
             cache_time = time.time() - t
             # Compare fetch times.
             self.assertGreater(normal_time, cache_time)
 
-    def test_iterate_contents_recursively_from_cache_on_time(self):
+    def test_traverse_recursively_from_cache_on_time(self):
         with self.build_dir(cache=True) as dir:
             # Fetch contents via HTTP.
             t = time.time()
-            _ = (_ for _ in dir.iterate_contents())
+            _ = (_ for _ in dir.traverse())
             normal_time = time.time() - t
             # Fetch contents from cache.
             t = time.time()
-            _ = (_ for _ in dir.iterate_contents())
+            _ = (_ for _ in dir.traverse())
             cache_time = time.time() - t
             # Compare fetch times.
             self.assertGreater(normal_time, cache_time)
 
     def test_get_size_from_cache_on_value(self):
         with self.build_dir(cache=True) as dir:
-            dir.get_size()
-            self.assertEqual(
-                dir._get_cache_manager().get_size(ABS_DIR_FILE_PATH), 4)
+            _ = dir.get_size()
+            self.assertEqual(dir.get_size(), 4)
 
     def test_get_size_recursively_from_cache_on_value(self):
         with self.build_dir(cache=True) as dir:
             _ = dir.get_size(recursively=True)
             self.assertEqual(
-                dir._get_cache_manager().get_size(
-                    f"{ABS_DIR_PATH}subdir/file4.txt"), 4)
-            
-    def test_get_size_non_recursively_from_cache_on_none(self):
-        with self.build_dir(cache=True) as dir:
-            _ = dir.get_size()
-            self.assertIsNone(
-                dir._get_cache_manager().get_size(
-                    f"{ABS_DIR_PATH}subdir/file4.txt"))
+                dir.get_size(recursively=True), 16)
 
     def test_get_size_from_cache_on_time(self):
         with self.build_dir(cache=True) as dir:
@@ -2004,6 +2180,51 @@ def test_get_size_recursively_from_cache_on_time(self):
             # Compare fetch times.
             self.assertGreater(normal_time, cache_time)
 
+    def test_file_shared_cache_on_cache_via_dir(self):
+        with (
+            self.build_dir(cache=False) as no_cache_dir,
+            self.build_dir(cache=True) as cache_dir
+        ):
+            # Access file via both dirs.
+            file_path = REL_DIR_FILE_PATH.removeprefix(REL_DIR_PATH)
+            no_cache_file = no_cache_dir.get_file(file_path)
+            cache_file = cache_dir.get_file(file_path)
+            # Count total size for both dirs.
+            _ = no_cache_dir.get_size()
+            _ = cache_dir.get_size()
+            # Time no-cache-file's "get_size"
+            t = time.time()
+            _ = no_cache_file.get_size()
+            normal_time = time.time() - t
+            # Time cache-file's "get_size"
+            t = time.time()
+            _ = cache_file.get_size()
+            cache_time = time.time() - t
+            # Compare fetch times.
+            self.assertGreater(normal_time, cache_time)
+
+    def test_file_shared_cache_on_cache_via_file(self):
+        with (
+            self.build_dir(cache=False) as no_cache_dir,
+            self.build_dir(cache=True) as cache_dir
+        ):
+            # Count size of files via both dirs using the "File" API.
+            file_path = REL_DIR_FILE_PATH.removeprefix(REL_DIR_PATH)
+            for file in no_cache_dir.get_files().values():
+                _ = file.get_size()
+            for file in cache_dir.get_files().values():
+                _ = file.get_size()
+            # Time no-cache-dir's "get_size"
+            t = time.time()
+            _ = no_cache_dir.get_size()
+            normal_time = time.time() - t
+            # Time cache-dir's "get_size"
+            t = time.time()
+            _ = cache_dir.get_size()
+            cache_time = time.time() - t
+            # Compare fetch times.
+            self.assertGreater(normal_time, cache_time)
+
 
 class TestAWSS3Dir(unittest.TestCase):
 
@@ -2030,8 +2251,31 @@ def iterate_aws_s3_dir_objects(
     def setUp(self):
         create_aws_s3_bucket(self.MOCK_S3, BUCKET, METADATA)
 
+        from fluke._handlers import AWSClientHandler
+
+        m1 = patch.object(AWSClientHandler, '_get_file_size_impl', autospec=True)
+        m2 = patch.object(AWSClientHandler, '_get_file_metadata_impl', autospec=True)
+        m3 = patch.object(AWSClientHandler, '_traverse_dir_impl', autospec=True)
+
+        def simulate_latency_1(*args, **kwargs):
+            time.sleep(0.2)
+            return m1.temp_original(*args, **kwargs)
+        
+        def simulate_latency_2(*args, **kwargs):
+            time.sleep(0.2)
+            return m2.temp_original(*args, **kwargs)
+        
+        def simulate_latency_3(*args, **kwargs):
+            time.sleep(0.2)
+            return m3.temp_original(*args, **kwargs)
+
+        m1.start().side_effect = simulate_latency_1
+        m2.start().side_effect = simulate_latency_2
+        m3.start().side_effect = simulate_latency_3
+
     def tearDown(self):
         self.MOCK_S3.stop()
+        patch.stopall()
     
     @staticmethod
     def build_dir(
@@ -2064,25 +2308,29 @@ def test_constructor_on_create_if_missing(self):
     def test_constructor_on_invalid_path_error(self):
         self.assertRaises(InvalidPathError, self.build_dir, path="NON_EXISTING_PATH")
 
-    def test_get_path_on_empty_path(self):
-        with self.build_dir(path='') as dir:
+    def test_get_path_on_none_path(self):
+        with self.build_dir(path=None) as dir:
             self.assertEqual(dir.get_path(), '')  
 
     def test_get_name(self):
         with self.build_dir() as dir:
             self.assertEqual(dir.get_name(), DIR_NAME)
 
-    def test_get_name_on_empty_path(self):
-        with self.build_dir(path='') as dir:
+    def test_get_name_on_none_path(self):
+        with self.build_dir(path=None) as dir:
             self.assertIsNone(dir.get_name())      
 
     def test_get_uri(self):
         with self.build_dir() as dir:
             self.assertEqual(dir.get_uri(), f"s3://{BUCKET}/{REL_DIR_PATH}")
 
-    def test_get_metadata_on_none(self):
+    def test_get_bucket_name(self):
         with self.build_dir() as dir:
-            self.assertEqual(dir.get_metadata(file_path='file2.txt'), None)
+            self.assertEqual(dir.get_bucket_name(), BUCKET)
+
+    def test_get_metadata(self):
+        with self.build_dir() as dir:
+            self.assertEqual(dir.get_metadata(file_path='file2.txt'), {})
 
     def test_get_metadata_on_invalid_file_error(self):
         with self.build_dir() as dir:
@@ -2185,26 +2433,26 @@ def test_get_contents_on_show_abs_path_and_recursively(self):
                 dir.get_contents(show_abs_path=True, recursively=True),
                 self.get_abs_contents(recursively=True))
             
-    def test_iterate_contents(self):
+    def test_traverse(self):
         with self.build_dir() as dir:
-            self.assertEqual(list(dir.iterate_contents()), CONTENTS)
+            self.assertEqual(list(dir.traverse()), CONTENTS)
 
-    def test_iterate_contents_on_show_abs_path(self):
+    def test_traverse_on_show_abs_path(self):
         with self.build_dir() as dir:
             self.assertEqual(
-                list(dir.iterate_contents(show_abs_path=True)),
+                list(dir.traverse(show_abs_path=True)),
                 self.get_abs_contents(recursively=False))
         
-    def test_iterate_contents_on_recursively(self):
+    def test_traverse_on_recursively(self):
         with self.build_dir() as dir:
             self.assertEqual(
-                list(dir.iterate_contents(recursively=True)),
+                list(dir.traverse(recursively=True)),
                 RECURSIVE_CONTENTS)
         
-    def test_iterate_contents_on_show_abs_path_and_recursively(self):
+    def test_traverse_on_show_abs_path_and_recursively(self):
         with self.build_dir() as dir:
             self.assertEqual(
-                list(dir.iterate_contents(show_abs_path=True, recursively=True)),
+                list(dir.traverse(show_abs_path=True, recursively=True)),
                 self.get_abs_contents(recursively=True))
             
     def test_ls(self):
@@ -2291,8 +2539,8 @@ def test_transfer_to(self):
             dir.transfer_to(dst=TestLocalDir.build_dir(path=tmp_dir_path))
         # Assert that the two directories contain the same contents.
         self.assertEqual(
-            ''.join(s for s in self.iterate_aws_s3_dir_objects()),
-            ''.join(s for s in os.listdir(tmp_dir_path)))
+            ''.join(s for s in sorted(self.iterate_aws_s3_dir_objects())),
+            ''.join(s for s in sorted(os.listdir(tmp_dir_path))))
         # Remove temporary directory.
         shutil.rmtree(tmp_dir_path)
 
@@ -2306,10 +2554,15 @@ def test_transfer_to_on_recursively(self):
                 dst=TestLocalDir.build_dir(path=tmp_dir_path),
                 recursively=True)
         # Assert that the two directories contain the same contents.
+            expected_results = []
+            for dp, dn, fn in os.walk(tmp_dir_path):
+                dn.sort()
+                for f in sorted(fn):
+                    expected_results.append(
+                        join_paths(dp, f).removeprefix(tmp_dir_path))
         self.assertEqual(
             ''.join(s for s in self.iterate_aws_s3_dir_objects(recursively=True)),
-            ''.join(join_paths(dp, f).removeprefix(tmp_dir_path)
-                for dp, _, fn in os.walk(tmp_dir_path) for f in fn))
+            ''.join(expected_results))
         # Remove temporary directory.
         shutil.rmtree(tmp_dir_path)
 
@@ -2370,7 +2623,7 @@ def test_transfer_to_on_include_metadata_set_to_false(self):
             # tmp directory without including metadata.
             dir.transfer_to(dst=tmp_dir, include_metadata=False)
         # Assert that no metadata have been transfered.
-        self.assertIsNone(tmp_dir.get_metadata(file_path=filename))
+        self.assertEqual(tmp_dir.get_metadata(file_path=filename), {})
         # Remove temporary directory.
         shutil.rmtree(tmp_dir_path)
 
@@ -2423,6 +2676,74 @@ def test_transfer_to_on_include_metadata_to_cloud_dir(self):
             get_aws_s3_object(BUCKET, REL_DIR_FILE_PATH).metadata,
             metadata)
         
+    def test_traverse_files(self):
+        with self.build_dir() as dir:
+            self.assertEqual(
+                ''.join(map(lambda file: file.get_path(), dir.traverse_files())),
+                ''.join(filter(lambda path: not path.endswith('/'), self.get_abs_contents(recursively=False))))
+
+    def test_traverse_files_on_recursively(self):
+        with self.build_dir() as dir:
+            self.assertEqual(
+                ''.join(map(lambda file: file.get_path(), dir.traverse_files(recursively=True))),
+                ''.join(self.get_abs_contents(recursively=True)))
+        
+    def test_get_files(self):
+        with self.build_dir() as dir:
+            files = dir.get_files()
+            self.assertEqual(
+                ''.join(map(lambda path: files[path].get_path(), files)),
+                ''.join(filter(lambda path: not path.endswith('/'),
+                    self.get_abs_contents(recursively=False))))
+
+    def test_get_files_on_recursively(self):
+        with self.build_dir() as dir:
+            files = dir.get_files(recursively=True)
+            self.assertEqual(
+                ''.join(map(lambda path: files[path].get_path(), files)),
+                ''.join(self.get_abs_contents(recursively=True)))
+        
+    def test_get_files_on_show_abs_path(self):
+        with self.build_dir() as dir:
+            files = dir.get_files(show_abs_path=True)
+            self.assertEqual(
+                ''.join(files),
+                ''.join(filter(lambda path: not path.endswith('/'),
+                    self.get_abs_contents(recursively=False))))
+        
+    def test_get_files_on_recursively_and_show_abs_path(self):
+        with self.build_dir() as dir:
+            files = dir.get_files(recursively=True, show_abs_path=True)
+            self.assertEqual(
+                ''.join(files),
+                ''.join(self.get_abs_contents(recursively=True)))
+        
+    def test_get_file(self):
+        with self.build_dir() as dir:
+            file_path = REL_DIR_FILE_PATH.removeprefix(REL_DIR_PATH)
+            file = dir.get_file(file_path)
+            self.assertEqual(file.get_path(), REL_DIR_FILE_PATH)
+
+    def test_file_shared_metadata_on_modify_from_dir(self):
+        with self.build_dir() as dir:
+            # Access file via dir.
+            file_path = REL_DIR_FILE_PATH.removeprefix(REL_DIR_PATH)
+            file = dir.get_file(file_path)
+            # Change metadata via "Dir" API.
+            dir.set_metadata(file_path, METADATA)
+            # Assert file metadata have been changed.
+            self.assertEqual(file.get_metadata(), METADATA)
+
+    def test_file_shared_metadata_on_modify_from_file(self):
+        with self.build_dir() as dir:
+            # Access file via dir.
+            file_path = REL_DIR_FILE_PATH.removeprefix(REL_DIR_PATH)
+            file = dir.get_file(file_path)
+            # Change metadata via "File" API.
+            file.set_metadata(METADATA)
+            # Assert file metadata have been changed.
+            self.assertEqual(dir.get_metadata(file_path), METADATA)
+        
     '''
     Test connection methods.
     '''
@@ -2430,13 +2751,13 @@ def test_open(self):
         dir = self.build_dir()
         dir.close()
         dir.open()
-        self.assertIsNotNone(dir._get_client())
+        self.assertTrue(dir._get_handler().is_open())
         dir.close()
 
     def test_close(self):
         dir = self.build_dir()
         dir.close()
-        self.assertIsNone(dir._get_client())
+        self.assertFalse(dir._get_handler().is_open())
 
     '''
     Test cache methods.
@@ -2465,50 +2786,46 @@ def test_purge(self):
             normal_time = time.time() - t
             self.assertGreater(normal_time, cache_time)
 
-    def test_iterate_contents_from_cache_on_value(self):
+    def test_traverse_from_cache_on_value(self):
         with self.build_dir(cache=True) as dir:
-            _ = (_ for _ in dir.iterate_contents())
-            self.assertEqual(list(dir.iterate_contents()), CONTENTS)
+            _ = (_ for _ in dir.traverse())
+            self.assertEqual(list(dir.traverse()), CONTENTS)
             
-    def test_iterate_contents_recursively_from_cache_on_value(self):
+    def test_traverse_recursively_from_cache_on_value(self):
         with self.build_dir(cache=True) as dir:
-            _ = (_ for _ in dir.iterate_contents(recursively=True))
+            _ = (_ for _ in dir.traverse(recursively=True))
+            expected_results = []
+            for dp, dn, fn in os.walk(REL_DIR_PATH):
+                dn.sort()
+                for f in sorted(fn):
+                    expected_results.append(
+                        join_paths(dp, f).removeprefix(REL_DIR_PATH))
             self.assertEqual(
-                ''.join([p for p in dir.iterate_contents(recursively=True)]),
-                ''.join(join_paths(dp, f).removeprefix(REL_DIR_PATH)
-                    for dp, _, fn in os.walk(REL_DIR_PATH) for f in fn))
+                ''.join([p for p in dir.traverse(recursively=True)]),
+                ''.join(expected_results))
             
-    def test_iterate_contents_non_recursively_from_cache_on_none(self):
-        with self.build_dir(cache=True) as dir:
-            _ = (_ for _ in dir.iterate_contents(recursively=True))
-            self.assertEqual(
-                ''.join(dir._get_cache_manager().iterate_contents(
-                    recursively=False,
-                    include_dirs=True)),
-                '')
-            
-    def test_iterate_contents_from_cache_on_time(self):
+    def test_traverse_from_cache_on_time(self):
         with self.build_dir(cache=True) as dir:
             # Fetch contents via HTTP.
             t = time.time()
-            _ = (_ for _ in dir.iterate_contents())
+            _ = (_ for _ in dir.traverse())
             normal_time = time.time() - t
             # Fetch contents from cache.
             t = time.time()
-            _ = (_ for _ in dir.iterate_contents())
+            _ = (_ for _ in dir.traverse())
             cache_time = time.time() - t
             # Compare fetch times.
             self.assertGreater(normal_time, cache_time)
 
-    def test_iterate_contents_recursively_from_cache_on_time(self):
+    def test_traverse_recursively_from_cache_on_time(self):
         with self.build_dir(cache=True) as dir:
             # Fetch contents via HTTP.
             t = time.time()
-            _ = (_ for _ in dir.iterate_contents())
+            _ = (_ for _ in dir.traverse())
             normal_time = time.time() - t
             # Fetch contents from cache.
             t = time.time()
-            _ = (_ for _ in dir.iterate_contents())
+            _ = (_ for _ in dir.traverse())
             cache_time = time.time() - t
             # Compare fetch times.
             self.assertGreater(normal_time, cache_time)
@@ -2518,40 +2835,26 @@ def test_load_metadata_from_cache_on_value(self):
         with self.build_dir(cache=True) as dir:
             dir.load_metadata()
             self.assertEqual(
-                dir._get_cache_manager().get_metadata(REL_DIR_FILE_PATH),
+                dir.get_metadata(REL_DIR_FILE_PATH),
                 METADATA)
 
     def test_get_size_from_cache_on_value(self):
         with self.build_dir(cache=True) as dir:
-            dir.get_size()
-            self.assertEqual(
-                dir._get_cache_manager().get_size(REL_DIR_FILE_PATH), 4)
+            _ = dir.get_size()
+            self.assertEqual(dir.get_size(), 4)
             
     def test_load_metadata_recursively_from_cache_on_value(self):
         with self.build_dir(cache=True) as dir:
             dir.load_metadata(recursively=True)
             self.assertEqual(
-                dir._get_cache_manager().get_metadata(f"{REL_DIR_PATH}subdir/file4.txt"),
+                dir.get_metadata(f"{REL_DIR_PATH}subdir/file4.txt"),
                 METADATA)
 
     def test_get_size_recursively_from_cache_on_value(self):
         with self.build_dir(cache=True) as dir:
             _ = dir.get_size(recursively=True)
-            self.assertEqual(
-                dir._get_cache_manager().get_size(f"{REL_DIR_PATH}subdir/file4.txt"), 4)
-            
-    def test_load_metadata_non_recursively_from_cache_on_none(self):
-        with self.build_dir(cache=True) as dir:
-            dir.load_metadata()
-            self.assertIsNone(
-                dir._get_cache_manager().get_metadata(f"{REL_DIR_PATH}subdir/file4.txt"))
-            
-    def test_get_size_non_recursively_from_cache_on_none(self):
-        with self.build_dir(cache=True) as dir:
-            _ = dir.get_size()
-            self.assertIsNone(
-                dir._get_cache_manager().get_size(f"{REL_DIR_PATH}subdir/file4.txt"))
-            
+            self.assertEqual(dir.get_size(recursively=True), 16)
+
     def test_load_metadata_from_cache_on_time(self):
         with self.build_dir(cache=True) as dir:
             # Fetch object metadata via HTTP.
@@ -2604,6 +2907,51 @@ def test_get_size_recursively_from_cache_on_time(self):
             # Compare fetch times.
             self.assertGreater(normal_time, cache_time)
 
+    def test_file_shared_cache_on_cache_via_dir(self):
+        with (
+            self.build_dir(cache=False) as no_cache_dir,
+            self.build_dir(cache=True) as cache_dir
+        ):
+            # Access file via both dirs.
+            file_path = REL_DIR_FILE_PATH.removeprefix(REL_DIR_PATH)
+            no_cache_file = no_cache_dir.get_file(file_path)
+            cache_file = cache_dir.get_file(file_path)
+            # Count total size for both dirs.
+            _ = no_cache_dir.get_size()
+            _ = cache_dir.get_size()
+            # Time no-cache-file's "get_size"
+            t = time.time()
+            _ = no_cache_file.get_size()
+            normal_time = time.time() - t
+            # Time cache-file's "get_size"
+            t = time.time()
+            _ = cache_file.get_size()
+            cache_time = time.time() - t
+            # Compare fetch times.
+            self.assertGreater(normal_time, cache_time)
+
+    def test_file_shared_cache_on_cache_via_file(self):
+        with (
+            self.build_dir(cache=False) as no_cache_dir,
+            self.build_dir(cache=True) as cache_dir
+        ):
+            # Count size of files via both dirs using the "File" API.
+            file_path = REL_DIR_FILE_PATH.removeprefix(REL_DIR_PATH)
+            for file in no_cache_dir.get_files().values():
+                _ = file.get_size()
+            for file in cache_dir.get_files().values():
+                _ = file.get_size()
+            # Time no-cache-dir's "get_size"
+            t = time.time()
+            _ = no_cache_dir.get_size()
+            normal_time = time.time() - t
+            # Time cache-dir's "get_size"
+            t = time.time()
+            _ = cache_dir.get_size()
+            cache_time = time.time() - t
+            # Compare fetch times.
+            self.assertGreater(normal_time, cache_time)
+
 
 class TestAzureBlobDir(unittest.TestCase):
 
@@ -2651,16 +2999,16 @@ def test_constructor_on_create_if_missing(self):
     def test_constructor_on_invalid_path_error(self):
         self.assertRaises(InvalidPathError, self.build_dir, path="NON_EXISTING_PATH")
 
-    def test_get_path_on_empty_path(self):
-        with self.build_dir(path='') as dir:
+    def test_get_path_on_none_path(self):
+        with self.build_dir(path=None) as dir:
             self.assertEqual(dir.get_path(), SEPARATOR)  
 
     def test_get_name(self):
         with self.build_dir() as dir:
             self.assertEqual(dir.get_name(), DIR_NAME)
 
-    def test_get_name_on_empty_path(self):
-        with self.build_dir(path='') as dir:
+    def test_get_name_on_none_path(self):
+        with self.build_dir(path=None) as dir:
             self.assertIsNone(dir.get_name())
 
     def test_get_container_name(self):
@@ -2673,9 +3021,9 @@ def test_get_uri(self):
             uri += f".dfs.core.windows.net/{REL_DIR_PATH}"
             self.assertEqual(dir.get_uri(), uri)
 
-    def test_get_metadata_on_none(self):
+    def test_get_metadata(self):
         with self.build_dir() as dir:
-            self.assertEqual(dir.get_metadata(file_path='file2.txt'), None)
+            self.assertEqual(dir.get_metadata(file_path='file2.txt'), {})
 
     def test_get_metadata_on_invalid_file_error(self):
         with self.build_dir() as dir:
@@ -2776,26 +3124,26 @@ def test_get_contents_on_show_abs_path_and_recursively(self):
                 dir.get_contents(show_abs_path=True, recursively=True),
                 self.get_abs_contents(recursively=True))
             
-    def test_iterate_contents(self):
+    def test_traverse(self):
         with self.build_dir() as dir:
-            self.assertEqual(list(dir.iterate_contents()), CONTENTS)
+            self.assertEqual(list(dir.traverse()), CONTENTS)
 
-    def test_iterate_contents_on_show_abs_path(self):
+    def test_traverse_on_show_abs_path(self):
         with self.build_dir() as dir:
             self.assertEqual(
-                list(dir.iterate_contents(show_abs_path=True)),
+                list(dir.traverse(show_abs_path=True)),
                 self.get_abs_contents(recursively=False))
         
-    def test_iterate_contents_on_recursively(self):
+    def test_traverse_on_recursively(self):
         with self.build_dir() as dir:
             self.assertEqual(
-                list(dir.iterate_contents(recursively=True)),
+                list(dir.traverse(recursively=True)),
                 RECURSIVE_CONTENTS)
         
-    def test_iterate_contents_on_show_abs_path_and_recursively(self):
+    def test_traverse_on_show_abs_path_and_recursively(self):
         with self.build_dir() as dir:
             self.assertEqual(
-                list(dir.iterate_contents(show_abs_path=True, recursively=True)),
+                list(dir.traverse(show_abs_path=True, recursively=True)),
                 self.get_abs_contents(recursively=True))
             
     def test_ls(self):
@@ -2881,8 +3229,8 @@ def test_transfer_to(self):
             dir.transfer_to(dst=LocalDir(path=tmp_dir_path))
         # Assert that the two directories contain the same contents.
         self.assertEqual(
-            ''.join(s for s in os.listdir(REL_DIR_PATH) if s.endswith('.txt')),
-            ''.join(s for s in os.listdir(tmp_dir_path)))
+            ''.join(s for s in sorted(os.listdir(REL_DIR_PATH)) if s.endswith('.txt')),
+            ''.join(s for s in sorted(os.listdir(tmp_dir_path))))
         # Remove temporary directory.
         shutil.rmtree(tmp_dir_path)
 
@@ -2961,7 +3309,7 @@ def test_transfer_to_on_include_metadata_set_to_false(self):
             # tmp directory without including metadata.
             dir.transfer_to(dst=tmp_dir, include_metadata=False)
         # Assert that no metadata have been transfered.
-        self.assertIsNone(tmp_dir.get_metadata(file_path=filename))
+        self.assertEqual(tmp_dir.get_metadata(file_path=filename), {})
         # Remove temporary directory.
         shutil.rmtree(tmp_dir_path)
 
@@ -3021,7 +3369,7 @@ def test_transfer_to_on_include_metadata_to_cloud_dir(self):
                 
     def test_transfer_to_as_destination(self):
         # Create a "remote" temporary dictionary.
-        tmp_dir_path = ABS_DIR_PATH.replace(DIR_NAME, 'TMP_DIR')
+        tmp_dir_path = REL_DIR_PATH.replace(DIR_NAME, 'TMP_DIR')
         os.mkdir(tmp_dir_path)
         # Copy the local dir's contents into this
         # "remote" tmp directory.
@@ -3029,10 +3377,78 @@ def test_transfer_to_as_destination(self):
             TestLocalDir.build_dir(REL_DIR_PATH).transfer_to(dir)
         # Assert that the two directories contain the same contents.
         self.assertEqual(
-            ''.join(s for s in os.listdir(REL_DIR_PATH) if s.endswith('.txt')),
-            ''.join(s for s in os.listdir(tmp_dir_path)))
+            ''.join(s for s in sorted(os.listdir(REL_DIR_PATH)) if s.endswith('.txt')),
+            ''.join(s for s in sorted(os.listdir(tmp_dir_path))))
         # Remove temporary directory.
         shutil.rmtree(tmp_dir_path)
+
+    def test_traverse_files(self):
+        with self.build_dir() as dir:
+            self.assertEqual(
+                ''.join(map(lambda file: file.get_path(), dir.traverse_files())),
+                ''.join(filter(lambda path: not path.endswith('/'), self.get_abs_contents(recursively=False))))
+
+    def test_traverse_files_on_recursively(self):
+        with self.build_dir() as dir:
+            self.assertEqual(
+                ''.join(map(lambda file: file.get_path(), dir.traverse_files(recursively=True))),
+                ''.join(self.get_abs_contents(recursively=True)))
+        
+    def test_get_files(self):
+        with self.build_dir() as dir:
+            files = dir.get_files()
+            self.assertEqual(
+                ''.join(map(lambda path: files[path].get_path(), files)),
+                ''.join(filter(lambda path: not path.endswith('/'),
+                    self.get_abs_contents(recursively=False))))
+
+    def test_get_files_on_recursively(self):
+        with self.build_dir() as dir:
+            files = dir.get_files(recursively=True)
+            self.assertEqual(
+                ''.join(map(lambda path: files[path].get_path(), files)),
+                ''.join(self.get_abs_contents(recursively=True)))
+        
+    def test_get_files_on_show_abs_path(self):
+        with self.build_dir() as dir:
+            files = dir.get_files(show_abs_path=True)
+            self.assertEqual(
+                ''.join(files),
+                ''.join(filter(lambda path: not path.endswith('/'),
+                    self.get_abs_contents(recursively=False))))
+        
+    def test_get_files_on_recursively_and_show_abs_path(self):
+        with self.build_dir() as dir:
+            files = dir.get_files(recursively=True, show_abs_path=True)
+            self.assertEqual(
+                ''.join(files),
+                ''.join(self.get_abs_contents(recursively=True)))
+        
+    def test_get_file(self):
+        with self.build_dir() as dir:
+            file_path = REL_DIR_FILE_PATH.removeprefix(REL_DIR_PATH)
+            file = dir.get_file(file_path)
+            self.assertEqual(file.get_path(), REL_DIR_FILE_PATH)
+            
+    def test_file_shared_metadata_on_modify_from_dir(self):
+        with self.build_dir() as dir:
+            # Access file via dir.
+            file_path = REL_DIR_FILE_PATH.removeprefix(REL_DIR_PATH)
+            file = dir.get_file(file_path)
+            # Change metadata via "Dir" API.
+            dir.set_metadata(file_path, METADATA)
+            # Assert file metadata have been changed.
+            self.assertEqual(file.get_metadata(), METADATA)
+
+    def test_file_shared_metadata_on_modify_from_file(self):
+        with self.build_dir() as dir:
+            # Access file via dir.
+            file_path = REL_DIR_FILE_PATH.removeprefix(REL_DIR_PATH)
+            file = dir.get_file(file_path)
+            # Change metadata via "File" API.
+            file.set_metadata(METADATA)
+            # Assert file metadata have been changed.
+            self.assertEqual(dir.get_metadata(file_path), METADATA)
         
     '''
     Test connection methods.
@@ -3041,13 +3457,13 @@ def test_open(self):
         dir = self.build_dir()
         dir.close()
         dir.open()
-        self.assertIsNotNone(dir._get_client())
+        self.assertTrue(dir._get_handler().is_open())
         dir.close()
 
     def test_close(self):
         dir = self.build_dir()
         dir.close()
-        self.assertIsNone(dir._get_client())
+        self.assertFalse(dir._get_handler().is_open())
 
     '''
     Test cache methods.
@@ -3076,52 +3492,48 @@ def test_purge(self):
             normal_time = time.time() - t
             self.assertGreater(normal_time, cache_time)
 
-    def test_iterate_contents_from_cache_on_value(self):
+    def test_traverse_from_cache_on_value(self):
         with self.build_dir(cache=True) as dir:
-            _ = (_ for _ in dir.iterate_contents())
+            _ = (_ for _ in dir.traverse())
             self.assertEqual(
-                ''.join([p for p in dir.iterate_contents()]),
+                ''.join([p for p in dir.traverse()]),
                 ''.join(CONTENTS))
             
-    def test_iterate_contents_recursively_from_cache_on_value(self):
-        with self.build_dir(cache=True) as dir:
-            _ = (_ for _ in dir.iterate_contents(recursively=True))
-            self.assertEqual(
-                ''.join([p for p in dir.iterate_contents(recursively=True)]),
-                ''.join(join_paths(dp, f).removeprefix(REL_DIR_PATH)
-                    for dp, _, fn in os.walk(REL_DIR_PATH) for f in fn))
-            
-    def test_iterate_contents_non_recursively_from_cache_on_none(self):
+    def test_traverse_recursively_from_cache_on_value(self):
         with self.build_dir(cache=True) as dir:
-            _ = (_ for _ in dir.iterate_contents(recursively=True))
+            _ = (_ for _ in dir.traverse(recursively=True))
+            expected_results = []
+            for dp, dn, fn in os.walk(REL_DIR_PATH):
+                dn.sort()
+                for f in sorted(fn):
+                    expected_results.append(
+                        join_paths(dp, f).removeprefix(REL_DIR_PATH))
             self.assertEqual(
-                ''.join(dir._get_cache_manager().iterate_contents(
-                    recursively=False,
-                    include_dirs=True)),
-                '')
+                ''.join([p for p in dir.traverse(recursively=True)]),
+                ''.join(expected_results))
             
-    def test_iterate_contents_from_cache_on_time(self):
+    def test_traverse_from_cache_on_time(self):
         with self.build_dir(cache=True) as dir:
             # Fetch contents via HTTP.
             t = time.time()
-            _ = (_ for _ in dir.iterate_contents())
+            _ = (_ for _ in dir.traverse())
             normal_time = time.time() - t
             # Fetch contents from cache.
             t = time.time()
-            _ = (_ for _ in dir.iterate_contents())
+            _ = (_ for _ in dir.traverse())
             cache_time = time.time() - t
             # Compare fetch times.
             self.assertGreater(normal_time, cache_time)
 
-    def test_iterate_contents_recursively_from_cache_on_time(self):
+    def test_traverse_recursively_from_cache_on_time(self):
         with self.build_dir(cache=True) as dir:
             # Fetch contents via HTTP.
             t = time.time()
-            _ = (_ for _ in dir.iterate_contents())
+            _ = (_ for _ in dir.traverse())
             normal_time = time.time() - t
             # Fetch contents from cache.
             t = time.time()
-            _ = (_ for _ in dir.iterate_contents())
+            _ = (_ for _ in dir.traverse())
             cache_time = time.time() - t
             # Compare fetch times.
             self.assertGreater(normal_time, cache_time)
@@ -3131,40 +3543,26 @@ def test_load_metadata_from_cache_on_value(self):
         with self.build_dir(cache=True) as dir:
             dir.load_metadata()
             self.assertEqual(
-                dir._get_cache_manager().get_metadata(REL_DIR_FILE_PATH),
+                dir.get_metadata(REL_DIR_FILE_PATH),
                 METADATA)
 
     def test_get_size_from_cache_on_value(self):
         with self.build_dir(cache=True) as dir:
-            dir.get_size()
-            self.assertEqual(
-                dir._get_cache_manager().get_size(REL_DIR_FILE_PATH), 4)
+            _ = dir.get_size()
+            self.assertEqual(dir.get_size(), 4)
             
     def test_load_metadata_recursively_from_cache_on_value(self):
         with self.build_dir(cache=True) as dir:
             dir.load_metadata(recursively=True)
             self.assertEqual(
-                dir._get_cache_manager().get_metadata(f"{REL_DIR_PATH}subdir/file4.txt"),
+                dir.get_metadata(f"{REL_DIR_PATH}subdir/file4.txt"),
                 METADATA)
 
     def test_get_size_recursively_from_cache_on_value(self):
         with self.build_dir(cache=True) as dir:
             _ = dir.get_size(recursively=True)
-            self.assertEqual(
-                dir._get_cache_manager().get_size(f"{REL_DIR_PATH}subdir/file4.txt"), 4)
-            
-    def test_load_metadata_non_recursively_from_cache_on_none(self):
-        with self.build_dir(cache=True) as dir:
-            dir.load_metadata()
-            self.assertIsNone(
-                dir._get_cache_manager().get_metadata(f"{REL_DIR_PATH}subdir/file4.txt"))
-            
-    def test_get_size_non_recursively_from_cache_on_none(self):
-        with self.build_dir(cache=True) as dir:
-            _ = dir.get_size()
-            self.assertIsNone(
-                dir._get_cache_manager().get_size(f"{REL_DIR_PATH}subdir/file4.txt"))
-            
+            self.assertEqual(dir.get_size(recursively=True), 16)
+
     def test_load_metadata_from_cache_on_time(self):
         with self.build_dir(cache=True) as dir:
             # Fetch object metadata via HTTP.
@@ -3217,6 +3615,51 @@ def test_get_size_recursively_from_cache_on_time(self):
             # Compare fetch times.
             self.assertGreater(normal_time, cache_time)
 
+    def test_file_shared_cache_on_cache_via_dir(self):
+        with (
+            self.build_dir(cache=False) as no_cache_dir,
+            self.build_dir(cache=True) as cache_dir
+        ):
+            # Access file via both dirs.
+            file_path = REL_DIR_FILE_PATH.removeprefix(REL_DIR_PATH)
+            no_cache_file = no_cache_dir.get_file(file_path)
+            cache_file = cache_dir.get_file(file_path)
+            # Count total size for both dirs.
+            _ = no_cache_dir.get_size()
+            _ = cache_dir.get_size()
+            # Time no-cache-file's "get_size"
+            t = time.time()
+            _ = no_cache_file.get_size()
+            normal_time = time.time() - t
+            # Time cache-file's "get_size"
+            t = time.time()
+            _ = cache_file.get_size()
+            cache_time = time.time() - t
+            # Compare fetch times.
+            self.assertGreater(normal_time, cache_time)
+
+    def test_file_shared_cache_on_cache_via_file(self):
+        with (
+            self.build_dir(cache=False) as no_cache_dir,
+            self.build_dir(cache=True) as cache_dir
+        ):
+            # Count size of files via both dirs using the "File" API.
+            file_path = REL_DIR_FILE_PATH.removeprefix(REL_DIR_PATH)
+            for file in no_cache_dir.get_files().values():
+                _ = file.get_size()
+            for file in cache_dir.get_files().values():
+                _ = file.get_size()
+            # Time no-cache-dir's "get_size"
+            t = time.time()
+            _ = no_cache_dir.get_size()
+            normal_time = time.time() - t
+            # Time cache-dir's "get_size"
+            t = time.time()
+            _ = cache_dir.get_size()
+            cache_time = time.time() - t
+            # Compare fetch times.
+            self.assertGreater(normal_time, cache_time)
+
 
 if __name__=="__main__":
     unittest.main()