about summary refs log tree commit diff stats
path: root/ranger/fsobject
diff options
context:
space:
mode:
Diffstat (limited to 'ranger/fsobject')
-rw-r--r--ranger/fsobject/directory.py1032
-rw-r--r--ranger/fsobject/file.py130
-rw-r--r--ranger/fsobject/fsobject.py528
3 files changed, 845 insertions, 845 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)
diff --git a/ranger/fsobject/file.py b/ranger/fsobject/file.py
index 8598a1d6..5831ed3f 100644
--- a/ranger/fsobject/file.py
+++ b/ranger/fsobject/file.py
@@ -6,82 +6,82 @@ from ranger.fsobject import FileSystemObject
 
 N_FIRST_BYTES = 256
 control_characters = set(chr(n) for n in
-		set(range(0, 9)) | set(range(14, 32)))
+        set(range(0, 9)) | set(range(14, 32)))
 
 # Don't even try to preview files which mach this regular expression:
 PREVIEW_BLACKLIST = re.compile(r"""
-		# look at the extension:
-		\.(
-			# one character extensions:
-				[oa]
-			# media formats:
-				| avi | mpe?g | mp\d | og[gmv] | wm[av] | mkv | flv
-				| vob | wav | mpc | flac | divx? | xcf | pdf
-			# binary files:
-				| torrent | class | so | img | py[co] | dmg
-		)
-		# ignore filetype-independent suffixes:
-			(\.part|\.bak|~)?
-		# ignore fully numerical file extensions:
-			(\.\d+)*?
-		$
+        # look at the extension:
+        \.(
+            # one character extensions:
+                [oa]
+            # media formats:
+                | avi | mpe?g | mp\d | og[gmv] | wm[av] | mkv | flv
+                | vob | wav | mpc | flac | divx? | xcf | pdf
+            # binary files:
+                | torrent | class | so | img | py[co] | dmg
+        )
+        # ignore filetype-independent suffixes:
+            (\.part|\.bak|~)?
+        # ignore fully numerical file extensions:
+            (\.\d+)*?
+        $
 """, re.VERBOSE | re.IGNORECASE)
 
 # Preview these files (almost) always:
 PREVIEW_WHITELIST = re.compile(r"""
-		\.(
-			txt | py | c
-		)
-		# ignore filetype-independent suffixes:
-			(\.part|\.bak|~)?
-		$
+        \.(
+            txt | py | c
+        )
+        # ignore filetype-independent suffixes:
+            (\.part|\.bak|~)?
+        $
 """, re.VERBOSE | re.IGNORECASE)
 
 class File(FileSystemObject):
-	is_file = True
-	preview_data = None
-	preview_known = False
-	preview_loading = False
+    is_file = True
+    preview_data = None
+    preview_known = False
+    preview_loading = False
 
-	@property
-	def firstbytes(self):
-		try:
-			return self._firstbytes
-		except:
-			try:
-				f = open(self.path, 'r')
-				self._firstbytes = f.read(N_FIRST_BYTES)
-				f.close()
-				return self._firstbytes
-			except:
-				pass
+    @property
+    def firstbytes(self):
+        try:
+            return self._firstbytes
+        except:
+            try:
+                f = open(self.path, 'r')
+                self._firstbytes = f.read(N_FIRST_BYTES)
+                f.close()
+                return self._firstbytes
+            except:
+                pass
 
-	def is_binary(self):
-		if self.firstbytes and control_characters & set(self.firstbytes):
-			return True
-		return False
+    def is_binary(self):
+        if self.firstbytes and control_characters & set(self.firstbytes):
+            return True
+        return False
 
-	def has_preview(self):
-		if not self.fm.settings.preview_files:
-			return False
-		if self.is_socket or self.is_fifo or self.is_device:
-			return False
-		if not self.accessible:
-			return False
-		if self.fm.settings.preview_script and \
-				self.fm.settings.use_preview_script:
-			return True
-		if self.image or self.container:
-			return False
-		if PREVIEW_WHITELIST.search(self.basename):
-			return True
-		if PREVIEW_BLACKLIST.search(self.basename):
-			return False
-		if self.path == '/dev/core' or self.path == '/proc/kcore':
-			return False
-		if self.is_binary():
-			return False
-		return True
+    def has_preview(self):
+        if not self.fm.settings.preview_files:
+            return False
+        if self.is_socket or self.is_fifo or self.is_device:
+            return False
+        if not self.accessible:
+            return False
+        if self.fm.settings.preview_script and \
+                self.fm.settings.use_preview_script:
+            return True
+        if self.image or self.container:
+            return False
+        if PREVIEW_WHITELIST.search(self.basename):
+            return True
+        if PREVIEW_BLACKLIST.search(self.basename):
+            return False
+        if self.path == '/dev/core' or self.path == '/proc/kcore':
+            return False
+        if self.is_binary():
+            return False
+        return True
 
-	def get_preview_source(self, width, height):
-		return self.fm.get_preview(self, width, height)
+    def get_preview_source(self, width, height):
+        return self.fm.get_preview(self, width, height)
diff --git a/ranger/fsobject/fsobject.py b/ranger/fsobject/fsobject.py
index d2207ab6..02b32958 100644
--- a/ranger/fsobject/fsobject.py
+++ b/ranger/fsobject/fsobject.py
@@ -2,13 +2,13 @@
 # This software is distributed under the terms of the GNU GPL version 3.
 
 CONTAINER_EXTENSIONS = ('7z', 'ace', 'ar', 'arc', 'bz', 'bz2', 'cab', 'cpio',
-	'cpt', 'deb', 'dgc', 'dmg', 'gz', 'iso', 'jar', 'msi', 'pkg', 'rar',
-	'shar', 'tar', 'tbz', 'tgz', 'xar', 'xpi', 'xz', 'zip')
+    'cpt', 'deb', 'dgc', 'dmg', 'gz', 'iso', 'jar', 'msi', 'pkg', 'rar',
+    'shar', 'tar', 'tbz', 'tgz', 'xar', 'xpi', 'xz', 'zip')
 DOCUMENT_EXTENSIONS = ('cfg', 'css', 'cvs', 'djvu', 'doc', 'docx', 'gnm',
-	'gnumeric', 'htm', 'html', 'md', 'odf', 'odg', 'odp', 'ods', 'odt', 'pdf',
-	'pod', 'ps', 'rtf', 'sxc', 'txt', 'xls', 'xlw', 'xml', 'xslx')
+    'gnumeric', 'htm', 'html', 'md', 'odf', 'odg', 'odp', 'ods', 'odt', 'pdf',
+    'pod', 'ps', 'rtf', 'sxc', 'txt', 'xls', 'xlw', 'xml', 'xslx')
 DOCUMENT_BASENAMES = ('bugs', 'bugs', 'changelog', 'copying', 'credits',
-	'hacking', 'help', 'install', 'license', 'readme', 'todo')
+    'hacking', 'help', 'install', 'license', 'readme', 'todo')
 
 import re
 from os import lstat, stat
@@ -20,270 +20,270 @@ from ranger.ext.lazy_property import lazy_property
 from ranger.ext.human_readable import human_readable
 
 if hasattr(str, 'maketrans'):
-	maketrans = str.maketrans
+    maketrans = str.maketrans
 else:
-	from string import maketrans
+    from string import maketrans
 _unsafe_chars = '\n' + ''.join(map(chr, range(32))) + ''.join(map(chr, range(128, 256)))
 _safe_string_table = maketrans(_unsafe_chars, '?' * len(_unsafe_chars))
 _extract_number_re = re.compile(r'([^0-9]?)(\d*)')
 
 def safe_path(path):
-	return path.translate(_safe_string_table)
+    return path.translate(_safe_string_table)
 
 class FileSystemObject(FileManagerAware):
-	(basename,
-	basename_lower,
-	dirname,
-	extension,
-	infostring,
-	path,
-	permissions,
-	stat) = (None,) * 8
-
-	(content_loaded,
-	force_load,
-
-	is_device,
-	is_directory,
-	is_file,
-	is_fifo,
-	is_link,
-	is_socket,
-
-	accessible,
-	exists,       # "exists" currently means "link_target_exists"
-	loaded,
-	marked,
-	runnable,
-	stopped,
-	tagged,
-
-	audio,
-	container,
-	document,
-	image,
-	media,
-	video) = (False,) * 21
-
-	size = 0
-
-
-	def __init__(self, path, preload=None, path_is_abs=False):
-		if not path_is_abs:
-			path = abspath(path)
-		self.path = path
-		self.basename = basename(path)
-		self.basename_lower = self.basename.lower()
-		self.extension = splitext(self.basename)[1].lstrip(extsep) or None
-		self.dirname = dirname(path)
-		self.preload = preload
-		self.display_data = {}
-
-		try:
-			lastdot = self.basename.rindex('.') + 1
-			self.extension = self.basename[lastdot:].lower()
-		except ValueError:
-			self.extension = None
-
-	def __repr__(self):
-		return "<{0} {1}>".format(self.__class__.__name__, self.path)
-
-	@lazy_property
-	def shell_escaped_basename(self):
-		return shell_escape(self.basename)
-
-	@lazy_property
-	def filetype(self):
-		try:
-			return spawn(["file", '-Lb', '--mime-type', self.path])
-		except OSError:
-			return ""
-
-	@lazy_property
-	def basename_natural(self):
-		return [c if i % 3 == 1 else (int(c) if c else 0) for i, c in \
-			enumerate(_extract_number_re.split(self.basename))]
-
-	@lazy_property
-	def basename_natural_lower(self):
-		return [c if i % 3 == 1 else (int(c) if c else 0) for i, c in \
-			enumerate(_extract_number_re.split(self.basename_lower))]
-
-	@lazy_property
-	def safe_basename(self):
-		return self.basename.translate(_safe_string_table)
-
-
-	for attr in ('video', 'audio', 'image', 'media', 'document', 'container'):
-		exec("%s = lazy_property("
-			"lambda self: self.set_mimetype() or self.%s)" % (attr, attr))
-
-	def __str__(self):
-		"""returns a string containing the absolute path"""
-		return str(self.path)
-
-	def use(self):
-		"""Used in garbage-collecting.  Override in Directory"""
-
-	def look_up_cumulative_size(self):
-		pass # normal files have no cumulative size
-
-	def set_mimetype(self):
-		"""assign attributes such as self.video according to the mimetype"""
-		basename = self.basename
-		if self.extension == 'part':
-			basename = basename[0:-5]
-		self._mimetype = self.fm.mimetypes.guess_type(basename, False)[0]
-		if self._mimetype is None:
-			self._mimetype = ''
-
-		self.video = self._mimetype.startswith('video')
-		self.image = self._mimetype.startswith('image')
-		self.audio = self._mimetype.startswith('audio')
-		self.media = self.video or self.image or self.audio
-		self.document = self._mimetype.startswith('text') \
-				or self.extension in DOCUMENT_EXTENSIONS \
-				or self.basename.lower() in DOCUMENT_BASENAMES
-		self.container = self.extension in CONTAINER_EXTENSIONS
-
-		keys = ('video', 'audio', 'image', 'media', 'document', 'container')
-		self._mimetype_tuple = tuple(key for key in keys if getattr(self, key))
-
-		if self._mimetype == '':
-			self._mimetype = None
-
-	@property
-	def mimetype(self):
-		try:
-			return self._mimetype
-		except:
-			self.set_mimetype()
-			return self._mimetype
-
-	@property
-	def mimetype_tuple(self):
-		try:
-			return self._mimetype_tuple
-		except:
-			self.set_mimetype()
-			return self._mimetype_tuple
-
-	def mark(self, boolean):
-		directory = self.fm.get_directory(self.dirname)
-		directory.mark_item(self)
-
-	def _mark(self, boolean):
-		"""Called by directory.mark_item() and similar functions"""
-		self.marked = bool(boolean)
-
-	@lazy_property
-	def realpath(self):
-		if self.is_link:
-			try:
-				return realpath(self.path)
-			except:
-				return None  # it is impossible to get the link destination
-		return self.path
-
-	def load(self):
-		"""
-		reads useful information about the filesystem-object from the
-		filesystem and caches it for later use
-		"""
-
-		self.display_data = {}
-		self.fm.update_preview(self.path)
-		self.loaded = True
-
-		# Get the stat object, either from preload or from [l]stat
-		self.permissions = None
-		new_stat = None
-		path = self.path
-		is_link = False
-		if self.preload:
-			new_stat = self.preload[1]
-			self.is_link = new_stat.st_mode & 0o170000 == 0o120000
-			if self.is_link:
-				new_stat = self.preload[0]
-			self.preload = None
-			self.exists = True if new_stat else False
-		else:
-			try:
-				new_stat = lstat(path)
-				self.is_link = new_stat.st_mode & 0o170000 == 0o120000
-				if self.is_link:
-					new_stat = stat(path)
-				self.exists = True
-			except:
-				self.exists = False
-
-		# Set some attributes
-
-		self.accessible = True if new_stat else False
-		mode = new_stat.st_mode if new_stat else 0
-
-		format = mode & 0o170000
-		if format == 0o020000 or format == 0o060000:  # stat.S_IFCHR/BLK
-			self.is_device = True
-			self.size = 0
-			self.infostring = 'dev'
-		elif format == 0o010000:  # stat.S_IFIFO
-			self.is_fifo = True
-			self.size = 0
-			self.infostring = 'fifo'
-		elif format == 0o140000:  # stat.S_IFSOCK
-			self.is_socket = True
-			self.size = 0
-			self.infostring = 'sock'
-		elif self.is_file:
-			if new_stat:
-				self.size = new_stat.st_size
-				self.infostring = ' ' + human_readable(self.size)
-			else:
-				self.size = 0
-				self.infostring = '?'
-		if self.is_link and not self.is_directory:
-			self.infostring = '->' + self.infostring
-
-		self.stat = new_stat
-
-	def get_permission_string(self):
-		if self.permissions is not None:
-			return self.permissions
-
-		if self.is_directory:
-			perms = ['d']
-		elif self.is_link:
-			perms = ['l']
-		else:
-			perms = ['-']
-
-		mode = self.stat.st_mode
-		test = 0o0400
-		while test:  # will run 3 times because 0o400 >> 9 = 0
-			for what in "rwx":
-				if mode & test:
-					perms.append(what)
-				else:
-					perms.append('-')
-				test >>= 1
-
-		self.permissions = ''.join(perms)
-		return self.permissions
-
-	def load_if_outdated(self):
-		"""
-		Calls load() if the currently cached information is outdated
-		or nonexistant.
-		"""
-		if not self.loaded:
-			self.load()
-			return True
-		try:
-			real_ctime = lstat(self.path).st_ctime
-		except OSError:
-			real_ctime = None
-		if not self.stat or self.stat.st_ctime != real_ctime:
-			self.load()
-			return True
-		return False
+    (basename,
+    basename_lower,
+    dirname,
+    extension,
+    infostring,
+    path,
+    permissions,
+    stat) = (None,) * 8
+
+    (content_loaded,
+    force_load,
+
+    is_device,
+    is_directory,
+    is_file,
+    is_fifo,
+    is_link,
+    is_socket,
+
+    accessible,
+    exists,       # "exists" currently means "link_target_exists"
+    loaded,
+    marked,
+    runnable,
+    stopped,
+    tagged,
+
+    audio,
+    container,
+    document,
+    image,
+    media,
+    video) = (False,) * 21
+
+    size = 0
+
+
+    def __init__(self, path, preload=None, path_is_abs=False):
+        if not path_is_abs:
+            path = abspath(path)
+        self.path = path
+        self.basename = basename(path)
+        self.basename_lower = self.basename.lower()
+        self.extension = splitext(self.basename)[1].lstrip(extsep) or None
+        self.dirname = dirname(path)
+        self.preload = preload
+        self.display_data = {}
+
+        try:
+            lastdot = self.basename.rindex('.') + 1
+            self.extension = self.basename[lastdot:].lower()
+        except ValueError:
+            self.extension = None
+
+    def __repr__(self):
+        return "<{0} {1}>".format(self.__class__.__name__, self.path)
+
+    @lazy_property
+    def shell_escaped_basename(self):
+        return shell_escape(self.basename)
+
+    @lazy_property
+    def filetype(self):
+        try:
+            return spawn(["file", '-Lb', '--mime-type', self.path])
+        except OSError:
+            return ""
+
+    @lazy_property
+    def basename_natural(self):
+        return [c if i % 3 == 1 else (int(c) if c else 0) for i, c in \
+            enumerate(_extract_number_re.split(self.basename))]
+
+    @lazy_property
+    def basename_natural_lower(self):
+        return [c if i % 3 == 1 else (int(c) if c else 0) for i, c in \
+            enumerate(_extract_number_re.split(self.basename_lower))]
+
+    @lazy_property
+    def safe_basename(self):
+        return self.basename.translate(_safe_string_table)
+
+
+    for attr in ('video', 'audio', 'image', 'media', 'document', 'container'):
+        exec("%s = lazy_property("
+            "lambda self: self.set_mimetype() or self.%s)" % (attr, attr))
+
+    def __str__(self):
+        """returns a string containing the absolute path"""
+        return str(self.path)
+
+    def use(self):
+        """Used in garbage-collecting.  Override in Directory"""
+
+    def look_up_cumulative_size(self):
+        pass # normal files have no cumulative size
+
+    def set_mimetype(self):
+        """assign attributes such as self.video according to the mimetype"""
+        basename = self.basename
+        if self.extension == 'part':
+            basename = basename[0:-5]
+        self._mimetype = self.fm.mimetypes.guess_type(basename, False)[0]
+        if self._mimetype is None:
+            self._mimetype = ''
+
+        self.video = self._mimetype.startswith('video')
+        self.image = self._mimetype.startswith('image')
+        self.audio = self._mimetype.startswith('audio')
+        self.media = self.video or self.image or self.audio
+        self.document = self._mimetype.startswith('text') \
+                or self.extension in DOCUMENT_EXTENSIONS \
+                or self.basename.lower() in DOCUMENT_BASENAMES
+        self.container = self.extension in CONTAINER_EXTENSIONS
+
+        keys = ('video', 'audio', 'image', 'media', 'document', 'container')
+        self._mimetype_tuple = tuple(key for key in keys if getattr(self, key))
+
+        if self._mimetype == '':
+            self._mimetype = None
+
+    @property
+    def mimetype(self):
+        try:
+            return self._mimetype
+        except:
+            self.set_mimetype()
+            return self._mimetype
+
+    @property
+    def mimetype_tuple(self):
+        try:
+            return self._mimetype_tuple
+        except:
+            self.set_mimetype()
+            return self._mimetype_tuple
+
+    def mark(self, boolean):
+        directory = self.fm.get_directory(self.dirname)
+        directory.mark_item(self)
+
+    def _mark(self, boolean):
+        """Called by directory.mark_item() and similar functions"""
+        self.marked = bool(boolean)
+
+    @lazy_property
+    def realpath(self):
+        if self.is_link:
+            try:
+                return realpath(self.path)
+            except:
+                return None  # it is impossible to get the link destination
+        return self.path
+
+    def load(self):
+        """
+        reads useful information about the filesystem-object from the
+        filesystem and caches it for later use
+        """
+
+        self.display_data = {}
+        self.fm.update_preview(self.path)
+        self.loaded = True
+
+        # Get the stat object, either from preload or from [l]stat
+        self.permissions = None
+        new_stat = None
+        path = self.path
+        is_link = False
+        if self.preload:
+            new_stat = self.preload[1]
+            self.is_link = new_stat.st_mode & 0o170000 == 0o120000
+            if self.is_link:
+                new_stat = self.preload[0]
+            self.preload = None
+            self.exists = True if new_stat else False
+        else:
+            try:
+                new_stat = lstat(path)
+                self.is_link = new_stat.st_mode & 0o170000 == 0o120000
+                if self.is_link:
+                    new_stat = stat(path)
+                self.exists = True
+            except:
+                self.exists = False
+
+        # Set some attributes
+
+        self.accessible = True if new_stat else False
+        mode = new_stat.st_mode if new_stat else 0
+
+        format = mode & 0o170000
+        if format == 0o020000 or format == 0o060000:  # stat.S_IFCHR/BLK
+            self.is_device = True
+            self.size = 0
+            self.infostring = 'dev'
+        elif format == 0o010000:  # stat.S_IFIFO
+            self.is_fifo = True
+            self.size = 0
+            self.infostring = 'fifo'
+        elif format == 0o140000:  # stat.S_IFSOCK
+            self.is_socket = True
+            self.size = 0
+            self.infostring = 'sock'
+        elif self.is_file:
+            if new_stat:
+                self.size = new_stat.st_size
+                self.infostring = ' ' + human_readable(self.size)
+            else:
+                self.size = 0
+                self.infostring = '?'
+        if self.is_link and not self.is_directory:
+            self.infostring = '->' + self.infostring
+
+        self.stat = new_stat
+
+    def get_permission_string(self):
+        if self.permissions is not None:
+            return self.permissions
+
+        if self.is_directory:
+            perms = ['d']
+        elif self.is_link:
+            perms = ['l']
+        else:
+            perms = ['-']
+
+        mode = self.stat.st_mode
+        test = 0o0400
+        while test:  # will run 3 times because 0o400 >> 9 = 0
+            for what in "rwx":
+                if mode & test:
+                    perms.append(what)
+                else:
+                    perms.append('-')
+                test >>= 1
+
+        self.permissions = ''.join(perms)
+        return self.permissions
+
+    def load_if_outdated(self):
+        """
+        Calls load() if the currently cached information is outdated
+        or nonexistant.
+        """
+        if not self.loaded:
+            self.load()
+            return True
+        try:
+            real_ctime = lstat(self.path).st_ctime
+        except OSError:
+            real_ctime = None
+        if not self.stat or self.stat.st_ctime != real_ctime:
+            self.load()
+            return True
+        return False