from ranger.fsobject import BAD_INFO, File, FileSystemObject
from ranger.shared import SettingsAware
from ranger import log
import ranger.fsobject
def sort_by_basename(path):
"""returns path.basename (for sorting)"""
return path.basename
def sort_by_directory(path):
"""returns 0 if path is a directory, otherwise 1 (for sorting)"""
return 1 - int( isinstance( path, Directory ) )
class NoDirectoryGiven(Exception):
pass
class Directory(FileSystemObject, SettingsAware):
enterable = False
load_generator = None
loading = False
filenames = None
files = None
filter = None
marked_items = None
pointed_index = None
pointed_file = None
scroll_begin = 0
scroll_offset = 0
old_show_hidden = None
old_directories_first = None
old_reverse = None
old_sort = None
sort_dict = {
'basename': sort_by_basename,
'size': lambda path: path.size,
'mtime': lambda path: -(path.stat and path.stat.st_mtime or 1),
'type': lambda path: path.mimetype,
}
def __init__(self, path):
from os.path import isfile
if isfile(path):
raise NoDirectoryGiven()
FileSystemObject.__init__(self, path)
self.marked_items = set()
# to find out if something has changed:
self.old_show_hidden = self.settings.show_hidden
self.old_directories_first = self.settings.directories_first
self.old_sort = self.settings.sort
self.old_reverse = self.settings.reverse
def mark_item(self, item, val):
item._mark(val)
if val:
if item in self.files:
self.marked_items.add(item)
else:
if item in self.marked_items:
self.marked_items.remove(item)
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:
self.marked_items.clear()
self._clear_marked_items()
def _gc_marked_items(self):
for item in self.marked_items.copy():
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)
self.marked_items.clear()
def get_selection(self):
"""READ ONLY"""
self._gc_marked_items()
if self.marked_items:
return set(self.marked_items)
elif self.pointed_file:
return set([self.pointed_file])
else:
return set()
def load_bit_by_bit(self):
"""
Returns a generator which load a part of the directory
in each iteration.
"""
# log("generating loader for " + self.path + "(" + str(id(self)) + ")")
from os.path import join, isdir, basename
from os import listdir
self.loading = True
self.load_if_outdated()
if self.exists and self.runnable:
yield
filenames = []
for fname in listdir(self.path):
if not self.settings.show_hidden and fname[0] == '.':
continue
if isinstance(self.filter, str) and self.filter in fname:
continue
filenames.append(join(self.path, fname))
yield
marked_paths = set(map( \
lambda obj: obj.path, self.marked_items))
files = []
for name in filenames:
if isdir(name):
try:
item = self.fm.env.get_directory(name)
except:
item = Directory(name)
else:
item = File(name)
item.load_if_outdated()
files.append(item)
yield
self.scroll_offset = 0
self.filenames = filenames
self.infostring = ' %d' % len(self.filenames) # update the infostring
self.files = files
self._clear_marked_items()
for item in self.files:
if item.path in marked_paths:
self.mark_item(item, True)
else:
self.mark_item(item, False)
self.old_directories_first = None
self.sort()
if len(self.files) > 0:
if self.pointed_file is not None:
self.move_pointer_to_file_path(self.pointed_file)
else:
self.filenames = None
self.files = None
self.infostring = BAD_INFO
self.content_loaded = True
self.loading = False
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.
"""
if not self.loading:
self.load_once()
if schedule is None:
schedule = 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_file = self.pointed_file
try:
sort_func = self.sort_dict[self.settings.sort]
except:
sort_func = sort_by_basename
self.files.sort(key = sort_func)
if self.settings.reverse:
self.files.reverse()
if self.settings.directories_first:
self.files.sort(key = sort_by_directory)
if self.pointed_index is not None:
self.move_pointer_to_file_path(old_pointed_file)
else:
self.correct_pointer()
self.old_directories_first = self.settings.directories_first
self.old_sort = self.settings.sort
self.old_reverse = self.settings.reverse
def sort_if_outdated(self):
"""Sort the containing files if they are outdated"""
if self.old_directories_first != self.settings.directories_first \
or self.old_sort != self.settings.sort \
or self.old_reverse != self.settings.reverse:
self.sort()
# Notice: fm.env.cf should always point to the current file. If you
# modify the current directory with this function, make sure
# to update fm.env.cf aswell.
def move_pointer(self, relative=0, absolute=None):
"""Move the index pointer"""
if self.empty(): return
i = self.pointed_index
if isinstance(absolute, int):
if absolute < 0:
absolute = len(self.files) + absolute
i = absolute
if isinstance(relative, int):
i += relative
self.pointed_index = i
self.correct_pointer()
return self.pointed_file
def move_pointer_to_file_path(self, path):
"""
Move the index pointer to the index of the file object
with the given path.
"""
if path is None: return
try: path = path.path
except AttributeError: pass
self.load_content_once()
if self.empty(): return
i = 0
for f in self.files:
if f.path == path:
self.move_pointer(absolute = i)
self.correct_pointer()
return True
i += 1
return self.move_pointer(absolute=self.pointed_index)
def search(self, arg, direction = 1):
"""Search for a regular expression"""
if self.empty() or arg is None:
return False
elif hasattr(arg, 'search'):
fnc = lambda x: arg.search(x.basename)
else:
fnc = lambda x: arg in x.basename
length = len(self)
if direction > 0:
generator = ((self.pointed_index + (x + 1)) % length for x in range(length-1))
else:
generator = ((self.pointed_index - (x + 1)) % length for x in range(length-1))
for i in generator:
_file = self.files[i]
if fnc(_file):
self.pointed_index = i
self.pointed_file = _file
return True
return False
def correct_pointer(self):
"""Make sure the pointer is in the valid range"""
if self.files is None or len(self.files) == 0:
self.pointed_index = None
self.pointed_file = None
else:
i = self.pointed_index
if i is None: i = 0
if i >= len(self.files): i = len(self.files) - 1
if i < 0: i = 0
self.pointed_index = i
self.pointed_file = self[i]
try:
if self == self.fm.env.pwd:
self.fm.env.cf = self.pointed_file
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.old_show_hidden != self.settings.show_hidden:
self.old_show_hidden = self.settings.show_hidden
self.load_content(*a, **k)
return True
import os
try:
real_mtime = os.lstat(self.path).st_mtime
except OSError:
real_mtime = None
if self.stat:
cached_mtime = self.stat.st_mtime
else:
cached_mtime = 0
if real_mtime != cached_mtime:
self.load_content(*a, **k)
return True
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
def __len__(self):
"""The number of containing files"""
if not self.accessible: raise ranger.fsobject.NotLoadedYet()
return len(self.files)
def __getitem__(self, key):
"""Get the file by its index"""
if not self.accessible: raise ranger.fsobject.NotLoadedYet()
return self.files[key]
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)