<SYSTEM_TASK:>
Solve the following problem using Python, implementing the functions described below, one line at a time
<END_TASK>
<USER_TASK:>
Description:
 def addToCache(self, localFilePath, jobStoreFileID, callingFunc, mutable=False):
 """ Used to process the caching of a file. This depends on whether a file is being written to file store, or read from it. WRITING The file is in localTempDir. It needs to be linked into cache if possible. READING The file is already in the cache dir. Depending on whether it is modifiable or not, does it need to be linked to the required location, or copied. If it is copied, can the file still be retained in cache? :param str localFilePath: Path to the Source file :param jobStoreFileID: jobStoreID for the file :param str callingFunc: Who called this function, 'write' or 'read' :param bool mutable: See modifiable in readGlobalFile """

        assert callingFunc in ('read', 'write')
        with self.cacheLock() as lockFileHandle:
            cachedFile = self.encodedFileID(jobStoreFileID)
            # The file to be cached MUST originate in the environment of the TOIL temp directory
            if (os.stat(self.localCacheDir).st_dev !=
                    os.stat(os.path.dirname(localFilePath)).st_dev):
                raise InvalidSourceCacheError('Attempting to cache a file across file systems '
                                              'cachedir = %s, file = %s.' % (self.localCacheDir,
                                                                             localFilePath))
            if not localFilePath.startswith(self.localTempDir):
                raise InvalidSourceCacheError('Attempting a cache operation on a non-local file '
                                              '%s.' % localFilePath)
            if callingFunc == 'read' and mutable:
                shutil.copyfile(cachedFile, localFilePath)
                fileSize = os.stat(cachedFile).st_size
                cacheInfo = self._CacheState._load(self.cacheStateFile)
                cacheInfo.cached += fileSize if cacheInfo.nlink != 2 else 0
                if not cacheInfo.isBalanced():
                    os.remove(cachedFile)
                    cacheInfo.cached -= fileSize if cacheInfo.nlink != 2 else 0
                    logger.debug('Could not download both download ' +
                                 '%s as mutable and add to ' % os.path.basename(localFilePath) +
                                 'cache. Hence only mutable copy retained.')
                else:
                    logger.debug('CACHE: Added file with ID \'%s\' to the cache.' %
                                jobStoreFileID)
                jobState = self._JobState(cacheInfo.jobState[self.jobID])
                jobState.addToJobSpecFiles(jobStoreFileID, localFilePath, -1, False)
                cacheInfo.jobState[self.jobID] = jobState.__dict__
                cacheInfo.write(self.cacheStateFile)
            else:
                # There are two possibilities, read and immutable, and write. both cases do
                # almost the same thing except for the direction of the os.link hence we're
                # writing them together.
                if callingFunc == 'read':  # and mutable is inherently False
                    src = cachedFile
                    dest = localFilePath
                    # To mirror behaviour of shutil.copyfile
                    if os.path.exists(dest):
                        os.remove(dest)
                else:  # write
                    src = localFilePath
                    dest = cachedFile
                try:
                    os.link(src, dest)
                except OSError as err:
                    if err.errno != errno.EEXIST:
                        raise
                    # If we get the EEXIST error, it can only be from write since in read we are
                    # explicitly deleting the file.  This shouldn't happen with the .partial
                    # logic hence we raise a cache error.
                    raise CacheError('Attempting to recache a file %s.' % src)
                else:
                    # Chmod the cached file. Cached files can never be modified.
                    os.chmod(cachedFile, stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH)
                    # Return the filesize of cachedFile to the job and increase the cached size
                    # The values passed here don't matter since rFS looks at the file only for
                    # the stat
                    self.returnFileSize(jobStoreFileID, localFilePath, lockFileHandle,
                                        fileAlreadyCached=False)
                if callingFunc == 'read':
                    logger.debug('CACHE: Read file with ID \'%s\' from the cache.' %
                                 jobStoreFileID)
                else:
                    logger.debug('CACHE: Added file with ID \'%s\' to the cache.' %
                                 jobStoreFileID)