Represent the data linked to a folder name.
Use instances of this class to hold data related to the path of a filename. Items in the tree will get associated back to the corresponding FileName and this FilePath object.
Represent the data linked to a folder name.
Use instances of this class to hold data related to the path of a filename. Items in the tree will get associated back to the corresponding FileName and this FilePath object.
Constructor of a FilePath.
PathTypeError
def __init__(self, filepath):
"""Constructor of a FilePath.
:param filepath: (str) Absolute or relative name of a folder
:raises: PathTypeError
"""
if os.path.exists(filepath) is False:
rel_path = os.path.abspath(filepath)
if os.path.exists(rel_path) is True:
filepath = rel_path
super(FilePath, self).__init__(filepath)
if os.path.exists(filepath) is False:
self._state = States().MISSING
elif os.path.isdir(filepath) is False:
raise PathTypeError(filepath)
self.__roots = list()
self.subjoined = None
Set a state value to a filename of this filepath.
It is not allowed to manually assign one of the "AT_LEAST" states. They are automatically fixed here depending on the roots states.
sppasTypeError, sppasOSError, sppasValueError
def set_object_state(self, value, entry):
"""Set a state value to a filename of this filepath.
It is not allowed to manually assign one of the "AT_LEAST" states.
They are automatically fixed here depending on the roots states.
:param value: (int) A state.
:param entry: (FileName, FileRoot) The instance to change state
:raises: sppasTypeError, sppasOSError, sppasValueError
"""
modified = list()
if isinstance(entry, (FileName, FileRoot)) is False:
entry = FileName(entry)
if isinstance(entry, FileName):
root_id = FileRoot.root(entry.id)
fr = self.get_root(root_id)
if fr is None:
raise sppasValueError(root_id, self.id)
modified = fr.set_object_state(value, entry)
elif isinstance(entry, FileRoot):
modified = entry.set_state(value)
if len(modified) > 0:
m = self.update_state()
if m is True:
modified.append(self)
return modified
Set a value to represent the state of the path.
It is not allowed to manually assign one of the "ATLEAST" states (they are automatically fixed by setting the state of a FileName with setobject_state method).
The state of LOCKED files is not changed.
def set_state(self, value):
"""Set a value to represent the state of the path.
It is not allowed to manually assign one of the "AT_LEAST" states
(they are automatically fixed by setting the state of a FileName
with set_object_state method).
The state of LOCKED files is not changed.
:param value: (State) A state of FileName.
"""
if value not in FileName.FILENAME_STATES:
raise sppasTypeError(value, str(FileName.FILENAME_STATES))
modified = list()
for fr in self.__roots:
m = fr.set_state(value)
modified.extend(m)
if len(modified) > 0:
m = self.update_state()
if m is True:
modified.append(self)
return modified
Return the instance matching the given entry.
Notice that it returns 'self' if filename is a directory matching self.id.
def get_object(self, filename):
"""Return the instance matching the given entry.
:param filename: Name of a file or a root (absolute of relative)
Notice that it returns 'self' if filename is a directory matching
self.id.
"""
abs_name = os.path.abspath(filename)
if abs_name == self.id:
return self
for fr in self.__roots:
if fr.id == abs_name:
return fr
fn = fr.get_object(filename)
if fn is not None:
return fn
return None
Return the identifier, i.e. the full name of an existing file.
FileOSError if filename does not match a regular file
def identifier(self, filename):
"""Return the identifier, i.e. the full name of an existing file.
:param filename: (str) Absolute or relative name of a file
:returns: (str) Identifier for this filename
:raise: FileOSError if filename does not match a regular file
"""
f = os.path.abspath(filename)
if os.path.isfile(f) is False:
f = os.path.join(self.id, filename)
if os.path.isfile(f) is False:
raise FileOSError(filename)
return f
Return the FileRoot matching the given id (root or file).
def get_root(self, name):
"""Return the FileRoot matching the given id (root or file).
:param name: (str) Identifier name of a root or a file.
:returns: FileRoot or None
"""
for fr in self.__roots:
if fr.id == name:
return fr
for fr in self.__roots:
for fn in fr:
if fn.id == name:
return fr
return None
Append a filename in the list of files.
Given filename can be either an absolute or relative name of a file or an instance of FileName. It can also be an instance of FileRoot.
Only an existing file can be added if the given entry is the name of the file. But any FileName() instance can be added, even if the file does not exists (of course, its path must match with this fp).
FileOSError if entry is a non-existing filename. Exception if given entry does not math the path.
def append(self, entry, all_root=False, ctime=0.0):
"""Append a filename in the list of files.
Given filename can be either an absolute or relative name of a file
or an instance of FileName. It can also be an instance of FileRoot.
Only an existing file can be added if the given entry is the name of the file.
But any FileName() instance can be added, even if the file does not exists
(of course, its path must match with this fp).
:param entry: (str, FileName, FileRoot) Absolute or relative name of a file
:param all_root: (bool) Add also all files sharing the same root as the given one, or all files of the given root
:param ctime: (float) Add files only if created/modified after time in seconds since the epoch
:returns: (FileName, FileRoot) the list of appended objects or None
:raises: FileOSError if entry is a non-existing filename. Exception if given entry does not math the path.
"""
new_objs = list()
new_files = list()
if isinstance(entry, FileRoot):
abs_name = os.path.dirname(entry.id)
if abs_name != self.id:
logging.debug("The root {:s} can't be appended: it is not matching the FilePath {:s}".format(entry.id, self.id))
raise FilesMatchingValueError(entry.id, self.id)
obj = self.get_root(entry.id)
if obj is None:
self.__roots.append(entry)
new_objs.append(entry)
if all_root is True:
new_files = entry.append(None, all_root=all_root, ctime=ctime)
else:
if isinstance(entry, FileName):
file_id = entry.id
else:
file_id = self.identifier(entry)
entry = FileName(file_id)
abs_name = os.path.dirname(file_id)
if abs_name != self.id:
logging.debug("The file {:s} can't be appended: its path is not matching the FilePath {:s}".format(file_id, self.id))
raise FilesMatchingValueError(file_id, self.id)
root_id = FileRoot.root(file_id)
fr = self.get_root(root_id)
if fr is None:
fr = FileRoot(root_id)
self.__roots.append(fr)
new_objs.append(fr)
new_files = fr.append(entry, all_root=all_root, ctime=ctime)
if len(new_files) > 0:
new_objs.extend(new_files)
if len(new_objs) > 0:
self.update_state()
return new_objs
return None
Remove a root entry of the list of roots.
Given entry can be either the identifier of a root or an instance of FileRoot.
TODO: REMOVE IF ENTRY is FILENAME
def remove(self, entry):
"""Remove a root entry of the list of roots.
Given entry can be either the identifier of a root or an instance
of FileRoot.
TODO: REMOVE IF ENTRY is FILENAME
:param entry:
:returns: (identifier) Identifier of the removed entry or None
"""
if isinstance(entry, FileRoot):
root = entry
else:
root = self.get_root(entry)
try:
idx = self.__roots.index(root)
identifier = self.__roots[idx].get_id()
self.__roots.pop(idx)
except ValueError:
identifier = None
self.update_state()
return identifier
Unlock all.
def unlock(self):
"""Unlock all.
:returns: number of unlocked filenames
"""
i = 0
for fr in self.__roots:
for fn in fr:
if fn.get_state() == States().LOCKED:
fn.set_state(States().CHECKED)
i += 1
if i > 0:
fr.update_state()
if i > 0:
self.update_state()
return i
Modify state depending on the checked root names.
The state is missing if the path is not existing on disk. Else, the state of a path is assigned as it: - locked if all roots are locked, - atleastonelocked if at least one of its roots is locked, - checked if all roots are checked, - atleastonechecked if at least one of its roots is checked and none of the others are locked, - unused if none of its roots are neither locked, checked nor missing.
def update_state(self):
"""Modify state depending on the checked root names.
The state is missing if the path is not existing on disk.
Else, the state of a path is assigned as it:
- locked if all roots are locked,
- at_least_one_locked if at least one of its roots is locked,
- checked if all roots are checked,
- at_least_one_checked if at least one of its roots is checked and none of the others are locked,
- unused if none of its roots are neither locked, checked nor missing.
:return: (bool) State was changed or not.
"""
state = States()
if os.path.exists(self.get_id()) is False:
if self._state == state.MISSING:
return False
else:
self._state = state.MISSING
return True
if len(self.__roots) == 0:
new_state = state.UNUSED
else:
at_least_checked = 0
at_least_locked = 0
checked = 0
locked = 0
for fr in self.__roots:
if fr.get_state() == state.CHECKED:
checked += 1
elif fr.get_state() == state.AT_LEAST_ONE_CHECKED:
at_least_checked += 1
elif fr.get_state() == state.LOCKED:
locked += 1
elif fr.get_state() == state.AT_LEAST_ONE_LOCKED:
at_least_locked += 1
if locked == len(self.__roots):
new_state = state.LOCKED
elif locked + at_least_locked > 0:
new_state = state.AT_LEAST_ONE_LOCKED
elif checked == len(self.__roots):
new_state = state.CHECKED
elif at_least_checked + checked > 0:
new_state = state.AT_LEAST_ONE_CHECKED
else:
new_state = state.UNUSED
if self._state != new_state:
self._state = new_state
return True
return False
def __repr__(self):
return 'Path: ' + self.get_id() + ' contains ' + str(len(self.__roots)) + ' file roots\n'
def __iter__(self):
for a in self.__roots:
yield a
def __getitem__(self, i):
return self.__roots[i]
def __len__(self):
return len(self.__roots)
def __contains__(self, value):
if isinstance(value, FileRoot):
return value in self.__roots
for fr in self.__roots:
x = value in fr
if x is True:
return True
root_id = FileRoot.root(value)
fr = self.get_root(root_id)
if fr is not None:
return True
return False