diff options
author | hut <hut@lavabit.com> | 2013-02-10 03:28:06 +0100 |
---|---|---|
committer | hut <hut@lavabit.com> | 2013-02-10 03:35:27 +0100 |
commit | d1a1173ddc315f21a3d468f43ac55aa43d31883d (patch) | |
tree | 10d728b37294856eb21e6e962089ac38507d868c /ranger/fsobject/directory.py | |
parent | 184e84284d2f4e48631a4b94b87ba76126470206 (diff) | |
download | ranger-d1a1173ddc315f21a3d468f43ac55aa43d31883d.tar.gz |
replaced tabs with 4 spaces in all python files
PEP 8 (Style Guide for Python Code) suggests the use of 4 spaces: http://www.python.org/dev/peps/pep-0008/#indentation If you need to use tools like "git blame", you can use the -w option to ignore this commit entirely. Patches will continue to work if you substitute tabs with 4 spaces everywhere except in the Makefile.
Diffstat (limited to 'ranger/fsobject/directory.py')
-rw-r--r-- | ranger/fsobject/directory.py | 1032 |
1 files changed, 516 insertions, 516 deletions
diff --git a/ranger/fsobject/directory.py b/ranger/fsobject/directory.py index f8271b6d..2aa47a64 100644 --- a/ranger/fsobject/directory.py +++ b/ranger/fsobject/directory.py @@ -19,531 +19,531 @@ from ranger.ext.human_readable import human_readable from ranger.container.settingobject import LocalSettingObject def sort_by_basename(path): - """returns path.basename (for sorting)""" - return path.basename + """returns path.basename (for sorting)""" + return path.basename def sort_by_basename_icase(path): - """returns case-insensitive path.basename (for sorting)""" - return path.basename_lower + """returns case-insensitive path.basename (for sorting)""" + return path.basename_lower def sort_by_directory(path): - """returns 0 if path is a directory, otherwise 1 (for sorting)""" - return 1 - path.is_directory + """returns 0 if path is a directory, otherwise 1 (for sorting)""" + return 1 - path.is_directory def sort_naturally(path): - return path.basename_natural + return path.basename_natural def sort_naturally_icase(path): - return path.basename_natural_lower + return path.basename_natural_lower def accept_file(fname, dirname, hidden_filter, name_filter): - if hidden_filter: - try: - if hidden_filter.search(fname): - return False - except: - if hidden_filter in fname: - return False - if name_filter and name_filter not in fname: - return False - return True + if hidden_filter: + try: + if hidden_filter.search(fname): + return False + except: + if hidden_filter in fname: + return False + if name_filter and name_filter not in fname: + return False + return True class Directory(FileSystemObject, Accumulator, Loadable, SettingsAware): - is_directory = True - enterable = False - load_generator = None - cycle_list = None - loading = False - progressbar_supported = True - - filenames = None - files = None - filter = None - marked_items = None - scroll_begin = 0 - - mount_path = '/' - disk_usage = 0 - - last_update_time = -1 - load_content_mtime = -1 - - order_outdated = False - content_outdated = False - content_loaded = False - - _cumulative_size_calculated = False - - sort_dict = { - 'basename': sort_by_basename, - 'natural': sort_naturally, - 'size': lambda path: -path.size, - 'mtime': lambda path: -(path.stat and path.stat.st_mtime or 1), - 'ctime': lambda path: -(path.stat and path.stat.st_ctime or 1), - 'atime': lambda path: -(path.stat and path.stat.st_atime or 1), - 'type': lambda path: path.mimetype or '', - } - - def __init__(self, path, **kw): - assert not os.path.isfile(path), "No directory given!" - - Loadable.__init__(self, None, None) - Accumulator.__init__(self) - FileSystemObject.__init__(self, path, **kw) - - self.marked_items = list() - - for opt in ('sort_directories_first', 'sort', 'sort_reverse', - 'sort_case_insensitive'): - self.settings.signal_bind('setopt.' + opt, - self.request_resort, weak=True, autosort=False) - - for opt in ('hidden_filter', 'show_hidden'): - self.settings.signal_bind('setopt.' + opt, - self.request_reload, weak=True, autosort=False) - - self.settings = LocalSettingObject(path, self.settings) - - self.use() - - def request_resort(self): - self.order_outdated = True - - def request_reload(self): - self.content_outdated = True - - def get_list(self): - return self.files - - def mark_item(self, item, val): - item._mark(val) - if val: - if item in self.files and not item in self.marked_items: - self.marked_items.append(item) - else: - while True: - try: - self.marked_items.remove(item) - except ValueError: - break - - def toggle_mark(self, item): - self.mark_item(item, not item.marked) - - def toggle_all_marks(self): - for item in self.files: - self.toggle_mark(item) - - def mark_all(self, val): - for item in self.files: - self.mark_item(item, val) - - if not val: - del self.marked_items[:] - self._clear_marked_items() - - # XXX: Is it really necessary to have the marked items in a list? - # Can't we just recalculate them with [f for f in self.files if f.marked]? - def _gc_marked_items(self): - for item in list(self.marked_items): - if item.path not in self.filenames: - self.marked_items.remove(item) - - def _clear_marked_items(self): - for item in self.marked_items: - item._mark(False) - del self.marked_items[:] - - def get_selection(self): - """READ ONLY""" - self._gc_marked_items() - if self.marked_items: - return [item for item in self.files if item.marked] - elif self.pointed_obj: - return [self.pointed_obj] - else: - return [] - - # XXX: Check for possible race conditions - def load_bit_by_bit(self): - """ - Returns a generator which load a part of the directory - in each iteration. - """ - - self.loading = True - self.percent = 0 - self.load_if_outdated() - - try: - if self.runnable: - yield - mypath = self.path - - self.mount_path = mount_path(mypath) - - if not self.settings.show_hidden and self.settings.hidden_filter: - # COMPAT - # hidden_filter used to be a regex, not a string. If an - # old config is used, we don't need to re.compile it. - if hasattr(self.settings.hidden_filter, 'search'): - hidden_filter = self.settings.hidden_filter - else: - hidden_filter = re.compile(self.settings.hidden_filter) - else: - hidden_filter = None - - filelist = os.listdir(mypath) - - if self._cumulative_size_calculated: - # If self.content_loaded is true, this is not the first - # time loading. So I can't really be sure if the - # size has changed and I'll add a "?". - if self.content_loaded: - if self.fm.settings.autoupdate_cumulative_size: - self.look_up_cumulative_size() - else: - self.infostring = ' %s' % human_readable( - self.size, separator='? ') - else: - self.infostring = ' %s' % human_readable(self.size) - else: - self.size = len(filelist) - self.infostring = ' %d' % self.size - if self.is_link: - self.infostring = '->' + self.infostring - - filenames = [mypath + (mypath == '/' and fname or '/' + fname)\ - for fname in filelist if accept_file( - fname, mypath, hidden_filter, self.filter)] - yield - - self.load_content_mtime = os.stat(mypath).st_mtime - - marked_paths = [obj.path for obj in self.marked_items] - - files = [] - disk_usage = 0 - for name in filenames: - try: - file_lstat = os_lstat(name) - if file_lstat.st_mode & 0o170000 == 0o120000: - file_stat = os_stat(name) - else: - file_stat = file_lstat - stats = (file_stat, file_lstat) - is_a_dir = file_stat.st_mode & 0o170000 == 0o040000 - except: - stats = None - is_a_dir = False - if is_a_dir: - try: - item = self.fm.get_directory(name) - item.load_if_outdated() - except: - item = Directory(name, preload=stats, - path_is_abs=True) - item.load() - else: - item = File(name, preload=stats, path_is_abs=True) - item.load() - disk_usage += item.size - files.append(item) - self.percent = 100 * len(files) // len(filenames) - yield - self.disk_usage = disk_usage - - self.filenames = filenames - self.files = files - - self._clear_marked_items() - for item in self.files: - if item.path in marked_paths: - item._mark(True) - self.marked_items.append(item) - else: - item._mark(False) - - self.sort() - - if files: - if self.pointed_obj is not None: - self.sync_index() - else: - self.move(to=0) - else: - self.filenames = None - self.files = None - - self.cycle_list = None - self.content_loaded = True - self.last_update_time = time() - self.correct_pointer() - - finally: - self.loading = False - self.fm.signal_emit("finished_loading_dir", directory=self) - - def unload(self): - self.loading = False - self.load_generator = None - - def load_content(self, schedule=None): - """ - Loads the contents of the directory. Use this sparingly since - it takes rather long. - """ - self.content_outdated = False - - if not self.loading: - if not self.loaded: - self.load() - - if not self.accessible: - self.content_loaded = True - return - - if schedule is None: - schedule = True # was: self.size > 30 - - if self.load_generator is None: - self.load_generator = self.load_bit_by_bit() - - if schedule and self.fm: - self.fm.loader.add(self) - else: - for _ in self.load_generator: - pass - self.load_generator = None - - elif not schedule or not self.fm: - for _ in self.load_generator: - pass - self.load_generator = None - - - def sort(self): - """Sort the containing files""" - if self.files is None: - return - - old_pointed_obj = self.pointed_obj - try: - sort_func = self.sort_dict[self.settings.sort] - except: - sort_func = sort_by_basename - - if self.settings.sort_case_insensitive and \ - sort_func == sort_by_basename: - sort_func = sort_by_basename_icase - - if self.settings.sort_case_insensitive and \ - sort_func == sort_naturally: - sort_func = sort_naturally_icase - - self.files.sort(key = sort_func) - - if self.settings.sort_reverse: - self.files.reverse() - - if self.settings.sort_directories_first: - self.files.sort(key = sort_by_directory) - - if self.pointer is not None: - self.move_to_obj(old_pointed_obj) - else: - self.correct_pointer() - - def _get_cumulative_size(self): - if self.size == 0: - return 0 - cum = 0 - realpath = os.path.realpath - for dirpath, dirnames, filenames in os.walk(self.path, - onerror=lambda _: None): - for file in filenames: - try: - if dirpath == self.path: - stat = os_stat(realpath(dirpath + "/" + file)) - else: - stat = os_stat(dirpath + "/" + file) - cum += stat.st_size - except: - pass - return cum - - def look_up_cumulative_size(self): - self._cumulative_size_calculated = True - self.size = self._get_cumulative_size() - self.infostring = ('-> ' if self.is_link else ' ') + \ - human_readable(self.size) - - @lazy_property - def size(self): - try: - size = len(os.listdir(self.path)) # bite me - except OSError: - self.infostring = BAD_INFO - self.accessible = False - self.runnable = False - return 0 - else: - self.infostring = ' %d' % size - self.accessible = True - self.runnable = True - return size - - @lazy_property - def infostring(self): - self.size # trigger the lazy property initializer - if self.is_link: - return '->' + self.infostring - return self.infostring - - @lazy_property - def runnable(self): - self.size # trigger the lazy property initializer - return self.runnable - - def sort_if_outdated(self): - """Sort the containing files if they are outdated""" - if self.order_outdated: - self.order_outdated = False - self.sort() - return True - return False - - def move_to_obj(self, arg): - try: - arg = arg.path - except: - pass - self.load_content_once(schedule=False) - if self.empty(): - return - - Accumulator.move_to_obj(self, arg, attr='path') - - def search_fnc(self, fnc, offset=1, forward=True): - if not hasattr(fnc, '__call__'): - return False - - length = len(self) - - if forward: - generator = ((self.pointer + (x + offset)) % length \ - for x in range(length - 1)) - else: - generator = ((self.pointer - (x + offset)) % length \ - for x in range(length - 1)) - - for i in generator: - _file = self.files[i] - if fnc(_file): - self.pointer = i - self.pointed_obj = _file - self.correct_pointer() - return True - return False - - def set_cycle_list(self, lst): - self.cycle_list = deque(lst) - - def cycle(self, forward=True): - if self.cycle_list: - if forward is True: - self.cycle_list.rotate(-1) - elif forward is False: - self.cycle_list.rotate(1) - - self.move_to_obj(self.cycle_list[0]) - - def correct_pointer(self): - """Make sure the pointer is in the valid range""" - Accumulator.correct_pointer(self) - - try: - if self == self.fm.thisdir: - self.fm.thisfile = self.pointed_obj - except: - pass - - def load_content_once(self, *a, **k): - """Load the contents of the directory if not done yet""" - if not self.content_loaded: - self.load_content(*a, **k) - return True - return False - - def load_content_if_outdated(self, *a, **k): - """ - Load the contents of the directory if it's - outdated or not done yet - """ - - if self.load_content_once(*a, **k): return True - - if self.files is None or self.content_outdated: - self.load_content(*a, **k) - return True - - try: - real_mtime = os.stat(self.path).st_mtime - except OSError: - real_mtime = None - return False - if self.stat: - cached_mtime = self.load_content_mtime - else: - cached_mtime = 0 - - if real_mtime != cached_mtime: - self.load_content(*a, **k) - return True - return False - - def get_description(self): - return "Loading " + str(self) - - def use(self): - """mark the filesystem-object as used at the current time""" - self.last_used = time() - - def is_older_than(self, seconds): - """returns whether this object wasn't use()d in the last n seconds""" - if seconds < 0: - return True - return self.last_used + seconds < time() - - def go(self, history=True): - """enter the directory if the filemanager is running""" - if self.fm: - return self.fm.enter_dir(self.path, history=history) - return False - - def empty(self): - """Is the directory empty?""" - return self.files is None or len(self.files) == 0 - - def __nonzero__(self): - """Always True""" - return True - __bool__ = __nonzero__ - - def __len__(self): - """The number of containing files""" - assert self.accessible - assert self.content_loaded - assert self.files is not None - return len(self.files) - - def __eq__(self, other): - """Check for equality of the directories paths""" - return isinstance(other, Directory) and self.path == other.path - - def __neq__(self, other): - """Check for inequality of the directories paths""" - return not self.__eq__(other) - - def __hash__(self): - return hash(self.path) + is_directory = True + enterable = False + load_generator = None + cycle_list = None + loading = False + progressbar_supported = True + + filenames = None + files = None + filter = None + marked_items = None + scroll_begin = 0 + + mount_path = '/' + disk_usage = 0 + + last_update_time = -1 + load_content_mtime = -1 + + order_outdated = False + content_outdated = False + content_loaded = False + + _cumulative_size_calculated = False + + sort_dict = { + 'basename': sort_by_basename, + 'natural': sort_naturally, + 'size': lambda path: -path.size, + 'mtime': lambda path: -(path.stat and path.stat.st_mtime or 1), + 'ctime': lambda path: -(path.stat and path.stat.st_ctime or 1), + 'atime': lambda path: -(path.stat and path.stat.st_atime or 1), + 'type': lambda path: path.mimetype or '', + } + + def __init__(self, path, **kw): + assert not os.path.isfile(path), "No directory given!" + + Loadable.__init__(self, None, None) + Accumulator.__init__(self) + FileSystemObject.__init__(self, path, **kw) + + self.marked_items = list() + + for opt in ('sort_directories_first', 'sort', 'sort_reverse', + 'sort_case_insensitive'): + self.settings.signal_bind('setopt.' + opt, + self.request_resort, weak=True, autosort=False) + + for opt in ('hidden_filter', 'show_hidden'): + self.settings.signal_bind('setopt.' + opt, + self.request_reload, weak=True, autosort=False) + + self.settings = LocalSettingObject(path, self.settings) + + self.use() + + def request_resort(self): + self.order_outdated = True + + def request_reload(self): + self.content_outdated = True + + def get_list(self): + return self.files + + def mark_item(self, item, val): + item._mark(val) + if val: + if item in self.files and not item in self.marked_items: + self.marked_items.append(item) + else: + while True: + try: + self.marked_items.remove(item) + except ValueError: + break + + def toggle_mark(self, item): + self.mark_item(item, not item.marked) + + def toggle_all_marks(self): + for item in self.files: + self.toggle_mark(item) + + def mark_all(self, val): + for item in self.files: + self.mark_item(item, val) + + if not val: + del self.marked_items[:] + self._clear_marked_items() + + # XXX: Is it really necessary to have the marked items in a list? + # Can't we just recalculate them with [f for f in self.files if f.marked]? + def _gc_marked_items(self): + for item in list(self.marked_items): + if item.path not in self.filenames: + self.marked_items.remove(item) + + def _clear_marked_items(self): + for item in self.marked_items: + item._mark(False) + del self.marked_items[:] + + def get_selection(self): + """READ ONLY""" + self._gc_marked_items() + if self.marked_items: + return [item for item in self.files if item.marked] + elif self.pointed_obj: + return [self.pointed_obj] + else: + return [] + + # XXX: Check for possible race conditions + def load_bit_by_bit(self): + """ + Returns a generator which load a part of the directory + in each iteration. + """ + + self.loading = True + self.percent = 0 + self.load_if_outdated() + + try: + if self.runnable: + yield + mypath = self.path + + self.mount_path = mount_path(mypath) + + if not self.settings.show_hidden and self.settings.hidden_filter: + # COMPAT + # hidden_filter used to be a regex, not a string. If an + # old config is used, we don't need to re.compile it. + if hasattr(self.settings.hidden_filter, 'search'): + hidden_filter = self.settings.hidden_filter + else: + hidden_filter = re.compile(self.settings.hidden_filter) + else: + hidden_filter = None + + filelist = os.listdir(mypath) + + if self._cumulative_size_calculated: + # If self.content_loaded is true, this is not the first + # time loading. So I can't really be sure if the + # size has changed and I'll add a "?". + if self.content_loaded: + if self.fm.settings.autoupdate_cumulative_size: + self.look_up_cumulative_size() + else: + self.infostring = ' %s' % human_readable( + self.size, separator='? ') + else: + self.infostring = ' %s' % human_readable(self.size) + else: + self.size = len(filelist) + self.infostring = ' %d' % self.size + if self.is_link: + self.infostring = '->' + self.infostring + + filenames = [mypath + (mypath == '/' and fname or '/' + fname)\ + for fname in filelist if accept_file( + fname, mypath, hidden_filter, self.filter)] + yield + + self.load_content_mtime = os.stat(mypath).st_mtime + + marked_paths = [obj.path for obj in self.marked_items] + + files = [] + disk_usage = 0 + for name in filenames: + try: + file_lstat = os_lstat(name) + if file_lstat.st_mode & 0o170000 == 0o120000: + file_stat = os_stat(name) + else: + file_stat = file_lstat + stats = (file_stat, file_lstat) + is_a_dir = file_stat.st_mode & 0o170000 == 0o040000 + except: + stats = None + is_a_dir = False + if is_a_dir: + try: + item = self.fm.get_directory(name) + item.load_if_outdated() + except: + item = Directory(name, preload=stats, + path_is_abs=True) + item.load() + else: + item = File(name, preload=stats, path_is_abs=True) + item.load() + disk_usage += item.size + files.append(item) + self.percent = 100 * len(files) // len(filenames) + yield + self.disk_usage = disk_usage + + self.filenames = filenames + self.files = files + + self._clear_marked_items() + for item in self.files: + if item.path in marked_paths: + item._mark(True) + self.marked_items.append(item) + else: + item._mark(False) + + self.sort() + + if files: + if self.pointed_obj is not None: + self.sync_index() + else: + self.move(to=0) + else: + self.filenames = None + self.files = None + + self.cycle_list = None + self.content_loaded = True + self.last_update_time = time() + self.correct_pointer() + + finally: + self.loading = False + self.fm.signal_emit("finished_loading_dir", directory=self) + + def unload(self): + self.loading = False + self.load_generator = None + + def load_content(self, schedule=None): + """ + Loads the contents of the directory. Use this sparingly since + it takes rather long. + """ + self.content_outdated = False + + if not self.loading: + if not self.loaded: + self.load() + + if not self.accessible: + self.content_loaded = True + return + + if schedule is None: + schedule = True # was: self.size > 30 + + if self.load_generator is None: + self.load_generator = self.load_bit_by_bit() + + if schedule and self.fm: + self.fm.loader.add(self) + else: + for _ in self.load_generator: + pass + self.load_generator = None + + elif not schedule or not self.fm: + for _ in self.load_generator: + pass + self.load_generator = None + + + def sort(self): + """Sort the containing files""" + if self.files is None: + return + + old_pointed_obj = self.pointed_obj + try: + sort_func = self.sort_dict[self.settings.sort] + except: + sort_func = sort_by_basename + + if self.settings.sort_case_insensitive and \ + sort_func == sort_by_basename: + sort_func = sort_by_basename_icase + + if self.settings.sort_case_insensitive and \ + sort_func == sort_naturally: + sort_func = sort_naturally_icase + + self.files.sort(key = sort_func) + + if self.settings.sort_reverse: + self.files.reverse() + + if self.settings.sort_directories_first: + self.files.sort(key = sort_by_directory) + + if self.pointer is not None: + self.move_to_obj(old_pointed_obj) + else: + self.correct_pointer() + + def _get_cumulative_size(self): + if self.size == 0: + return 0 + cum = 0 + realpath = os.path.realpath + for dirpath, dirnames, filenames in os.walk(self.path, + onerror=lambda _: None): + for file in filenames: + try: + if dirpath == self.path: + stat = os_stat(realpath(dirpath + "/" + file)) + else: + stat = os_stat(dirpath + "/" + file) + cum += stat.st_size + except: + pass + return cum + + def look_up_cumulative_size(self): + self._cumulative_size_calculated = True + self.size = self._get_cumulative_size() + self.infostring = ('-> ' if self.is_link else ' ') + \ + human_readable(self.size) + + @lazy_property + def size(self): + try: + size = len(os.listdir(self.path)) # bite me + except OSError: + self.infostring = BAD_INFO + self.accessible = False + self.runnable = False + return 0 + else: + self.infostring = ' %d' % size + self.accessible = True + self.runnable = True + return size + + @lazy_property + def infostring(self): + self.size # trigger the lazy property initializer + if self.is_link: + return '->' + self.infostring + return self.infostring + + @lazy_property + def runnable(self): + self.size # trigger the lazy property initializer + return self.runnable + + def sort_if_outdated(self): + """Sort the containing files if they are outdated""" + if self.order_outdated: + self.order_outdated = False + self.sort() + return True + return False + + def move_to_obj(self, arg): + try: + arg = arg.path + except: + pass + self.load_content_once(schedule=False) + if self.empty(): + return + + Accumulator.move_to_obj(self, arg, attr='path') + + def search_fnc(self, fnc, offset=1, forward=True): + if not hasattr(fnc, '__call__'): + return False + + length = len(self) + + if forward: + generator = ((self.pointer + (x + offset)) % length \ + for x in range(length - 1)) + else: + generator = ((self.pointer - (x + offset)) % length \ + for x in range(length - 1)) + + for i in generator: + _file = self.files[i] + if fnc(_file): + self.pointer = i + self.pointed_obj = _file + self.correct_pointer() + return True + return False + + def set_cycle_list(self, lst): + self.cycle_list = deque(lst) + + def cycle(self, forward=True): + if self.cycle_list: + if forward is True: + self.cycle_list.rotate(-1) + elif forward is False: + self.cycle_list.rotate(1) + + self.move_to_obj(self.cycle_list[0]) + + def correct_pointer(self): + """Make sure the pointer is in the valid range""" + Accumulator.correct_pointer(self) + + try: + if self == self.fm.thisdir: + self.fm.thisfile = self.pointed_obj + except: + pass + + def load_content_once(self, *a, **k): + """Load the contents of the directory if not done yet""" + if not self.content_loaded: + self.load_content(*a, **k) + return True + return False + + def load_content_if_outdated(self, *a, **k): + """ + Load the contents of the directory if it's + outdated or not done yet + """ + + if self.load_content_once(*a, **k): return True + + if self.files is None or self.content_outdated: + self.load_content(*a, **k) + return True + + try: + real_mtime = os.stat(self.path).st_mtime + except OSError: + real_mtime = None + return False + if self.stat: + cached_mtime = self.load_content_mtime + else: + cached_mtime = 0 + + if real_mtime != cached_mtime: + self.load_content(*a, **k) + return True + return False + + def get_description(self): + return "Loading " + str(self) + + def use(self): + """mark the filesystem-object as used at the current time""" + self.last_used = time() + + def is_older_than(self, seconds): + """returns whether this object wasn't use()d in the last n seconds""" + if seconds < 0: + return True + return self.last_used + seconds < time() + + def go(self, history=True): + """enter the directory if the filemanager is running""" + if self.fm: + return self.fm.enter_dir(self.path, history=history) + return False + + def empty(self): + """Is the directory empty?""" + return self.files is None or len(self.files) == 0 + + def __nonzero__(self): + """Always True""" + return True + __bool__ = __nonzero__ + + def __len__(self): + """The number of containing files""" + assert self.accessible + assert self.content_loaded + assert self.files is not None + return len(self.files) + + def __eq__(self, other): + """Check for equality of the directories paths""" + return isinstance(other, Directory) and self.path == other.path + + def __neq__(self, other): + """Check for inequality of the directories paths""" + return not self.__eq__(other) + + def __hash__(self): + return hash(self.path) |