diff options
Diffstat (limited to 'ranger/fsobject')
-rw-r--r-- | ranger/fsobject/directory.py | 1032 | ||||
-rw-r--r-- | ranger/fsobject/file.py | 130 | ||||
-rw-r--r-- | ranger/fsobject/fsobject.py | 528 |
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 |